Skip to content

Handle out-of-order events also across batches in qlever update-wikidata - #309

Merged
hannahbast merged 7 commits into
mainfrom
fix-cross-batch-out-of-order
Aug 10, 2026
Merged

Handle out-of-order events also across batches in qlever update-wikidata#309
hannahbast merged 7 commits into
mainfrom
fix-cross-batch-out-of-order

Conversation

@hannahbast

@hannahbast hannahbast commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

The bug fix from #299 (an out-of-order delete must not cancel a causally newer insert of the same triple, and vice versa) only works within a batch, because the insert_triples / delete_triples dicts are reset for each batch. When tailing the live stream, a batch spans only a few seconds, while the stream can reorder events by minutes. Therefore, an out-of-order event can arrive in a LATER batch than the event it causally precedes and is then applied ungated, with the exact damage shape of ad-freiburg/qlever#2979.

This change keeps a history of the causally newest event per triple ACROSS batches: each insert and delete is first gated against that history, with the same tie semantics as the per-batch gate (a delete must be strictly newer than the last insert, an insert wins a tie against the last delete). The history is pruned by event date to the last --triple-history-minutes of stream time (default: 30, far above the observed reordering of minutes; 0 disables the history). Pruning also happens every 10,000 messages within a batch, because a batch can span hours of stream time during catch-up.

NOTE: The bug was observed live on 2026-07-28, found by the new qlever check-sync-with-wikidata (#308) on a random sample of 1000 entities: the ja.wikipedia sitelink for the 2026 Kumamoto earthquake was first attached to a duplicate entity (Q140735390) and then moved to the right one (Q140735282). The delete for the duplicate (rev 2523832444) arrived 2:23 minutes AFTER the insert for the right one (rev 2523832445, one revision later) and stripped the article triples, which are byte-identical for both entities except for schema:about. The gating logic was validated against five scenarios (the observed reproducer, the reverse hazard of a late insert after an applied delete, in-order deletes and re-inserts across batches, and the original intra-batch case of #299). The in-order behavior is unchanged.

The gating from #299 (an out-of-order delete must not cancel a causally
newer insert of the same triple, and vice versa) only worked WITHIN a
batch, because the `insert_triples` / `delete_triples` dicts are reset
for each batch. When tailing the live stream, a batch spans only a few
seconds, while the stream can reorder events by minutes, so an
out-of-order event can arrive in a later batch than the event it
causally precedes, ungated.

Observed live (found by `qlever check-sync-with-wikidata`): a sitelink
was moved from a duplicate entity to the right one; the delete for the
duplicate (rev 2523832444) arrived 2:23 minutes after the insert for
the right entity (rev 2523832445) and stripped the article triples,
which are identical for both entities except for `schema:about`.

Keep a history of the causally newest event per triple ACROSS batches
(new option `--triple-history-minutes`, default 30, 0 disables), gate
inserts and deletes against it with the same tie semantics as the
per-batch gate, and prune the history by event date, also within a
batch (a batch can span hours of stream time during catch-up).
Copilot AI review requested due to automatic review settings July 28, 2026 15:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the out-of-order gating logic in qlever update-wikidata so it also works across batch boundaries, not just within a single batch. It does so by maintaining a time-pruned, per-triple history of the causally newest insert/delete event seen, preventing older late-arriving events from undoing newer applied updates.

Changes:

  • Add --triple-history-minutes (default: 30; 0 disables) to gate inserts/deletes across batches using a bounded per-triple history.
  • Track latest_event_date and periodically prune triple_history at batch start and every 10,000 messages during long catch-up batches.
  • Apply cross-batch gating checks for both delete- and insert-triple processing, with tie semantics consistent with the existing intra-batch gate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +754 to 758
latest_event_date,
args.triple_history_minutes,
)

# Check if we can use a cached SPARQL query file
Comment thread src/qlever/commands/update_wikidata.py Outdated
@hannahbast hannahbast changed the title Gate out-of-order events also across batches in qlever update-wikidata Handle out-of-order events also across batches in qlever update-wikidata Aug 10, 2026
Replace the "gate" terminology in the option help text and the comments
(an event that is superseded by a causally newer event is now said to be
ignored). Factor the two near-identical cross-batch checks (one for
deletes, one for inserts) into a single helper
check_and_update_triple_history, which also removes the deeply indented
duplicated blocks. Add type annotations to prune_triple_history and a
NOTE that cached batches contribute nothing to the triple history
(Copilot comments).
A prune is linear in the size of the history, which matters when
tailing the live stream, where a prune would otherwise run once per
batch, that is, every few seconds. Now a prune only actually happens
when the history has at least doubled in size since the last prune
(with a floor of 10,000 entries), which makes the total pruning cost
linear in the number of insertions. Keeping entries longer than the
window is harmless for correctness, the window is only a bound on the
memory usage.
use-triple-history condition into a variable
@hannahbast
hannahbast merged commit 7e960f7 into main Aug 10, 2026
10 checks passed
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.

2 participants