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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant