Article

When FIFO Is the Wrong Contract: Designing a Coalescing Event Queue in C++

In high-frequency state-update systems, preserving every event in FIFO order can delay the current value behind obsolete updates. This article explains key-based event coalescing through merge semantics, C++ concurrency, ordering, backpressure, and the event types that must remain complete.

Share

Koharu's reading tip

Whether events may be coalesced is a semantic decision before it is a performance decision. Read with one question in mind: can you explain exactly why a newer update makes an older one unnecessary?

Koharu's reading tip

When a system receives high-frequency state updates, a FIFO queue that drops nothing can look like the safest default. But if the consumer needs the current state rather than a complete history, carefully processing obsolete updates can delay the value that matters now.

On August 7, 2026, the ISO C++ Blog highlighted Ajay Pandey’s design for a coalescing event queue. The core issue is not merely speed; it is deciding which updates for the same key may be combined without changing correctness.

So when is it safe to replace FIFO semantics? The answer begins with an event-level merge contract and extends through C++ synchronization, ordering, capacity, and shutdown.

Keeping every same-key update can make the current state arrive late

Suppose one device emits the state values 10, 20, and 30 within 200 milliseconds. FIFO dispatches all three in order. If a dashboard or health component only needs the current value, however, 10 and 20 are already stale by the time they are consumed.

A coalescing event queue groups updates with the same logical key inside a short, bounded window. The linked ACCU Overload article compares policies that keep the latest value, protect a terminal state, or aggregate numeric updates.

Updates in one window Latest value wins Window aggregate
K:10K:20K:30 K:30 count=3, avg=20

What disappears is not an arbitrary event but an intermediate state that the application has declared redundant. If every transition matters—as it does for an audit trail or transaction stream—the same-looking data must not be coalesced.

Key, merge policy, and flush timing define the coalescing contract

A correct design makes at least three choices explicit: the key that decides which events are related, the merge policy that combines old and new values, and the flush condition that releases pending state downstream.

The merge policy comes from domain meaning, not container shape. Latest-value-wins replaces an older update; a terminal-state policy protects a state such as closed from later non-terminal updates; and an aggregate policy retains required information such as count, sum, minimum, or maximum.

Ordering also needs an explicit choice. As the Diffusion documentation on conflation shows, replacing an event at its old queue position and appending its replacement at the end produce different cross-key orders. A public contract should say whether a key keeps its first position in the window or moves to the position of its latest update.

Multiple producers and one coalescer keep C++ ownership understandable

The highlighted design lets multiple producers submit events through a short enqueue path while one worker owns the pending-state map and executes merge policies. Separating ingestion, coalescing, and dispatch prevents downstream serialization or I/O from becoming producer-side waiting time.

Pending storage can pair an arrival-ordered array with an index from key to array position. A new key is appended, while an existing key applies the merge policy at its current position. With one worker mutating this state, ownership is easier to reason about.

The worker can wait with std::condition_variable and std::unique_lock. The current C++ draft specification for condition_variable allows a wait to return spuriously and defines the predicate overload as repeated checking until the predicate is satisfied. Including pending work and a close flag in that predicate makes wake-up and shutdown conditions explicit; shutdown should drain pending events before joining the worker.

Dispatch should run after releasing the mutex. Calling external callbacks or performing I/O while locked would couple producer latency to downstream performance. The published implementation is a conceptual design sketch, not a performance guarantee for a particular workload.

A wider coalescing window trades fewer emissions for more delay

A short window limits queueing delay but provides fewer opportunities for same-key updates to overlap. A longer window can collapse more updates, but it delays propagation of the newest state. The useful value is therefore not a universal constant; it depends on input volatility and the freshness tolerated downstream.

Operational metrics should separate accepted events, emitted events, replacements, bypasses, flush frequency, batch size, and dispatch duration. A ratio such as 1 - emitted / accepted can indicate how much traffic was reduced, but a high reduction ratio alone is not success. Queueing delay and the freshness observed by the consumer must be evaluated beside it.

Flushing terminal or priority events before the window expires adds another rule. Framing the decision as an SLO—how quickly each event class must become visible—makes the latency-versus-reduction tradeoff concrete.

Coalescing does not replace backpressure or capacity limits

If the queue retains at most one pending event per key, memory usage depends more on the number of active keys in the window than on the total number of incoming events. A workload with many distinct keys can still grow the pending set, so coalescing alone does not solve capacity management.

Backpressure is a different contract. Reactive Streams defines it as demand signaling across an asynchronous boundary so that a fast source cannot force an arbitrary amount of buffering on a destination. Coalescing changes what survives; backpressure controls how much can be accepted.

When capacity is reached, the design still needs an event-specific policy: signal pressure to producers, drop only updates that are allowed to be lossy, flush early, or spill important events to durable storage. Partitioning by key is another option, but hot keys, partition-local ordering, shutdown, and observability add complexity. It is a step to consider after measuring contention in the single-coalescer design.

Audit records, transactions, and commands must preserve meaningful history

Audit records, accounting transactions, non-idempotent commands, event-sourcing events, and exact-replay inputs give meaning to each intermediate event. Overwriting them merely because they share a key trades away correctness rather than redundant work.

If state updates and commands share a pipeline, provide an explicit bypass path. For coalesced data, deterministic tests should cover latest-value, terminal-state, and aggregate policies. Separate boundary tests should cover window expiry, bypass dispatch, shutdown draining, concurrent producers, and dispatch outside the lock.

The expected result is no longer “every event appears in FIFO order.” It is “the state defined by the coalescing contract appears with the promised timing and ordering.” Changing the queue contract changes the test oracle too.

Replace FIFO only when an older update is provably unnecessary

A coalescing event queue fits consumers that need current state or a windowed aggregate rather than complete history, and only when the application can state how a newer update supersedes an older one. With key selection, merge policy, ordering, flush, and bypass semantics documented, the C++ synchronization machinery can be designed to preserve that meaning.

Coalescing is neither backpressure nor a durable log. Capacity and demand still need separate controls, while history-bearing events must remain in FIFO or durable storage.

The first question is not “how many events can we remove?” It is “which information can disappear while the result remains correct?” A queue that answers that question can deliver the present without making the consumer drain the past.

Source

Share

Related Articles

These articles share nearby categories or tags, so you can keep reading along the same thread.