Skip to content

The replay worked. Why did it reserve stock again?

One order, five failure boundaries · Part 4 of 5 · 7 min read

Read the worked explanation ↓

A dashboard calculation was wrong for three months. The team has a complete archive of order events, so someone starts a new consumer group and reprocesses them. The dashboard numbers improve. Then the warehouse reports new reservations for old orders.

There is one important change to the system from the previous episode. The replay job uses the generic event handler without consulting the warehouse's durable fulfillment identity, or bypasses it through a new handler namespace. Had it enforced the same business identity, that could have blocked a second reservation. We are investigating a flawed replay path, not claiming that every replay defeats idempotency.

Interview question. Processing receipts are retained for 48 hours. Event history is retained for 90 days. Rebuild a corrected dashboard without sending old emails or reserving stock again. Explain how to catch up with live traffic and safely switch readers to the corrected view.

Think first

An operator wants to replay 90 days of order events through a consumer whose receipts last 48 hours. Rebuild analytics without reserving stock again or sending old emails, then cut over safely.

Take a few minutes to form an approach, then compare it with the worked explanation.

Reveal the worked explanation

The missing receipt proves very little

An old event e901 arrives on day 30. The handler checks its processing-receipt table, which no longer contains e901. It treats the event as new and performs the effect again.

Two histories are now indistinguishable to that lookup:

A. e901 never completed
B. e901 completed, but its receipt expired

Receipt lookup in either case: not found

An absent receipt does not prove an effect never happened. Starting a new Kafka consumer group creates a new reading position. It does not create a new legitimate business operation. And changing fulfillment-live to fulfillment-replay may bypass the very receipt scope that made repeated delivery safe.

This is a different problem from the 48-hour retry protection we designed earlier. Here, an operator is deliberately introducing old records into a path that can still change the world.

Rebuilding data is not repeating an action

For the dashboard, I would not run the warehouse or email handler at all. I would build a restricted projection worker that reads historical facts and writes only to a new dashboard generation. It has no credentials to reserve inventory, no permission to send notifications, and no ability to publish fulfillment commands.

That separation matters more than a boolean called replayMode buried in application code. If someone accidentally invokes the wrong method during a backfill, the execution environment still cannot ship old orders.

Say the corrected view is daily-order-totals-v2. Every historical order contributes a deterministic record keyed by the view generation and original event ID. Duplicate archive delivery does not add its amount twice. The input must contain the facts the calculation requires, such as accepted amount, currency, and event date. Looking up today's catalog price would answer a different historical question.

Nothing in this requires the whole business system to be event sourced. It requires sufficiently complete history for the particular derived view. If required facts were never recorded, replay cannot invent them.

Catch up without disturbing live readers

First capture an archive boundary for each source partition and a manifest describing source ranges, schema versions, transformation version, output generation, and checkpoint positions. Historical records are applied to the new projection while the old dashboard continues serving requests.

When the archive catch-up reaches its boundary, continue from the appropriate positions in the live stream. If archive and live data overlap, use original event IDs to suppress repeated contributions. If there is a gap, stop and report it. Kafka partitions have separate offsets, so “replay to offset 900” is not a sufficient cross-partition boundary.

Do not give the backfill all the capacity it asks for. It may share database disks, network bandwidth, or broker capacity with live traffic. Throttle against those actual bottlenecks. A separate consumer group protects reading progress but does not provide resource isolation.

Before switching the dashboard, compare totals by day and currency, sample known orders, verify covered source positions, inspect rejected records, and check whether the new view meets the freshness objective. An overall total can appear correct while missing one partition and double-counting another.

Only then change the dashboard's generation pointer. Keep the old generation for a defined recovery window. Rollback is possible only while the old view remains compatible and sufficiently up to date.

Four different kinds of memory

The checkout operation record, the broker's send-deduplication window, the consumer's generic receipt, and the warehouse's durable business fulfillment record are not interchangeable. They may have different lifetimes because they defend against different problems.

A short receipt window can be appropriate for ordinary redelivery. It cannot protect a privileged process that may replay any event from years ago. Increasing retention may help, but it still does not make it safe to resend irreversible emails or reapply changed business rules.

What if the request is different: “We found 200 accepted orders that genuinely never reached fulfillment. Finish them”? That is reconciliation, not analytics replay. Consult the authoritative fulfillment outcome for each business identity, preserve the original operation IDs, and follow its recovery protocol. Do not send every historical event through the live handler and hope the receipt table sorts things out.

The interesting question in an interview is therefore not just how to read old offsets. It is which historical facts may be recomputed and which real-world actions must never be repeated. Enforce that boundary with the worker's permissions and output contracts, then make completeness and cutover measurable.

