docs: revise storage strategy with abundant RAM context (7GB/5GB avail)

Page cache absorbs repeated ReadLongTerm() reads - actual cost is
string allocation not disk I/O. NAND write count is unaffected by RAM.
ParsedPlan in-memory residency becomes trivial. Extend write-behind
window to 30min+shutdown. Reorder implementation priority: stats.json
write-behind first (98% reduction, simplest), then sessions write-
behind, then ParsedPlan residency (resolves D-1/D-6 together).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-24 13:19:35 +09:00
parent a52434e5c2
commit a5db687abe

View file

@ -403,3 +403,54 @@ RecordUsage() / RecordPrompt() → in-memory のみ更新
- **クラッシュ後のリカバリ**: dirty データが失われた場合、セッション履歴は最後のチェックポイント以降が消える。ユーザーへの通知が必要か検討。
- **フラッシュ中の競合**: フラッシュgoroutineと `Save()` の同時呼び出しを防ぐため、既存の mutex を流用。
- **MEMORY.md のターンキャッシュ**: `edit_file` ツールが MEMORY.md を書き込んだ場合、**同ターン内のキャッシュを無効化**する仕組みが必要(`MemoryStore.InvalidateCache()` を edit_file のコールバックから呼ぶなど)。そうしないと同ターン内の後続の `GetPlanStatus()` などが古いキャッシュを読む。
### RAM が潤沢な場合の設計変更
対象デバイスは RAM 7GB / available 5GB 超(例: `free -m` で available ~5260MB
この前提が上記の各戦略に与える影響を整理する。
#### 読み取りレイテンシの実態
`buff/cache` が 4.6GB 程度を占めるということは、OS のページキャッシュが空き RAM をほぼ全て使い切っている状態。`ReadLongTerm()` の複数回呼び出しは**実際にはディスクアクセスしていない**2回目以降はページキャッシュヒット、マイクロ秒オーダー
読み取りの実コストは「ディスクI/O」ではなく「**syscall + 文字列 Split/Join のアロケーション**」。ターンスコープキャッシュの主な効果はレイテンシ削減より**GC 圧力の軽減**に変わる。
#### 書き込み寿命はRAMに影響されない
書き込みは `O_SYNC` ではなくても `os.Rename` でアトミックに書かれるが、カーネルはライトバックキャッシュを経由して最終的に SD に書く。ページキャッシュが書き込みを吸収しても**最終的な NAND への書き込み回数は変わらない**。書き込み削減の優先度は変わらず高い。
#### インメモリ表現の常駐が現実的になる
RAM が逼迫していない場合、`MemoryStore``*ParsedPlan` をフィールドとして持たせる設計D-1, D-6 の解決策のメモリコストは無視できる。MEMORY.md が数KB〜数十KB であっても、パース済み構造体として常駐させて差し支えない。
```go
// 設計案: MemoryStore がパース済み状態を保持
type MemoryStore struct {
workspace string
memoryFile string
mu sync.RWMutex
cached *ParsedPlan // nil = 未ロード
cachedAt time.Time
}
// edit_file ツールが書き込んだ後に InvalidateCache() を呼ぶことで
// 同ターン内の再読み込みをトリガーできる
```
これにより `GetMemoryContext()` 内の `ReadLongTerm()` 多重呼び出し問題D-1と、
public メソッドが `content` を隠す問題D-6が同時に解消される。
#### write-behind 窓をさらに広げられる
RAM が十分にあるため、セッションデータを長時間インメモリに保持するリスクがない。
write-behind の戦略を「5分 or 20件」から**「グレースフルシャットダウン時のみ + 30分タイマー」**に緩和しても、
クラッシュ時の損失最大30分の会話と実装の単純さのトレードオフとして許容できる可能性がある。
プロジェクトの可用性要件に応じて判断する。
#### 優先実装順の修正
RAM 制約がない前提での推奨順:
1. **`stats.json` の write-behind** — 実装が最も単純タイマー1本追加、書き込み削減率が最大98%
2. **`sessions/*.json` の write-behind** — セッション単位の dirty フラグ + シャットダウンフック
3. **`MemoryStore` への `*ParsedPlan` 常駐** — D-1/D-6 を根本解決、読み取りアロケーションをゼロに
4. **ターンスコープキャッシュ** — 3 が実装されれば自然に解決するため不要になる可能性あり