💸

Litestream で思ったよりお金が掛かっているとき、もしかしたら昔のデータが消えていないかもよ

2023/04/25に公開

これはなに

RDB 使いたいけど予算がない!!!ってときに便利な litestream ですが、なんかレプリケーションのコストが高かったので調査したログです。

以下 GCP 環境ですが、ほかでも類似の事例があるかも。

TL;DR

generation の retention が効いていなかった。対策は以下の通り。

  1. 利用する IAM ロールにデータ削除の権限が付いているか確認する
  2. retention-check-interval を短くする
  3. retention を短くする

https://litestream.io/reference/config/#retention-period

そもそも Litestream on GCP の使い方

下記参照。

https://litestream.io/guides/gcs/
https://zenn.dev/walf443/articles/4059d4a3787cf3
https://memo.yammer.jp/posts/cloud-run-litestream
https://qiita.com/faable01/items/ac7418d671c6db5b966f

本題: GCS で思った以上にお金がかかる

対象のシステムは以下の構成です。

  • Cloud Run 上にデプロイされたアプリケーションで SQLite を読み書きする
  • SQLite は Litestream で GCS にレプリケーションされる
  • 1時間に1回 Cloud Scheduler で Cloud Run エンドポイントが叩かれる

Cloud Run の起動時間も大したことなく、データ量もほとんどありません (スナップショットが ~100MB くらい)。

しかし、実際に稼働させていると以下の課題が出てきました。

  • 毎日 GCS (Regional Standard Class A Operations) に対して 10円程度ずつ課金される
  • 1日 0.3円ずつぐらいコストが上昇傾向にある

Regional Standard Class A Operations ってなんやねん

https://cloud.google.com/storage/pricing?hl=ja によると、以下のオペレーションが該当するようです。

けっこうな操作が該当しており、そもそも何の操作をしているのかを掴まないと厳しそうです。

ちなみに 1000 ops ごとに $0.005, だいたい 0.7 円くらいです。
1日10円くらい掛かっているので、15000 ops くらい行われていそうです。そんなに???

Audit Logs を有効にする

ということで、Audit Logs を有効にし、どんなオペレーションが走っているのか確認してみます。

https://cloud.google.com/logging/docs/audit/configure-data-access?hl=ja
https://cloud.google.com/storage/docs/audit-logging?hl=ja#available-logs

やることは

  1. IAM と管理 → 監査ログ を開く
  2. Google Cloud Storage をチェック、「管理読み取り」「データ読み取り」「データ書き込み」にチェックを入れて保存をクリック

GCS の読み書きオペレーションごとに Cloud Logging にログが流れるので、その分お金が掛かります。後で止めるのを忘れずに

Audit Logs を確認する

ログが貯まった頃合いで Cloud Logging を確認します。

大半が storage.object.list です。どういうことだ?

GCS を見に行く

ここでハッとして GCS を見に行ったのですが、レプリケーション先のバケット内のディレクトリ (Litestream の用語としては generations と呼ぶやつ) が大量に存在していることに気付きました。

ファイル構造としては、たとえば以下のようになっています。

[レプリケーション先ファイル名]/generations/
├── 150037d2828fc967/
│   ├── snapshots/ 
│   │   └── 00000000.snapshot.lz4
│   └── wal/
│       ├── 00000000_00000000.wal.lz4
│       ...
├── 1c85baf85db8e169/
│   ....
├── 45ab8cbb0e236995/
│   ....
├── 468b4bf4c44ecd32/
...

上記の 150037d2828fc967 みたいなディレクトリが 500 個くらいありました。

もし Cloud Run がコールドスタートするたびに毎回上記のディレクトリを舐めているとすると、確かにオペレーション回数が嵩むのも納得できます。

Litestream の generation の取扱

これってそもそも正しい動作なのか?ということで Litestream のドキュメントを見に行くと、

https://litestream.io/reference/config/#retention-period

Replicas maintain a snapshot of the database as well as a contiguous sequence of SQLite WAL page updates. These updates take up space so new snapshots are created and old WAL files are dropped through a process called “retention”.
The default retention period is 24h. You can change that with the retention field. Retention is enforced periodically and defaults to every 1h. This can be changed with the retention-check-interval field.

ということで retention というプロセスがあり、しかも 24H を超えると削除してくれるらしいです。これが働いていれば何も問題がないわけですが、どういうわけか動いていないですね?

retention が働かないのはなぜ?

様々調べていくと、概ね以下のような要因がありそうです。

  • 削除権限がない
    • そもそもオブジェクトの削除権限がなければ削除できない、それはそう
  • retention-check-interval が長すぎる
    • 起動後から retention-check-interval が経つまで削除されない?
  • retention が長すぎる
    • retention period が長すぎる?

今回のシステムでは GCS に対する強めの権限を付けていたので、retention-check-intervalretention かなぁというアタリを付けました。

特に今回のシステムでは Cloud Run インスタンスは Cloud Scheduler に kick されたあとはすぐに動作を停止するため、retention-check-interval は大いに関係しそうだなと感じました。

litestream.yml を編集して解決

以下のように設定を編集しました。

dbs:
  - path: {データベースファイルのパス}
    replicas:
      - url: gcs://{バケット名}/{パス}
+       retention: 12h
+       retention-check-interval: 3m
+       snapshot-interval: 4h
+       sync-interval: 30s

本題の retention-check-interval は、少なくとも 3 分くらいはコンテナが立ち上がっていそうだなということで 3m にセットしました。その他のパラメータは気分です。

上記設定を入れ込んでデプロイしたあと、次の Cloud Scheduler 発火タイミングで古い generation がごそっと削除され、最終的にバケット内の generation が 20 未満まで減少しました。

おわりに

ということで、立ってすぐ死ぬような環境だったら retention-check-interval を気にしてみるといいかもよというお話でした。
また、冒頭で説明したとおり、当然 IAM ロールにストレージオブジェクトの削除権限が必要です。併せて確認をどうぞ。

Discussion