Repository navigation
perf: open long threads from their tail, drop superseded diffs, bound client history - #611
Merged
Merged
Conversation
… client history Storage performance on the Turso store, three parts. Superseded turn-change snapshots lose their diffs (#522): when a turn's new cumulative diff is appended, the previous snapshot of that turn is emptied in the same transaction, as the fold itself resolves the turn; a one-time pass does the same for existing threads, marking each as done. Rows and positions never change, so nothing on the wire moves. Cold threads no longer stall the host, and long ones open from their tail (#524): one hydration per session reads and folds the log off the mailbox at a writer-ordered snapshot, holding the records that arrive meanwhile and answering waiting subscriptions first; a stored turn index, maintained at append time and built lazily after a full read, lets the first window come from the last rows only. Cursors are stored row positions. The client keeps history for the selected thread and the four left most recently, and drops pages read far above the tail once the reader has stayed at the tail (#525). Closes #522 Closes #524 Closes #525
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.
Storage performance on the Turso store (#594). Closes #522, #524, #525. One commit; three parts, each built and verified on a copy of the maintainer's data (3,047 threads, 9.7 GB of rows).
1. Superseded turn-change snapshots lose their diffs (#522)
Codex sends the whole cumulative diff of a turn on every change, so a turn stored every intermediate snapshot; only the last one is ever read. Now, when a
turn_changes_updatedis appended, the store writer empties the diff of the previous snapshot of that turn in the same transaction, resolving the turn the way the fold does (tcode_core::session::TurnSnapshots: first turn with that provider id, else the current turn, within one turn lifetime across rewinds); the residentSessionLogdoes the same in memory, so host and store agree and the wire's oldsuperseded_turn_changes/without_diffsrule is gone. A one-time pass at startup does this for existing threads, one per writer transaction, listing candidates fromsessionsand marking each thread in adiff_passtable (created idempotently; no schema version change) so it never rescans; an undecodable row leaves the thread untouched and marked. No row is added, removed or moved, so nothing on the wire changes.On the data copy: 79,410 snapshots in 819 threads emptied, stored bytes 9.73 GB → 5.55 GB (file size unchanged until
VACUUM INTO: 10.8 GB → 6.6 GB), every thread folds equal to its original (independent check), restart rewrites nothing. The pass holds the writer up to ~0.6 s on the largest thread, once. Replayed provider warnings are no longer logged by the fold (live ones are logged once aton_event).2. Cold threads no longer stall the host; long threads open from their tail (#524, re-scoped)
One hydration per session (
crates/runtime/src/app/history.rs): the store writer answersSnapshotLogwithnext_rowin queue order, the rows below it are read and folded onHostCx::unblock, records that arrive meanwhile are held in order and sent after the waiting windows, a subscription's ack follows its window, and the log is kept only if the session is resident. Pages andReadItemOutputof non-resident threads use the same hydration; orchestrate reads and title regeneration wait for a writer barrier.A
turn_indextable (session → turn count and turn-opening rows, maintained in the append's transaction from the resident fold, built once after a full read, forgotten by any unfolded write) lets a thread with more than 400 rows answer its first window from the tail rows alone — read backwards in 256-row steps to the baseline's turn start or the window byte budget — while the full read continues for the resident fold. The early window is identical to the whole log's (asserted).total_turnscomes from the index because counting turn starts is both as slow as the read and inexact.Measured (real
spawn_host, 1 kHz ping as another session): largest thread first window 434 ms → 8.3 ms with the index (first open without an index: same time as before but off the mailbox); other sessions' round trips ≤ 3.4 ms during an open instead of up to 434 ms; peak RSS unchanged (the full read still happens).Cursors are now stored row positions rather than record indices — identical on all real data; they differ only in a log with blank or undecodable rows. No protocol field changed; an Unreleased note sits above
PROTOCOL_VERSION.Left out of #524's text: two-file layout, crash truncation, byte offsets, an item-output index, gzip, persisted search text.
3. The client bounds the history it holds (#525)
The store keeps replicas for the selected thread and the four threads left most recently, drops a deleted or unlisted thread's at once, and drops pages read far above the tail after 30 s at the tail or on leaving, keeping the tail's own pages so nothing is refetched; the chat list splices out rows that vanish above every surviving row so the tail does not move. Live records are ignored until the selected thread's window has arrived. 50 visited threads: 20,100 held records / 8.7 MB → 2,010 / 0.9 MB. No protocol change.
Tests
Core: superseded-snapshot cases (reused turn id, snapshot that opens or names its turn, rewind lifetime, late snapshot) with
Timeline ==. Services: in-place diff drop with literal rows and marker, undecodable row keeps every row, turn index forgotten by unfolded writes. Runtime: append-time drop in the same commit (fails if rows are numbered by record index), startup pass over cold and resident threads, a SIGKILL harness (ignored; 30 kills, never a half-done thread), shared cold reads, the event that starts a read, leaving before the read completes, pages and outputs of an unopened thread, a child completing mid-read, two mux clients, and a long thread opening from its tail with the whole log's window (fails without the index or with a stale one). UI: 50-thread retention through a real host, the scroll-up/stay-at-tail trim cycle through the realChatView, list splice scenario. Driver fixes: tests that assumed a synchronous snapshot now drive the pipe or wait for the state (incl.set_session_replica_for_test, the paged fixture,classifier_stop_and_review…recording through the host's real path,orchestrate_dispatch_resolves_cwd_before_replychecking inside the update).Checks run
On the rebased tree:
cargo fmt --all --check,cargo clippy --workspace --all-targets --locked -- -D warnings,cargo nextest run --workspace --lockedtwice (965 passed, 13 skipped),cargo machete, Web and iOS checks. Looked at in the desktop app on a scratch data dir: scroll-up and return, tail stays put, released threads reopen at the tail; both themes, wide and 760 pt. Not run: Android, Windows, Linux.PROTOCOL_VERSIONbump: a note was added under "Unreleased" above theconstant in
crates/protocol/src/lib.rs(the number itself changes onlywhen the release is cut — CONTRIBUTING.md, principle 9).