Skip to content

perf(sync): skip per-room build for quiet rooms on incremental sync#241

Merged
sufforest merged 2 commits into
mainfrom
wzy/sync-quiet-room-pregate
Jul 7, 2026
Merged

perf(sync): skip per-room build for quiet rooms on incremental sync#241
sufforest merged 2 commits into
mainfrom
wzy/sync-quiet-room-pregate

Conversation

@sufforest

@sufforest sufforest commented Jul 7, 2026

Copy link
Copy Markdown
Owner

First of a performance series. Profiling /sync (the highest-volume endpoint) showed the joined-room loop builds every room's full payload — timeline, state delta, receipt + unread scans, member counts — and only then discards quiet rooms via room_is_unchanged. So an incremental poll does work proportional to all joined rooms even when nothing changed.

Fix: pre-gate the loop with a few cheap point-gets (the room's latest timeline pos, receipts max pos, per-(user,room) account-data max pos, and the in-memory typing pos) against the caller's cursor, and skip the build entirely when none advanced past it. This turns a quiet incremental poll from O(rooms × full-build) into O(rooms × a few point-gets).

Correctness is the whole risk (a wrong skip drops an update or busy-loops the client). It's conservative — any doubt or DB error falls through to the build — and the authoritative room_is_unchanged gate still runs for whatever passes, so it can only save work, never drop an update. A review pass confirmed the invariant (room_has_changes_since = false ⟹ room_is_unchanged = true): every section room_is_unchanged checks (timeline/state/state_after/ephemeral/account_data) maps onto one of the four positions, all in the same stream_counter space as since, and Live state events carry a timeline stream_pos so state-only changes are covered.

Test (tests/sync_quiet_room_pregate.rs): a quiet room is omitted, and a room whose only change is a receipt, account-data, state-only, or typing update still surfaces.

Initial sync is unchanged; this cuts incremental-sync CPU for the common case (most joined rooms quiet on any given poll).


Also includes a drive-by lockfile bump: crossbeam-epoch 0.9.18 → 0.9.20 for the just-published RUSTSEC-2026-0204 (invalid pointer deref in fmt::Pointer for Atomic/Shared). Transitive via moka/metrics-util; the buggy path isn't reached by vela, but it fails cargo deny on main. Lockfile-only, folded in here to avoid a separate PR + rebase.

sufforest added 2 commits July 6, 2026 20:11
the joined-room loop built every room's full sync payload (timeline,
state delta, receipts + unread scans, member counts) and only then
discarded quiet rooms via room_is_unchanged — so an incremental poll did
work proportional to ALL joined rooms even when nothing changed.

pre-gate the loop with a few point-gets (room's latest timeline pos,
receipts max pos, per-(user,room) account-data max pos, in-memory typing
pos) vs the caller's cursor, and skip the build entirely when none
advanced. conservative: any doubt or DB error builds, and the
authoritative room_is_unchanged gate still runs for whatever passes, so
this can only save work, never drop an update.
new advisory (invalid pointer deref in the fmt::Pointer impl for
Atomic/Shared) fixed in 0.9.20. transitive via moka + metrics-util; the
buggy path (Debug-formatting an invalid crossbeam pointer) isn't reached
by vela, but it fails `cargo deny` on main so bump it. lockfile-only.
@sufforest sufforest merged commit d3d9f96 into main Jul 7, 2026
11 of 12 checks passed
@sufforest sufforest deleted the wzy/sync-quiet-room-pregate branch July 7, 2026 03:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant