📝

SQLite WALモード — メリットとデメリット

Write-Ahead LoggingがSQLiteの同時実行性問題をどう解決するか

SQLiteのデフォルトモード(DELETEジャーナル)では、書き込み中に読み取りもブロックされる。この制限を克服したのがWAL(Write-Ahead Logging)モードだ。

基本原理

デフォルトのDELETEモードでは変更をDBファイルに直接書き込み、元データをジャーナルファイルにバックアップする。WALモードでは逆に変更を別の-walファイルに先に記録し、後でDBファイルに反映(チェックポイント)する。

この構造のおかげで、readerは元のDBファイルをそのまま読み、writerはWALファイルに記録するため、互いにブロックしない。

Solid Queueとの相性22

Solid QueueはバックグラウンドジョブをSQLiteに保存し、頻繁にINSERT/UPDATEを実行する。これがWALモードで解決される正確なケースだ。DELETEモードだとジョブ処理中にWebリクエストのDB読み取りがブロックされ、レスポンス遅延が発生していたはず。

WALモードの有効化

方法1: Rails 8(database.yml)— 推奨

config/database.ymlのpragmasにjournal_mode: walを設定する。Rails 8ではデフォルト。

方法2: SQLで直接実行

sqlite3 storage/production.sqlite3 "PRAGMA journal_mode=WAL;"

Railsコンソール: ActiveRecord::Base.connection.execute("PRAGMA journal_mode=WAL")

一度設定するとDBファイルに永久保存される。アプリ再起動してもWALモードが維持される。

WALモードの無効化(DELETEに復帰)

sqlite3 storage/production.sqlite3 "PRAGMA journal_mode=DELETE;"

Rails: ActiveRecord::Base.connection.execute("PRAGMA journal_mode=DELETE")

注意: WAL→DELETE切替時はオープン中のコネクションがない状態で実行すること。アプリを停止してから実行するのが安全。切替後、-wal、-shmファイルは自動削除される。

現在のモード確認

sqlite3 storage/production.sqlite3 "PRAGMA journal_mode;"

Rails: ActiveRecord::Base.connection.execute("PRAGMA journal_mode").first

DELETEジャーナル vs WAL — 動作方式比較

項目 DELETE(デフォルト) WALモード
書き込み方式 DBファイルに直接上書き -walファイルにappend
Read+Write同時 不可(readもブロック) 可能
Write+Write同時 不可 不可(シングルWriter)
BusyException 頻発 大幅減少
管理ファイル数 DB+ジャーナル(2個) DB+-wal+-shm(3個)
NFS互換 互換 非互換(corruptionリスク)

このプロジェクトでの判断

懸念事項 該当状況
複数マシンからのアクセス Fly.io 1台なら問題なし
WALファイル肥大化 SQLiteが自動checkpoint(デフォルト1000ページ)
バックアップ注意 sqlite3 .backupコマンド使用で安全
Solid Queue write競合 WALで解決される正確なケース

結論: デメリットよりメリットが圧倒的。Rails 8のSQLiteガイドでもプロダクションでWALモードを推奨している。

Rails 8 database.yml デフォルト設定

production: primary: <<: *default database: storage/production.sqlite3 pragmas: journal_mode: wal synchronous: normal mmap_size: 134217728 # 128MB journal_size_limit: 67108864 # 64MB busy_timeout: 5000 # 5秒

チェックポイントの種類

種類 動作 ブロッキング
PASSIVE 可能な分だけ反映、干渉なし なし
FULL 全フレーム反映(デフォルト自動) 最小
RESTART FULL+WAL最初から再開 中程度
TRUNCATE RESTART+WALファイルをゼロに 高い

キーポイント

1

WALモード有効化: PRAGMA journal_mode=WAL(Rails 8デフォルト)

2

readerとwriterが同時アクセス可能に — DELETEモードでは不可能だったこと

3

WALファイルが1000ページ(約4MB)に達すると自動checkpointでDBに反映

4

busy_timeout設定でWriterロック待機時間を調整(Rails 8: 5000msデフォルト)

5

バックアップ時に.sqlite3 + -wal + -shmファイル全て含める(sqlite3 .backup推奨)

メリット

  • reader/writer同時アクセス — DELETEモードではwrite中readもブロックされる
  • write性能向上 — 小さなトランザクションが頻繁な場合(Solid Queueが正確にこのケース)
  • BusyException大幅減少 — readerとwriterが互いにブロックしないため
  • fsync呼び出し減少 — checkpoint時のみ必要でI/O負担軽減
  • Rails 8デフォルト設定 — 追加設定なしですぐ適用

デメリット

  • WALファイル肥大化 — checkpointが適時に行われないと-walファイルが膨張し続ける(ディスク使用量増加)
  • NFS/ネットワークファイルシステム非互換 — 複数マシンからのアクセスでcorruptionリスク(Fly.io 1台なら問題なし)
  • 読み取りがごくわずかに遅くなる可能性 — WALファイルも確認が必要なため(実務で体感はほぼなし)
  • バックアップ時注意 — .sqlite3ファイルだけコピーではダメ、-wal、-shmファイルも一緒にコピー必要
  • 3ファイル管理 — DBファイル+-wal+-shm、移動・コピー時に必ずセットで処理

ユースケース

Rails 8 + Solid Queue環境 — ジョブキューのwriteとWebリクエストのreadが同時発生 Solid Cache/Cable — キャッシュ・WebSocketの頻繁な小規模トランザクション 読み取り中心のアプリで間欠的な書き込みが読み取りをブロックしてはいけない場合 Fly.io単一マシンデプロイ — WALのNFS制約が問題にならない環境