Bound the chat-change replay and expire retired rows - #347
Draft
ndisidore wants to merge 4 commits into
Draft
Conversation
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).
Preview:
|
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.
ndisidore
force-pushed
the
chore/do-load-shed
branch
from
August 26, 2026 17:57
e5cd754 to
787a44f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On every page load and reconnect,
subscribeToChatwas 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.