learn-streaming-data-with-phoebe / Session 5 of 6
Learn Streaming Data with Phoebe · Session 5 of 6

Late data

Session 4 ended on a dip you caused on purpose: strict event-time windows made every bar honest and silently dropped six stragglers. Tonight you choose, deliberately, how long an hour stays open - and then you meet the correction mechanism that makes the whole day exact anyway. The window is a deadline. The log is a memory. You need both.

🟠 Advanced Your windows are honest and leaking 45 min
0-3 · Welcome 3-18 · Watermarks, and the log as truth 18-40 · Build-along: repair the dip, then replay the day 40-45 · Q&A
Part 0

Where the artifact stands

The order stream is deduplicated and honestly bucketed: 88.0% accurate, every bar in the right hour, 6 late events dropped on the floor. The drop was the strict window doing exactly what it promised. Tonight the promise gets renegotiated - and then, with the replay lever, outgrown.

🎫 Clear all three quiz questions to stamp your passport Live = presented in the 45 minutes Self-study = the full depth, after class

★ What you walk out with: the watermark as a promise the system makes to itself about completeness, allowed lateness and the side output as the tools for keeping late money counted, and the replay move - the log-powered correction that turns "fresh but 93.6%" into "exact, end of day". Plus the reason no watermark setting can ever reach 100% at 2pm.

Part 1 · covers watermarks

The watermark is a promise 8 min live

Event-time windows raised a question that processing time never faces: when is an hour FINISHED? Streaming systems answer with a watermark, and the Flink docs define it exactly: "A Watermark(t) declares that event time has reached time t in that stream, meaning that there should be no more elements from the stream with a timestamp t' <= t".

The watermark sweeps; the window closes; a straggler knocks 1pm - 2pm window on-time orders, by event time Watermark(2:00) "no more elements with timestamp <= 2:00" 12:55 order arrives 2:50pm ✕ dropped by default The watermark is a completeness promise. A late event is that promise, broken - and dropped by default.
🔍 Click to zoom - a watermark sweeping past the window boundary while one straggler arrives behind it.
Live A promise about completeness, and who gets to make it 5 min ▶

Read the definition again slowly: a watermark is not a measurement, it is a declaration. The system announces "event time has reached t - nothing with a timestamp at or before t is still coming", and then acts on its own announcement: windows ending at or before t close and emit. A late event is what it sounds like when that promise turns out to be false. And the default consequence is unsentimental - late elements are dropped when the watermark is past the end of the window. That is precisely the mechanism behind session 4's six lost stragglers.

Which makes the watermark setting a business decision wearing an engineering costume. "How long does the lunch hour stay open for corrections?" is not a tuning knob - it is a question about how your data actually arrives, and about who would rather wait than restate a number. Somebody has to own the answer, and it is rarely the person writing the pipeline config.

Real world

A store's order tablet drops off wifi at 12:40 and syncs everything at 2:50pm. If the watermark allows three hours of lateness, the lunch-hour bar quietly corrects itself mid-afternoon and nobody notices. If it allows five minutes, lunch revenue is permanently understated in the live view - and the first person to discover it is whoever reconciles against the ledger.

Self-study Allowed lateness, and the side output for everything later 4 min ▶

Flink splits the decision into two levers. Allowed lateness keeps a window reopenable for a bounded time after the watermark passes its end: a straggler inside that grace period triggers the window to fire again, and those late firings are "updated results of a previous computation". Downstream consumers have to be ready for a number that was already published to change - which is exactly what a self-correcting hourly bar means.

And for events later than even THAT, there is sideOutputLateData: instead of vanishing, too-late events are captured to a side channel where they can be counted, monitored, and investigated. The professional standard is simple: late data may be excluded from a view, but it should never be invisible. A dropped-events counter you can see is a data-quality metric. A silent drop is a mystery you will meet again during an audit.

Part 2 · covers the log as truth

The log makes corrections possible 7 min live

Watermarks bound how long a LIVE view waits. But the events themselves never expired - they are sitting in the log, immutable and in order. That is where the real correction power lives, and two writers nailed it years apart.

Every view is a cached answer. The log is the question, kept forever. Live windowed view NOW - seconds fresh, 93.6% End-of-day replay view EXACT - $6,115, 100.0% same events, same order, two honest answers The log: immutable, ordered, the source of truth code fix fix, then rebuild A bug in a view is not a disaster: fix the code, replay the log, and the view converges on the truth.
🔍 Click to zoom - one immutable log, two views built from it, and a code fix triggering a rebuild.
Live Two sentences that carry the whole idea 4 min ▶

Nathan Marz compressed it to an equation: "Query = Function(All Data)". If the data is immutable - facts appended, never edited - then a bug in your query logic is a recoverable event: fix the function, recompute from the master dataset, and history itself is repaired. Mutable systems cannot do this, because the bug already overwrote the evidence.

Martin Kleppmann then flipped the mental model of where truth lives: "The database you read from is just a cached view of the event log." Replay the events in the same order and you reconstruct exactly what happened. And here is where session 4 pays off: event-time windows are deterministic - the same log through the same windows gives the same answer, every run. Determinism is what turns "replay" from a hopeful word into a guarantee that the rebuilt view CONVERGES.

Real world

This is what replay is for a stream: the batch job's old superpower, bought back. The nightly batch was complete because it waited for everything. A log-backed stream gets NOW from the live view and EXACT from the replay - off the same events, with no second data source to reconcile.

Self-study CDC and event sourcing - the deeper track 3 min ▶

Two named patterns take the log-as-truth idea further, and both live in chapter 11 of Kleppmann's Designing Data-Intensive Applications. Change data capture turns a database's own changes into a stream: every insert, update and delete becomes an event other systems can consume, so an existing application database becomes a log producer without being rewritten. Event sourcing goes further and stores the events AS the system of record - the application's state is always a view computed from them.

Same shape at two levels of commitment: CDC bolts the log onto a mutable database; event sourcing builds the whole system on the log from day one. The book is the canonical treatment, and it is the right next read once tonight's replay lever has made the idea physical.

Build-along

Repair the dip, then replay the day ★ 22 min · everyone builds

Two new levers tonight. The watermark renegotiates how long an hour stays open. The replay reprocesses the whole log after the day ends. One buys back the stragglers; the other buys back everything.

Rebuild session 4's end state: Stream it ON, Idempotent consumer ON, Event-time windows ON. Confirm the headline reads 88.0% with 6 late events dropped - honest bars, leaking money.

Flip "Watermark: allow 3h lateness" and watch two numbers at once. The dropped counter falls back to 0 - the tablet-sync stragglers are caught and folded into their TRUE hours - and accuracy climbs to 93.6%. The dip is repaired without giving back the honest buckets: still event time, still the lunch rush in the lunch hour.

Now ask the obvious question: why not 100%? Read the counters. The dashboard shows $3,485 of the $3,723 that has truly happened by 2pm. The missing money has not ARRIVED yet - it is sitting on an offline tablet somewhere - and no semantics on earth can count an event the system has not received. Completeness is a moving target: 2pm's truth itself grows as stragglers land.

Find the replay lever. It is passport-gated at 4 stamps - and there is a visible bypass link for anyone who has not been collecting them, used honestly. Flip it: the view jumps to end of day, $6,115, accuracy 100.0%. Say precisely what happened: the FULL log was reprocessed through the SAME deterministic event-time windows, so the rebuilt view converged on the whole day's truth. Fresh view for now, replay for exact - both from one log.

Choosing the lateness number: session 4's homework asked how late an event can be before your business would rather drop it than restate the hour. That number IS your allowed lateness - the simulator's 3 hours is just the coffee shop's answer, because that is how long its tablets stay offline. Pick yours from arrival behavior and restatement tolerance, and write down who owns it.

Homework

Before session 6 15 min

Check yourself

Three questions

1. What does a Watermark(t) actually declare?

Straight from the Flink docs: the watermark declares event time has reached t, meaning no more elements with a timestamp t' <= t are expected. It is a promise about completeness - not a measurement of it, which is why late events can still break it.

2. By default, what happens to an event that arrives after the watermark has passed the end of its window?

The Flink default is that late elements are dropped when the watermark is past the end of the window. Allowed lateness can keep a window reopenable for a bounded grace period, and sideOutputLateData can capture the rest - but both are choices you must make, not defaults.

3. Why can an end-of-day replay converge on the exact truth?

Two properties compound: the log preserves every fact in order (Kleppmann: replay the events and reconstruct exactly what happened), and event-time windows give identical answers on identical input. Fix code, reprocess, converge - Marz's Query = Function(All Data), made physical.

Answer all three to see your score.

Sources this session stands on

What was covered

✓Apache Flink docs · Watermarks + allowed latenessthe Watermark(t) definition verbatim, late elements dropped by default, allowed lateness, sideOutputLateData
✓Marz · How to beat the CAP theorem (2011)Query = Function(All Data), immutability, recompute from the master dataset as fault tolerance
✓Kleppmann · blog (2015)the database as a cached view of the event log; replay in order to reconstruct exactly what happened
◐Kleppmann · DDIA ch11: CDC + event sourcingnamed as the deeper track - sourced via public notes, the book is the canonical treatment

Session 5 cheat sheet

Watermark(t)A declaration that event time has reached t - no more elements with timestamp t' <= t expected. A promise, not a measurement.
Default fate of late eventsDropped once the watermark passes the end of the window. Session 4's six lost stragglers, explained.
Allowed lateness + side outputA bounded reopen window (late firings update a published result), then sideOutputLateData catches the rest. Counted, never invisible.
The lateness number is a policyHow long an hour stays open is a business decision with an owner - derived from how your data arrives, not from a default.
The log as truthQuery = Function(All Data). The database you read is a cached view of the event log; replay rebuilds it exactly.
Completeness is a moving target93.6% at 2pm because unarrived events cannot be counted by any semantics. Replay closes the day at 100.0%, $6,115.