Skip to content
About
Main content

Dedup window versus replay

Simulate a modelNeeds Python

Problem

Online deduplication remembers recent event ids for a while (a TTL) and drops repeats inside that window. That handles retries, which arrive seconds apart. It cannot handle a : re-processing last week's data after a bug fix sends events whose ids expired long ago, so they all look new. Replays need a different mechanism: replacement semantics, where the output for a period is overwritten as a whole, so running it twice gives the same result as running it once.

Build a that dedups with an in-memory dictionary of event_id -> time and a 60-second TTL. Feed it 1,000 events, then replay the same 1,000 events 5 minutes later. Count how many are applied twice. Then change the sink to "overwrite the for this date" () and repeat.

The question. How many replayed events does a 60-second catch, and what does overwriting the partition do?


Predict

Replayed five minutes later, how many of the 1,000 events does a 60-second dedup window apply a second time?

Run it

  1. 1Install uv once. It fetches Python and the lab's packages for you.

    Terminal
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. 2Get the script. It is the whole lab: read it, then download it.

    dedup-window-versus-replay.py45 linesDownload
    View the code
    dedup-window-versus-replay.py
    # /// script
    # requires-python = ">=3.10"
    # ///
    # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
    # https://thedataengineering.com/lab-code/LICENSE.txt
    """Dedup window against replay.
    
    A consumer that dedups with event_id -> time and a 60-second TTL, fed 1,000
    events and the same 1,000 again 5 minutes later; then a sink that overwrites
    the whole partition for the date (idempotency by replacement).
    """
    import argparse, json
    
    p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    p.add_argument("--events", type=int, default=1000, help="Events.")
    p.add_argument("--ttl", type=float, default=60, help="Dedup TTL in seconds.")
    p.add_argument("--replay-after", type=float, default=300, help="Seconds before the replay.")
    p.add_argument("--json", help="Also write the results to this JSON file.")
    args = p.parse_args()
    
    events = [(f"e{i}", "2025-10-20", i % 7) for i in range(args.events)]
    def feed(at):
        return [(at + i * 0.01, e) for i, e in enumerate(events)]
    
    # 1. TTL dedup into an append-only sink
    seen, sink = {}, []
    for t, (eid, day, v) in feed(0) + feed(args.replay_after):
        if eid in seen and t - seen[eid] < args.ttl: continue
        seen[eid] = t; sink.append((eid, day, v))
    twice = len(sink) - len({e for e, _, _ in sink})
    
    # 2. Replace the partition for the date
    partitions = {}
    for at in (0, args.replay_after):
        batch = [e for _, e in feed(at)]
        partitions["2025-10-20"] = sorted(batch)                     # overwrite, not append
    same = len(partitions["2025-10-20"]) == args.events
    print(f"TTL dedup ({args.ttl:g} s), replay after {args.replay_after:g} s: {twice:,} of {args.events:,} events applied twice")
    print(f"overwrite the date's partition: {len(partitions['2025-10-20']):,} rows after the replay (identical: {same})")
    
    if args.json:
        with open(args.json, "w") as f:
            json.dump({"tables": {"sinks": [{"sink": "append + TTL dedup", "rows_after_replay": len(sink), "applied_twice": twice},
                                            {"sink": "overwrite the partition", "rows_after_replay": len(partitions['2025-10-20']), "applied_twice": 0}]}}, f)
    
  3. 3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.

    Terminal
    uv run dedup-window-versus-replay.py
    uv run dedup-window-versus-replay.py --help     # every knob
Knobs
Knob · defaultWhat it changes
--events1000 Events.
--ttl60 Dedup TTL in seconds.
--replay-after300 Seconds before the replay.

Our run

A replay five minutes later

A replay five minutes later
SinkRows after the replayApplied twice
append + TTL dedup2,0001,000
overwrite the partition1,0000
Reference runApple M2 · 8 cores · 8 GB
Output · uv run dedup-window-versus-replay.py
TTL dedup (60 s), replay after 300 s: 1,000 of 1,000 events applied twice
overwrite the date's partition: 1,000 rows after the replay (identical: True)
macOS 26.6.2 · Python 3.13.3 · run 2026-10-20

What we found

The TTL dedup applied 1,000 of the 1,000 events a second time, ending with 2,000 rows. Overwriting the date's partition ended with 1,000 rows, the same as before the replay, as expected.


Read it

This is why a dedup window is a correctness parameter, and why replay needs a different mechanism.

Your dedup store keeps 24 hours and you are replaying six weeks: online dedup cannot help. Design the sink for replay up front: overwrite a partition, swap a table atomically, or apply records only if their version is newer than what is stored.