お世話になっております。
しゃまとんです😊

最近 litestream というソフトウェアが話題(?)ですね。
Go 周辺の界隈の方々だと、そんな流れがきていたのを目にされていたのではないでしょうか。

ということで、なぜか私も琴線に触れたので N 番煎じとなりそうではありますが litestream を試してみました。私もケチケチ星人なので手元で minio を使ってやって行きたいと思います。

litestream + minio の初手については公式とかこの辺が参考になります。

今回の検証リポジトリは shamaton/litestream-sample にありますので、適宜持ってきてくださいませ。

事前準備

local で litestream から minio に連携するには、 https でのアクセスが必要です。 そのため、証明書まわりを適当に作成しておきます。証明書の細かい入力は空 Enter でOKです。(OpenSSL でないとうまくいかないかも)

# make setup
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./tmp/certs/private.key -out ./tmp/certs/public.crt

docker-compose.yml

docker-compose.yml はこのようにしました。

version: '3'
services:

  # s3
  minio:
    image: minio/minio:RELEASE.2022-05-08T23-50-31Z
    environment:
      MINIO_ROOT_USER: "user"
      MINIO_ROOT_PASSWORD: "password"
    command: server /data --console-address :9001
    volumes:
      - ./tmp/minio:/data:cached
      - ./tmp/certs:/root/.minio/certs
    ports:
      - "9000:9000"
      - "9001:9001"

  # bucket creator
  minio-client:
    image: minio/mc:RELEASE.2022-05-09T04-08-26Z
    depends_on:
      - minio
    entrypoint: >
            /bin/sh -c " until (/usr/bin/mc config host add --insecure myminio https://minio:9000 user password) do echo '...waiting...' && sleep 1; done; /usr/bin/mc mb --insecure myminio/mybucket; exit 0; "

  # try to restore database
  litestream-restore:
    image: litestream/litestream:sha-10e25d7c9eb2524254ad533d9bf55a50ec5ac4d7
    entrypoint: /bin/sh
    command: -c "rm -rf /data/*; litestream restore /data/db.sqlite; exit 0;"
    volumes:
      - ./tmp/sqlite:/data
      - ./litestream.yml:/etc/litestream.yml:ro
    depends_on:
      minio-client:
        condition: service_completed_successfully

  # my application
  app:
    build:
      context: .
    volumes:
      - ./tmp/sqlite:/data
    ports:
      - "3000:3000"
    healthcheck:
      test: curl -s http://localhost:3000/health
    depends_on:
      litestream-restore:
        condition: service_completed_successfully

  # replicate database
  litestream-replicate:
    image: litestream/litestream:sha-10e25d7c9eb2524254ad533d9bf55a50ec5ac4d7
    command: replicate
    volumes:
      - ./tmp/sqlite:/data
      - ./litestream.yml:/etc/litestream.yml:ro
    depends_on:
      app:
        condition: service_healthy

minio には証明書を設定するようにしておきます。こうすることで HTTPS で受け付けるようになります。litestream では https でしか接続できないようになっているための対応です。minio とやり取りするコンテナで証明書のエラーがでます。 minio-client のコンテナの挙動にも影響するため、 --insecure の option を設定しています。

litestream.yml には skip-verify を設定しておきます。

dbs:
  - path: /data/db.sqlite
    replicas:
      - type: s3
        bucket: mybucket
        endpoint: minio:9000
        force-path-style: true
        skip-verify: true
        sync-interval: 1s
        access-key-id: user
        secret-access-key: password

services に定義してある順にコンテナが動くようになっています。 ざっくり動作を説明すると、

  1. minio (S3相当) を起動
  2. minio-client で bucket がなければ作成
  3. litestream で minio にデータがあれば restore を行う
  4. sqlite に操作を行う application を起動する
  5. litestream で minio に replication を行う

のようになります。起動時に細かな設定をする必要ありません。

# make up
docker compose up --build

初回の起動を行うと、minio/mybucket には restore するものがないため local でデータベース及びテーブルの作成を行います。

litesteam-sample-minio-client-1          | Bucket created successfully `myminio/mybucket`.
litesteam-sample-app-1                   | driver.Tx(3da7f36c-8b51-4eaf-92be-788b4f3d3874): started
litesteam-sample-app-1                   | Tx(3da7f36c-8b51-4eaf-92be-788b4f3d3874).Query: query=PRAGMA foreign_keys args=[]
litesteam-sample-app-1                   | Tx(3da7f36c-8b51-4eaf-92be-788b4f3d3874).Query: query=SELECT COUNT(*) FROM `sqlite_master` WHERE `type` = ? AND `name` = ? args=[table users]
litesteam-sample-app-1                   | Tx(3da7f36c-8b51-4eaf-92be-788b4f3d3874).Exec: query=CREATE TABLE `users`(`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `age` integer NULL) args=[]
litesteam-sample-app-1                   | Tx(3da7f36c-8b51-4eaf-92be-788b4f3d3874): committed
litesteam-sample-app-1                   | created tables.

シェルをもう一つ用意して確認とユーザーを追加してみます。

curl http://localhost:3000/select
[]
curl http://localhost:3000/insert
{"id":1,"name":"at1653305812","age":11}
curl http://localhost:3000/insert
{"id":2,"name":"at1653305815","age":47}
curl http://localhost:3000/select
[{"id":1,"name":"at1653305812","age":11},{"id":2,"name":"at1653305815","age":47}]

docker compose の output では litestream が replication を行っていることが確認できます。

litesteam-sample-litestream-replicate-1  | litestream 10e25d7c9eb2524254ad533d9bf55a50ec5ac4d7
litesteam-sample-litestream-replicate-1  | initialized db: /data/db.sqlite
litesteam-sample-litestream-replicate-1  | replicating to: name="s3" type="s3" bucket="mybucket" path="" region="" endpoint="minio:9000" sync-interval=1s
litesteam-sample-litestream-replicate-1  | litestream initialization complete
litesteam-sample-litestream-replicate-1  | /data/db.sqlite: init: no wal files available, clearing generation
litesteam-sample-litestream-replicate-1  | /data/db.sqlite: init: no wal files available, clearing generation
litesteam-sample-litestream-replicate-1  | /data/db.sqlite: sync: new generation "a5f4ded6f6104872", no generation exists
litesteam-sample-litestream-replicate-1  | /data/db.sqlite(s3): snapshot written a5f4ded6f6104872/0000000000000000
litesteam-sample-litestream-replicate-1  | /data/db.sqlite(s3): wal segment written: a5f4ded6f6104872/0000000000000000:0000000000000000 sz=16512

次に restore を検証したいので local のデータを消します(rm -rf tmp/sqlite)。そして、再度 docker compose up してみます。 すると、 restore を行うコンテナでログが出力されていることがわかります。(litestream-restoreのコンテナでもローカルのデータを削除しますが敢えて)

litesteam-sample-litestream-restore-1    | restoring snapshot 322c6769aca882c0/0000000000000000 to /data/db.sqlite.tmp
litesteam-sample-litestream-restore-1    | applied wal 322c6769aca882c0/0000000000000000 elapsed=63.853833ms
litesteam-sample-litestream-restore-1    | renaming database from temporary location
litesteam-sample-litestream-restore-1 exited with code 0

restore されデータが存在しているはずなので、確認してみます。

curl http://localhost:3000/select
[{"id":1,"name":"at1653305812","age":11},{"id":2,"name":"at1653305815","age":47}]

確かに restore されていることが確認できました。 サクッとできる感じがなんかよいですね!

今回、試すにあたってなんかうまくいかんな〜、みたいな感じで無駄に時間をかけていたのですが @mattn さんが最近公開された記事がとても参考になりました。

mattn さんに感謝しつつ、終わりとしたいと思います〜
以上です〜👋

余談

どうやら http で扱えるようになったようです。

参考