PERF: Money rule 1 — serve interactive reads from committed precomputed projections (#663) #687

Open
opened 2026-10-02 05:36:38 +00:00 by kayg · 1 comment
Owner

Context: #663 matrix and DESIGN §58 rule 1. Scope: Money must serve interactive reads from committed precomputed projections. Shared client machinery belongs only to #665–#668.

Evidence at c4a61e8cf0:

  • Endpoint: GET /api/v1/money/budgets/{id}/accounts.
  • Source: crates/plugins/money/src/store.rs:432. Changed source is read and parsed under the User lock on a request. Kernel-identity parse cache is reuse, not an indexed committed projection.
  • Representative endpoint numbers, server/client revisions, fixture size, lock/HDD qualification and limits are in the #663 production/HDD table. Those numbers do not prove this rule passes; the structural gap above is separate evidence.

Expected and regression tests:
Hold a plugin writer/indexer transaction during reads; GET returns the last complete committed projection through the WAL reader pool. Instrument external-call/file-read/parse counts and require zero on a warm UI read. Prove changed input is indexed before its new revision is published; queued provider side effects reconcile after reconnect/restart. Test a cold Index rebuild, concurrent source edits, deleted items and two-User/share isolation. Source files and authoritative security state keep their DESIGN ownership.

Performance test:
Extend the existing bench profile for this hot path and the #549/#641 harness. Use production builds on root@10.69.69.63, bench/hdd-emu.sh and flock -w 14400 /root/perf.lock. Record load inside the lock; ≥5 samples, median/p95/max, average CPU/RSS and one realistic large-data/burst case. Separate warm, cold, accepted and durable boundaries. First usable 10k view ≤1.5 s; cached open/warm return ≤100 ms and accepted action ≤150 ms where applicable. Compare only a matching baseline in docs/perf/baseline.json; missing profiles require a new recorded baseline, not a made-up comparison.

Reuse/ownership:
Preserve lossless Markdown and exact integer arithmetic (DESIGN §48). Do not print amounts, costs or transaction contents in issue evidence, logs or artifacts. #641 owns active account traversal; coordinate after it lands.
Reuse #555 userStorage, #549 route caches, Files signed keysets/change feed and the existing Db reader_pool. #641 owns blaze measurements; #642 owns Settings opening; #640 owns Mail layouts; #639 owns linked Note opening. Integrate their active/completed branches before changing related code.

Acceptance:
Existing tests and status expectations stay intact. Run the per-crate gates (and calternal-server for route/contract changes), web gates if changed, and one time-boxed real-server regression/adversarial round for any new API contract. Preserve calternal-fs as the only filesystem interface and the server as the only writer. UI changes need pointer/touch/keyboard/screen-reader coverage and real-production captures at 390/820/1440 in both themes. Do not change shared motion for keyboard input.

Representative measurement from the audit (not a full-view budget result):
/api/v1/money/budgets/{id}/accounts, five serial requests after fixture readiness, all HTTP 200. Median/p95/max 44.7/50.8/50.8 ms; five-request burst median/p95/max 45.1/45.4/45.4 ms. Serial window server CPU 80 ms, RSS 444309504 bytes; no ETag on these sampled responses. Load inside lock 3.66/2.1/0.93.
Shared release server source cc25c441b7a974185622a1dee853cf38686d2b67, binary SHA-256 2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9; embedded production SPA; Chromium browser on the build host through SSH/HTTPS. Server/Home/Index on perf VM HDD emulator: direct-I/O loop, 8 ms read/write dm-delay, 200 IOPS and 150 MiB/s caps. Every measured phase held flock -w 14400 /root/perf.lock. Qualification QD1 115.3 IOPS/8.028 ms median, QD16 200.7 IOPS/96.993 ms. Fixture: 366 Daily notes, 10,980 Logs, 100 Files/Photos, 20 Notes/Tasks, three Budgets and 100 transactions; Mail empty, Admin one User.
Structural source evidence above is the newer audit base, not the measured binary revision. No claim that these revisions are equivalent. The baseline in docs/perf/baseline.json uses another fixture/build/transport; no regression ratio is valid here. See #663 for matching baseline endpoint values and coverage gaps.

Context: #663 matrix and DESIGN §58 rule 1. Scope: Money must serve interactive reads from committed precomputed projections. Shared client machinery belongs only to #665–#668. Evidence at c4a61e8cf090170f35b1bed3350d9de20c83ecd5: - Endpoint: `GET /api/v1/money/budgets/{id}/accounts`. - Source: `crates/plugins/money/src/store.rs:432`. Changed source is read and parsed under the User lock on a request. Kernel-identity parse cache is reuse, not an indexed committed projection. - Representative endpoint numbers, server/client revisions, fixture size, lock/HDD qualification and limits are in the #663 production/HDD table. Those numbers do not prove this rule passes; the structural gap above is separate evidence. Expected and regression tests: Hold a plugin writer/indexer transaction during reads; GET returns the last complete committed projection through the WAL reader pool. Instrument external-call/file-read/parse counts and require zero on a warm UI read. Prove changed input is indexed before its new revision is published; queued provider side effects reconcile after reconnect/restart. Test a cold Index rebuild, concurrent source edits, deleted items and two-User/share isolation. Source files and authoritative security state keep their DESIGN ownership. Performance test: Extend the existing bench profile for this hot path and the #549/#641 harness. Use production builds on root@10.69.69.63, bench/hdd-emu.sh and flock -w 14400 /root/perf.lock. Record load inside the lock; ≥5 samples, median/p95/max, average CPU/RSS and one realistic large-data/burst case. Separate warm, cold, accepted and durable boundaries. First usable 10k view ≤1.5 s; cached open/warm return ≤100 ms and accepted action ≤150 ms where applicable. Compare only a matching baseline in docs/perf/baseline.json; missing profiles require a new recorded baseline, not a made-up comparison. Reuse/ownership: Preserve lossless Markdown and exact integer arithmetic (DESIGN §48). Do not print amounts, costs or transaction contents in issue evidence, logs or artifacts. #641 owns active account traversal; coordinate after it lands. Reuse #555 userStorage, #549 route caches, Files signed keysets/change feed and the existing Db reader_pool. #641 owns blaze measurements; #642 owns Settings opening; #640 owns Mail layouts; #639 owns linked Note opening. Integrate their active/completed branches before changing related code. Acceptance: Existing tests and status expectations stay intact. Run the per-crate gates (and calternal-server for route/contract changes), web gates if changed, and one time-boxed real-server regression/adversarial round for any new API contract. Preserve calternal-fs as the only filesystem interface and the server as the only writer. UI changes need pointer/touch/keyboard/screen-reader coverage and real-production captures at 390/820/1440 in both themes. Do not change shared motion for keyboard input. Representative measurement from the audit (not a full-view budget result): `/api/v1/money/budgets/{id}/accounts`, five serial requests after fixture readiness, all HTTP 200. Median/p95/max 44.7/50.8/50.8 ms; five-request burst median/p95/max 45.1/45.4/45.4 ms. Serial window server CPU 80 ms, RSS 444309504 bytes; no ETag on these sampled responses. Load inside lock 3.66/2.1/0.93. Shared release server source `cc25c441b7a974185622a1dee853cf38686d2b67`, binary SHA-256 `2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9`; embedded production SPA; Chromium browser on the build host through SSH/HTTPS. Server/Home/Index on perf VM HDD emulator: direct-I/O loop, 8 ms read/write dm-delay, 200 IOPS and 150 MiB/s caps. Every measured phase held `flock -w 14400 /root/perf.lock`. Qualification QD1 115.3 IOPS/8.028 ms median, QD16 200.7 IOPS/96.993 ms. Fixture: 366 Daily notes, 10,980 Logs, 100 Files/Photos, 20 Notes/Tasks, three Budgets and 100 transactions; Mail empty, Admin one User. Structural source evidence above is the newer audit base, not the measured binary revision. No claim that these revisions are equivalent. The baseline in docs/perf/baseline.json uses another fixture/build/transport; no regression ratio is valid here. See #663 for matching baseline endpoint values and coverage gaps.
Author
Owner

Round 3 evidence from #641 for the Money work in #663/#687:

  • On a register change, the web store first checks its 12-register LRU keyed by budget, account, and month. A cache miss requests account-filtered transactions and coalesces the month and account-list reads. The sidebar prefetches up to three neighboring accounts, so one navigation can overlap several register reads. The budget's account list is cached client-side and month requests are shared while pending. This is the work per step before the view can reuse a warm register.
  • The transactions handler resolves the Money store, reads the ledger, builds transaction views, then filters by account. The accounts handler resolves the store, finds the last data month, replays the ledger, and builds the account list. On source changes, MoneyStore::load rereads/revalidates the budget, account and month documents, parses changed content, then runs project(...); parse's fingerprint cache only avoids reparsing unchanged files. Relevant locations: apps/web/src/lib/money/store.svelte.ts, apps/web/src/lib/money/api.ts, crates/plugins/money/src/routes.rs, and crates/plugins/money/src/store.rs:432.
  • The prior warm Chrome Blaze run recorded 78 ms desktop and 71 ms phone for Money, with measured key-handler work at or below 1 ms. The trace's longest RunTask was 56 ms and the largest visible categories were paint/layerization; a sampled task included 15.8 ms keydown dispatch and 11.6 ms app work. The trace does not identify one browser source for all 70–80 ms, and those browser tasks do not establish server time. Keep server request/projection latency separate from browser event-to-paint when measuring #687.

This supports the structural gap already recorded in #687: interactive reads still resolve the store and can rebuild a projection under the User read lock. The next useful measurement is per-step browser paint beside each server request's queue, lock, read/parse and projection time; the current trace cannot attribute the full task duration to one of those stages. No transaction content or amounts are included here.

Round 3 evidence from #641 for the Money work in #663/#687: - On a register change, the web store first checks its 12-register LRU keyed by budget, account, and month. A cache miss requests account-filtered transactions and coalesces the month and account-list reads. The sidebar prefetches up to three neighboring accounts, so one navigation can overlap several register reads. The budget's account list is cached client-side and month requests are shared while pending. This is the work per step before the view can reuse a warm register. - The transactions handler resolves the Money store, reads the ledger, builds transaction views, then filters by account. The accounts handler resolves the store, finds the last data month, replays the ledger, and builds the account list. On source changes, `MoneyStore::load` rereads/revalidates the budget, account and month documents, parses changed content, then runs `project(...)`; `parse`'s fingerprint cache only avoids reparsing unchanged files. Relevant locations: `apps/web/src/lib/money/store.svelte.ts`, `apps/web/src/lib/money/api.ts`, `crates/plugins/money/src/routes.rs`, and `crates/plugins/money/src/store.rs:432`. - The prior warm Chrome Blaze run recorded 78 ms desktop and 71 ms phone for Money, with measured key-handler work at or below 1 ms. The trace's longest `RunTask` was 56 ms and the largest visible categories were paint/layerization; a sampled task included 15.8 ms keydown dispatch and 11.6 ms app work. The trace does not identify one browser source for all 70–80 ms, and those browser tasks do not establish server time. Keep server request/projection latency separate from browser event-to-paint when measuring #687. This supports the structural gap already recorded in #687: interactive reads still resolve the store and can rebuild a projection under the User read lock. The next useful measurement is per-step browser paint beside each server request's queue, lock, read/parse and projection time; the current trace cannot attribute the full task duration to one of those stages. No transaction content or amounts are included here.
Sign in to join this conversation.
No labels
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#687
No description provided.