Data systems · E26 · Implementation

Idempotent event handling starts with the right identity

Typed deduplication keys prevent repeated messages from becoming repeated state changes. A bounded cache is useful—but it is not exactly-once delivery.

Typed Python eventsOrderedDict cacheCustom router
Different transport envelopes may describe one event. Semantic identity, rather than message appearance, controls deduplication.
Figure 1. Same event. Different envelope.. Different transport envelopes may describe one event. Semantic identity, rather than message appearance, controls deduplication. Event identity schematic. Original vector illustration.

Follow the information

From input to outcome

Transport envelopes can differ while describing the same semantic event. The cache gates the handler, and its finite retention bounds the deduplication guarantee.

Transport envelopes can differ while describing the same semantic event. The cache gates the handler, and its finite retention bounds the deduplication guarantee.
Figure 2. Information flow. Solid arrows carry observations, tensors or artifacts; other routes are explicitly labelled. Signal shapes, matrices and network icons are schematic, not measured samples or literal neuron counts. Open full-size SVG ↗ On narrow screens, scroll the diagram horizontally.

Read this alongside Figure 1: Different transport envelopes may describe one event. Semantic identity, rather than message appearance, controls deduplication. The module map and layer-level figures below expand the operations in this route.

Idempotent event handling starts with the right identity: architectureTransport messages: May repeat or arrive late → Typed event parser: Fill / lifecycle / order → Semantic identity: Type-specific key → Bounded seen cache: Duplicate suppression → State handlers: One accepted transition. A high-level module map; comparison branches and training details are explained in the article.DATA SYSTEMS / E26 / MODULE MAP01 INPUTTransport messagesMay repeat or arrive late02 MODULETyped event parserFill / lifecycle / order03 MODULESemantic identityType-specific key04 MODULEBounded seen cacheDuplicate suppression05 OUTPUTState handlersOne accepted transition
Source-grounded module map. Boxes summarize operations, not individual neurons; comparison arms and training paths are detailed below. On a small screen, scroll the diagram horizontally.
Transport messages — May repeat or arrive late

The architecture in context

The system we are building

An online system can receive the same logical event more than once. The router derives different keys for different event types: a fill uses an execution identity, while a lifecycle event prefers a sequence number. This is more meaningful than hashing a transport envelope whose timestamps or serialization may change between deliveries.

Who does what in the stack

Typed Python events
Separate message meaning from transport representation.
OrderedDict cache
Bounds local duplicate-tracking memory.
Custom router
Dispatches accepted events and records diagnostics.

The project adds typed routing, deduplication and diagnostic counters around the transport. These are useful inference-system patterns whenever events update memory or trigger an action. The code excerpt describes identities, not a recipe for placing orders.

Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph.
Framework responsibility map. Each row maps a library or custom component to its job; rows are not a sequential inference graph. Open full-size SVG ↗

Open up the implementation

Give deduplication a memory horizon

A concrete operation-level view of this implementation; no unobserved neural architecture is implied.
A concrete operation-level view of this implementation; no unobserved neural architecture is implied. Open full-size SVG ↗

Fill identities, lifecycle sequences and order identities have different semantics, so they need different keys. Transport timestamps are often unsuitable because a retry may receive a new envelope timestamp. A local cache suppresses repeats only while the key remains retained and the process state survives.

The mathematical contract

accept⁡(e)=1[k(e)∉S],S←BoundedInsert⁡(S,k(e))\operatorname{accept}(e)=\mathbf 1[k(e)\notin S],\qquad S\leftarrow\operatorname{BoundedInsert}(S,k(e))

A larger cache extends retention but cannot fix a key that merges distinct events. Restart, eviction and multi-process consumers are separate failure modes. Access checks do not turn an insertion-ordered dictionary into an access-refreshing LRU unless order is explicitly updated.

Implementation and resource card

Capacity / budget
Memory scales with retained keys. Bounded insertion-order state is not a persistent exactly-once guarantee.
Execution evidence
This revision inspects and explains the archived implementation. It does not rerun the original workload. No unrecorded convergence time, throughput or accelerator result is supplied.
Current reproduction context
Current workstation, supplied by the author: Apple M4, 128 GB unified RAM, 40 GPU cores and 16 CPU cores. This is context for prospective reproduction, not attribution of every archived run. Python and framework versions are not fully locked for these historical sources; declarations, when available, are identified separately.

From explanation to a reproducible check

Replay a logical duplicate with a new envelope, then a distinct event with the same order ID. Force eviction and restart. Verify both the intended suppression and its declared boundary.

Preserve input identities, configuration and failure records with the result. A successful numerical check only establishes the operation it exercises: it does not certify an entire dataset, model or deployed system. Reproduce the interface on a small deterministic input before optimizing throughput or increasing workload size.

A closer look at the implementation

The code that carries the idea

The actual order-event branch keys by order identity and type, despite a broader timestamp description in the docstring. Code behavior takes precedence over the comment. A missing lifecycle sequence falls back to a weaker composite identity, so collision behavior must be considered explicitly.

Python · file · lines 416–445
    def _get_dedup_key(self, event: BaseEvent) -> str:
        """
        Generate de-duplication key based on event type
        
        Different event types need different de-dup strategies:
        - FillEvent: order_id + exec_id (unique execution)
        - LifecycleEvent: seq (if available) or order_id + type
        - OrderEvent: order_id + type + timestamp
        """
        if isinstance(event, FillEvent):
            # Fill: order_id + exec_id (most specific)
            return f"fill:{event.order_id}:{event.exec_id}"
        
        elif isinstance(event, LifecycleEvent):
            # Lifecycle: prefer seq, fallback to order_id + type
            if event.seq:
                return f"lifecycle:{event.seq}"
            return f"lifecycle:{event.order_id}:{event.type}"
        
        elif isinstance(event, OrderEvent):
            # Order: order_id + type (may get multiple OrderCreated, but type differs)
            return f"order:{event.order_id}:{event.type}"
        
        elif isinstance(event, CancelEvent):
            # Cancel: order_id (should only get one per order)
            return f"cancel:{event.order_id}"
        
        else:
            # Fallback: type + timestamp
            return f"event:{type(event).__name__}:{event.ts.timestamp()}"

Verbatim archive excerpt from event_router.py. Context-dependent historical code, not a standalone runnable program. Comments retain their original wording; the article distinguishes implemented behavior from stale or overbroad comments.

The boundary that matters

The seen-event container is bounded and insertion-ordered; membership checks do not make it a full access-refreshing LRU. After eviction, an old duplicate can be accepted again. Process restart and multi-process coordination create additional boundaries that a local cache does not solve.

Keep building

Other posts of interest