Order topics match updates, not inserts#2736
Draft
stopachka wants to merge 2 commits into
Draft
Conversation
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
d61272a to
8c9027b
Compare
Contributor
|
View Vercel preview at instant-www-js-scope-page-info-topics-jsv.vercel.app. |
An ordered+limited query's page-info topic exists to catch a row whose sort key changes (a re-sort), but it also fired on inserts: for `messages where conversation.id = X order by createdAt` the topic is `[:ave _ createdAt _]`, so every new message in any conversation (each writes createdAt) re-ran the query. New rows entering the window are already caught by the where/link (or id-enumeration) topic, so the page topic only needs updates. Value-changed updates now emit a `:mutated` marker and the page topic subscribes to it instead of `:ave`/`:ea`: inserts no longer match it (killing the cross-parent spam) while a row reordering into the window still does. Gated behind the `skip-order-topic-updates-only` toggle.
8c9027b to
e8588fb
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.
The problem, by example
A chat query: the most recent messages in one conversation.
A reactive query subscribes to topics (
[idx entity attr value]); a write invalidates the query when its change matches one. The query above includes:Every topic is scoped to this conversation except the
order bytopic, whose entity and value are both_. So it matches a write tocreatedAton any message in any conversation.What goes wrong
createdAtis written on every new message. So a message created in another conversation matches[:ave _ messages/createdAt _]and re-runs this conversation's query (which recomputes the same 5 messages). With many conversations open, every message insert anywhere re-runs every open chat query. This isn't chat-specific: it's anywhere ... order by <attr> limit N.The fix: order topics match updates, not inserts
The
order bytopic exists to catch a row whose sort position changes — a re-sort, including an out-of-window row reordering into the window. Those are updates. A row entering the window for the first time is an insert, and inserts are already caught by the other topics:where/link topic ([:vae #{convoId} conversations/messages _])where→ thewheretopicwhere-less query → the id-enumeration topic ([:ea _ id _])So the page topic doesn't need to fire on inserts — and firing on them is exactly what made it app-wide spam. The fix: value-changed updates emit a
:mutatedmarker, and the page topic subscribes to:mutatedinstead of:ave/:ea:_) → no regression.where/link topic.Why there's no regression
The page topic keeps its app-wide entity, so it still catches a re-sort of any matching row, including one currently outside the
limitwindow. Everything the old topic caught is still caught::mutatedpage topic:mutatedpage topic (entity is_)The only writes the page topic stops matching are inserts, which are redundant there. (Apps that mutate the sort key across many parents still re-run on those updates — that's inherent and necessary; the common append-only
createdAt/serverCreatedAtcase has none.)Dead ends (what we tried first)
Three approaches looked reasonable and were ruled out — recorded so we don't revisit them. All three try to fix the topic by its entity; the actual lever turned out to be the action (insert vs update).
1. Scope the page topic's entity to the visible window
Rewrite
[:ave _ createdAt _]→[:ave #{the matched rows} createdAt _]. A write to another conversation's message isn't in the matched set, so the spam stops.It silently misses a row reordering into the window:
Measured on a real
order by priority limit 5link query: an off-window reorder is caught today but not with window-scoping (OLD: true → NEW: false). It would break live reordering for leaderboards, kanban boards, priority lists, etc. (where-less and scalar-whereordered queries already scope their page topic and already have this gap; window-scoping would newly extend it to the common link-wherecase.)2. A per-app opt-in flag
Only enable scoping for apps whose sort key is append-only (e.g. Aura's
createdAt). Safe, but a band-aid: it doesn't fix the general bug, needs manual curation, and leaves the spam in place for everyone else.3. Scope to the full where-matching set (not just the window)
[:ave #{every message in X} createdAt _]would catch every in-conversation reorder and drop cross-conversation writes — correct, but the set is wrong on two counts:Also dropped
$serverCreatedAtinstead ofcreatedAt: just moves the wildcard to[:ea _ id _]—idis written on every insert too, so the spam is identical.wheres already scope their page topic): works, but relies on that field being backfilled on every row, and only helps the one app.The insight that unlocked it
The spam is inserts (new messages elsewhere); a reorder is an update. New rows are already caught by the where/link/enumeration topic, so the page topic only needs updates — match
:mutated, leave the entity app-wide. No set, no per-app flag, no regression.What changes
topics/topics-for-triple-updateadds a:mutatedmarker to value-changed updates (triple.clj/datalog.cljindex specs allow it). It's a topic-only marker, not a real index permutation.accumulate-resultsrewrites the page-info topic's index to:mutated.skip-order-topic-updates-onlytoggle (default on) to revert without a deploy.Tests
order-topic-matches-updates-not-inserts(instaql_test): an off-window row reordered into the window still invalidates; a new message in another conversation does not (with the toggle off it reverts to the old app-wide behavior, which matches both).pagination/pagination-with-checked-fields/$not-with-refstopic snapshots (page topics now key on:mutated).invalidator-test/changes-produce-correct-topicsfor the:mutatedmarker on updates.