4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Litestream を動かしてみた (ベンチマークも少し)

Last updated at Posted at 2022-06-01

はじめに

GW 中 Twitter で話題になっていた Litestream という SQLite のレプリケーションツールについて調べてみました。

Litestream は以下のような特徴があります。

  • OSS
  • セットアップが容易
  • WAL ファイル単位でのレプリケーション
    • 指定した時刻の内容のデータベースにリカバリ可能
  • レプリケーション先に Amazon S3 を使用可能
    • S3 互換のオブジェクトストレージも使用可能
    • ファイルシステムや SFTP も使用可能

環境

今回は以下の環境を使用しました。

  • DBサーバー
    • OS: CentOS 7
    • SQLite3 : 標準の yum リポジトリからインストール
    • Litestream : 2022/5/17 時点の main ブランチ (benbjohnson/litestream@98673c6)
  • レプリケーション先
    • MinIO - S3 互換のオブジェクトストレージ
      • DB サーバーとは別のホストにセットアップ

※ Litestream を使って、古くから運用している複数の Trac プロジェクトの DB (SQLite) に一括してクエリを投げるツールを作りたかったので、かなりレガシーな構成を使ってしまいました。

インストールメモなど

MinIO

公式サイトのインストール手順 (Linux 向け) に従い、以下の手順でインストール & 実行しました。

$ wget https://dl.min.io/server/minio/release/linux-amd64/minio
$ cat << EOF > run.sh
> #!/bin/sh
>
> MINIO_ROOT_USER=admin
> MINIO_ROOT_PASSWORD=password
> ./minio server ../data --console-address ":9001"
> EOF

$ chmod u+x minio run.sh
$ ./run.sh

この方法で起動するとコンソールにストレージ (API) と管理用 Web 画面の URL/認証情報が表示されます。

管理画面で bucket を作成しておきます。

Litestream のビルド

本稿記述時の最新の安定板は v0.3.8 ですが、v0.4.0 が beta2 まで来ていたので main ブランチの最新をソースからビルドしてみました。

  1. git clone https://github.com/benbjohnson/litestream.git
  2. go1.18.2.linux-amd64.tar.gz を取得して /opt/go1.18.2 に展開
  3. /opt/go1.18.2/bin に PATH を通して go install ./cmd/litestream/ を実施!
    → CentOS7 の git が古くてビルドエラー!!
  4. git を 2 系に更新
    see 【2021年確認済み】centos7系にgit2系をインストールする(依存関係エラー解消版) | きなこもちエクステンド!
  5. 再度 go install ./cmd/litestream/
    ~/go/bin/litestream がビルドされた

yum groupinstall "Development Tools" してる環境だったので上記の手順でビルドができました。
開発環境未導入の場合は他にも必要なパッケージがあるかもしれません。

Hello, world

チュートリアルの内容を参考に、別ホストに立てたオブジェクトストレージ (MinIO) へのレプリケーションとリストアを動かしてみます。

チュートリアルの後でベンチマーク取得や PITR を試すことも考慮して、少し真面目に環境整備しつつ試してみました。

ディレクトリ構成

以下のような構成で実行環境を作ります。

path/to/work
├─conf      litestream 設定ファイル配置先
├─db        SQLite データベースファイル格納先
├─restore   リストア作業用
└─script    起動/データ生成などのスクリプト置き場

データベースファイルの作成

Getting Started に記載されているサンプルのデータベースファイルを作成します。
作業中何度もデータベースを再構築することが予想されるので、db ディレクトリに以下の Makefile を用意して、db ディレクトリ内の *.sql ファイルから *.db ファイルを作成可能にしました。

.SUFFIXES:
.SUFFIXES: .sql .db

SQLS=$(shell ls *.sql)
DBS=$(SQLS:%.sql=%.db)

.sql.db:
        @cat $< | sqlite3 `basename $< .sql`.db
        @ls -l `basename $< .sql`.db

all:    $(DBS)

clean:
        @rm -vrf *.db *.db-shm *.db-wal *.db-litestream

サンプルデータベース構築用の .sql ファイルを作成後 make を実行します。

$ vi helloworld.sql
$ cat helloworld.sql
CREATE TABLE fruits (name TEXT, color TEXT);

INSERT INTO fruits (name, color) VALUES ('apple', 'red');
INSERT INTO fruits (name, color) VALUES ('banana', 'yellow');

