世界を動かす技術を、日本語で。

Litestream v0.5.0

概要

  • Litestream はSQLiteアプリケーション向けのリアルタイムバックアップ・リストアツール
  • 新バージョンで パフォーマンス向上 と効率的なポイントインタイムリカバリ(PITR)を実現
  • LTXファイルフォーマット による柔軟なトランザクション管理と高速復元
  • 世代管理の廃止 でシンプルな運用性を提供
  • 今後は VFSによるリードレプリカ 対応も予定

LitestreamとLiteFSの進化

  • Litestream はSQLiteアプリケーションの耐障害性を強化するオープンソースツール
  • サイドカープロセス として動作し、WALチェックポイントをリアルタイムでオブジェクトストレージへ転送
  • サーバ障害時でも 迅速なデータベース復元 を実現
  • LiteFS はFUSEファイルシステムを用い、より高度なライブレプリケーションを目指したプロジェクト
    • マルチリージョンでのプライマリ・リードレプリカ構成を可能に
    • しかし、ユーザーは シンプルなLitestream を選好
  • LiteFSで得た知見 をLitestreamへフィードバックし、機能強化を継続

LTXファイルフォーマットの導入

  • SQLiteは ページ単位 でデータを管理
  • Litestreamは ページ単位の変更 のみを監視
    • 自動インクリメントの主キーなど、特定のテーブル構造で復元が非効率になる課題
  • LiteFS のために開発された LTXファイルフォーマット をLitestreamにも導入
    • LTXは トランザクション単位 での情報保持と 圧縮・コンパクション をサポート
    • 複数のLTXファイルを階層的に圧縮し、 任意時点への高速リストア を実現
  • Litestream自身が コンパクション処理 を担当し、SQLite本体には依存しない設計

世代管理の廃止と新アーキテクチャ

  • 従来は 世代(generation) という概念で複数Litestreamプロセスの競合を回避
    • 各世代ごとにバックアップを分離管理
  • LTX導入後は世代管理を廃止
    • トランザクションID (TXID)による一意な管理へ移行
    • WALの連続性が途切れた場合は、新たなLTXファイルで再スナップショット
    • 任意時点の状態 をシンプルに参照可能

Litestream v0.5.0へのアップグレード

  • 新バージョンは 旧WALセグメントファイル からのリストアに非対応
  • アップグレード手順は 新バージョンを導入するだけ で完了
    • 旧WALファイルはそのまま保持、 ltxディレクトリ に新LTXファイルを保存
    • 設定ファイルは 後方互換性 を維持
  • 1データベースにつき1レプリカ先 のみサポート(公式仕様に昇格)
    • 複数レプリカの分岐や競合のリスク回避
  • コマンド体系も一部変更
    • 例: litestream wal → litestream ltx
    • TXID 参照の導入

v0.5.0のその他の改善点

  • LTXファイルフォーマットライブラリ の強化
    • ページ単位の圧縮とインデックス付与で 高速なページ抽出 が可能
    • 任意時点クエリや 部分的なデータ取得 機能の拡張基盤
  • CGOの排除modernc.org/sqlite への移行
    • クロスコンパイルやビルド自動化の容易化
  • NATS JetStream 向けレプリカタイプを追加
    • 既存のJetStream利用者は 追加のオブジェクトストレージ不要
  • S3/Google Storage/Azure Blob Storage クライアントの最新版対応
    • 新S3 API サポート

今後の展望

  • Litestream VFS for read replicas を開発中
    • S3から直接ページを読み込みつつ、バックグラウンドでデータベース全体を同期
    • 即時レプリカ起動 と高可用性の実現
  • PoC(Proof of Concept) はすでに完成、今後のリリースに期待

Hackerたちの意見

LiteFS/Litestreamに関する面白い話:

「でも、市場が答えを出した!ユーザーはLitestreamを好んでる。正直、わかるよね。Litestreamは運用も簡単だし、理解もしやすい。だから、またそっちに焦点を戻したんだ。」

それは納得できるね。LiteFSはFUSEを使ってたから、カスタムファイルシステムをどうやって実行してマウントするかを考えなきゃいけなかった。Litestreamは、SQLiteデータベースファイル(とそのWALファイル)を指すだけの単一のコンパイル済みGoバイナリなんだ。

関連情報: Litestream: Revamped (99コメント) https://fly.io/blog/litestream-revamped/ https://news.ycombinator.com/item?id=44045292

Litestreamが大好き!使いやすいし、全然クラッシュしない。systemdユニットサービスとして使うのをおすすめしてるよ。バックアップツールとしてだけじゃなくて、データベースのミラーリングにも使ってる。リードレプリカ機能が楽しみ!

もしかしたら誤解してるかもしれないけど、これを使う理由は何?MySQLやPostgres、他のちゃんとしたデータベースじゃダメなの?SQLiteを無理やりそれらの機能をやらせてる感じがする。

SQLiteだけで十分なら、なんでPostgresを使うの?Postgresは、ユーザーが少なくて高度なデータベース機能がいらないシンプルなアプリにはオーバースペックすぎるよ。

人々が楽しんでいる大きな利点の一つは、アプリケーションサーバーとDBの間のネットワーク遅延がなくなることだよ。SQLiteだと、DBがすぐそばにあって、NVMEで直接接続されてることが多い。これで全てのアクセス遅延が改善されて、他のDBではアンチパターンとされるN+1クエリのようなパターンも可能になるんだ。

よくわからないけど、やったことないから、考えとしては多くの小さな顧客特有のデータベースを持って、それを顧客の近くでSQLiteで動かすってことかな。でも、信頼性のあるSQLiteのユースケースについてもっと詳しい人から話を聞きたいな。

いい質問だね。最近のSQLiteの盛り上がりでは十分に答えられてないと思う。俺の意見では、PostgresやMySQLを簡単に使えるなら、それを使った方がいいよ。DBの使い方には細かいクセがあって、最初は気づかないことも多いから、何回か痛い目にあったことがある。サポートされてない機能や、意味が違ったりすることもあるし。俺的には、どのプロジェクトにも「実験的なもの」に使える予算があって、それを超えると手に負えなくなることが多い。ほとんどのプロジェクトでは、新しいデータベースにお金を使うほどのメリットはないと思う。

一般的な答え(特にFly.ioから)は「エッジコンピューティング/クエリ」だね。MySQLやPostgresにクエリを送ってデータを返してもらうのにはネットワークの遅延があるけど、Litestreamを使えば、全SQLite DBの読み取りレプリカをエッジに置ける。クエリはローカルの読み取りレプリカに対してだけ速くて効率的になる。時間が経つにつれてその読み取りレプリカを更新するのにはネットワークの遅延がまだあるけど、全体の書き込み数に基づいて分散されるから、クエリの数には依存しないし、「最終的に整合性がある」ワークフローではもっと耐障害性が高い(ネットワークが再接続されて、欠けていた書き込みを再生するのを待っている間に、エッジの読み取りレプリカからクエリに答えられる)。SQLiteがバックになっているから、MySQLやPostgresのような大きな(または「ちゃんとした」)データベースから期待されるSQLのフルリレーショナルDBクエリの力もまだ持ってる。

かなり速くて、運用のオーバーヘッドも少ない。それだけ。だけど、ほとんどのアプリはPostgresみたいなクラシックなNティアデータベースアーキテクチャを使うべきだと思う。俺たちもそうしてるし(Litestreamはトークンシステムみたいな一部のものをバックアップしてるけど)。

今はこの考えに近いかな。SQLiteの垂直スケーリングが必要なケースでは、顧客が単にブロックストレージデバイスを設定して定期的にスナップショットを取ってた。Litestreamはほぼ同じアイデアで、クラウドにいることでブロックデバイスのスナップショットを暗黙的に得られる。余計な機械を気にする必要もないし、パスやファイルを忘れることもない。S3へのストリーミングレプリケーションは、リカバリーの観点からはあまり価値がないと思う。他の解決策は数秒以内にホットで準備完了のレプリカをサポートしてるし。

どんなデータベースを使うにしても、何らかのバックアップソリューションが必要だよ。Litestreamはストリーミングバックアップソリューションで、耐久性のためのレプリケーションとしても機能する。MySQLやPostgresなんかは、設定のオーバーヘッドがかなり大きいけど、マネージドデータベースを使うなら別だけど、小規模なデータにはその価格に見合わないと思う。

自分でデータベースを運営して、インシデントやバックアップ、レプリカ、フェイルオーバーとかに悩まされるのを避けるために、安いS3みたいなストレージを使って、アプリケーションをステートレスに運営できるよ。もし自分のためにしっかり管理されたデータベースにアクセスできるなら、多くのユースケースでそっちを選ぶべきだと思う。

これは単一ユーザー向けのアプリのインフラだね。SQLiteはMSAccessみたいなファイルデータベースの代わりに最適だけど、サーバーが落ちたらデータベースも死んじゃうから、データが全部消えちゃう。だから、データベースをサービスとして提供することで、バックエンドを用意しなくても、クオリティ・オブ・ライフを向上させるってわけ。そうじゃないと、プロビジョニングやアップデートとか、面倒なサービスの維持に時間を取られちゃうし、本当に必要なのは自動でバックアップされるファイルか、ローカルファイルシステムの欠点を避けるためのウェブ上のどこかに置かれたファイルだけなんだよね。

みんなにちょっと警告。昔、レガシーなビジネスアプリをAzureに移行する責任があったんだけど、そのアプリはローカルのMSSQLサーバーと一緒に動いてた(Litestreamが使ってるのと同じパターン)。下でも言われてるけど、そのアプリはローカルアクセス(つまり<1msの遅延)を前提に開発されてたから、N+1が大量にあった。これが原因で、別の構成に移行するのがほぼ不可能になった。だから、このスタイルのアプリホスティングが流行らなかったり、ある規模に達したときにこれが行き止まりのストレージになることを心配しているなら、やらない方がいいと思う。そうしないと選択肢がすごく限られちゃうから。ただ、単一のボックスでかなりのところまで行けると思うから、あまり気にしなくてもいいかもね! :)

俺は昔、アプリサーバーとデータベースが同じラックにあった製品で働いてたから、似たような低遅延だった。でも、その製品は成功したから、N+1が何千ものクエリを生み出して、1msが簡単に500ms以上になっちゃった。毎月New Relicを見て、遅いポイントを見つけてた。Railsアプリだったから、N+1に陥るのは簡単だったけど、修正も比較的簡単だった。

N+1って何?

まあ、それを考えると、そんなに大きなトレードオフじゃないよね。だって、君が言ってることは、そんなサービスを使うと自分のコードがどれだけクソか見せつけられるってことじゃん。それはサービスのせいじゃないし。:)

悪いクエリの書き方は、結局いつか痛い目にあうよ。これをこのアプローチの欠点とは呼ばないかな。

一台のボックスでかなりのところまで行けると思うよ。シングルインスタンスで20TB以上のRAMと何百ものコアを使えるから、これはオプションとしてあまり探求されていないかも。さらに、ユーザーやテナントで分けるセルベースのアーキテクチャと組み合わせると、サービス自体を分けるよりももっと面白くなると思う。

2年近くの凍結の後、FlyがLitestreamの開発を再開するのを見れてめっちゃ嬉しい!Litestreamが大好きで、今作るアプリには全部使ってる。彼らは「1日あたり数セント」って宣伝してるけど、実際はそれよりも安いよ。ストレージの必要量によって変わるけど、実際に運用中のアプリで、LitestreamからS3へのレプリケーションは月に2~3セント($0.02~$0.03)しかかからなかったよ。[0] [0] https://mtlynch.io/litestream/#using-logpaste-in-production

俺が見たいのは、シングルライターのSQLiteデータベースをオブジェクトストレージにレプリケートして、めっちゃ安いリードレプリカを作れるシステムだな。誰かそんなのに取り組んでる人いる?そんなシステムには、WALの更新をレプリカに伝播させるサイドチャネル(Kafkaとか)も必要だから、動いてるレプリカが自分でインクリメンタルに更新できて、新鮮な状態を保てるんだ。

Litestreamは基本的にそれだね。ただ、OPの記事の下の方に「次に作る大きな機能は、リードレプリカ用のLitestream VFSです」って書いてある。https://litestream.io/guides/s3/ これってTursoにもだいたい当てはまると思うけど、TursoはバニラじゃなくてSQLite互換のデータベースになりつつあるね。https://docs.turso.tech/features/embedded-replicas/introduct... https://docs.turso.tech/cloud/durability

LiteFSはそれができるけど、カスタムFUSEファイルシステムを動かさないといけないから、Litestreamの方が人気があるんだよね。https://fly.io/docs/litefs/how-it-works/#capturing-sqlite-tr... 今、Litestreamはそれに取り組んでるところで、コードはもうここにあるよ。https://github.com/benbjohnson/litestream/blob/v0.5.0/vfs.go でも、まだ動作するドキュメント化された機能にはなってない。

つまり、OPの「次は何?」ってそのままのことだよね。https://fly.io/blog/litestream-v050-is-here/#whats-next 彼らはもうプロトタイプを持ってるし、結構荒削りだけど。俺はそれを自分のGo SQLiteドライバーに移植してるところで、すでにいくつかの問題にぶつかってる。でも、少なくとも動作する形に持っていくのは可能そうだ。https://github.com/benbjohnson/litestream/issues/772 https://github.com/ncruces/go-sqlite3/compare/main...litestr...

同じことが気になるけど、条件付き書き込みを使って新しいプライマリを自動的に選出できるのかも気になるな(Fly.ioが言ってるように、CASAAS: Compare-and-Swap as a Serviceってやつね)。

これはトランスクリプトなの?

今はその話題にはあまり興味ないけど、これは本当に良く書かれたブログ記事/発表だね。

ありがとう、何が良かったの?

modernc.org/sqliteを品質向上のために選んだのは面白い情報だね。これから新しいプロジェクトでも同じようにしようと思う。NATS Jetstreamのユースケースには興味があるな。応援してるし、Litestreamの素晴らしい仕事を続けてね。

公開されているベンチマーク(と自分のアプリケーション)から見ると、modernc.org/sqliteには小さな(ほとんど気づかない)パフォーマンスのペナルティしかないし、CGOを排除できるメリットの方がはるかに大きいね。今後のプロジェクトでも迷わず使うよ。