今日のゴール
- Write-Ahead Log(WAL)の概念を理解する
- Nindaの永続化機能を使いこなす
- クラッシュからの復旧方法を学ぶ
なぜ永続化が必要か
インメモリのタプルスペースは高速ですが、プロセスが終了するとデータが失われます。
| 状況 | インメモリのみ | WAL有効 |
|---|---|---|
| 正常終了 | データ消失 | 復旧可能 |
| クラッシュ | データ消失 | 復旧可能 |
| 電源断 | データ消失 | 復旧可能 |
WALの基本原理
WAL(Write-Ahead Log)は、すべての操作を先にログファイルに記録してからメモリに反映する仕組みです。
ポイント:
- ログ書き込みが完了してからメモリに反映
- クラッシュ時はログを再生して状態を復元
WALの有効化
# WALパスを指定して作成
let manager = newSpaceManager("/tmp/ninda_data.wal")
let ts = manager.space()
# 通常通り操作(自動的に永続化)
await ts.writeAsync(toTuple(strVal("important"), intVal(42)))
# 明示的にフラッシュ(バッファをディスクに書き出し)
manager.flush()
# シャットダウン
manager.shutdown()
WALなしとの比較
# WALなし(テスト用、高速だがデータ消失のリスク)
let manager = newSpaceManager()
# WALあり(本番環境推奨)
let manager = newSpaceManager("/var/lib/ninda/data.wal")
復旧プロセス
再起動時、WALから自動的にデータが復元されます。
# 1回目のセッション
let manager1 = newSpaceManager("/tmp/test.wal")
await manager1.space().writeAsync(toTuple(strVal("data"), intVal(100)))
manager1.shutdown()
# 2回目のセッション(復旧)
let manager2 = newSpaceManager("/tmp/test.wal")
let result = await manager2.space().tryReadAsync(pattern)
# → 前回のデータが復元されている
WALエントリ形式
各操作は以下の形式でログに記録されます:
| OpType | 説明 |
|---|---|
walWrite |
タプル書き込み |
walTake |
タプル削除 |
walExpire |
有効期限切れ |
コンパクション
削除されたタプルのログエントリを圧縮してディスク使用量を削減します。
# 大量のデータを書き込み → 削除
for i in 1..1000:
await ts.writeAsync(toTuple(strVal("temp"), intVal(i)))
discard await ts.takeAllAsync(toPattern(strVal("temp"), nilValue()))
# コンパクション実行
manager.compact()
コンパクション前後:
- 前: write(1), write(2), ..., take(1), take(2), ...(2000エントリ)
- 後: (0エントリ、有効なタプルなし)
ベストプラクティス
| プラクティス | 理由 |
|---|---|
| 重要なデータのみ永続化 | パフォーマンスとディスク使用量のバランス |
| 定期的なフラッシュ | バッファ損失を防ぐ |
| 定期的なコンパクション | ディスク使用量を抑える |
| WALファイルのバックアップ | 災害復旧に備える |
# 推奨設定例
const FLUSH_INTERVAL = 1000 # 1秒ごと
const COMPACT_INTERVAL = 3600000 # 1時間ごと
まとめ
学んだこと
| トピック | ポイント |
|---|---|
| WAL | 操作を先にログに記録してから反映 |
| 自動復旧 | 再起動時にWALを再生 |
| コンパクション | 不要なログエントリを圧縮 |
| ベストプラクティス | 定期フラッシュ、定期コンパクション |
いつ使うか
| 場面 | 推奨 |
|---|---|
| 本番環境 | WAL有効 |
| テスト・開発 | WALなし(高速) |
| 一時データのみ | WALなし |
演習問題
問題16-1: 復旧テスト
WALを有効にしてデータを書き込み、プロセスを強制終了(Ctrl+C)した後、再起動してデータが復元されることを確認してください。
ヒント
-
manager.flush()を呼んでからCtrl+Cで終了 - 同じWALパスで再度
newSpaceManagerを作成 -
tryReadAsyncで前回のデータが存在するか確認 -
shutdown()を呼ばずに終了した場合もWALから復旧できる
問題16-2: コンパクション効果測定
1000件のタプルを書き込み→削除→コンパクションを行い、WALファイルサイズの変化を測定してください。
ヒント
-
getFileSize("/tmp/test.wal")でファイルサイズを取得 - 書き込み後、削除後、コンパクション後の3時点で測定
- コンパクション後はサイズが大幅に減少するはず
- Nimの
osモジュールのgetFileSizeを使用
問題16-3: 定期フラッシュ
バックグラウンドで1秒ごとに自動フラッシュするワーカーを実装してください。
ヒント
-
asyncCheckでバックグラウンドタスクを起動 -
while trueループでsleepAsync(1000)とflush()を繰り返す - 終了フラグを用意してグレースフルシャットダウンに対応
- アトミックなブール値または
Channelで終了シグナルを送る