動画で読む
結論 — durable なのは「状態」で、コンピュートは使い捨てでいい
durable workflow(途中で落ちても再開できる長時間ワークフロー)で本当に守るべきは「状態」であって、状態を保持するためのインフラそのものではありません。obeli.sk の記事「SQLite is all you need for durable workflows」はそう主張します。専用のキューや外部 DB サービスを足す前に、SQLite ファイル 1 つで足りる範囲はどこまでか。Sidekiq + Redis でジョブを回している私の運用に当てて読みました。
AetherEchoes(このブログ)は記事の予約公開・ページ再生成・eyecatch 自動生成を Sidekiq 8 + Redis 8 で回しています。元記事を読んで最初に思ったのは「うちの Redis は本当に要るのか」でした。結論から言うと私は Redis を残しますが、その理由を言語化できたのは収穫でした。なお AetherEchoes の記事は AI が下書きを書き、私が一次情報に当たって確認・編集してから公開しています。この記事も元記事と Litestream の公式ドキュメントを自分で読み直して書きました。
durable execution とは — 実行ログ・リプレイ・リトライの 3 点
durable execution(耐久実行)の核は、ワークフローの進捗を永続ログに刻み、落ちたら履歴から状態を作り直す、という 3 つの仕組みに尽きます。元記事はこれを「実行ログ(execution log)/ リプレイ(replay)/ リトライ(retry)」と整理していました。
順に書きます。実行ログは、ワークフローがどこまで進んだかを 1 行ずつ永続化する台帳です。リプレイは、ワーカーが落ちて再起動したとき、その台帳を頭から読み直して途中状態を復元する動きを指します。リトライは、失敗したアクティビティ(外部 API 呼び出しなど個々の処理)をもう一度実行することです。この 3 つが揃うと、ワーカー自身は状態を持たなくてよくなる。状態はメモリではなく、台帳の側にあるからです。
ここが Sidekiq の発想と少しずれる点でした。Sidekiq のジョブは基本「投げたら終わり」で、途中状態を持つ長時間ワークフローは自分で組む必要があります。durable execution はその「途中状態の持ち方」を最初から仕組みにしている。Temporal がこの考え方の代表で、元記事はそれを SQLite 1 ファイルで安く実現する、という話でした。
なぜ SQLite で足りるのか — 消えるのはネットワークホップ
SQLite を状態ストアに使う最大の利点は、外部 DB サービスを足さずにトランザクション付きの永続状態が手に入ること、つまりネットワークホップとコントロールプレーンが丸ごと消えることです。元記事の表現を借りれば「no network hop, no extra control plane, and no new operational surface area」です。
これは運用者にとって地味に効きます。Redis や Postgres を別プロセスで立てると、その接続・監視・バックアップ・バージョン追従が全部「運用面(operational surface、運用で面倒を見る対象の広さ)」として乗ってきます。SQLite はアプリのプロセス内に同居するので、その面が増えない。ワーカーをステートレスにできるので、コンピュートは安く使い捨てにできて、状態だけが SQLite ファイルに残る。AI のバースト的なワークロード(急に来て急に止まる処理)と相性がいい、という指摘も腹落ちしました。
ファイルが消えたら困るので、バックアップは Litestream で取ります。Litestream は SQLite の変更を非同期で S3 互換ストレージに流し続けるツールで、ローカルの速さと遠隔バックアップを両立させます。ここまでが「SQLite で足りる」側の話です。
Sidekiq + Redis 運用者として当てはめる
私のスタックに当てると、SQLite 案がそのまま刺さるのは「状態を持つ長時間処理」だけで、既存の短命ジョブは Redis のままで十分でした。全部を SQLite に寄せるのは過剰だ、というのが私の結論です。
AetherEchoes のジョブは大きく 2 種類あります。1 つは予約公開や revalidate のような短命な単発ジョブで、これは Sidekiq cron で予約公開と revalidate を統合したとき書いた通り、Redis に積んで時刻が来たら実行すれば終わりです。途中状態がないので durable execution の仕組みは要らない。もう 1 つは、記事生成から動画生成・YouTube upload まで数分かかる多段パイプライン(6 つのスキルに分けて運用しているあれです)で、これは途中で落ちると「どこまで終わったか」が問題になります。SQLite に実行ログを刻む案が効くのは後者です。
ただ私は当面 Redis を残します。理由は 2 つあって、1 つは既に Redis 8 を監視・バックアップ込みで運用していて、surface を減らす動機より乗り換えコストの方が大きいこと。もう 1 つは、多段パイプラインの途中状態を私はまだ DB の status カラムで素朴に持っていて、本格的な実行ログが要るほど複雑になっていないことです。surface を減らす話は魅力的ですが、減らす対象が既に枯れていると旨味は薄い。動いている Redis を外す週末は、たぶん Redis を外したこと自体のデバッグで終わります。
限界 — 非同期レプリケーションは「最新の書き込み」を落としうる
SQLite と Litestream の構成には明確な限界があって、レプリケーションが非同期な以上、ボリュームごと消えると直前のローカル書き込みは復元から漏れうる、という点です。元記事も「a restore can miss the newest local writes if the SQLite volume disappears」と正直に書いています。
これは高可用性(HA、落ちないことを強く保証する性質)が要る用途には向きません。同期的な durability 保証や、複数ノードで状態を共有してスケールさせたい場合は、素直に Postgres を選ぶべき、というのが元記事の線引きでした。私もここは同意で、課金や在庫のように「1 件も落とせない」状態を扱うなら SQLite 単体には寄せません。逆にブログの動画パイプラインのように、最悪 1 本やり直せば済む処理なら、非同期レプリケーションの隙間は許容範囲です。
つまり判断軸は「その状態を 1 件失ったとき、やり直せるか」です。やり直せる処理なら SQLite の安さを取り、やり直せない処理なら同期保証のある DB を取る。durable という言葉に引きずられて全部を同じ強度で守ろうとすると、要らない複雑さを抱え込みます。
まとめ — 守る強度を処理ごとに変える
SQLite で durable workflow を組む案の本質は、「状態は守るが、守る強度は処理ごとに変えていい」という割り切りだと読みました。実行ログ・リプレイ・リトライの 3 点で状態を残し、コンピュートは使い捨て、バックアップは Litestream に非同期で逃がす。その代わり最新の数件は失いうる、という明示的なトレードオフです。
私は Sidekiq + Redis を当面残しますが、次に多段パイプラインの途中状態が複雑になったら、まず SQLite と実行ログを試す候補に入れました。全面移行ではなく、やり直せない処理だけ強い保証に寄せ、やり直せる処理は安く済ませる。どこで線を引くかを処理単位で決める、という読み方が一番の収穫でした。
Tags
よくある質問
- durable execution(耐久実行)とは何ですか?
- ワークフローの進捗を永続ログに刻み、ワーカーが落ちても履歴から途中状態を作り直して再開できる仕組みです。実行ログ・リプレイ・リトライの 3 点で成り立ち、状態をメモリでなく台帳側に置くため、ワーカー自身はステートレスにできます。
- なぜ専用キューでなく SQLite で足りると言えるのですか?
- SQLite はアプリのプロセス内に同居するため、外部 DB を足さずにトランザクション付きの永続状態が手に入り、ネットワークホップ・コントロールプレーン・運用面の増加が起きないからです。バックアップは Litestream で S3 互換ストレージへ非同期に流します。
- Sidekiq + Redis はやめるべきですか?
- 用途次第です。予約公開のような短命な単発ジョブは途中状態がなく Redis のままで十分でした。SQLite の実行ログが効くのは、数分かかる多段パイプラインのように「どこまで終わったか」を保持したい長時間処理に限られます。
- SQLite と Litestream 構成の限界は何ですか?
- レプリケーションが非同期なため、ボリュームごと消えると直前のローカル書き込みが復元から漏れうる点です。高可用性や同期的な durability 保証、複数ノードでの状態共有が要る場合は Postgres を選ぶのが妥当です。