Batch vs Streaming: Advanced
Three layers that answer one question
Every event feeds both layers; the serving merge adds the batch view to the realtime view at read time.
A worked merge
| article_id | batch view (through 00:00) | realtime view (since 00:00) | served total |
|---|---|---|---|
| a-1042 | 1,208,311 | 4,517 | 1,212,828 |
| a-1077 | 88,402 | 12,960 | 101,362 |
| a-1101 | no row | 2,344 | 2,344 |
The constraints of 2012
- A crash could replay a message into a count that had already absorbed it
- Stream state lived in worker memory and vanished on restart
- A full recompute took longer than the freshness consumers accepted
- The event feed kept hours of data, so history lived only in the archive
- Engines checkpoint state and read position together, so a restart resumes without double counting
- State is persisted to local disk and checkpointed, holding billions of keys
- Recompute is a replay of the log through the same job, at many times the live rate
- Logs retain weeks or months, so the log itself can be the history
- ▸Every metric definition exists twice, and both forms must change on the same day
- ▸A fix in one layer only leaves served totals right in one slice and wrong in the other
- ▸Two runtimes mean 2 sets of failure modes, on-call skills and capacity plans
A product manager wants views from known crawler IP ranges excluded from article totals starting today. Under the Lambda design above, what is the smallest change that makes served totals correct from tomorrow on?
One job, one log, one codebase
Jay Kreps named the pattern in 2014, while at LinkedIn, in the article Questioning the Lambda Architecture, written as a response to the 2-codebase cost.
During a replay, 2 copies of the same job read the same log; the consumer moves from v1 to v2 once v2 is current.
A replay with numbers
| clock | job v1 | job v2 | dashboard reads |
|---|---|---|---|
| Mon 09:00 | live, old rule | started at oldest offset, 30 days behind | article_views_v1 |
| Mon 17:00 | live, old rule | 8 hours in, about 17 days replayed | article_views_v1 |
| Mon 23:41 | live, old rule | caught up, now live | article_views_v1 |
| Mon 23:45 | live, old rule | live | article_views_v2 (switched) |
| Tue 09:00 | stopped, table dropped | live, the only copy | article_views_v2 |
Retention sets the replay horizon
A Kappa pipeline retains 14 days of events. On day 60 the team finds that a deploy on day 15 has been dropping events from one app version. Which statement about recovery is correct?
One transform, bounded or unbounded input
Execution still differs
| aspect | batch run | streaming run |
|---|---|---|
| input | bounded, fully known before the job starts | unbounded, arriving while the job runs |
| operator lifetime | stage by stage, each finishing before the next | every operator alive at once, pipelined |
| state | sort and shuffle files for 1 job, discarded at exit | local stores, checkpointed, kept across restarts |
| state size bound | the input size | the distinct key count, growing until keys expire |
| failure recovery | rerun the failed stage from its materialized input | restore the last checkpoint, reread the log from that offset |
| output | the complete result, written once | a changelog of inserts and replacements, written continuously |
| resources | a burst, released at exit | a reservation held around the clock |
| ends when | the input is consumed | someone stops it |
A team writes a running count of views per article, with no window, tests it over a 2-hour sample file, and deploys it in stream mode. Which runtime difference is most likely to surprise them in production?
Four tiers, one rule
| tier | typical cadence | usually built as |
|---|---|---|
| seconds | under 1 minute | stream job over a log |
| minutes | 1 to 15 minutes | micro-batch |
| hours | hourly to every 6 hours | scheduled job |
| daily | once a day or slower | nightly job or snapshot export |
Annotating a real graph
Each node carries its own cadence; the analysis asks whether that cadence is one its inputs can support and its consumers need.
| node | own cadence | slowest input | effective freshness | consumer expects | finding |
|---|---|---|---|---|---|
| order_events | seconds | none | seconds | fine | |
| product_catalog | daily | operations database, daily export | daily | fine | |
| enriched_orders | 5 min | product_catalog, daily | order columns 5 min; category and margin up to 24 h | fraud_model, 30 s | unmet: polled 10 times per run, margin a day stale |
| revenue_by_category | 5 min | enriched_orders, 5 min | amounts 5 min; category daily | finance_dashboard, hourly | hidden cost: 12 runs per refresh |
The 5-step analysis
A pipeline streams clickstream events (seconds) into a sessions table rebuilt by a micro-batch every 10 minutes, which joins a user_segments table refreshed weekly. A marketing tool reads sessions every 15 minutes to pick a banner by segment. Which finding is correct?
The workload
Before: 2 jobs, 2 tables and a merge that every consumer pays for on every read.
After: 1 job, 1 table, and an archive the pipeline keeps but no longer reads.
One definition of a completed listen
- Deletednightly_recompute and merge_queryThe batch implementation and read-time merge go; both consumers read episode_listens_v3.
- Promotedspeed_counter becomes listen_counterThe stream job holds the only definition of a completed listen.
- Addeda version in the output nameThe job writes episode_listens_vN; each replay is a new N and never overwrites the table being read.
- Addeda sink that replaces by episode_idEach count change emits a replacement row, and the sink overwrites by key.
- Addeda diff queryRun before every cutover, it lists episodes whose counts differ between old and new tables.
The log becomes the master dataset
The team wants to correct 60 days of history after a tagging bug, but the log retains 30 days. Which plan keeps the single-codebase property?
New checks replace the nightly overwrite
| step | when | what happens | what proves it worked |
|---|---|---|---|
| 1 | Mon 06:00 | Start listen_counter_v3 at the oldest retained offset, writing episode_listens_v3 | Job is reading; v3 row count rising |
| 2 | Tue 00:30 | v3 reaches the live offset and keeps pace | Gap between log head and v3 under 5 seconds |
| 3 | Tue 00:30 to Thu 02:00 | v3 and the Lambda pair both serve; the diff query runs hourly | Diff lists only episodes with listens in the last 24 hours, differing under 0.1% |
| 4 | Thu 09:00 | Dashboard and ranking switch to episode_listens_v3 | No read of the batch or rt tables for 24 hours |
| 5 | Fri 09:00 | Stop nightly_recompute and speed_counter; drop their tables | Archive still written; retention alert set at 25 days |
Two weeks after cutover, a creator reports an episode's total fell 3% overnight. Under the new design, which cause is possible, and which old-design cause is ruled out?
> A food delivery company shows every restaurant its orders and revenue for the day on a partner dashboard, and feeds the same counts into a courier dispatch model that reacts to demand spikes. The numbers come from a nightly batch over the order archive plus a stream job for today, merged at read time, and the 2 disagreed last month when a refund rule changed in one job only. The team is deciding whether to collapse the design into a single replayable stream job, and how fresh each piece of it actually needs to be.
Why Lambda split the pipeline in two, how Kappa rejoined it, and what each node's freshness tier really costs.
- Category
- Pipeline Architecture
- Difficulty
- advanced
- Duration
- 35 minutes
- Challenges
- 11 hands-on challenges
Topics covered: Lambda Architecture, Kappa: Stream Only, Batch Replay, Unified Engines: Where Lines Blur, Per-Node Freshness Tier Analysis, Lambda to Kappa Worked Example
Lesson Sections
- Lambda Architecture (concepts: Lambda Architecture)
Three layers that answer one question Lambda architecture computes every answer twice and merges the 2 results. The batch layer keeps an append-only master dataset of every event ever received and periodically recomputes a batch view from scratch over all of it. The speed layer processes events as they arrive and keeps a realtime view covering only the events the last batch run has not yet absorbed. The serving layer merges the 2 views at query time: a recomputed answer plus a thin slice of rece
- Kappa: Stream Only, Batch Replay (concepts: Kappa Architecture)
One job, one log, one codebase Kappa architecture removes the batch layer and keeps the log. Every event is written to an append-only log that retains messages for a configured window, and a single stream job reads it and maintains the output table. To change logic or undo a bug, the team starts a second copy of the job at the oldest retained position, lets it rebuild a fresh output table from history, and points consumers there once it catches up. Recomputation becomes replay: the program that
- Unified Engines: Where Lines Blur (concepts: Batch vs Streaming)
One transform, bounded or unbounded input A unified engine treats a batch as a stream that happens to end. The transform is written once as a graph of operators, filters, projections, grouped aggregations and joins, and the engine decides at launch whether to feed that graph a bounded input, such as a month of files, or an unbounded one, such as a live log. Only the launch parameters change between the 2 runs. A declarative query works the same way: the grouped count below fully describes the re
- Per-Node Freshness Tier Analysis (concepts: Batch vs Streaming)
Four tiers, one rule A freshness tier is the delay a node promises between an event happening and that event showing in its output. Four tiers cover most pipelines: seconds, minutes, hours and daily. Writing the tier beside every node exposes a rule an unannotated diagram hides: a node is only as fresh as its slowest input, however often it runs. Annotating a real graph An online store runs the pipeline below. Order events arrive on a log. A product catalog is exported from the operations databa
- Lambda to Kappa Worked Example (concepts: Kappa Architecture)
The workload A podcast platform serves listens per episode to creators and its recommendation ranking, from 3,000 listen events per second. Under Lambda, a nightly recompute of episode_listens_batch starts at 02:00 over a file archive of every listen and lands roughly 07:00; a speed layer keeps episode_listens_rt since midnight; consumers read both through a merge query. The codebases drifted twice in a year, and the layer holding the stricter rule set which way totals moved at 07:00. A crawler