SQLiteだけで耐久性のあるワークフローを実装できる
(obeli.sk)- Durable Execution を実現するために、必ずしも別途複雑なインフラが必要なわけではなく、重要なのはワークフロー状態を安全に保持すること
- SQLite は専用のデータベースサービスなしでトランザクションベースの永続状態を提供し、ネットワークホップや追加のコントロールプレーンなしにワークフローの進行状態を安全に維持できる
- Litestream を使って SQLite の変更を S3 互換オブジェクトストレージへ非同期にストリーミングすれば、状態をランタイムの近くに保ちながら、バックアップ・マイグレーション・検査目的でコピーできる
- AI エージェントのような バースト性・実験的ワークフロー では、各エージェントが独立した小規模な SQLite 状態単位を持つマイクロ VM またはコンテナ構成のほうが、単一の大規模共有システムより単純で安価かつ障害分離に有利
- 高可用性や広範な共有スケーラビリティが必要な場合は Postgres が適しているが、多くのワークフローシステムは初期段階でそのレベルのインフラを必要としない
永続的実行の核心
- 永続的実行はしばしば永続的インフラが必要であるかのように語られるが、実際に重要なのは ワークフロー状態(workflow state) であり、コンピュートは安価で使い捨てのままにできる
- Obelisk ではワークフローの進行状態は 実行ログ(execution log) に保存され、永続化された履歴からリプレイされ、アクティビティは再試行できる
SQLite が適している理由
- SQLite は別のデータベースサービスを追加せずに トランザクションベースの永続状態 を提供する
- ネットワークホップ、追加のコントロールプレーン、新たな運用負荷なしにワークフローの進行状態を安全に維持できる
- 多くのシステムでは、ローカルのデータベースファイルこそがちょうどよいレベルのインフラである
Litestream によるポータビリティの確保
- Litestream は SQLite の変更を S3 互換オブジェクトストレージ に非同期ストリーミングする方式で、状態をランタイムの近くに保ちながら、バックアップ・マイグレーション・検査用コピーを支援する
- 注意事項: Litestream の複製は非同期方式のため、SQLite ボリュームが失われる前にコピーされていない最新のローカル書き込みは、復元時に欠落する可能性がある
- 多くの AI および実験的ワークフローでは許容可能な水準だが、高可用性の共有データベース とは異なるモデル
- 実用的な運用モデル: Obelisk サーバーを SQLite データベースとともに実行し、Litestream でバックアップし、必要に応じてオブザーバーがデータベースを pull — 同じファイルをローカルでのリプレイ・デバッグ・エージェント行動分析に再利用できる
AI エージェントに特に有利な理由
- AI エージェントおよび AI 生成ワークフローは、バースト性・実験的な特性を持ち、エージェントまたはテナントごとに小規模で自己完結的な状態単位 を持つほうが推論に有利
- マイクロ VM またはコンテナ ベースの小規模サーバーフリート上で、それぞれ独立した SQLite データベースとオブジェクトストレージのバックアップを運用する構成は、1つの大規模な常時共有システムより単純で安価であり、障害分離の面でも有利
Postgres を使うべき場合
- Obelisk は Postgres もサポートしており、高可用性、広範な共有スケーラビリティ、あるいはネットワークデータベースのほうが適したデプロイ特性が必要な場合に選択される
- 非同期オブジェクトストレージ複製が望む 耐久性モデル ではない場合にも Postgres のほうが適している
- 多くのワークフローシステムは、初期段階から状態が実際に要求する水準を超えるインフラで始める必要はない
- ローカル SQLite + Litestream S3 バックアップ + 安価なワーカーの組み合わせだけで、最小インフラの永続システム を構成でき、AI エージェント環境で最も合理的なデフォルトになりうる
1件のコメント
Hacker Newsのコメント
Temporal でワークフローを構築し始めたが、ローカルアプリとしてはかなり軽量にデプロイでき、分離されたローカル環境では SQLite を使っている。
API のリトライ処理、ワークフローやジョブの整理が本当にシンプルになるので、一度試してみることを勧める。思想的にはこの記事が提案していることとまさに同じ方向だが、エージェントが扱いやすい非常に豊富で柔軟なインターフェースが加わる。Web UI でワークフローを確認したり、エージェント実行を見直したりするのも簡単だ。
Temporal はシステムにずっと高い信頼性を、ほとんどただ同然で加えてくれる。分散かつ信頼性の高いシステムは難しいので、車輪の再発明は避けたほうがいいと思う。
SQLite データベースを簡単にのぞいて、ワークフローで何が起きているかを把握し、個々のジョブを組み合わせ、ワークフローを手軽に呼び出せるようにしたいなら、Temporal は検討に値する。
これに合わせて、エージェント用のファイル利用はほとんどやめた。Markdown や JSON も良いが、小さなローカルアプリを作るときには落とし穴のように感じる。LLM は SQLite をうまく扱えるし、そこから Markdown や JSON など好きな形式にレンダリングできる。エージェントが jq を実行したり Markdown を grep したりする代わりに特定の行だけを問い合わせられれば、トークンもかなり節約できる。複数のファイルよりも、データ構造をより規律あるものにしやすい、移植性の高い自己完結型のデータ管理システムを得られる。小さなローカルプロジェクトが大きくなったり、より正式なものになったりすれば MySQL/Postgres にもつなげられ、しかもすでにスキーマとデータ規律を備えた状態になっている。
Temporal は規模が大きくなるとずっと複雑になる。Cassandra の運用は楽しくないし、Ringpop と TChannel は問題が起きたときのデバッグが難しい。SQL バックエンド対応は整合性要件のため水平スケール用レプリカをサポートせず、単一インスタンスしか使えない。
コードの書き方によっては、ワークフローに埋め込まれたコードの修正も複雑になる。履歴イベントの順序を変える変更は、すでにデプロイ済みのワーカーの決定性を壊してしまう。
私たちは Temporal をかなり使っているが、単純なスクリプトや自動化から始めた人たちは皆これを気に入っていて、その上に実運用システムを作った人たちは皆嫌っている。運用の未熟さかもしれないが、ここのコメント欄で見えるバラ色のイメージと自分の経験は一致しなかった。
自分ではやったことがないが、実際の経験談をもっと聞いてみたい。
実運用アプリに SQLite を使おうという執着は理解できない。SQLite は組み込みデータベースであり、同時実行性の管理にはまったく向いていない
こういうことのために Postgres や MySQL のようなデータベースサーバーがある。これらの本来の役割は、複数のプロセスが別々のマシンから同時にデータを更新できるようにすることだ
これはコンピュータサイエンスの基本原則であり、「何でも SQLite」と叫ぶ側は少し経験不足に見える
SQLite は多くの実際のワークロードで優れた本番用データベースであり、それは広く文書化されている。Postgres とは大きく異なるため、まったく別の技術として学ぶ必要がある
ひとつの見方として、システム内で自然に強いパーティショニングが生まれる部分には SQLite が適している場合がある
実際に手放すことになるのは 高可用性/フェイルオーバー や災害復旧くらいだが、これにも解決策はある。単一サーバーのシステムはたいてい驚くほど堅牢だ。複雑な制御プレーンがなければ、システムは増えるほど稼働率が下がることが多いからだ
技術の変化を踏まえて既存の「ベストプラクティス」を再評価するのが好きだ。特に単純さを高める方向ならなおさらだ。家族向けのソーシャルメディアサイトを VPS 1 台の SQLite DB で動かすのは素晴らしい。ユーザーは約 15 人で、保守はほとんど不要だ。FreshRSS インスタンスと “now” ページも SQLite で動かしている
職場でもここ数十年、SQLite をあらゆる用途に使ってきた。一時的な作業キュー、ローカルで大量のログを高速に取り込み・照会する仕組み、そして simonw のすばらしい https://github.com/simonw/datasette を使ったリアルタイム表示・フィルタリングなどに使ってきた
「何でも SQLite」というより、「思っているよりはるかに多くの場所で SQLite」に近いと考えている
kentonv/Cloudflare のエッジ SQLite の取り組みがこの考えをもう少し一般化したのかもしれないが、もともと存在していた流れだ。https://blog.cloudflare.com/sqlite-in-durable-objects/
こうした小さく有用な事例を知り、それを活用したいと思うことは、経験不足ではなく、むしろ経験の指標かもしれない
SQLite は、他のすべてのデータベースエンジンを合わせたよりも多く使われている可能性が高い。実世界には数十億もの SQLite のコピーが存在する。Android デバイス、iPhone や iOS デバイス、Mac、Windows 10/11 のインストール環境、Firefox/Chrome/Safari、Skype、iTunes、Dropbox クライアント、TurboTax と QuickBooks、PHP と Python、ほとんどのテレビとセットトップボックス、ほとんどの車載マルチメディアシステム、そして数え切れないほど多くのアプリケーションに組み込まれている
https://sqlite.org/mostdeployed.html
こうするとスケーラビリティははるかに理解しやすくなる。分割して配置し、さらに分割して配置すればよい。ユーザー N 人ごとにシャードを 1 つ追加するイメージだ
その代わり、クロスシャードクエリ、たとえば分析、そしてユーザーの離脱や陳腐化が進んだときに負荷をどう平準化するかといった別の問題が生じる
しかし、大規模ユーザー数において挿入/更新によって生じる共有インデックスのスケーリング問題全体は回避できる
もはやリレーショナルデータベースというより、階層型データベースになる
次のものをすべて Go + SQLite に置き換えた: Intercom、Zendesk、メールマーケティング、Kanban、Todo、決済スタック、イシュートラッカー、フォーラム、稼働時間モニター、PagerDuty クローン
販売している製品が数十個あるので、いっそ全部自分で作ればいいのではと思った
すべて同じサーバー上で動いており、メモリ消費も非常に少ない。使っていた SaaS ツールを全部これらで置き換えた
専用サーバーへ移行することで、管理型クラウドソリューションに払っていた費用の約 1/10 に減らせたうえ、同じ高可用性を維持しながらレイテンシもさらに低くなった。VPS の noisy neighbor によってテールレイテンシが悪化していたことも一因だった
以前はこうしたものに大金を使っていたが、いまは運用 4 か月目で、必要だったのは細かなアップデートだけだった
デプロイは本当に単純。Docker も Kubernetes も使わず、systemd サービスと開発マシンでビルドして配布したバイナリだけ
MaxMind や IPData のようなサービスにもお金を払っていたが、自前で IP ジオロケーションサービスを作り、テストでは既存の大半のソリューションより性能が良かった
始まりは Uptime Robot の置き換えで、その後自信がついて PagerDuty を置き換えた。さらに Intercom も置き換えた
最後に、「決済スタックは自作するな」といつも言われていたが、YOLO だと思ってその失敗を自分でやってみることにした。既存の決済ソリューションを調べて自分で開発・デプロイし、今のところ問題はまったく起きていない
フロントには Caddy を置いている
ほとんどの SaaS 製品で提供される機能のうち、実際に使っていたのは 1〜5% にすぎず、本当に必要な機能はこうした「エンタープライズ級」プラットフォームの中でますます深く埋もれて、ワークフローを難しくしていることに気づいた
商用製品は、パートナーや顧客が私の安さを知るのを好まないだろうから見せないが、私はこれをやりくり上手だと呼んでいる
無料アプリなら見せられる。最近リリースしたもので、ユーザーは 2 万人以上いる: https://macrocodex.app/
このアプリでは Zendesk クローンだけを使っている。メールは Cloudflare ルーティングで処理しているので、運用コストはほとんどかからない
ファイルからマルチパーティション・データベースまでには大きな隔たりがある。本番運用がかかった状況でデータベースをコンテナで動かすのは自分の好みではない
個人的には、多くの ETL はエンタープライズデータベースを持ち込まずにローカルで処理できる。そういう場合、DuckDB は SQLite より 5〜10 倍優れており、専用の Postgres データベースを立ち上げるよりはるかに単純で高速だ
一般的なスクリプティングでは、20 行の awk スクリプトと、DuckDB ベースのはるかにクリーンで堅牢かつ保守しやすい同等の SQL スクリプトとは比べものにならない
MotherDuck が IPO のためにパンプ・アンド・ダンプをしなければならないような状況でないことを願う。ありふれた企業の強欲さのせいでこのツールを失うことになれば悲しいだろう
20 行の awk スクリプトの話は面白い。昨日 Ubuntu Summit でほぼ同じ主張をした。ある時点を超えると GNU coreutils でシェルスクリプトを書くのは非現実的になり、DuckDB の SQL スクリプトのほうが複雑さや保守性、そしてしばしば性能の面でもうまくスケールする。スライドはこちら: https://blobs.duckdb.org/slides/duckdb-ubuntu-summit-2026.pd... ページ 32〜36
また MotherDuck は DuckDB 上でクローズドソースの DBaaS を開発している。DuckDB 上に構築され、DuckDB から MotherDuck に接続できるが、シアトルに本社を置く別個の VC 出資企業だ
DuckDB はアムステルダムのブートストラップ企業、つまり売上ベースの会社である DuckLabs が開発している。プロジェクトの知的財産は、3 つ目の組織であるオランダの非営利団体 DuckDB Foundation が保有している。詳しくは https://duckdb.org/faq#how-are-duckdb-the-duckdb-foundation-... を参照
S3 上の SQLite DB を安全に 同時更新 できるようにするライブラリを作った[0]
あまり知られていない SQLite sessions 拡張と、小さなメタデータファイルに対する S3 compare-and-swap を使って、かなり効率的かつ安全に動作するようにしている。Lambda 関数に状態保存用 DB が必要だが、データベースインスタンス全体のコストは払いたくない小規模プロジェクトで楽しく使っている
[0]: https://github.com/psanford/s3db
SQLite は 単一ノードアプリケーション では Postgres と比べても驚くほど高性能だ
Postgres はメモリをはるかに多く使い、入出力はプロセス間通信を経由しなければならない。一方 SQLite は共有接続プールを通じて、すべてをプロセス内に置ける
エージェントハーネス用に複数のストレージエンジンをテストしているが、SQLite では単一 vCPU で同時セッション 7,500 まで可能だったのに対し、Postgres はクラッシュするか接続が枯渇した
[0] https://github.com/impalasys/talon/pull/23#issuecomment-4577...
現在のスレッドを外れた瞬間、レイテンシの観点では負けゲームになる。スレッド間通信を強制しないなら、SQLite はマイクロ秒単位で動作できる
単一ノードという文脈では Postgres はやりすぎだ。SQLite と競うものとして期待すべきではない
ほぼ、インメモリの HashMap と Redis をベンチマークして、理想条件で HashMap の結果が良いことに驚くようなものだ
何年もSQLiteの話を読んでいて、自宅プロジェクトで使ってみたが、Postgresから来ると型システムがあまりにも貧弱で衝撃を受けた。
本当に劣っているのに、なぜあそこまで称賛されているのかわからない。
https://sqlite.org/datatype3.html
https://www.postgresql.org/docs/current/datatype.html
日付/時刻の扱いは30年前のデータベースを使っているような感覚で、挿入時にも何も強制されない。なぜこれほど多くの人が好むのか、誰か説明してほしい。
PRAGMA journal_mode = WAL
PRAGMA foreign_keys = ON
Something non-null
PRAGMA busy_timeout = 1000This is fine for most applications, but see the manual
PRAGMA synchronous = NORMALIf you use it as a file format
PRAGMA trusted_schema = OFFバインディングによっては追加オプションが必要になる場合もある。たとえばPythonアプリケーションはsqlite3モジュールのデフォルト値を使うべきではない。そのデフォルトは単純に間違っている。3.12以前は、標準ライブラリ外のバインディングを使う以外に代替手段もなかった: https://docs.python.org/3/library/sqlite3.html#transaction-c...
strict tableも使うべきだ。 https://www.sqlite.org/stricttables.html
使い勝手は悪いが、CHECK制約も使える。たとえばSQLite組み込みの日付サポートで不可能ではないが不格好だ:
CHECK (
date(my_date_col) IS NOT NULL
AND my_date_col = date(my_date_col)
)
IS NOT NULLが必要なのは、dateが不正な日付に対してNULLを返すためだ。もう一方の検査はユリウス日も受け入れてしまうので、date('2026')は紀元前4707年のある時点になってしまう。特にstrict table以前は期待外れだったという点には同意する。
DuckDBを見るべきだ。ちゃんとした型を持つSQLiteに近い。ただしOLTP、つまり配列の構造体ではなく、OLAP、つまり構造体の配列なので、一般的なSQLiteの負荷では性能が悪くなる可能性がある。実際にどちらかを検討するアプリケーションなら、大きな違いはない気もする。
複数の大規模なPostgresクラスタを使った後でSQLiteに移行し、月間アクティブユーザーが7桁のサービス全体がSQLite durable objectsで支えられている。
アクセスパターンは違う考え方が必要だが、その利点には十分価値があった。
いい切り口だ。主要な問題がワークフロー状態を永続的に保存し、可視化できて、簡単に復旧できるようにすることなら、SQLiteで十分な場合は多い。
このアイデアの次の反復として、「永続的なワークフローにはログさえあればよい」が出てくるのを早く見たい。
「ログさえあればよい」式のソリューションが失敗しうる理由の一つは、信頼できないログが注入攻撃になる場合だ[1]。
SBOMを確認し、CI/CDパイプラインも含めるのを忘れないようにすべきだ[2]。
[1] https://news.ycombinator.com/item?id=48315440
[2] https://github.com/jqwik-team/jqwik/issues/708#issuecomment-...
真面目な話、専門家であるということは、その仕事に合った道具を使うことだ。