Skip to content

perf: release thread history the client is no longer looking at - #589

Closed
Tryanks wants to merge 2 commits into
feat/storage-redb-indexfrom
feat/client-history-memory
Closed

Tryanks wants to merge 2 commits into
feat/storage-redb-indexfrom
feat/client-history-memory

Conversation

@Tryanks

@Tryanks Tryanks commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fourth slice of the storage work in #535, stacked on #588. Closes #525. Client only: no host or protocol change.

The client store no longer holds every thread it has ever visited, nor every page it has ever scrolled up to.

 ui store
-  seven per-thread maps (records, status, git status, cursor, …)
-  hydrated_sessions + its test seam
+  threads: HashMap<String, ThreadReplica>
on(leave thread)
  keep the replica for the selected thread and the 4 left most recently (KEPT_THREADS)
  release the rest                                   # no timer
on(thread removed from index)
  release immediately
on(re-select)
  kept     → resume with its cursor
  released → open fresh

each prepended page records whether it was fetched while the reader followed the tail
when the reader has stayed at the tail for 30 s, or leaves the thread
  drop the pages fetched away from the tail, move `from` forward within the same layout epoch
  pages the tail needed are never dropped             # prefetch stays satisfied, nothing refetched

Why a count, not the issue's 5-minute TTL. A thread that is not selected receives no records, so keeping it only saves one baseline window of at most 512 KB on return. A TTL bounds age, not memory: 50 threads visited inside five minutes are all held. A count needs no clock.

Not changed: prepending a page still refolds the held window (0.12 ms at the tail, 38 ms with every page of a 17 MB thread held). What is bounded now is what happens afterwards: returning to the tail, re-selecting and the next scroll-up start from the tail window again. Records arriving live while one thread stays selected for hours are not trimmed. The Timeline::upsert_item forward scan (about 57 % of a large fold) is shared with the host and the compaction gate and is left for a separate change.

Evidence

Scripted run through the real store and a real host, 50 threads of 0.93 MB each:

Threads held Records Bytes
Before 50 21,200 26.1 MB
After 5 2,120 2.6 MB
  • Tests added: visiting_many_threads_keeps_the_replicas_of_the_last_few (real host) and pages_read_far_up_are_released_once_the_reader_stays_at_the_tail (real ChatView, phone geometry: rows stay on screen at every step, a 20 s glance keeps the pages, after 30 s the replica equals the tail window with the last row's position unchanged and no refetch). Seven mutations (keep all threads, no drop on delete, no cursor on re-select, no trim, trim the tail's pages too, …) each fail one of the two.
  • Seven tests that read the private cursor fields now read the same facts through the held history, assertions unchanged.
  • App: scroll-up, jump-to-latest and the 30 s cycle watched on a scratch profile in both themes at 1200 pt and 485 pt.
cargo fmt / clippy --workspace -D warnings / machete   ✓
cargo nextest run --workspace --locked                 902 passed, 6 skipped
Web and iOS checks with -D warnings                    ✓

An intermittent nextest LEAK report on unrelated tcode-ui unit tests reproduces on the parent commit (2 of 8 runs) and is not from this change.

Merge Danger

Door: two-way

In-memory client state only. Reverting restores the unbounded cache.

Blast Radius: chat-scrolling

  • Returning to a thread left more than four threads ago now opens fresh instead of resuming its cursor, so it re-fetches its baseline window.
  • Scrolling far up, resting at the tail for 30 s, then scrolling up again re-fetches pages that were previously held.
  • Not run: the phone example (needs a paired host), Android, Windows, Linux.

A client kept every record of every thread it had visited for its whole
lifetime. It now keeps replicas for the selected thread and the four
threads left most recently, and drops a deleted thread's at once. A
thread that is not selected receives no records, so holding it longer
only saves one baseline window.

Pages read far above the tail are dropped once the reader has stayed at
the tail for 30 seconds, or leaves the thread. The pages the tail itself
needed are kept, so nothing dropped is fetched again and the list does
not move.

Closes #525
archiving_the_viewed_child_returns_to_its_parent waited only for the
parent's status, which arrives on its own topic, so on a slow runner the
child was opened before any parent record was held and there was nothing
to keep. The driver now waits for the conversation to be on screen, as a
user would see it, and the test also requires the parent to resume from
its cursor.
@Tryanks

Tryanks commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Linux x64 failed store::tests::archiving_the_viewed_child_returns_to_its_parent on the first push. Settled at step 1 of the failing-test procedure (the driver, not the code): the test waited only for the parent's status before opening the child, and status and history arrive on separate topics, so on a slow runner no parent window was held yet and there were no records to keep. Before this PR the assertion was session_records.contains_key("parent"), which was true as soon as the thread had been selected, records or not. The app shows the parent's conversation before the user can open the child, so the driver now waits for the window to be held; the assertion is kept and the test additionally requires the parent to resume from its cursor. 20/20 under stress, and green with --test-threads 64 alongside the workspace suite.

@Tryanks Tryanks changed the title perf: bound the history a client holds perf: release thread history the client is no longer looking at Oct 6, 2026
@Tryanks

Tryanks commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

Folded into #591, which carries this commit unchanged as one PR for the whole storage phase.

@Tryanks Tryanks closed this Oct 6, 2026
@Tryanks
Tryanks deleted the feat/client-history-memory branch October 6, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant