Memory: an idle Home lets go of its id index (#398) #463

Closed
kayg wants to merge 0 commits from issue/398-unload-2 into dev
Owner

Part of #398: kayg asked how low idle memory can go. Replaces #460, rebased onto current dev and made ready for production.

What

  • An idle Home lets go of its id index. The index is about 10 MB at 100,000 notes, most of an idle server's live heap. It is dropped after Options::unload_after (10 minutes by default) once all of these hold:

    • no lookup in that time;
    • no client of that person connected;
    • no note open;
    • no startup scan or job running.

    Changes queued for ids.jsonl are written first. The next lookup reads the file again, as a server start does.

  • Config: in the server, CALTERNAL_NOTES_UNLOAD_SECS sets the delay in seconds; 0 keeps the index for good.

  • Never while a client is connected: the new EventRecorder::connected(user) (default false) asks the engine whether that person has a live view open. The engine counts the live listeners on that person's journal, without opening anything.

  • Nothing polls an idle server. Each lookup notes the time. One short-lived thread waits for the quiet stretch to end, unloads, trims the heap (calternal_memory::give_back) and exits. While someone is connected, it looks again once per period.

  • Plain ASCII names skip Unicode normalising in names::nfc and names::fold. Reading the index of 100,000 notes back took 155 to 170 ms; it now takes 45 ms (release build, staging). Scans and link parsing use the same functions and speed up too.

  • Docs: unload.rs has a module comment, and the option and the trait method have doc comments.

Numbers, perf VM, 100k notes

Before each first request after unloading I dropped the page cache, so the file is read from disk.

SSD (3 runs) rotational disk (3 runs)
idle RSS before unloading MB 34–46 34–51
idle RSS unloaded MB 19–25 23–33
anonymous memory unloaded MB 5–11 9–19
first note opened after unloading ms 125, 169, 265 145, 221, 243
the same note again ms ~1 ~1

All first opens are under the 300 ms target. These numbers come from the same unload logic before the knob and the client check were added; neither changes what is measured here.

Tests

  • index::unloads_and_reloads: changes queued just before an unload come back after it.
  • tests/unloading.rs:
    • an unused Home unloads, and lookups answer after it;
    • an open note keeps the index;
    • a connected client keeps it, and it unloads once the client disconnects.
  • Engine connected_follows_live_views.
  • index::reload_speed (ignored): a timing test.

cargo fmt, clippy -D warnings and the workspace tests are clean (Rust 1.99.0). The unloading tests passed 3 runs in a row.

🤖 Generated with Claude Code

https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T

Part of #398: kayg asked how low idle memory can go. **Replaces #460**, rebased onto current dev and made ready for production. ## What - **An idle Home lets go of its id index.** The index is about 10 MB at 100,000 notes, most of an idle server's live heap. It is dropped after `Options::unload_after` (10 minutes by default) once all of these hold: - no lookup in that time; - no client of that person connected; - no note open; - no startup scan or job running. Changes queued for `ids.jsonl` are written first. The next lookup reads the file again, as a server start does. - **Config:** in the server, `CALTERNAL_NOTES_UNLOAD_SECS` sets the delay in seconds; `0` keeps the index for good. - **Never while a client is connected:** the new `EventRecorder::connected(user)` (default `false`) asks the engine whether that person has a live view open. The engine counts the live listeners on that person's journal, without opening anything. - **Nothing polls an idle server.** Each lookup notes the time. One short-lived thread waits for the quiet stretch to end, unloads, trims the heap (`calternal_memory::give_back`) and exits. While someone is connected, it looks again once per period. - **Plain ASCII names skip Unicode normalising** in `names::nfc` and `names::fold`. Reading the index of 100,000 notes back took 155 to 170 ms; it now takes 45 ms (release build, staging). Scans and link parsing use the same functions and speed up too. - **Docs:** `unload.rs` has a module comment, and the option and the trait method have doc comments. ## Numbers, perf VM, 100k notes Before each first request after unloading I dropped the page cache, so the file is read from disk. | | SSD (3 runs) | rotational disk (3 runs) | |---|---|---| | idle RSS before unloading MB | 34–46 | 34–51 | | **idle RSS unloaded MB** | **19–25** | 23–33 | | anonymous memory unloaded MB | 5–11 | 9–19 | | **first note opened after unloading ms** | **125, 169, 265** | 145, 221, 243 | | the same note again ms | ~1 | ~1 | All first opens are under the 300 ms target. These numbers come from the same unload logic before the knob and the client check were added; neither changes what is measured here. ## Tests - `index::unloads_and_reloads`: changes queued just before an unload come back after it. - `tests/unloading.rs`: - an unused Home unloads, and lookups answer after it; - an open note keeps the index; - a connected client keeps it, and it unloads once the client disconnects. - Engine `connected_follows_live_views`. - `index::reload_speed` (ignored): a timing test. `cargo fmt`, `clippy -D warnings` and the workspace tests are clean (Rust 1.99.0). The unloading tests passed 3 runs in a row. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
`names::nfc` and `names::fold` normalised every name, though most are
plain ASCII, where NFC changes nothing and lowercasing is ASCII's.
Reading the id index of 100,000 notes back took 155-170 ms, now
45 ms (staging, release build); scans and link parsing fold every
name too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Memory: an idle Home lets go of its id index (#398)
Some checks failed
CI / server (pull_request) Successful in 23s
CI / rust (pull_request) Successful in 2m23s
CI / web (pull_request) Failing after 36m18s
d8ebacdfc3
The id index (10 MB at 100,000 notes) was most of an idle server's
heap. A Home unused for `Options::unload_after` (10 minutes by default;
`CALTERNAL_NOTES_UNLOAD_SECS`, 0 keeps it for good) drops it, once no
client of that person is connected, no note is open and no scan or job
runs. Changes queued for `ids.jsonl` are written first; the next lookup
reads the file again, as a start does (45 ms at 100,000 notes).

Nothing polls an idle server: each lookup notes the time, and one
short-lived thread waits for the quiet stretch to end, unloads, trims
the heap and exits. While someone is connected it looks again once a
period.

`EventRecorder::connected` (default false) lets a plugin ask whether a
person has a live view open; the engine answers from its live
listeners.

Perf VM, 100k notes: idle RSS 19-25 MB unloaded (34-46 before), the
first note opened after unloading 125-265 ms with the page cache
dropped (145-243 on a rotational disk).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Landed in dev via train p.

Landed in dev via train p.
kayg closed this pull request 2026-10-16 03:17:24 +00:00
Some checks failed
CI / server (pull_request) Successful in 23s
CI / rust (pull_request) Successful in 2m23s
CI / web (pull_request) Failing after 36m18s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!463
No description provided.