Append cost no longer grows with the note (#527) #529
Closed
kayg
wants to merge 0 commits from
issue/527-append-fast into dev
pull from: issue/527-append-fast
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:feat/contrast
kayg:fix/shared-row-swipes
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!529
Loading…
Reference in a new issue
No description provided.
Delete branch "issue/527-append-fast"
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?
Fixes #527. Found in #512 (comment 56705,
perf-tails-2026-10-17.md).Root cause
To append, the room copied the whole note (
ydoc::fork(state)) only to learn where the text ends: Yjs puts new text after the last piece of text in the document, deleted or not, and yrs keeps that piece private. Building the copy re-integrates every piece, and yrs does this about quadratically when many writers chain off each other. Every keyed append is its own writer (the writer id comes from the idempotency key), so a log is exactly that shape. Measured on the copy alone: 0.1 ms at 100 lines, 2.4 ms at 1k, 15–26 ms at 3.5k.Fix
The room remembers where its last append ended, plus the document's state vector at that moment (
room/tail.rs). If the state vector is unchanged, the document has exactly the same pieces in the same order, so the next append is built directly as a one-item Yjs update (ydoc::append_update) and applied to the live doc. Deletes don't move the end. Anything that adds text (typing, outside edits, the server's keys at save) changes the state vector, and that one append takes the old copy path, which finds the end again.What stays the same:
Patch and Replace are untouched.
Tests
tests/appends.rs:notes_crash.rs::kill_nine_mid_append_on_a_long_note: 4 concurrent writers append to a 1,000-line note, and the server gets 12 SIGKILLs. Unconfirmed appends are retried, and so are some confirmed ones. Each confirmed append is there exactly once, the file is never torn, and the file equals the note after a save.examples/append_bench.rs: append p50/p95 at 100, 1k and 3.5k lines.Bench (
append_bench, durable keyed appends, local SSD, 3 alternating runs, 200 samples per size)After the fix, appends cost the same at every size; what remains is the history fsync (~8–10 ms). The host was shared with other builds, so absolute numbers are noisy. The trend is not.
Gates (worktree,
-j4)cargo fmt --all --check: rc=0cargo clippy --workspace --all-targets -- -D warnings: rc=0cargo test --workspace: rc=0, 456 passed, 0 failed, 2 ignored (allnotes_crash*,kill_nine_*, live-typing, writers and isolation tests included)Follow-up (not in this PR)
save.rs::sync_titlestill forksbase_stateon every save to read the last saved text: the same quadratic copy, once per save rather than once per append. Caching that text would fix it.🤖 Generated with Claude Code
https://claude.ai/code/session_01WCcFhwwgMK66k9LMVqogNJ
Independent review of #529: MERGE WITH CHANGES (small ones)
I found no way to lose, double or misplace text. There is one gap where the "byte-identical to the old path" claim is false: a third-party client that uses Y.Text formatting. The fix is a few lines. Reviewed
71de555againstorigin/dev, with yrs 0.28.0 source.What holds (checked in yrs and by tests)
TextRef::insert(yrstypes/text.rs:212) walks to the index and then skips every deleted block to the right. So the old path always puts an append after the last item in the list, deleted or not. A delete changes only the delete set, never the list, so a cachedlastthat has been deleted is still exactly where the old path would put the next append. Undo of an insert is a delete only (handled the same way). An "undelete" doesn't exist in Yjs: undo and restore make new items, so the state vector changes. GC turns deleted content intoContentDeletedbut keeps the item and its IDs (it becomes aGCblock only when the parent is deleted, and the root text never is). Squashing only merges blocks from the same client, and every append has its own writer.api_edit, and random writers start fresh.after_appendonly caches when the state vector grew by exactlywriter: len, solast = writer:len-1exists wheneverseen == now. The update can therefore never be parked, and it is integrated just like the old one. No right origin is correct as long as the append is truly the last item; see finding 1 for the case where it isn't.prune_pendingplaces them during our apply,after != expectedand the cache is dropped.api_editruns on&mut Room. Nothing awaits between reading the state vector,tail::append(synchronous: read, build, apply, update the cache) andcommit_local. Remote updates are applied by the same actor, so they can't interleave. The cache lives only onRoom, and the doc is never swapped in place (Awareness::newonly inload.rs::new_room, which setstail: None). Reload, rejoin and unload/reload all start cold.clock_of > 0check comes before this code. A cold cache means one copy. History append still happens before broadcast.Tests I added and ran (review worktree, not pushed)
crates/notes/src/tests/appends_review.rs,cargo test -p calternal-notes --lib appends:delete_whole_tail_then_append_matches: five appends, then delete the last append, append, delete all text, append twice. Byte-identical every time.frontmatter_map_write_falls_back: a write to another root map in the same doc. It falls back, then the fast path resumes, byte-identical.fuzz_deletes_restarts_parked: 60 seeds × 200 steps, with:Every append was compared byte for byte with
by_copy, and the snapshot and text were compared after every step. 4003 fast appends, no difference.The PR's own fuzz already covers deletes at the tail, concurrent typing at the tail and out-of-order delivery. It doesn't cover restarts or other root types; mine adds those.
Finding 1: a trailing format marker breaks "byte-identical" (low severity, fix before merge)
Scenario: any client that writes Y.Text formatting at the end of
md(insert_with_attributes/format) leaves a non-deleted closingFormat(bold=null)item as the last item. First-party clients never do this (no.format/attributes onmdinapps/,packages/), but any Yjs client that joins can.find_positionstops before that marker (it's not deleted, so it isn't skipped), so A1 goes before it, with right origin = the marker.after_appendstill cacheslast = A1end, although A1 is no longer the last item.Test output (
trailing_format_marker_bytes):Impact:
same()passed before the assert.tail.rsdoc ("right after the last piece of text, deleted or not") are both untrue in this case.Fix (pick one):
tail::append, for both the copy path and the fast path.append_update(HAS_RIGHT_ORIGIN+write_right_id).(a) is simpler and keeps the fast path for every first-party note. Also add
trailing_format_marker_bytes(above) as a regression test.Finding 2: doc wording (trivial)
tail.rs/ydoc::append_updatesay the end is "the last piece of text … deleted or not". It's precise only when no live non-text item (format marker) trails it. Reword once Finding 1 is fixed.Not a blocker, worth knowing
Every append with a fresh writer adds a state vector entry forever.
tail::appendclones and compares the whole state vector each time, so the fast path is still O(#writers), not O(1). That's tiny next to a fork, but it does grow with the log.Not run:
notes_crash::kill_nine_mid_append_on_a_long_note(all build slots were busy with e2e). The crash and idempotency paths are unchanged by this diff (see 5).Review addressed in the latest commit on
issue/527-append-fast.1. Trailing format mark. Fixed with option (a).
tail::appendnow remembers the end only when the applied append has no right origin (ydoc::added_lastreads the info byte of the one item in the applied update), so it really is the last item. If a format mark stays after it, the next append goes by copy again, byte-identical. Why no right origin means "last":TextRef::insertskips only deleted items to the right, so it gets no right origin only when nothing at all follows. The fast path itself never writes a right origin, and it only runs from such a remembered end with the state vector unchanged.tail.rsandydoc::append_updatenow say this precisely; "deleted or not" is gone.tests/appends_review.rs, withtrailing_format_marker_bytesas the regression test. One clippy fix was needed:is_multiple_of.2. Constant-cost check instead of the state-vector compare: not done. It wouldn't make an append O(1). yrs itself computes the full state vector on every append:
TransactionMut::before_state, whichencode_update_v1and commit both need, plus theclock_ofidempotency check. So the clone and compare are a constant factor on top of work yrs already does. A "length plus last writer's clock" check is not correct: a typist who keeps typing advances an existing writer's clock without adding an entry. An update counter driven by an observer would be correct, but it adds a subscription per room for no change in asymptotic cost. The compare stays.Gates (worktree, build-slot,
-j4,CARGO_TARGET_DIR=/home/kayg/Developer/calternal-target-527):cargo fmt --all --check→fmt rc=0cargo clippy --workspace --all-targets -- -D warnings→Finished \dev` profile [unoptimized + debuginfo] target(s)/clippy rc=0`cargo test --workspace→test rc=0, 54 result lines all ok, 460 passed, 0 failed, 2 ignored, including:Not merged.
Landed in train y, #565.
Pull request closed