tidepool

Tape and Tally

one day of scanner events, read live and read nightly

Every time a slide scanner finishes a slide, it can send a small message: this slide, this scanner, this time. A batch job collects a whole day of those messages and counts them once, overnight. A streaming job counts them as they come in. This page runs one made-up day of those messages through both, so you can watch what the stream has to handle that the batch job mostly does not: partitions, readers that fall behind or crash, messages that turn up late, and messages that get read twice.

The wire room

Press Play. The clock runs from 06:00 to 02:30 the next morning. Four scanners at two made-up sites send events into a log split into 3 partitions (the tapes). Consumers read the tapes and keep an hourly tally. At 02:00 a nightly batch job counts the whole day from the same log. Things to try, and what the words mean, are below the wire room.

06:00

Scanners

Site Alink up
SC-1 SC-2
Site Blink up
SC-3 SC-4

The log: one tape per partition

Consumer group

Hourly tally, by when each slide was scanned

Scans made so far
Counted by the stream
Dropped as too late
Events read twice
Watermark
Last window published
Events waiting (total lag)
Nightly batch

Each consumer handles 1 event a minute, a made-up figure. Above 15 per scanner per hour, a single consumer falls behind.

How long the stream waits for stragglers before it publishes an hour and stops accepting events for it.

Off: every message read is counted. On: a message read a second time is recognised by its id and skipped.

Wire log

Simplified: four scanners, three partitions, consumers that each handle a fixed 1 event a minute, offsets committed every 15 minutes, a rebalance that stops everyone for 2 minutes, and one global watermark taken from the newest event time seen. Real systems tune all of these. "Crash one" always picks the consumer with the most uncommitted work, the worst case for replays. The batch job here reads the complete log and removes repeats, so it is always right; real batch jobs have their own bugs. The scan counts are random but repeatable: Reset gives the same day again.

Things to try

  1. Let a normal day run. The hatched bar for the current hour fills up, then turns solid about 15 minutes after the hour ends, once the watermark passes. That delay is the price of waiting for stragglers. Compare the batch's answer: exact, but not until 02:00, seven hours after the last scan.
  2. Cut site B's link around 11:00. Site B keeps scanning and holds its events. When the link comes back 90 minutes later, they all arrive at once with event times up to 90 minutes old. The 11:00 window was already published, so its site B scans land in red: dropped as too late. Reset, set the watermark delay to 90 minutes, and try again: nothing is dropped, but every hour is published 90 minutes after it ends (and the last two hours never publish, since no later events arrive to move the watermark). The batch job counts them all either way, because it waits for the whole day.
  3. Crash a consumer mid-morning. It had read some events since its last commit. Its partitions go back to the committed offset and another consumer reads those events again, so the stream counts them twice. Reset, tick "skip event_ids already seen", and crash it again: the repeats are recognised and skipped. (With one consumer left doing all the work, a few events may also come out late; item 5 shows why.)
  4. Add consumers. With three partitions, a fourth consumer has nothing to read and sits idle. Each partition is read by exactly one consumer in the group, so the partition count caps how many can share the work. Raise the scan rate to 40 first: P0 still falls behind with three consumers, because one partition cannot be split between readers.
  5. Push the scan rate up and remove a consumer (or press Rush, which triples the rate for an hour). Lag grows on the busiest tape (P0 carries two scanners). A reader that is behind on one partition but up to date on the others makes events from the slow partition look late to the watermark, and some get dropped even though no network link failed.
  6. Let the day end. The 18:00 window never gets published, because the watermark only moves when newer events arrive, and after 19:00 none do. Real streams handle a quiet input in their own ways; this toy just waits.

What the words mean

Topic, partition, offset, key

The log here works like Apache Kafka's. A topic is a named log of events, split into partitions so several machines can share it. Each event in a partition gets the next number, its offset. Events with the same key (here, the scanner id) always go to the same partition, and a partition is read in the order it was written; there is no order across partitions.1 Three partitions and four scanners means one tape carries two scanners, so the load is uneven.

Consumer group, lag, rebalance

Consumers that share a group id split the partitions between them: each partition goes to exactly one consumer in the group, and one consumer may hold several. A consumer beyond the partition count gets nothing. When a consumer joins, leaves, or dies, the group reassigns partitions, and in the classic version of this rebalance nobody reads while it happens.2 Lag is how far a reader is behind the newest offset in its partition; some tools measure it from the last committed offset instead.7

Commits and reading twice

A consumer records how far it has got by committing an offset. Committing after the work is done means a crash between the work and the commit causes the same events to be processed again: at-least-once delivery. The usual answer is to make the write idempotent, for example by keying it on a stable event id so a repeat changes nothing.3

Event time, windows, watermarks

Event time is when the slide was scanned; processing time is when the message got read. Hourly windows here are by event time, so a late message still belongs to its own hour. A watermark tells the engine how late data may be: Spark's Structured Streaming, for one, takes the newest event time seen minus a delay you choose, keeps a window open until the watermark passes its end, and can drop data older than that.4 A longer delay drops less and publishes later. This page uses that idea in simplified form.

Where batch fits

Structured Streaming runs a stream as a series of small batch jobs by default, with latency down to about 100 milliseconds, using the same query you would write for a batch.5 The nightly job is the other end of the same idea: one big batch, run by a scheduler. Airflow, a common choice for scheduling such jobs, describes itself in its documentation (here version 2.3.3) as a workflow tool and says plainly that it is not a streaming system.6 Many setups use both: a stream for the dashboard during the day, and a nightly batch that recounts from the log and corrects whatever the stream got wrong, a pattern often called the lambda architecture.8

Already out there

Both look closely at the log itself. This page tries putting a nightly batch beside the stream, with event-time windows, late data, and replays, on a day of made-up scanner traffic.

Sources

  1. Topics, partitions, keys, and per-partition order: Apache Kafka, Introduction; Confluent Developer, Kafka partitions.
  2. One consumer per partition within a group, idle extra consumers, and rebalances pausing consumption: O'Reilly, Kafka: The Definitive Guide, chapter 4.
  3. At-least-once delivery from committing after processing, and idempotent consumers: Conduktor, Delivery semantics for Kafka consumers.
  4. Watermarks in Structured Streaming: Apache Spark, DataFrame.withWatermark; Databricks, Event-time aggregation and watermarking in Structured Streaming (2017).
  5. Micro-batch processing and latency down to about 100 ms: Apache Spark, Structured Streaming Programming Guide.
  6. "Airflow is not a data streaming solution": Apache Airflow documentation, version 2.3.3.
  7. Consumer lag: Conduktor, Consumer lag monitoring.
  8. The lambda architecture: Databricks, Lambda architecture glossary.