← Back to PRs

#19528: feat: directory-per-session store to eliminate monolithic JSON bottleneck

by binary64 open 2026-02-17 22:44 View on GitHub →
size: L
## Problem The session store is a single monolithic JSON file (`sessions.json`) that grows to 80MB+ with 650+ sessions. Every read/write operation: - Parses the entire file (~80ms+ for `JSON.parse` alone) - Serializes and rewrites the entire file atomically - Requires global file locking, causing contention and orphaned `.tmp` files This is O(n) for what should be O(1) operations. ## Solution: Directory-per-session layout ``` sessions.d/ agent--main--telegram--direct--james/ meta.json # ~200 bytes agent--main--cron--14bbd904/ meta.json ``` ### Key changes in `src/config/sessions/store.ts`: - **Auto-detection:** If `sessions.d/` directory exists, uses directory mode. Otherwise falls back to legacy JSON (backward compatible). - **Diff-based writes:** `updateSessionStore()` snapshots the store before mutation, then only writes changed entries and deletes removed ones — no more full rewrites. - **Per-entry I/O:** `readSessionUpdatedAt()` reads a single `meta.json` instead of parsing the entire store. - **Atomic writes:** Each `meta.json` uses temp-file + rename, same as the existing pattern. - **Migration:** `migrateSessionStoreToDirectory(storePath)` splits an existing JSON file into the directory layout, backs up the original file, and is safe to call multiple times. ### Performance characteristics: | Operation | Before (JSON) | After (Directory) | |-----------|--------------|-------------------| | Read one session | O(n) parse entire file | O(1) read single meta.json | | Write one session | O(n) serialize + write all | O(1) write single meta.json | | List sessions | O(n) parse | O(1) readdir | | Locking contention | Global file lock | Per-session potential | ### Migration path: 1. No breaking changes — existing `sessions.json` continues working as-is 2. Call `migrateSessionStoreToDirectory(storePath)` to opt in 3. After migration, original JSON is backed up as `sessions.json.pre-directory-migration.<timestamp>` 4. All subsequent operations use directory mode automatically ### New exports: - `migrateSessionStoreToDirectory(storePath)` — trigger migration - `resolveSessionStoreDir(storePath)` — get directory path - `sanitizeSessionKey(key)` / `desanitizeSessionKey(dirName)` — key ↔ dirname conversion ## Tests Added `store.directory.test.ts` with 395 lines covering: - Key sanitization round-trips - Directory store path resolution - Migration from JSON to directory (including backup verification) - Load/save/update in directory mode - Diff-based writes (only changed entries written) - Cache invalidation for directory stores - Maintenance (pruning, capping) in directory mode <!-- greptile_comment --> <h3>Greptile Summary</h3> Migrates session storage from monolithic JSON to directory-per-session layout, addressing O(n) bottleneck with 80MB+ files. Implementation adds auto-detection, diff-based writes, per-entry I/O, atomic operations, and backward-compatible migration. **Key changes:** - New directory layout (`sessions.d/`) with per-session `meta.json` files (~200 bytes each) - Diff-based writes in `updateSessionStore()` - only changed/removed entries are written - `readSessionUpdatedAt()` now reads single file instead of parsing entire store - Cache invalidation properly handles directory stores via explicit clearing on writes - Migration function `migrateSessionStoreToDirectory()` with automatic backup - Comprehensive test coverage (395 lines) for migration, CRUD ops, concurrent writes, and cache behavior **Technical review:** - Atomic writes use temp-file + rename pattern correctly - Lock-based serialization prevents concurrent write conflicts - Cache is explicitly invalidated on writes, avoiding stale data - Directory mtime caching is safe due to explicit invalidation - Key sanitization (`:` → `--`) works for current session key patterns but could collide if keys naturally contained `--` (not present in codebase) - One documentation mismatch found (comment claims percent-encoding, implementation doesn't) <h3>Confidence Score: 4/5</h3> - Safe to merge - well-designed performance optimization with comprehensive tests and backward compatibility - Implementation is sound with proper atomicity, locking, and cache management. The only issue found is a minor documentation mismatch. The change is backward compatible and includes thorough test coverage. Performance improvement is significant (O(n) → O(1) for single-session operations) and addresses a real production bottleneck. - No files require special attention - both implementation and tests are well-structured <sub>Last reviewed commit: bc69f6b</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs