You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix library sync backfill O(N^2) hotspots on both sides of the protocol (#3061)
* fix library sync backfill O(N^2) hotspots on both sides of the protocol
Addresses #3058 (shared models, content_identity) and #3060 (device-owned
entries). The progressive slowdown on initial pair came from several
independent hotspots that compound:
Sender side
- PeerLog::get_since now pushes LIMIT into SQL; callers fetch limit + 1 to
derive has_more. Previously every SharedChangeRequest reloaded and
parsed the entire remaining shared_changes log in memory before
truncating, making sender work O(N * batches).
- Entry and content_identity query_for_sync batch FK to UUID conversion
across the whole batch via a new convert_fks_to_uuids_batch helper —
one DB round trip per FK type instead of per record per FK.
Schema
- New index idx_entries_indexed_at_uuid backing the (indexed_at, uuid)
cursor in Entry::query_for_sync. Without it every batch request fell
back to a full-table scan.
Receiver side
- New task-local in_backfill scope wraps the backfill apply phases.
Entry::apply_state_change uses it to skip per-entry entry_closure
rebuild; the existing post_backfill_rebuild pass does a single bulk
rebuild at the end. emit_batch_resource_events short-circuits during
backfill and the coordinator emits one Event::Refresh after post-
backfill rebuild so the UI invalidates cached views.
- Entry FK resolution in backfill splits mappings into self-referential
(parent_id) and the rest. Non-self FKs resolve in one batch across the
whole batch; only parent_id still runs per-entry so children can see
just-inserted parents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* address PR review feedback: drop unresolved FKs, guard limits
convert_fks_to_uuids_batch now returns the set of record indices whose FK
could not be resolved (missing target row or non-integer value). Callers
in entry::query_for_sync and content_identity::query_for_sync drop those
records from the outgoing sync batch instead of zipping the partially
converted payload back. Previously the sender would ship a record with
its local int field intact and no *_uuid field; the receiver's
map_sync_json_to_local interpreted that as already-resolved and wrote
the sender-local integer directly to the local DB, corrupting the FK.
Also: reject limit == 0 on SharedChangeRequest / get_shared_changes up
front (would have returned 0 rows with has_more = true and spun), and
clamp the SQL LIMIT bind with i64::try_from(..).unwrap_or(i64::MAX)
instead of a wrapping `as i64`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* further review fixes: treat absent FK fields as failed, drop NULL indexed_at
convert_fks_to_uuids_batch previously treated a missing local_field as
null. convert_fk_to_uuid errors in that case — only an explicit JSON
null is a legitimate null FK — so the batch helper now flags absent
fields as failed to match the per-record contract.
Entry::query_for_sync no longer falls back to modified_at when
indexed_at is NULL. The cursor filter/order uses indexed_at exclusively,
so a returned cursor derived from modified_at wouldn't match the next
query's predicate. The indexed_at backfill migration populated existing
rows; any NULL here is a data bug, logged and skipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* cargo fmt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments