Skip to content
About
Main content

Trigger mode versus sink

Simulate a modelNeeds Python

Problem

A that fires and then again (when late data arrives) can emit either the full total each time (accumulating) or only the change since the last firing (discarding). The must match: an overwriting sink by window wants totals, an additive counter wants changes. A mismatch double-counts or loses data, with no error.

Emit a window total twice (3,100, then 3,260). Write both into (a) a table keyed by window with overwrite and (b) an additive counter, in accumulating mode, then in (emit 3,100, then +160).

The question. Which combinations of mode and sink give the right total of 3,260?


Predict

Discarding mode into a sink that overwrites by window key: what total comes out (correct is 3,260)?

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.

    trigger-mode-versus-sink.py33 linesDownload
    View the code
    trigger-mode-versus-sink.py
    # /// script
    # requires-python = ">=3.10"
    # ///
    # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
    # https://thedataengineering.com/lab-code/LICENSE.txt
    """Trigger mode against sink.
    
    A window fires twice: an early total of 3,100, then the final 3,260. The
    firings are emitted in accumulating mode (each firing carries the full total)
    or discarding mode (each carries only what is new: 3,100, then +160), into a
    sink that overwrites by window key or one that adds.
    """
    import argparse, json
    
    p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    p.add_argument("--json", help="Also write the results to this JSON file.")
    args = p.parse_args()
    
    firings = {"accumulating": [3100, 3260], "discarding": [3100, 160]}
    rows = []
    for mode, values in firings.items():
        for sink in ("overwrite by window key", "additive counter"):
            table = {}
            for v in values:
                if sink.startswith("overwrite"): table["w1"] = v
                else: table["w1"] = table.get("w1", 0) + v
            rows.append({"mode": mode, "sink": sink, "result": table["w1"], "correct": "yes" if table["w1"] == 3260 else "no"})
    for r in rows: print(f"{r['mode']:<13} into {r['sink']:<24} -> {r['result']:>5}  correct: {r['correct']}")
    
    if args.json:
        with open(args.json, "w") as f:
            json.dump({"tables": {"combinations": rows}}, 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 trigger-mode-versus-sink.py
    uv run trigger-mode-versus-sink.py --help     # every knob

Our run

Four combinations

Four combinations
ModeSinkResultCorrect
accumulatingoverwrite by window key3.26Kyes
accumulatingadditive counter6.36Kno
discardingoverwrite by window key160no
discardingadditive counter3.26Kyes
Reference runApple M2 · 8 cores · 8 GB
Output · uv run trigger-mode-versus-sink.py
accumulating  into overwrite by window key  ->  3260  correct: yes
accumulating  into additive counter         ->  6360  correct: no
discarding    into overwrite by window key  ->   160  correct: no
discarding    into additive counter         ->  3260  correct: yes
macOS 26.6.2 · Python 3.13.3 · run 2026-10-20

What we found

Accumulating into the overwriting table gave 3,260; into the additive counter, 6,360. Discarding into the additive counter gave 3,260; into the overwriting table, 160, only the last change.


Read it

Trigger mode and sink semantics are one decision, not two: totals belong in sinks that overwrite by key, deltas in sinks that add. Writing the mode into the output's contract is the only way a can tell which it is reading.