One more failure can erase the evidence without waiting 48 hours. A new region can be promoted from a replica that never received the accepted order. What happens to all these guarantees then?

Go deeper

Replay details you will need in production

Retention must cover permitted histories

Four clocks often get confused: API idempotency retention, broker producer deduplication, consumer processing receipt retention, and business-effect identity retention. Their responsibilities differ. Amazon SQS FIFO’s five-minute send-deduplication window, for example, is not evidence that a stock reservation was never performed six months earlier.

If a system permits arbitrarily old manual replay into a live side-effect handler, no finite receipt TTL protects that path forever. Restrict which operations can be replayed and retain business identities for the lifetime of the actual business invariant. Do not turn every retention problem into “store all event receipts forever.”

As a sizing exercise, 1,000 distinct event receipts per second for 90 days produces 7,776,000,000 receipts. At a hypothetical 64 bytes per raw receipt, that is about 497.7 GB before indexes, storage overhead, replicas, and backups. The storage cost may be acceptable for important business identities, but it does not authorize replay to send emails again.

Reaching the present without a silent gap

A replay manifest should record the source history used, partition positions at the archive boundary, schema and transformation versions, destination generation, and completed checkpoints. Archives and live logs can overlap. Preserve event identities for overlap deduplication, and explicitly detect holes.

Avoid cutting over when only the grand total matches. Compare source ranges and totals by finer categories, check known samples, measure event rejection counts, and require live catch-up within a stated freshness bound. After switching a view-generation pointer, keep the previous generation only as long as it remains a valid rollback target.

Projected state is not authoritative fulfillment state

Rebuilding a dashboard or search index uses historical facts to create a new derived representation. Finishing 200 truly unfulfilled orders needs a separate reconciliation process against authoritative fulfillment records and external provider outcomes. The same archive may help diagnose both cases, but it should not have permission to create physical effects merely because the consumer group is named backfill.

Check your reasoning

  • Notes that missing receipts after TTL do not prove work was never done
  • Uses a restricted projection process without fulfillment or email permissions
  • Builds a new derived generation with stable per-event contribution identity
  • Plans archive boundaries, live catch-up, gap detection, and backfill throttling
  • Checks completeness and freshness before cutover and distinguishes reconciliation from analytics replay

Common mistake: A new consumer group or a replayMode flag does not automatically prevent the wrong business handler from executing.

You must rebuild a dashboard from old events. Which is the strongest safety control?

Read the answer explanation

Run a separate projection worker without permissions to reserve inventory or send email — The worker can derive data but is incapable of performing the dangerous side effects. Retention and application flags are not sufficient boundaries for unrestricted replay.

Apply it elsewhere

You need to rebuild an enterprise search index after fixing permission filtering. The source archive includes old access-control events. How do you rebuild without exposing revoked documents during cutover?

Compare your approach

Build a new index generation with a complete source boundary and current authorization checks. Treat revocation as a correctness boundary at query time as well as an indexing event. Catch up, test denied-document samples, verify index completeness, and switch readers only when access-control and freshness objectives hold.

One more constraint

Your archive contains orders but not the accepted historical price. Can you correctly recompute past revenue from today's catalog price?

Quick revision

The answer in an interview

A missing deduplication receipt after 48 hours cannot tell me whether an old business effect never happened or whether its receipt expired. A new consumer group only changes reading progress. It does not make a previously fulfilled order new.

I would not replay analytics through a handler that can reserve inventory or send email. I would use a separately permissioned projection worker and create a new dashboard generation. Each original event contributes deterministically and is deduplicated within that generation. I would record archive boundaries per partition, apply history, catch up from the live stream without gaps, and compare output against source ranges and known results before switching a reader pointer.

Genuinely unfinished fulfillment work is a different task. It requires reconciliation against authoritative business outcomes using the original fulfillment identities.

Remember: Replay purpose, permissions, sufficient historical facts, no source gaps, validated cutover.

Trap: Raising every dedupe TTL cannot make unrestricted re-execution of irreversible actions safe.

References

  • Idempotent requests · Stripe API documentation

    Parameter comparison, replayed results, and behavior after key pruning. This is a Stripe-specific example, not a universal API contract.

  • KafkaConsumer · Apache Kafka 4.1 client API documentation

    Per-partition positions, committed offsets, consumer groups, and offset gaps. Version is pinned for reproducibility, not claimed to be latest.

  • SendMessage · Amazon SQS API reference

    The five-minute FIFO send-deduplication interval and its boundary.

  • Event Sourcing: External Updates and External Queries · Martin Fowler

    Reconstruction without repeating external updates, and using historical rather than current external facts.

  • Event Sourcing Pattern · Microsoft Azure Architecture Center

    Materialized views, sufficient event history, and reconstruction of derived state.