Skip to content

Bound the chat-change replay and expire retired rows - #347

Draft
ndisidore wants to merge 4 commits into
mainfrom
chore/do-load-shed
Draft

Bound the chat-change replay and expire retired rows#347
ndisidore wants to merge 4 commits into
mainfrom
chore/do-load-shed

Conversation

@ndisidore

Copy link
Copy Markdown
Member

On every page load and reconnect, subscribeToChat was replaying the workspace's entire code-change history which meant scanning every row for every chat ever, deserializing changes that can each be 2 MiB, skipping the dead ones by hand, and firing every delivery un-awaited. Worse, "retired" rows (edits already folded into a durable message) were only cleaned up if that same chat kept materializing, so a chat you edited once and abandoned kept its rows on disk forever.

Now the collection has two sparse indexes: replay reads exactly the live rows per chat (nothing else), and a 60-second TTL sweep deletes expired retired rows with one ranged read whenever anyone subscribes or a chat materializes. Deliveries go out in awaited pages of 16 so a big live window can't blow the DO's 128 MiB heap.

Same playbook we already ran for the action log in #298 and #334, applied to the last unbounded read path. Zero frontend changes, and clients see identical behavior.

Two sparse indexes on the chatChanges collection -- liveByChat for
subscribe-replay, retiredByTimestamp for the TTL sweep -- backfilled by
a version-4 migration mirroring the action-index one.
Replaces the per-chat full-prefix prune (which never ran for a chat
that stopped materializing, leaking its retired rows forever) with one
ranged read over the retiredByTimestamp index, also run at
subscribeToChat entry.
Replace the full-collection scan (all chats, retired rows included,
every delivery un-awaited) with per-chat pages over the liveByChat
index, awaited for backpressure. Live subscriptions now attach before
the catch-ups so a materialization landing mid-replay still reaches
the client; a delivery failure unsubscribes and is swallowed (the
frontend never awaits subscribeToChat).
@github-actions github-actions Bot added the kernel Changes to the Workshop kernel label Aug 26, 2026
@github-actions

Copy link
Copy Markdown

Preview: pr347-chore-do-load-shed

https://pr347-chore-do-load-shed-router.cloudflare-os-previews.workers.dev

Dashboard · deleted when this PR closes

Hoist the chatChanges primary-key composition into an exported
chatChangeKey() (the actionLastChangedKey pattern), replacing six
hand-built copies -- schema, sweep, transform-window bounds,
deleteAllChatChanges, replay cursor, and the migration test fixture --
so the key format can't silently drift. Sweep by streaming index rows
to key strings instead of buffering full records (payloads run to
2 MiB), and drop its transaction wrapper: each delete is independently
atomic and a partial sweep is a valid state. Fold the two index
backfill migrations' shared guard/stamp/log ritual into
#backfillIndexes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kernel Changes to the Workshop kernel

1 participant