WIP: Add CardDAV cache and read path (ADR 0064 pieces 3 and 4) #734
Closed
kayg
wants to merge 1 commit from
issue/carddav-read into dev
pull from: issue/carddav-read
merge into: kayg:dev
kayg:main
kayg:fix/shared-row-swipes
kayg:feat/style-sheet-fixes
kayg:feat/contrast
kayg:feat/pass8
kayg:t3/paged-search-reconcile-final
kayg:storage/wal-checkpoint-off-save-1051
kayg:review/calendar-2
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:feat/files-s1-identity
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!734
Loading…
Reference in a new issue
No description provided.
Delete branch "issue/carddav-read"
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?
Contact notes now have a disposable CardDAV cache and a read-only DAV adapter for ADR 0064 pieces 3 and 4. The cache stores vCard 3.0 bytes, strong ETags and property indexes in users//carddav.db. A journal subscriber projects creates, typing, imports, Trash, restore and undo, with a replay barrier before reads.
The adapter implements well-known discovery, the stable-user principal, one address book, PROPFIND, multiget, paged sync-collection and getctag. Initial sync pages use a fixed snapshot; delta pages include deletions and correct continuation tokens. VCard property selection stays in the existing contacts format crate, and reads use the cached field index.
The default server does not mount these routes. Constructing the router requires an authenticator; app passwords and server wiring remain piece 5. There are no anonymous credentials in product code. Writes, Lists and photos remain their later ADR pieces.
Validation:
Required timing gates are pending. Both the perf VM (10.69.69.73) and Mac Studio (10.69.69.102) time out over SSH from this host. RFC fixtures are the approved fallback for unavailable macOS 27 traffic. No timing numbers are claimed. The perf example measures conversion plus a committed 1,000-card rebuild, a 1,000-card HTTP multiget, and an unchanged HTTP sync check. Run it only on 10by32-01-std under /usr/local/bin/perf-lock.sh, with CALTERNAL_PERF_VM=10by32-01-std, using cargo run --release -j3 -p calternal-carddav --example perf after sourcing /etc/calternal/env.sh.
Full gate output and the observed test-race failure are attached to issue #657. This PR stays a draft while the required VM gates are unavailable. No merge or deployment was performed.
Refs #657. ADR 0064.
Gate totals: 1171 passed, 20 ignored, 0 failed. Head:
82c5e64f51.Complete gate output
Finished the perf-contacts measurement job for ADR 0064 pieces 3 and 4.
Measured head:
82c5e64f51, branch issue/carddav-read (detached checkout). PR base: dev at3d0c6e33d5. Product code is unchanged and the worktree is clean. No commits, pushes, merges, deployments or issue closures.Result: byte identity and the 2 ms sync check pass. The 30 ms rebuild gate fails cold and warm. The ADR 30 ms serving gate passes warm but fails cold for PROPFIND and multiget. These failures remain blockers; diagnostics do not replace or discard failed samples. No limits changed.
Environment and method: job-selected dedicated VM perf-test-02, KVM, 4 CPUs and 8 GiB cap; /srv/homes on rotational sdb/XFS and /home/agent/perf-state on nonrotational sda/ext4. Each operation ran serially through /usr/local/bin/perf-run.sh, which holds perf-lock.sh and the build lock, inside perf.slice (CPU quota 4, memory max 8 GiB); build-slot.sh was nested inside that held lock. Cold uses --cold before launching a fresh process and has no request warmup. Warm uses a fresh process with one identical untimed operation before its timed operation. Cache drop is the guest cache drop provided by perf-run.sh, not a claim that the hypervisor disk cache was cleared. Five independent measured invocations per operation per condition. p50/p95 use nearest rank; with five samples p95 is the maximum. Every sample must be strictly below its gate.
Raw baseline milliseconds, in execution order:
The 10,000-card rebuild is reported without inventing a new limit. The rebuild timer matches the original PR harness: source notes already in memory; note parsing, vCard 3.0 conversion, property indexing, hashing and the committed SQLite replacement are included. Home discovery/file reads and cache opening are outside that gate. Notes really reside under /srv/homes/perf/contacts-657; original cards and per-user carddav.db files reside under /home/agent/perf-state/perf-contacts-657. Every measured rebuild at both sizes compares href, wire bytes, strong ETag and serialized property index against a separate cache populated incrementally. All 20 comparisons passed. Cache generation and sync tokens rotate intentionally; this is row-byte identity, not physical SQLite file identity.
HTTP timing includes the router, unchanged-source replay barrier, XML parsing, cache reads and complete response body collection, before network time. As in the original gate, it uses the supplied test authenticator rather than the future piece 5 app-password path. PROPFIND uses Depth 1 and getetag/getcontenttype/getcontentlength; multiget requests getetag plus full address-data for all 1,000 contacts. All measured responses returned 207 with the expected counts: zero changed cards, 1,001 PROPFIND resources, or 1,000 multiget cards. PROPFIND bodies are 429,370 bytes; multiget bodies are 498,726 bytes. The source panics if reads attempt a source scan, conversion or change replay.
Where the failed gate spends time: baseline conversion/index creation was 6.024511–7.888589 ms for 1,000 cards. The slowest baseline rebuild spent 71.404090 ms in Cache::rebuild out of 79.292679 ms total. A separate syscall profile reproduced a failing cold rebuild at 69.806454 ms: conversion/indexing 6.676828 ms, SQLite 63.129626 ms, including 32.883864 ms in 414 pread64 calls, 0.977013 ms in 833 pwrite64 calls and 24.808069 ms in two fdatasync calls.
Five further diagnostic repetitions per failing operation/condition follow. These use an external LD_PRELOAD counter reset immediately before the operation and dumped immediately after its timer stops. Timings below are diagnostic and are not substituted into the baseline table.
The later warm rebuild and cold HTTP diagnostics did not reproduce the initial outliers. Their syscall measurements show the variable read/durability cost, but do not establish the precise cause of every earlier outlier. Warm diagnostic rebuilds made no pread64 or fdatasync calls in their measured transactions; cold requests each made 206 pread64 calls. All other counted pread/pwrite/fsync variants were zero. perf.slice showed zero CPU throttling and zero memory limit/OOM events. No product change was made on this evidence alone.
Verbatim aggregate gate output (exit 1):
The optimized release build completed successfully with --locked -j3 under build-slot.sh. Evidence, temporary harness sources, exact invocation logs, raw JSON, machine details and all diagnostic runs are attached; nothing was committed to the repository.
Complete measurement evidence (ZIP SHA-256: 58fb3a077df8eae05abc5bf922091354dd1f01c231aa12fb070bec73e35ec59b).
Starting perf-contacts-fix on branch issue/carddav-read-perf-fix, fetched base
82c5e64f51. Read the full prior tables and profile in comments 67096 and 67097. Reproducing 5 cold + 5 warm runs before changing code. The prior profile locates SQLite reads and durability waits; rebuild already uses one transaction and prepared inserts. Using assigned perf-test-02 as in the previous job (the task explicitly identifies this replacement VM). Hard rule requires a new branch and PR because updates to open PR branches are ignored; will link the follow-up from #734. No limits will change.Profile review before edits: prior unmodified-head syscall profile uses counters scoped to the operation. Cold rebuild: 414 pread64, 833 pwrite64, 2 fdatasync; cold reads: 206 pread64, zero writes or syncs. Source review confirms rebuild has one transaction and two prepared insert statements. cards() selects complete precomputed rows ordered by href (SQLite uses the href index followed by table lookups); multiget prepares a new get statement for every requested href. No Home file reads or format conversions occur on the read path. SQLite bundled source defaults mmap_size to zero, so every cold page is fetched through xRead. Test a bounded 64 MiB mapping with SQLite-managed WAL coherence and cached get statements, retaining all existing durability settings.
Fresh before measurements at
82c5e64f51reproduced the failures. Same prior harness, fresh data namespace, Homes under /srv/homes/perf/contacts-fix-657 and state on the OS SSD. Each operation uses perf-run.sh and perf-lock.sh, 5 cold + 5 warm invocations; warm performs one untimed identical operation. Every sample is retained. All 20 rebuild row comparisons passed.Verbatim before gate output:
Testing bounded SQLite mapping and prepared lookup reuse first; no durability setting changed.
First measured change: bounded 64 MiB SQLite mapping and cached get statement reuse. WAL NORMAL unchanged. Cold requests now pass; rebuild still fails. No samples discarded.
Rebuild remains blocked by database replacement I/O; profiling that operation before the next change.
Further diagnostic profile after mapping: rebuild uses zero explicit pread64 calls, about 834 pwrite64 calls, and cold commits make two fdatasync calls. This reproduces the I/O volume, although the failing warm outliers again did not reproduce during the separate profiles. Cold request profiles now make zero explicit page reads (mapped page faults are not counted as pread64). Testing 32 KiB SQLite pages to amortize replacement writes, with automatic checkpoints kept near the original 4 MiB (128 pages), WAL NORMAL unchanged. A one-time transactional VACUUM at open migrates old caches; migration tests cover rows, cursor and sync tokens.
The 32 KiB page experiment still fails, so it will not be kept. Rebuild reaches 40.727440 ms and sync reaches 3.170842 ms. Byte comparisons pass.
Returning to 4 KiB pages. The next change removes duplicate payload storage: the live-card table will point to its immutable revision instead of storing another vCard and property index. Both revision and live pointer still commit atomically. A validated unchanged sync token can return directly instead of issuing a revision query.
Removing duplicate payloads helps but still misses the gate: rebuild 41.987866 ms, sync 2.025800 ms, PROPFIND 46.479288 ms. Multiget passes. Retaining every sample below.
Next: stage rebuild B-trees in memory and atomically install them through SQLite backup using the destination WAL NORMAL transaction, avoiding reads of old pages; keep a compact live metadata table for PROPFIND so it never reads bodies/indexes; store the head alongside the cursor in the same transaction so an unchanged sync reads only metadata. No checkpoint or durability setting is relaxed.
The final product change passes every measured gate. Fresh incremental baselines and all 20 measured rebuild comparisons pass. Also compared final rows at both sizes with the prior job’s independent incremental databases: identical href, vCard bytes, strong ETag and serialized property index. Homes remain on /srv/homes; state remains on the OS SSD. Same 5 cold + 5 warm method and strict limits. No durability setting or checkpoint threshold changed.
Verbatim performance gate output:
Running final I/O diagnostics, fmt, CardDAV tests, clippy and workspace tests before publishing the follow-up PR.
Final I/O evidence: cold rebuild explicit page reads fell from 414 to zero and write fragments from 833 to 471; warm writes fell from 832 to 470. Cold commits still make two fdatasync calls: durability was retained. Mapping uses page faults, so zero pread64 calls does not mean zero physical I/O. The compact PROPFIND table occupies 24 SQLite pages (98,304 bytes) for 1,000 cards; the former duplicated payload tables occupied about 400 pages together. Multiget reads precomputed wire bytes through the live revision pointer, with prepared statement reuse. No Home reads or card conversion occur in requests. Separate diagnostic profile follows; the successful uninstrumented gate samples remain the results.
Exact comparison and layout evidence:
The first workspace gate exited 101 in the unrelated notes test room::save::tests::review::history_durability::independent_652_real_syncclient_joining_snapshot_is_not_saved. Evidence: history_durability.rs:464:37, JSON parse error "EOF while parsing a value", after the Bun child returned no output. This worktree has no node_modules. The previous job log records the same SyncClient test requiring JavaScript dependencies. Fmt, all 20 CardDAV tests and workspace clippy with warnings denied passed. Installing dependencies from the frozen Bun lockfile, then rerunning the real SyncClient tests and the full workspace gate. The first failed log is retained; no test or limit is being weakened.
Installed the frozen Bun dependencies (118 packages). The two real SyncClient tests now pass without changing product code or tests:
The full workspace gate is running again. The first workspace failure is preserved in workspace-first.log and the gate history. Fmt, 20 CardDAV tests and workspace clippy still pass; source code is unchanged.
Finished perf-contacts-fix. Head:
a45e7482b5. Branch: issue/carddav-read-perf-fix.Follow-up PR #739 targets issue/carddav-read, keeping its diff focused on the performance fix. The hard rule requires a new branch and PR because updates to an open PR branch are ignored. PR #734 and issue #657 remain open. No merge or deployment was performed.
All final gates pass. Payloads are stored once in revision history; live metadata holds the revision pointer, ETag and byte length. Rebuild constructs fresh tables in memory and installs them atomically through SQLite backup. PROPFIND reads compact metadata, multiget reuses prepared lookups through a bounded 64 MiB mapping, and unchanged sync checks read the committed head metadata. Legacy migration preserves rows, cursor, epoch and paged sync snapshots. WAL NORMAL, default 4 KiB pages, default automatic checkpoints and all limits remain unchanged. Staging temporarily uses memory proportional to the projected book.
Method matches the preceding job: assigned replacement VM perf-test-02, 4 CPU / 8 GiB parent perf.slice; Homes on rotational /srv/homes (/dev/sdb1, XFS), state on the OS SSD (/dev/sda1, ext4). Every operation is serial under perf-run.sh and perf-lock.sh. Five independent cold and five warm invocations per operation; cold drops guest caches, warm performs one identical untimed operation first. All samples are retained. Nearest-rank p50/p95 means p95 is the maximum with five samples; every measured sample must be strictly below its gate. The 10,000-card rebuild remains report only.
The rebuild timer includes in-memory note parsing, contact conversion, indexing, hashing, staging, SQLite replacement and commit. HTTP timing includes the router, unchanged-source barrier, XML parsing, cached reads and complete body collection with the original test authenticator. Source callbacks panic on an attempted Home scan, conversion or change replay during reads. Responses remain 207 with expected resource counts and unchanged PROPFIND/multiget body sizes.
Before, fetched base
82c5e64f51:After:
Verbatim final performance gate output (exit 0):
Verbatim code gate history and final test totals:
The first workspace run failed when a Bun bridge returned empty output while JavaScript dependencies were absent. Installing from the frozen Bun lockfile made both real SyncClient tests pass in 0.40 seconds, then the full workspace retry passed. No product or test source changed for that retry. The original failure and all retry logs are retained.
All 20 measured rebuilds at 1,000 and 10,000 cards compare every href, wire byte, strong ETag and serialized property index with independent incremental baselines. Final rows also match the previous job's independent incremental databases exactly. This is row-byte identity; generations intentionally rotate.
Final diagnostic profiles show cold rebuild explicit reads falling from 414 to zero and write fragments from 833 to 471. Cold commits still perform two fdatasync calls. Mapping uses page faults, so zero pread calls does not imply zero physical I/O. The PROPFIND metadata table occupies 24 SQLite pages for 1,000 cards. Profiles are separate diagnostics and do not replace any gate samples. All unsuccessful intermediate experiments are retained, including the removed larger-page experiment.
Complete raw measurements, profiles and gate logs (ZIP SHA-256: 1eb9a9198a227ca0bd58293e9feedf0421ea119af618f7adbae15703ae7bb801). Evidence stays outside the repository; the commit contains only CardDAV product code and tests.
Replaced by #776: same change rebased onto current dev (branch
issue/carddav-read-r2), build and tests green on dev-02. This PR is left open and untouched; please close it when the replacement is accepted.Closing: superseded by #776 (named in the PR body or comments). Branch kept.
Pull request closed