はじめに
S3 がファイルシステムとして使えるようになったと聞いたので、SQLite で使えないか調べてみました。その調査結果の記事になります。
まず S3 とファイルシステムの違いを整理しておく
調査を始める前に、そもそも S3 と通常のファイルシステムって何が違うのか整理してみました。
| S3 | ファイルシステム(EFS等) | |
|---|---|---|
| 操作の単位 | オブジェクト全体 | バイト・ページ単位 |
| 部分書き込み | 不可 | 可 |
| ファイルロック | 不可 | 可 |
| 一貫性 | 結果整合性(※) | 即時整合性 |
※ S3 は 2020 年以降 PUT/DELETE の結果整合性は解消されたんですが、並行書き込みの調停機能は持っていません。
SQLite はファイルシステムの機能(バイト単位の書き込みや flock によるロック)に依存して設計されているので、この時点でちょっと嫌な予感がしてきますね。
問題1:部分書き込みができない → データ破損につながる
SQLite はページ単位でファイルを管理している
SQLite はデータベースファイルを 4KB 単位のページ に分割して管理しています。
1件のレコードを更新するときも、変更があったページだけを上書きするので効率的に動作します。
[page 1][page 2][page 3][page 4] ...
↑
ここだけ書き換える
S3 だとどうなるか
S3 はオブジェクト全体を PUT するしかなく、特定のバイト範囲だけを書き換える API がそもそも存在しません。
「page 2 だけ更新」→ 不可能
「ファイル全体を再アップロード」→ 毎回数十〜数百 MB の転送が発生
しかもこれ、「ダウンロード → メモリ上で編集 → アップロード」という手順を踏んでいる間に別のプロセスが同じファイルを触ると、後からアップロードした側が前の変更を丸ごと上書きしてしまうんです。
結果として起きること
- 書き込みのたびにファイル全体の転送が走るので、レイテンシが数秒単位になる
- 並行して書き込みが走ると、片方の変更が消える(サイレントなデータロスト)
問題2:ファイルロックが使えない → 同時書き込みでデータが壊れる
SQLite はファイルロックで同時書き込みを防いでいる
SQLite は複数プロセスからの同時書き込みを防ぐために、OS のファイルロック(flock / fcntl)を使っています。
プロセスA が書き込み中 → ファイルをロック
プロセスB が書き込み試行 → ロックが解除されるまで待機
プロセスA 完了 → ロック解除 → プロセスB が書き込み開始
S3 だとどうなるか
S3 にはファイルロックの概念がありません。
Lambda みたいに複数インスタンスが同時に「ダウンロード → 編集 → アップロード」を走らせると、ロック機構がないので普通に被ります。
Lambda A: ダウンロード (v1)
Lambda B: ダウンロード (v1)
Lambda A: 編集して アップロード (v2)
Lambda B: 編集して アップロード (v2') ← A の変更を上書き
結果として起きること
- A が書き込んだデータが B のアップロードでそのまま消える
- SQLite の内部状態(ページの整合性)が壊れて、データベースファイル自体が破損する
- 破損後は
SQLITE_CORRUPTエラーが出続けて、復旧にはバックアップからのリストアが必要になる
これはかなりつらいですね……
問題3:WAL モードも機能しない → 読み取りまで不安定になる
WAL モードってなに?
SQLite の WAL モードは、書き込みを .db-wal という別ファイルに先行記録することで、書き込み中も読み取りをブロックしない仕組みです。
mock.db ← 確定済みのデータ
mock.db-wal ← 書き込み中のログ
mock.db-shm ← 共有メモリ(プロセス間の調停用)
S3 だとどうなるか
WAL モードは .db・.db-wal・.db-shm の 3 ファイルが原子的かつ一貫した状態で読み書きされることを前提にしています。
でも S3 だとこれらが独立したオブジェクトとして存在するので、こんなことが起きます。
-
.dbだけ更新されて.db-walが古い状態のまま読まれる -
.db-shmが別の Lambda インスタンスと共有されない(それぞれ独立したダウンロードになる)
結果として起きること
- 読み取り結果がおかしくなる(あるはずのデータが見えない、消したはずのデータが見える)
- WAL のチェックポイント処理が正常に終わらず、
.db-walが肥大化し続ける
本題:Mountpoint for Amazon S3 を使っても解決しなかった
2023 年にリリースされた Mountpoint for Amazon S3 は、S3 バケットをローカルのファイルシステムみたいにマウントできるサービスです。
「これなら SQLite も使えるんじゃ?」と期待して調べてみたんですが、こんな制約がありました。
| 操作 | Mountpoint の対応状況 |
|---|---|
| 新規ファイルの作成 | ○ |
| ファイルの読み取り | ○ |
| 既存ファイルへの上書き | × 非対応 |
| ファイルの一部を書き換える | × 非対応 |
ファイルロック(flock) |
× 非対応 |
SQLite の書き込みに必要な「既存ファイルのページ単位の上書き」と「ファイルロック」がどちらも非対応……。
つまり、Mountpoint は S3 をファイルシステムっぽく見せてくれるだけで、S3 自体の制約は何も変わらないんですよね。ちょっと期待しただけに残念でした。
まとめ
| 問題 | 原因 | 結果 |
|---|---|---|
| 部分書き込み不可 | S3 はオブジェクト全体を PUT する設計 | 書き込みが遅い・並行書き込みでデータロスト |
| ファイルロック不可 | S3 にロック機構がない | 同時書き込みでデータベースファイル破損 |
| WAL 非対応 | 3 ファイルの原子的な操作が保証されない | 読み取り不整合・WAL の肥大化 |
| Mountpoint でも同じ | 上書き・ロックが非対応 | 根本的な解決にならない |
SQLite はファイルシステムのプリミティブに深く依存しているため、S3(およびその上の抽象)での書き込みは無理でした。