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ファイルをゼロに | 高い |
キーポイント
WALモード有効化: PRAGMA journal_mode=WAL(Rails 8デフォルト)
readerとwriterが同時アクセス可能に — DELETEモードでは不可能だったこと
WALファイルが1000ページ(約4MB)に達すると自動checkpointでDBに反映
busy_timeout設定でWriterロック待機時間を調整(Rails 8: 5000msデフォルト)
バックアップ時に.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、移動・コピー時に必ずセットで処理