Skip to content

Selecting a thread schedules a full 180 MB store rewrite (lastVisitedAt stamp) #636

Closed

Description

@currentbits

Found in perf research round 31b (2026-08-21).

threads:get (user selection) calls services.getThreadDetail with markVisited true, which does:

  • electron/services.js:3436 — store.updateThread(threadId, { lastVisitedAt: Date.now() }) followed by store.save()

store.save() arms the debounced flush, and a flush is a JSON.stringify of the entire ~180 MB store on the main thread plus a full 180 MB tmp-write + rename (electron/store.js:1175). So every sidebar click at idle costs a whole-store rewrite — a few hundred ms of main-thread stringify and 180 MB of SSD writes, to persist one timestamp. Clicking through 10 threads while nothing is running writes ~1.8 GB.

This is a distinct trigger from the streaming-append flushes tracked in #225 (which is still the root fix). Cheap targeted fix that ships independently:

  • Drop the store.save() on the markVisited path. The mutation still sets nothing on disk immediately, but the field rides along on the next real flush (any append, settings change, etc.), and the exit hook (store.js:1012 _flushOnExit) covers quit. Worst case on a hard crash: unread-dot state is a click stale — trivially acceptable.
  • Alternative if an explicit contract is preferred: a store.touch() that sets _dirty without arming the flush timer.

Either way the change is a couple of lines plus a test asserting a threads:get does not schedule a flush.

Activity

  1. added and removed on Aug 21, 2026
  2. currentbits commented on Aug 21, 2026

    @currentbits
    OwnerAuthor

    Shipped in #638.

    Visit uses store.markDirty() instead of store.save(): no debounce timer, so an idle sidebar click no longer stringifies and rewrites the whole store. The stamp stays in memory, the exit hook covers quit, the next real flush persists it. Hard crash: unread-dot is a click stale.

  3. currentbits commented on Aug 21, 2026

    @currentbits
    OwnerAuthor

    Landed on main as 755f0a2 (local, not yet pushed): getThreadDetail's markVisited path now calls the new Store.touch() — dirty flag + exit hook, no flush timer — so selecting a thread no longer schedules a whole-store rewrite. Regression test added in electron/test/last-visited.test.js (visit leaves _timer null, stamp still reaches disk on the next flush and survives a reload). Full electron suite 1696/1697; the one failure is the known #620 flake.

  4. added and removed on Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions