Zenn
💻

Litestreamをローカルで試す

2025/03/16に公開

概要

Litestream試した事が無かったので、試してみたいと思います。今回はローカルだけで動作させてみて雰囲気が分かればと思います。

Litestreamとは?

https://litestream.io/

Litestreamは、SQLiteデータベースのストリーミングレプリケーションを提供するオープンソースのツールです

レプリケーション先としてはAWS S3、Google Cloud Storageなどを設定する事ができます。
一覧は👇で確認できます。

https://litestream.io/guides/#replica-guides

仕組み

独立したバックグラウンド プロセスとして実行され、WAL(Write-Ahead Log)ページを非同期に複製します。

  • WALの役割: SQLiteのWALモードでは、データベースの変更が一時的に-walファイルに記録され、後でメインのデータベースファイルに反映されます
  • シャドウWAL: Litestreamは長時間の読み取りトランザクションを開始し、他のプロセスによるチェックポイントを防ぎます。新しいWALページはシャドウWALと呼ばれる領域にコピーされ、必要に応じて手動でチェックポイントが実行されます
  • スナップショットとジェネレーション: データベースの正確な復元には、スナップショットとその後のすべてのWALフレームが必要です。これらの連続したファイル群をジェネレーションと呼びます
  • 保持期間の管理: 復元時間はスナップショット以降のWALファイルの数とサイズに依存するため、Litestreamは定期的に新しいスナップショットを作成し、古いWALファイルを削除して管理します

ローカルで試す環境の作成

MinIO環境作成

Docker環境でレプリケーション先を公式ドキュメントにもある MinIO を使ってLitestreamを試してみたいと思います。

MinIOとは?
Amazon S3と互換性のあるオブジェクトストレージサーバーです。オープンソースで無償利用でき、クラウドやコンテナ、エッジ環境などさまざまな環境で利用できます

早速 compose.yml を以下内容で作成して、先にMinIOだけ設定しときます。

volumes:
  minio_data:

name: litestream_example # 好きな名前で
services:
  minio:
    image: minio/minio:RELEASE.2025-02-18T16-25-55Z
    volumes:
      - minio_data:/minio/data
    command: server --console-address ':9001' /minio/data
    ports:
      - 9000:9000
      - 9001:9001

起動し、http://localhost:9001 にアクセスするとログイン画面が表示されるかと思います。
今回はローカルで試すだけなのでUsernameとPasswordはデフォルトの minioadmin:minioadmin でログインします。

image1.png

「Create a Bucket」からBucketを作成しときます。今回は「litestream-bucket」という名前で作成しました。

image2.png

Litestream環境作成

次にSQLiteを扱う側の環境を構築していこうと思います。本来は何かしらのアプリケーションが存在し、SQLite DBが更新され、Litestreamでレプリケーションされる想定ですが、今回はSQLite CLIで直接DBを更新し、Litestreamでレプリケーションしようと思います。

LitestreamのDocker Image にSQLiteのバイナリをコピーしてImageを作成してみたいと思います。

FROM keinos/sqlite3:3.49.0 AS builder

FROM litestream/litestream:0.3

COPY --from=builder /usr/bin/sqlite3 /usr/bin/sqlite3
COPY --from=builder /run-test.sh /run-test.sh

RUN /run-test.sh

RUN apk update && \
    apk add --no-cache git bash

SQLiteのDocker Imageには以下のImageを使わせてもらいました。

https://hub.docker.com/r/keinos/sqlite3

最後の正しく sqlite3 コマンドが機能するかテストする run-test.sh もこちらのImage内で使っているものを使っています。
もしテストが失敗した場合は👇の様なメッセージが表示されImageのビルドに失敗します。
=> ERROR [app stage-1 3/3] RUN /run-test.sh

LitestreamのDocker Imageは以下の公式のImageを使っています。

https://hub.docker.com/r/litestream/litestream

compose.yml にLitestreamの設定も追加します。

services:
  # ここから追加
  app:
    build: .
    image: litestream
    entrypoint: ["/bin/sh", "-c"]
    command: ["while sleep 1000; do :; done" ]
    working_dir: /usr/src
  minio:
    # ....

これで docker compose up すると MinIO と Litestream 環境が立ち上がるかと思います。

動作確認

早速Litestreamの挙動を確認していきたいと思います。まず先ほどのDocker環境を起動しLitestreamのコンテナでSQLite databaseを作成します。

作成する内容はLitestreamのドキュメントのこちらの内容で試してみたいと思います。

