Litestreamを使ってSQLiteをS3にレプリケーションしてみた

## はじめに
昨今の円安でAWSコストを抑えるために[Litestream](https://litestream.io/)を試してみました。

## 環境
MacOS Monterey 12.5
Docker 20.10.21
Compose v2.13.0
Rails 7.0.4(Ruby 3.1)
SQLite 3.34.1
Litestream v0.3.8

## Rails(Docker)環境の下準備

```Dockerfile
FROM ruby:3.1
ENV APP_ROOT /usr/src/app
ARG RUBYGEMS_VERSION=3.3.20

RUN apt-get update -qq \
  && apt-get install -y vim sqlite3 nodejs npm \
  && rm -rf /var/lib/apt/lists/* \
  && npm install --global yarn

# Download the static build of Litestream directly into the path & make it executable.
# This is done in the builder and copied as the chmod doubles the size.
ADD https://github.com/benbjohnson/litestream/releases/download/v0.3.8/litestream-v0.3.8-linux-amd64-static.tar.gz /tmp/litestream.tar.gz
RUN tar -C /usr/local/bin -xzf /tmp/litestream.tar.gz

WORKDIR $APP_ROOT
COPY Gemfile $APP_ROOT/
RUN gem update --system ${RUBYGEMS_VERSION} && \
    bundle install

COPY . $APP_ROOT
CMD ["rails", "server", "-b", "0.0.0.0"]
```

```docker-compose.yml
version: '3'
services:
  web:
    build: .
    environment:
      RAILS_ENV: development
      TZ: Asia/Tokyo
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
```

```rb:Gemfile
source 'https://rubygems.org'
gem 'rails', '~>7.0.3'
```

## Rails環境構築
```
$ docker　compose run --rm web rails new . --api
$ docker　compose build
$ docker compose run --rm web rails db:create
$ docker compose up -d
```

`http://localhost:3000`へアクセスして起動確認:ok_hand:
![hello_rails.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61433/af229c9a-a8c8-2dd8-ca7a-2fd075db416c.png)

## S3の準備
[litestreamのガイド](https://litestream.io/guides/s3/)に従いAWSアクセスキーとS3バケットを準備する。ガイド中にもあるがIAMポリシーは　`AmazonS3FullAccess`もしくは以下のカスタムポリシーを指定する。
```
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::<BUCKET>"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET>/*",
                "arn:aws:s3:::<BUCKET>"
            ]
        }
    ]
```

## Litestreamの設定

```litestream.yml
dbs:
  - path: ./db/development.sqlite3
    replicas:
     - url: s3://litestream-sample-f
```
※[litestream.ymlファイル内で環境変数の展開が出来ない](https://github.com/benbjohnson/litestream/discussions/123)ようなのでdocker-compose.yml内で `LITESTREAM_ACCESS_KEY_ID` と `LITESTREAM_SECRET_ACCESS_KEY` へ値を渡すようにしました。

```docker-compose.yml
version: '3'
services:
  web:
    build: .
    environment:
      RAILS_ENV: development
      TZ: Asia/Tokyo
      LITESTREAM_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      LITESTREAM_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
```

## S3へレプリケート

```
$ docker compose run --rm web litestream replicate
litestream v0.3.8
initialized db: /usr/src/app/db/development.sqlite3
replicating to: name="s3" type="s3" bucket="litestream-sample-f" path="." region="" endpoint="" sync-interval=1s
/usr/src/app/db/development.sqlite3: sync: new generation "f8b26dafeae57bf6", no generation exists
/usr/src/app/db/development.sqlite3(s3): snapshot written f8b26dafeae57bf6/00000000
```
無事にレプリケーション出来ました:cracker:


# レプリケーション先(S3)の階層
```
litestream-sample-f/
  └─generations/
      └─f8b26dafeae57bf6/
         ├─snapshots/
         │      00000000.snapshot.lz4
         │
         └─wal/
                00000000_00000000.wal.lz4
                00000001_00000000.wal.lz4
```

## まとめ
設定自体はさほど難しくなくサクッと出来ましたが、FargateやApp Runnerといったフルマネージドサービスでの実運用を考えると色々と配慮する点が多そうだなと感じました。例えば、
:one:コンテナ**起動時**にはS3→コンテナへのリストア
:two:コンテナ**起動中**はコンテナ→S3へのレプリケート
といったコンテナ状態におけるDB挙動の操作やコンテナ複数台起動時には整合性が取れなくなるのでEFSマウントの必要性など考えられるかなと思います。

逆にSLAが低く、多少落としても怒られないような小規模システムであればRDSやAuroraを使うよりもかなりお安くなるので選択肢の一つとして持っておいても損はないかなと思います:santa: