Skip to content

Anchor queries at the head, bound the replay window, stop acking failed writes - #34

Merged
johardi merged 3 commits into
epic/303-ssefrom
feat/301-no-history-replay
Jul 27, 2026
Merged

Anchor queries at the head, bound the replay window, stop acking failed writes#34
johardi merged 3 commits into
epic/303-ssefrom
feat/301-no-history-replay

Conversation

@johardi

@johardi johardi commented Jul 25, 2026

Copy link
Copy Markdown
Member

Two commits, stacked on #33 (retarget to epic/303-sse once that merges); part of protegeproject/webprotege-gwt-ui#303.

Commit 1 — the service half of protegeproject/webprotege-gwt-ui#301: the events query gains a latestOnly mode returning an empty window at the project's current head (no Mongo read), and the normal query is bounded — ordered by sequence, capped at 500 (configurable), endTag = end of the returned window so far-behind clients page forward. Kills the unbounded full-history replay on every project open. Client half: protegeproject/webprotege-gwt-ui#313.

Commit 2 — the event-history companion of protegeproject/webprotege-gwt-ui#299: a failed archive write was logged and ACKed, permanently deleting the event from catch-up history. It now rethrows so the container redelivers, with bounded exponential-backoff retry (BeanPostProcessor — the container is built inside the ipc library) and reject-without-requeue on exhaustion. Redelivery upserts by event id, so no duplicates (integration-tested).

Full module suite: 17/17 under JDK 17 (Testcontainers Mongo + Rabbit).

@johardi
johardi changed the base branch from feat/296-shared-sequence to epic/303-sse July 27, 2026 19:21
johardi added 3 commits July 27, 2026 12:21
The archive numbered events from a single global counter incremented with
a read-then-write under a JVM-local lock: not per project, and not safe
with more than one service instance. Each project now has its own counter
document advanced with a single findAndModify $inc, which MongoDB runs
atomically server-side, so ordinals stay dense and gapless per project
under concurrency. A startup migration seeds each project's counter to
the maximum ordinal already archived, using setOnInsert so it is
idempotent and never rewinds a counter that has advanced — newly minted
numbers always stay above any bookmark a connected client still holds.

After the archive write, the service now re-publishes the change bundle
as a SequencedPackagedProjectChangeEvent carrying the assigned ordinal.
The gateway will push that instead of the raw event, which is what lets
it stamp truthful event tags — and because publication happens strictly
after persistence, anything a client hears about is already fetchable
through the pull path. Delivery hardening of this publish is #299's
scope; the failure is not silently swallowed here.

A compound index on (projectId, timeStamp) backs the pull query, and a
read-only current-sequence accessor is exposed for the project-open
anchor (#301).

Fixes the service half of protegeproject/webprotege-gwt-ui#296
(part of protegeproject/webprotege-gwt-ui#303).
…s window

The events query gains a latestOnly mode: instead of returning archived
events it answers with an empty window positioned at the project's
current head, read from the per-project sequence counter. A client
opening a project uses it to start listening for live changes without
downloading the past.

The normal query is no longer unbounded: results come back ordered by
sequence with a configurable cap (default 500), and the response's end
tag marks the end of the returned window, so a far-behind client pages
forward naturally over successive requests instead of receiving the
whole archive in one response. The compound index from the sequence work
backs the ordered query.

The service half of protegeproject/webprotege-gwt-ui#301
(part of protegeproject/webprotege-gwt-ui#303).
A failed archive write was caught, logged, and acknowledged, so the
event vanished from the catch-up history forever — exactly the store
that reconnecting clients rely on to fill their gaps. The failure now
propagates so the listener container redelivers the message, and a
bounded retry policy with exponential backoff (tunable via
webprotege.events.retry.*) turns transient store hiccups into recovered
writes while stopping a genuinely poison message from hot-looping —
after the attempts are exhausted it is rejected without requeue.
Redelivery cannot duplicate archive rows because the event id is the
Mongo document id, so a replayed save upserts; an integration test pins
that. The listener container is built inside the ipc library, so the
retry policy is attached through a bean post-processor.

A retried attempt that already minted a sequence ordinal leaves a benign
gap in the numbering; ordinals are opaque and monotonic to clients, so
nothing waits on the missing value.

The event-history companion to protegeproject/webprotege-gwt-ui#299
(part of protegeproject/webprotege-gwt-ui#303).
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