$ make
-rw-r--r-- 1 lab lab 2048  5月 25 13:29 helloworld.db

設定ファイルの用意

レプリケーション対象のデータベースファイルやレプリケーション先の指定は litestream の起動引数で与えることもできますが、設定内容の差分が取りやすいよう -config オプションで設定ファイルを指定して起動する方法を使ってみます。

今回のようにシンプルな方法で MinIO を起動した場合はレプリケーション先定義を url: s3://~ で行うのではなく、以下のように type, endpoint 等を個別に設定した方がよいようです。

# $ cat conf/helloworld.conf
dbs:
  - path: /home/lab/work/db/helloworld.db
    replicas:
      - type: s3
        endpoint: http://xxx.xxx.xxx.xxx:9000
        bucket: test-bucket
        path: helloworld.db
        forcePathStyle: true
        access-key-id: minioadmin
        secret-access-key: minioadmin

起動 ~ DB 更新 ~ リストア

litestream を起動します。

$ ~/go/bin/litestream replicate -config ./conf/helloworld.conf

データベースを更新します。

$ sqlite3 db/helloworld.db
sqlite> INSERT INTO fruits (name, color) VALUES ('grape', 'purple');
sqlite> .quit

リストアします。-config オプション指定時はデータベースファイルが未指定でも動作することを期待しましたが、今回使用したバージョンでは引数エラーになりました。

$ ~/go/bin/litestream restore -o restore/restore_test.db -config ./conf/helloworld.conf
database path or replica URL required

$ ~/go/bin/litestream restore -o restore/restore_test.db -config ./conf/helloworld.conf db/helloworld.db
2022/05/25 13:41:56.403709 restoring snapshot 646d4479d1fa4d35/0000000000000000 to restore/restore_test.db.tmp
2022/05/25 13:41:56.452500 applied wal 646d4479d1fa4d35/0000000000000000 elapsed=10.050914ms
2022/05/25 13:41:56.458167 applied wal 646d4479d1fa4d35/0000000000000001 elapsed=5.653181ms
2022/05/25 13:41:56.458175 renaming database from temporary location

$ sqlite3 restore/restore_test.db 'SELECT * FROM fruits'
apple|red
banana|yellow
grape|purple

動かして分かったこと

journal_mode=WAL の設定は自動で行なわれる

公式サイトの Tips & Caveats - Litestream にはレプリケーションを行うためには WAL を使用するモードで SQLite を実行する必要があると記載されています。

今回使用した環境ではデータベースファイルを新規作成すると WAL を使用しないモード (jorunal_mode=DELETE) になっていたのですが、Hello, world 実行後のデータベースファイルを確認すると WAL を使用するモードに変更されていました。

litestream が DB ファイルを開いた時に モードを変更してくれる ようです。

Litestream が作成するテーブル・ファイル

レプリケーションを実行すると、対象データベースに _litestream で始まるふたつのテーブルが作成されてました。

$ sqlite3 db/helloworld.db '.schema'
CREATE TABLE fruits (name TEXT, color TEXT);
CREATE TABLE _litestream_seq (id INTEGER PRIMARY KEY, seq INTEGER);
CREATE TABLE _litestream_lock (id INTEGER);

また、データベースファイルと同じディレクトリに以下のファイル/ディレクトリが作成されます。
レプリケーションを実行したデータベースを再構築する場合、データベースファイルに加えてこれらのファイル/ディレクトリも削除する必要があります。

db/helloworld.db-shm
db/helloworld.db-wal
helloworld.db-litestream/

レプリケーションデータの構造・管理

レプリケーション先の MinIO の bucket 内は以下の構造になっていました。

test-bucket/                    : bucket 名
└─ helloworld.db                : conf ファイルの replicas/path に指定した文字列 
    └─generations/
        ├─0a40f6ba782b4810/
        │  ├─snapshots/
        │  │      0000000000000000.snapshot.lz4
        │  │
        │  └─wal/
        │      ├─0000000000000000/
        │      │      0000000000000000.wal.lz4
        │      │      0000000000001080.wal.lz4
        │      │
        │      └─0000000000000001/
        │              0000000000000000.wal.lz4
        │
        └─73384c2e8925edf1/
  • litestream 起動時 generations 以下にデータ保存ディレクトリが作成される。(ディレクトリ名はランダムな16桁文字列で構成される。)
    • 起動時以外の作成タイミングは未調査
  • generation ディレクトリ以下には snapshot (データベース全体のバックアップ) と WAL データが保存されている
  • litestream 起動時、および 24 時間に 1 回 snapshot が取得される (デフォルト設定の場合)
  • 次回の snapshot 取得までは WAL ファイルの差分が取得される
  • snapshot の取得周期やバックアップデータの保持に関するパラメータは以下の通り。
    • snapshot-interval=5m, retention=1h 設定して連続動作させたが 1h 以上過去のバックアップデータが残存した
    • retention-check-interval > retention の場合は確認処理が走らない? (未調査)
パラメータ名 機能 デフォルト値
snapshot-interval snapshot の取得周期 24h
retention バックアップデータの保持期間 24h
retention-check-interval 保持期間切れの確認周期 1h

詳細は Snapshots & generationsLitestream入門 を参照ください。

レプリケーション中のオーバーヘッドはどの程度?

SQLite のパフォーマンスチューニング、または DBIx::Sunny 0.16 の話 で公開されているベンチマークコードを参考に、litestream のオーバーヘッドを測定してみます。

上記のベンチマークでは

  • (1) journal_mode=DELETE の場合
  • jorunal_mode=WAL の場合で
    • (2) synchronous=FULL
    • (3) synchronous=NORMAL
    • (4) synchronous=OFF

の 4 パターンで計測を行なっていました。(synchronous については SQLite のノウハウ を参照)

Tips & Caveats で synchronous は NORMAL に設定することを検討する言及があったので、本稿では以下の2パターンでレプリケーション有無でのパフォーマンスを計測することにしました。

  1. jorunal_mode=WAL && synchronous=FULL
  2. jorunal_mode=WAL && synchronous=NORMAL

ベンチマークスクリプトの修正

参考にさせて頂いたエントリで使用されていたベンチマークスクリプトに以下の修正を加えたものを使用しました。

6d5
< use File::Temp qw/tempfile/;
9,10c8,9
< for my $n ( 1..4 ) {
<     (undef ,my $db) = tempfile(OPEN => 0);
---
> for my $n ( 1..2 ) {
>     my $db = "db/case${n}.db";
28,30c27,28
<             $self->stash->{dbh}->do('PRAGMA journal_mode = WAL') if $n > 1;
<             $self->stash->{dbh}->do('PRAGMA synchronous = NORMAL') if $n == 3;
<             $self->stash->{dbh}->do('PRAGMA synchronous = OFF') if $n > 3;
---
>             $self->stash->{dbh}->do('PRAGMA journal_mode = WAL');
>             $self->stash->{dbh}->do('PRAGMA synchronous = NORMAL') if $n == 2;
45c43
<         concurrency => 4,
---
>         concurrency => 2,

計測結果

上記のスクリプトを使用した処理速度 (正確には 1 秒あたりの INSERT レコード数) の測定結果は以下の通りです。データのディスクへの反映を同期的に行なう FULL の場合は 15% 程度の性能劣化が見られましたが、NORMAL では有意な性能劣化は見られませんでした。

Litestream FULL NORMAL
なし 336.273 7276.586
あり 286.998 7291.277
85.3% 100%

この結果だけで性能は評価できませんが、本格的な検証をする際は NORMAL に設定しておいた方がよいことは間違いなさそうです。
synchronous=NORMAL の設定は永続化されないため、SQLite データベースファイルへ接続した際に毎回実施する必要がある点に注意が必要です。

まとめ

チュートリアルの内容を中心に、オブジェクトストレージへのレプリケーションとリストア (全体と時刻指定 (PITR)) を試してみて特徴を把握することができました。

今回はコンソールから起動していましたが Infrastructure guides には systemd 配下のサービスとして起動する方法や Docker/k8s での起動方法、Windows サービスとしての起動方法も説明されていており、様々な場面で運用できそうです。
(コンテナ構成については LiteStream をサイドカー構成にしたデータベース永続化 が非常に参考になります。)

今後は リードレプリカの実装が予定されてる ようなので、使えるようになったら試してみたいと思います。
( Use fsnotify #369 がマージされたら Windows でのビルドと実行も試してみたい。)

参考リンク

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?