$ sqlite3 fruits.db
SQLite version 3.44.4 2025-02-19 00:18:53
Enter ".help" for usage hints.
sqlite> CREATE TABLE fruits (name TEXT, color TEXT);
sqlite> INSERT INTO fruits (name, color) VALUES ('apple', 'red');
sqlite> INSERT INTO fruits (name, color) VALUES ('banana', 'yellow');
sqlite> SELECT * FROM fruits;
apple|red
banana|yellow
sqlite> .quit

次にMinIOにアクセスする為のAccessKeyを作成します。http://localhost:9001 にアクセスしサイドメニューの「Access Keys」を選択し「Create access key +」から新しいAccessKeyを作成しときます。

image3.png

次に別ターミナルを立ち上げドキュメント通り litestream replicate fruits.db s3://litestream-bucket.minio:9000/fruits.db の様にコマンドで実行しようと思ったんですが、色々エラーが出てうまくいかなかったので litestream.yml の設定ファイルを作成しそちらを読み込んで実行する形で試してみたいと思います。

# 以下の様なlitestream.ymlをfruits.dbと同じ場所に作成しとく
$ cat litestream.yml
dbs:
  - path: /usr/src/fruits.db
    replicas:
      - type: s3
        bucket: litestream-bucket
        path: fruits.db
        endpoint: http://minio:9000
        region: us-east-1
        access-key-id: xxxxxxxx
        secret-access-key: xxxxxxx
        force-path-style: true
$ litestream replicate -config ./litestream.yml

これで実行されて、👇の様にBucket内に fruits.db が作成されていれば成功です。

image4.png

継続的なレプリケーション

次に継続的にMinIOにバックアップが行われているか試してみたいと思います。litestream replicate -config ./litestream.yml を実行した状態で別ターミナルでレコードを新たに登録してみます。

$ sqlite3 fruits.db
SQLite version 3.44.4 2025-02-19 00:18:53
Enter ".help" for usage hints.
sqlite> INSERT INTO fruits (name, color) VALUES ('grape', 'purple');
sqlite> .quit

この後 litestream replicate -config ./litestream.yml を実行していたターミナルを Ctrl+Cで replicate をストップさせ restore_fruits.db ファイルへMinIOから復元させてみます。

$ litestream restore -o restore_fruits.db -config ./litestream.yml  /usr/src/fruits.db 
$ ls restore*
restore_fruits.db restore_fruits.db.tmp-shm restore_fruits.db.tmp-wal
$ sqlite3 restore_fruits.db
SQLite version 3.44.4 2025-02-19 00:18:53
Enter ".help" for usage hints.
sqlite> .table
_litestream_lock  _litestream_seq   fruits
sqlite> SELECT * FROM fruits;
apple|red
banana|yellow
grape|purple
sqlite> .quit

ちゃんと継続的にレプリケーションされていそうです。ちなみに .tmp-shm.tmp-wal ファイルは litestream replicate を実行すると .shm .wal ファイルが作成されますが .tmp-* ファイルは残ったままになっていました。

試しにMinIOを停止させてみる

実験的にレプリケーションしている状態で、MinIOのコンテナを停止させデータ登録を行ってみます。

$ sqlite3 fruits.db
SQLite version 3.44.4 2025-02-19 00:18:53
Enter ".help" for usage hints.
sqlite> INSERT INTO fruits (name, color) VALUES ('mango', 'orange');
sqlite> .quit

litestream 側で以下のエラーが発生しました。

level=ERROR msg="monitor error" db=/usr/src/fruits.db replica=s3 error="RequestError: send request failed\ncaused by: Get \"http://minio:9000/litestream-bucket?delimiter=%2F&prefix=fruits.db%2Fgenerations%2F5f5a48c771697f1e%2Fsnapshots%2F\": dial tcp: lookup minio on 127.0.0.11:53: no such host"

再度MinIOのコンテナを起動してやると書き込みが完了したログが出たので復元して確認してみます。

$ sqlite3 restore_fruits2.db
SQLite version 3.44.4 2025-02-19 00:18:53
Enter ".help" for usage hints.
sqlite> .table
_litestream_lock  _litestream_seq   fruits
sqlite> SELECT * FROM fruits;
apple|red
banana|yellow
grape|purple
mango|orange
sqlite> .quit

ちゃんと書き込まれていました 👀 裏でずっとリトライを実施している様子でした。

今回はここまでで、次回は実際にどこかデプロイして挙動を試してみたいと思います。

参考URL

https://qiita.com/ydclab_0006/items/9503303f7f3112dc760a

https://jp.navicat.com/company/aboutus/blog/876-sqliteを始めよう

Discussion

ログインするとコメントできます