Repository navigation
fix(web): bound session catalog and history read cost - #871
Merged
Merged
Conversation
Cache Pi session metadata by file identity and parse non-current history off the request thread, so an unchanged catalog, a cancelled search, and a later history page do not read or synchronously parse every JSONL body again. Fixes #834 Co-authored-by: tt-a1i <tt-a1i@users.noreply.github.com>
The installed Node types do not treat fs.Stats as generic. Keep the bigint file identity structural, and format the session read tests. Co-authored-by: tt-a1i <tt-a1i@users.noreply.github.com>
Parse non-current history with Pi on the parent, in 4000-line turns separated by timers, and reuse the result while the file identity matches. A worker made the first snapshot wait on startup and kept a later prompt from being sent. Co-authored-by: tt-a1i <tt-a1i@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fixes #834. Every Web session read scanned full JSONL history, and pagination did not bound that load.
SessionManager.listAll, which reads and parses every session file (and buildsallMessagesText) before keeping at most 500. Sidebar fork and unread evidence then opened those files again.SessionManager.open, which parses the whole file on the request thread before applying the projection limit.AbortSignaldid not cover the pre-scan.Coordinated with #835 / PR #854: this change does not touch the live-message SSE budget or the files that PR edits.
Value
An unchanged catalog is not read again. Append, rename, delete, and replacement invalidate only the affected file. Search cancellation and the byte/time budget include the catalog pre-scan, so a cancelled or oversized query does not keep scanning the directory. Opening a non-current session reads the file asynchronously, parses long history in timer turns so the request thread can run other work, and a later page reuses that result instead of reading the file again. Pi remains the parser and the branch authority.
Approach
SessionCatalogcaches Pi-compatible metadata by file identity (dev,ino,size,mtimeNs,ctimeNs). A repeated unbounded list shares one in-flight scan. A bounded list used by search stops scheduling body reads on abort, on the 16MiB cap, or at the deadline. Compact structural entries feed the existing fork and turn-evidence readers, so the sidebar does not open every file.SessionLoaderreads the file asynchronously and parses it with Pi'sparseSessionEntries. A file of at most 4000 lines is one parse. A longer file yields withsetTimeout(0)before each 4000-line turn, including the first, so a caller timer can run between turns (setImmediatewould starve those timers). The result isSessionManager.inMemorywith the real session path, cached by file identity for four sessions. Writes still go throughSessionManager.open.Validation
Same synthetic script as #834, plus
/proc/self/iorcharduring each call and a repeatingsetTimeout(0)probe for the longest gap (maxStallMs). Node v22.23.3, linux x64, one process. The issue's published numbers were Node 26.8.1 on another machine; the before/after pair below is this machine. Before,timerDelayMsmatched total time becauseSessionManager.openandlistAllheld the thread until they returned. After, that single start-of-call timer runs during the async read, somaxStallMsis the main-thread delay.The catalog script grows one directory through 100, then 500, then 1000 files of about 64KiB. Before, every call re-read every body (about 4× the file bytes:
listAllplus sidebar opens). After, a call reads a body only when the file identity is not cached. The 1000-file first call reads 31.39MiB because the earlier 500 files are already cached; a cold 100-file directory reads 6.28MiB, matching the file bytes. A repeat reads nothing (statdoes not incrementrchar). 1000 files still project 500 and omit 500. History still projects 40 entries.Before stall is the issue's
timerDelayMs(it could not run until the synchronous parse finished). After stall ismaxStallMs. The 56MiB history still takes about the same wall time on the first read, because the bytes are still parsed, but the longest timer gap is 31.3ms instead of 225ms. The repeat does not read the file; the remaining few milliseconds are projecting the cached branch.Correctness tests compare catalog fields with
SessionManager.listAlland branch, fork, and unread results withSessionManager.openplus the existing readers. The long-history test checks branch length, leaf id, and the earliest entry against that oracle, and countssetTimeout(0)turns scheduled after the body read returns. A fully synchronous parse schedules none (verified by removing the yields). The tests do not use wall-clock timeouts, retries, or widened test timeouts.On
e6ecc22943dd08231a6442f21352c89694cdcae1:bun run checkpassed.bun run testexited 0: Node tests 2667 total, 2658 passed, 9 skipped, 0 failed; UI tests 99 files, 1531 passed. CI run 38922966105 completed with conclusion success, including both Web E2E shards.Impact