Files slice 1: Home identity, joined index, and reconcile #1010
Open
kayg
wants to merge 0 commits from
feat/files-s1-identity into dev
pull from: feat/files-s1-identity
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
kayg:feat/contrast
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:shared/restore-and-stale-918-919
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/smooth-pane-transitions
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:feat/tasks-delete-promote-duplicate
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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!1010
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/files-s1-identity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status (2026-11-10, head
5517ce595)-D warnings, workspace tests and the 1M listing gate pass.Plan
Implement Files slice 1 as a backend Home identity layer over the two real Home roots selected by amended ADR 0062. Each root keeps the same relative folders and its own files; the resolver joins their listings and refuses to serve or reconcile when either expected root is missing, mismatched, or unmounted. No overlay, symlink, hard-link, or content-addressed storage is involved.
Reuse the accepted #641 folder marker contract:
.calternal-idcarries a canonical UUIDv7 through folder renames and moves. Marker collisions are resolved deterministically and recorded; an ambiguous path keeps both real entries. Markdown IDs remain incalternal-idfrontmatter. Other file IDs use an app-managed row plus a portable hidden marker, with no content-hash-only identity inference. Reuse the Notes event/index and watcher behavior through shared APIs where it fits; do not create a second copy of those implementations.The per-user index is rebuildable from durable markers and stores both roots in one logical namespace. First-page listing uses a composite B-tree key and keyset cursor: O(log N + page size). Normal ID and parent lookup is O(log N); a single-row update is O(log N). Reconciliation is O(N) only in background batches with bounded working memory. No request sorts a directory or loads the whole Home.
Failure table
Required gates
cargo test, clippy, and web checksThe account envelope stays 50 million files and 1 PiB logical size. The first-page target remains under 100 ms. Scale fixtures contain metadata only and sparse logical sizes. Results and benchmark artifacts will be posted in PR comments per ADR 0003.
Namespace overlap and implementation path
Reviewing #970 showed that it already owns the shared Files current namespace and the three-root storage contract. The current #1010 draft added a separate
HomeIndexunder Notes metadata and modeled the SSD Home plus state as its two roots, so that draft duplicates the index and does not match ADR 0062.I will make this branch build on #970's
calternal-files::Namespacehistory and remove the separateHomeIndex. The Files resolver will validate SSD Home, HDD Home, and SSD app state, store its index under app state, and reconcile the two Home trees through the same current namespace. #1010 remains targeted atdev; until #970 lands, its commits are ancestors of this branch, so the shared implementation has one source in Git history and the dependent diff can shrink after #970 merges.The current gate table remains pending until this combined implementation has exact-head results. I will keep the existing Notes watcher/index paths shared and report any touched Notes hot-path deltas with the required locked ABAB comparison.
Takeover status (head
46faabb28, merged with devd4b32af94; Rust identical tod00b48b6e)Previous agent stopped on a usage cap. State found: branch had no conflicts with #970 beyond one in
crates/perf/src/scenarios/cold.rs(kept both functions).Fixed a real startup bug (commit
afb3d29b6):WatchEvents::recvcould report the queue closed while a rescan was still pending (the last sender is dropped right after it sends the startup rescan when no watcher is installed). The startup scan then never ran andscan.waithung. This was the cause ofshutdown_settlement_state_machinefailing ~3 of 4 runs on this branch (0 of 3 on dev). Testrescan_sent_just_before_the_inbox_drops_is_delivered(2,000 iterations) added. After the fix: 6 of 6 runs pass.New test #1015 waits on:
legacy_attachment_ids_survive_every_retry_boundary(crates/notes/src/home_files/tests.rs): files written before identity existed get one ID each on first scan, kept after a repeated scan, a restart and a rename.calternal-dev-01-compute02, via~/bin/slot)cargo fmt --all --checkcargo clippy --workspace --all-targets --locked -j4 -- -D warningscargo test --workspace --locked -j4 --no-fail-fastshutdown_settlement_state_machinex6 on fixed headLeftover predecessor unit
files-s1-full-test-3-1af2was stopped (nofiles-s1*units remain on the perf VMs). One load-only timeout incalternal-contacts-actionscrash_after_trash_move_...did not reproduce in 5 isolated runs nor in the next full run (flaky rule: no block).Perf: fresh release binaries (head
d00b48b6e, devd4b32af94, sha256 checked) staged on perf-01; queued behind Mail/p928/Photos on perf-01 with a blockingperf-lock, then perf-02. Plan: 1M listing gate, ABAB x5 (dev legacy layout vs head joined layout) with a dev-copy control each cycle, inside both locks. Results follow in a comment.Perf: ABAB x5 + control, inside both perf locks (perf-01 then perf-02), session 2026-11-09 06:54–07:55Z
Head
d00b48b6e(Rust identical to PR head46faabb28) vs devd4b32af94, release builds, sha256 checked. Dev runs the legacy single-root layout, head runs the joined three-root layout (SSD Home, HDD Home, app state). Per cycle: A1 dev, B1 head, A2 dev, B2 head, then a dev-copy control (same dev binary, copied). 5 cycles, same boot (7a65f072), sizes 1k/10k/100k,--runs 1 --cold-runs 1 --hot-only --gate. Medians of per-arm p50/p95 (n = 10 dev, 10 head, 5 control). Locks released at 07:55Z.Control (dev vs dev-copy) is within about 1% on the cold metrics, so the cold deltas below are not noise.
Scale gate (1M entries,
files-listing-bench, page 50)cold_first_us=104 p50_us=59 p99_us=99 active_map_bytes=53085308 peak_rss_kib=56460. The plan's gate is a first page under 100 ms: pass (about 0.1 ms).Regressions vs dev (they block merge under the no-regression rule)
cold.first_pagep50cold.indexcold.notes_per_scold.disk_writtenstartup.healthzhome.page_bigp50open.api_getp95notes.append.firstnotes.appendp50open.notecold.*runs once per arm, so these are medians of 10 arms; the control's median stays within about 1% of dev.Improvements
home.first_page1k -37%;notes.append.first100k -79%;sync.save_ackp95 100k -41%;sync.round_trip100k -19%; memory flat to -7%.Flat (inside control noise)
home.page,home.page_next,home.page_tag,home.page_title,search.*,sync.durable_write,shutdown,open.api_get.warm,mem.*.Likely cause (not verified)
The cold open now reads and writes the shared current namespace (joined-tree reconcile plus bounded 8,192-row commits) in addition to the Notes discovery dev does, so the first page waits behind extra index writes (+17% bytes written at 100k).
startup.healthz+2 ms is the extra root validation and resolver open.State
Not READY FOR MERGE under the no-perf-regression rule: the cold-path,
home.page_bigandopen.api_getp95 deltas need a fix or kayg's explicit waiver. Everything else is green: fmt, clippy-D warnings, full workspace tests, scale gate, legacy-attachment ID test, settlement flake fixed.Full table (all metrics)
Perf fix design (round 1): why head was slower than dev, and the fix
Profiled on compute02 (directional; the perf VMs decide) with release builds of dev
d4b32af94(legacy layout), #970 head381ce6750(joined layout) and this PR's head46faabb28(joined layout), 10k cold runs, strace on the server.Root cause per metric
cold.first_page,cold.index,cold.notes_per_s,cold.disk_writtenfiles/current.dbinstead of #970's two:current_filesPK,current_files_by_path, the new fullcurrent_files_by_scan_generation, the newcurrent_childrentable and its(parent_key, name_key, id)index. UUIDv7-keyed inserts into extra trees dirty many more pages per 8,192-row commit.pwrite+ 19,717preadoncurrent.dbvs 4,500 + 3,169 for #970 (same 10k Home, same scan code). All other syscall counts (statx, openat, read) matched within 1%.open.api_getp95,home.page_big(100k, hot restart)Home::reconcile_filestakesself.index()once and holds the Home's index mutex for the whole Files walk, including everygetdents/statx/marker read over both trees. Any request needingindex()(note open,path_of, list validation) waits behind it. At 100k the walk is seconds long, so a few requests in each run land on it (p95, not p50).home_files.rsreconcile_files/reconcile_files_paths); matches the shape (p50 flat, p95 4×).startup.healthzFix
page_childrenrange-scanscurrent_files_by_path(path_key, id)from<parent>/; when it meets a descendant<parent>/<child>/…it seeks once past that child's whole subtree.current_children(and its backfill +namespace_schema) is dropped; a database from the earlier draft has them removed once on open.current_files_by_scan_generationbecomes a partial indexWHERE path <> 'Notes' AND path NOT GLOB 'Notes/*'— exactly the predicate of the stale-row query, so the walk still uses it, and Notes writes never touch it. The stale query readsscan_generation < ?(walk generations only grow), a true range.Namespace::commit_scan_page): one WAL sync per page instead of three, and a crash leaves the whole page or none. Unchanged inode evidence is not rewritten.home_files/access.rs). Safe because the walk is the only writer of non-Notes rows and runs one at a time from the watcher loop, while Notes writes only touchNotes/*.mdrows that the walk skips.The first Notes page already comes from
meta.dband never waits for identity assignment; the Files walk starts only after the Notes scan, so nothing here moves onto the first-page path.Big-O, before → after (N = rows in the user's namespace, P = page size, W = rows in one walk page = 256)
page_children)Identity guarantees are unchanged: IDs, markers, inode evidence and the collision journal are written by the same code in the same order; only lock scope and transaction grouping change. Gates to re-run: identity tests (incl. 2,000-iteration rescan,
legacy_attachment_ids_survive_every_retry_boundary), workspace tests/clippy/fmt, the 1M listing gate, and the locked ABAB ×5 with a dev-copy control.Perf round 3: ABAB ×5 with a #970 arm and a dev-copy control (both perf locks, FIFO turn)
Session 2026-11-09 15:56–16:56Z (21:26–22:26 IST), boot
7a65f072(same epoch as the earlier sessions), perf-01 lock viaperf-turn files-1010then perf-02's lock, both released at the end. Per cycle: dev (legacy layout), head (joined layout), #970381ce6750(joined), dev, head, dev-copy control. Sizes 1k/10k/100k,--runs 1 --cold-runs 1 --hot-only --gate; medians of per-arm p50/p95, n = 10 dev, 10 head, 5 #970, 5 control. All 30 arms exited 0. sha256 checked at turn time.0c99d74e9(release built from9520d6fed; the two differ only in a#[cfg(test)]module)d4b32af94(the merge base)startup.healthz: with it gone, healthz is flat at 1k and 100k.1M listing gate
files_listing entries=1000000 page_size=50 cold_first_us=99 p50_us=58 p99_us=85 active_map_bytes=53085308 rss_after_open_kib=56548 peak_rss_kib=56548First page under 100 ms: pass (0.099 ms).
Full table
Verdict after 3 rounds, and a simpler design (stopping the patching here per the 3-round rule)
What the three rounds fixed (in this PR)
1dae71c03)cold.disk_written+16.6% → +7.5% (now = #970)86f2411ba)open.api_getp95 +345% → +128%Notes/*.mdonly to skip themstat(0c99d74e9)startup.healthzflat at 1k/100kWhat still fails, and whose it is
cold.first_page100kcold.index,cold.notes_per_s10k/100kcold.disk_written100khome.page_bigp50 100knotes.append.first100kcold.first_page1k/10ksynchronous=FULL), on the first-page pathcold.index1kopen.api_getp95 100kstartup.healthz10kBecause #1010 is stacked on #970, it can't be ≤ dev on the cold metrics until #970 is. The remaining #1010-only costs are both fixed costs at Home open and start, so the simpler design removes them instead of tuning them:
Simpler design for the rest of #1010
BEGIN … COMMIT(one fsync per Home open instead of ~9; also below #970's 2). Expected: the ~50 ms at 1k/10k goes away,cold.first_page/cold.indexat small sizes ≤ #970.(mtime, ctime, inode)from the last completed walk. At start,statfolders only: a folder whose stamp is unchanged keeps its rows without being listed. Cost goes from O(entries) to O(folders) (the 100k-note fixture has a handful). Watchers cover changes while running, the 15-minute reconcile stays the safety net, and overflow still forces a full walk. This mirrors the Notes warm scan (scan-state.tsv), which already makes hot start cheap.Big-O after: Home open O(1) fsyncs. Hot start O(folders) stats plus O(changed folders × entries). Request latency is independent of Home size while idle detection holds.
Gates for that round: identity tests (incl. the 2,000-iteration rescan and
legacy_attachment_ids_survive_every_retry_boundary), a new test that an unchanged folder is not re-listed and a changed one is, workspace fmt/clippy/tests, the 1M listing gate, and the same ABAB ×5 with #970 and control arms.Dependency: #970 needs its cold identity-load fix (ID-ordered bulk load and/or deferred checkpoint, evidence linked above) before #1010 can be ≤ dev on
cold.*andhome.page_big.Round 4: the simpler design is built (head
018a90d00)Commits since round 3:
69ec4addbperf(files): create the namespace schema in one transaction. One WAL sync per new Home instead of ~9 on the first-page path; none on an up-to-date database.7c7586f87feat(files): restart trusts folder stamps; Files walks run only while quiet.statevery stamped folder (O(folders), no listing). If none changed since the last completed walk, listings are complete at once.018a90d00merges #970's head381ce6750(no content change), so the #970 perf arm is an exact ancestor.Tests added (all pass, 10/10 repeats of the restart set on a loaded host):
Gates on compute02 at
018a90d00:cargo fmt --all --checkcargo clippy -j4 --workspace --all-targets --locked -- -D warningscargo test -j4 --workspace --locked --no-fail-fast69ec4addbalone:cargo test -p calternal-filesThe 1M listing gate and the ABAB ×5 (dev, head, #970, dev-copy control) run in the next
perf-turnslot (queued behind tiering-v3).Perf round 4a: ABAB ×5, dev / head / #970 / dev-copy control (head
018a90d00)Session 2026-11-09 19:41–20:37Z (01:11–02:07 IST), boot
7a65f072(same epoch as rounds 1–3). Plainperf-turn files-1010(FIFO) took perf-01's lock, then the control host took perf-02's; both released at the end. Per cycle: devd4b32af94(legacy), head, #970381ce6750(joined, an exact ancestor of head), dev, head, dev-copy control. Sizes 1k/10k/100k,--runs 1 --cold-runs 1 --hot-only --gate, fixtures generated from the same seed and verified by sha256. Medians of per-arm p50/p95. All 30 arms exited 0.1M listing gate:
files_listing entries=1000000 page_size=50 cold_first_us=101 p50_us=60 p99_us=91. Pass (first page 0.10 ms ≤ 100 ms).#1010 share (head vs #970):
cold.first_page−1.0% / −1.1% / −0.4%,cold.index−0.9% / −0.3% / +1.6%,cold.disk_written+0.2% / −0.1% / +0.1% (1k/10k/100k). The one-transaction schema removed round 3's ~50 ms at 1k/10k.home.page_bigp50 @100k +16% (p95 −5.6%) andhome.pagep50 +6–12%. Per-arm ranges overlap (page_big head 23.6–36.8 ms, #970 22.2–27.0 ms; rank test p≈0.06). The same #970 binary measured page_big at 29.5 ms in round 3 and 25.5 ms here, a swing larger than the share. These requests run in the first seconds after start under continuous load, when the deferred Files walk is still waiting for quiet. The dev-copy control can't size joined-layout noise, so round 4b adds a head-copy control and a second #970 arm per cycle (#970 n=10).Head vs dev stays red on the cold path and on
home.page_big@100k. Those match #970 alone (cold.first_page @100k +105% vs #970 +106%), so #1010 cannot go READY before #970 is ≤ dev. Status: #1010-share flat on cold; list path pending round 4b.Round 4b result and round 5 (head
5517ce595)Round 4b (2026-11-10 00:52–01:47Z, 06:22–07:17 IST; same epoch; plain
perf-turn; 30/30 arms exit 0) added a head-copy control (the same head binary run twice) and a second #970 arm per cycle. 1M listing gate:cold_first_us=101 p50_us=60 p99_us=78, pass.home.page_big@100k, −15% onhome.page@10k and −33% onstartup.healthz@100k. Round 4a's "+16% page_big share" is inside that band; 4b measured +3.9% (p95 −3.2%).cold.index@100k +2.5%,cold.first_page@100k +2.9%,cold.disk_written@100k +0.1% (p≤0.01), andsearch.*/open.api_get.warm@1k +2–6% (p≈0.03–0.08).home.page_big@100k +11% at p=0.08 is not established.Round 5, aimed at those shares:
549871e17perf(files): Notes rows keep #970's table bytes. #1010 had addedkindandscan_generationcolumns to #970's sharedcurrent_files, so every Notes row carried two extra fields through every WAL rewrite. Those move to a Files-onlycurrent_walk_rows(id, kind, seen)(indexed by(seen, id)for the sweep); readers take kind from aLEFT JOIN. Notes puts, cold scans and deletes of Notes-owned rows never touch the side tables. The sweep now excludes exactly the rows Notes owns (SSDNotes/*.md), so a deleted non-Markdown file belowNotes/is swept too. A draft database migrates once.5517ce595perf(notes): only requests and typing mark a Home busy.Home::index()no longer marks activity: it was a CAS loop plus a clock read on every index call, including the cold scan's hot loop.mark()is now a relaxed load and a store at most every 5 ms.Gates on compute02 at
5517ce595:cargo fmt --all --checkcargo clippy -j4 --workspace --all-targets --locked -- -D warningscargo test -j4 --workspace --locked --no-fail-fastOne first-run failure in an unrelated SyncClient durability test passed 10/10 on rerun; filed as #1049 (flaky rule, non-blocking).
Round 5's ABAB (dev, head, #970, head-copy, #970, dev-copy, ×5) is queued on
perf-turn. Status remains #1010-share: cold flat within noise; small shares being re-measured. Head vs dev stays red on what #970 alone costs, so #1010 can't be READY before #970 is ≤ dev.Perf round 5: #1010-share flat (head
5517ce595)Session 2026-11-10 10:09–11:05Z (15:39–16:35 IST), boot
7a65f072(same epoch as every earlier round). Plainperf-turn files-1010(FIFO) took perf-01's lock, then the control host took perf-02's; both released at the end. Per cycle: devd4b32af94(legacy), head, #970381ce6750(an exact ancestor), head-copy, #970, dev-copy; ×5; 1k/10k/100k;--hot-only --gate; same seed-1 fixtures as rounds 4a/4b. All 30 arms exited 0.1M listing gate:
files_listing entries=1000000 page_size=50 cold_first_us=130 p50_us=69 p99_us=101. Pass (first page 0.13 ms ≤ 100 ms).#1010 share (head vs #970), what round 5 targeted:
cold.index@100k +0.8% (pooled 4a/4b had +2.5%, p≤0.01),cold.disk_written@100k +0.0% (was +0.1%),cold.first_page@100k +3.6% (head-copy band −4.4%),home.page_big@100k −6.3%.open.long_note@10k is ~16 ms or ~47 ms in every arm, dev included;search.query@100k swings +100% between dev and dev-copy.startup.healthz: +0.4% / −1.9% / +0.7% share at 1k/10k/100k.Head vs dev stays red only where #970 alone is red (
cold.first_page@100k +107% vs #970 +100%;cold.index,cold.disk_written,home.page_bigandopen.note@100k track #970).Status: #1010-share flat. Not READY: #1010 can only be READY once #970 is ≤ dev (its cold identity-load fix is in progress on the tiering lane). When that lands, I'll merge #970's head and re-run this ABAB.
Round 5 table (medians of per-arm p50/p95)
n arms: dev 5, head 5, head-copy 5, #970 10, dev-copy 5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.