Module 24 of 52 · Engineering and platforms

Streaming and change data capture

30 min 6 outcomes Terminal walkthrough + ordering challenge 7 sources cited

By the end of this module you will be able to:

  • Frame the choice between batch and streaming as freshness economics rather than fashion
  • Explain what a log, a topic, a partition and a consumer group are, and what each guarantees
  • State what changed in Apache Kafka 4.0 and what it means for the operating model
  • Define change data capture and explain why reading the database log beats polling a table
  • Reason about at-most-once, at-least-once and exactly-once delivery for a named workload
  • Place stream processing and the streaming lakehouse on the platform map

Change data capture treats the transaction log as the source of truth

Change data capture reads the transaction log rather than the tables, so every consumer receives the same committed writes in the same order.

Change data capture reads the transaction log rather than the tables, so every consumer receives the same committed writes in the same order. Because the topic retains those records, a consumer can be rebuilt by replaying from a stored position instead of asking the source database again.

Change data capture treats the transaction log as the source of truth A labelled chain in four regions. The top row reads left to right: source database, transaction log in red tint, CDC connector, topic. The arrows between them read committed writes, ordered commits and change events. Two arrows drop from the topic into a consumer row: a streaming service that keeps its own running state, and a warehouse loader that applies the same records in order. Those arrows read ordered records and the same records. A dashed curve leaves the topic on the right, passes under the consumer row and turns up into the warehouse loader, labelled replay from a stored position. CHANGE DATA CAPTURE · THE LOG IS THE SOURCE OF TRUTH STEP 1Source databaseRows change asthe app writes STEP 2Transaction logEvery commit isappended in order STEP 3CDC connectorTails the log,never the tables STEP 4TopicOrdered eventskept for replay committed writes ordered commits change events CONSUMER 1Streaming serviceReads each change event asit arrives and keeps itsown running state CONSUMER 2Warehouse loaderApplies the same recordsin order, and can rewindto an earlier position ordered records the same records replay from a stored position

Three delivery guarantees, and what each one costs you

At-most-once loses a message, at-least-once applies a duplicate, and exactly-once buys single application with coordination and stored state, so the guarantee is a property of the whole path and the write at the far end must still be idempotent.

At-most-once loses messages, at-least-once repeats them, and exactly-once buys single application with coordination and stored state. Choose by the bill you can pay: the guarantee you want is a property of the whole path, not a setting on the broker.

Three delivery guarantees, and what each one costs you A comparison board. A left label column names four rows: semantic, promise, failure, cost. Three columns read At-most-once, At-least-once and Exactly-once. Promises: sent once and never retried; retried until the receiver confirms; applied once even after a retry. The failure row is tinted red and reads a message is lost, a duplicate applied, extra coordination. The cost row carries a red chip in each column: cheapest to run, idempotent writes, slower and more state. Arrows along the name row read adds retries and adds coordination. A footer note reads that exactly-once is an end-to-end property you design for. DELIVERY GUARANTEES · WHAT EACH ONE PROMISES AND COSTS SEMANTIC PROMISE FAILURE COST At-most-once Sent once and never retried A message is lost Cheapest to run At-least-once Retried until the receiver confirms A duplicate applied Idempotent writes Exactly-once Applied once even after a retry Extra coordination Slower, more state adds retries adds coordination Exactly-once is an end-to-end property you design forThe broker promises its own part only. The write at the far end must still be idempotent.

Batch and stream as choices on a latency-correctness boundary

Batch, micro-batch and stream sit on one line running from reproducibility to sub-second latency, so the decision window picks the architecture: streaming a daily reconciliation is overhead, and batching a fraud alert arrives too late to act on.

Batch and stream are not opposites; they are choices on a latency-and-correctness boundary. Batch wins for reproducibility, large joins, complex SQL. Stream wins for sub-second decisions and continuous state. Kappa architecture (Kreps) collapses the two into one stream; Lambda (Marz) runs both side by side.

Batch and stream as choices on a latency-correctness boundary Four cards left to right: Batch (reproducible, large joins), Micro-batch (5 min latency, emphasised), Stream (sub-second), Lambda/Kappa (combined patterns). Verb arrows vs. A red-accent callout names the choice as latency-vs-reproducibility, not philosophical. BATCH vs STREAM · LATENCY-CORRECTNESS BOUNDARY 1 BATCHHadoop / SparkBatchReproducible, largejoins2 MICRO-BATCHSpark StructuredMicro-batch5-min latency,near-real-time3 STREAMApache FlinkStreamSub-second decisions,stateful4 COMBINEDKreps + MarzLambda / KappaBoth, or stream astruth vsvsor both The choice is latency-vs-reproducibility, not philosophical Streaming a daily reconciliation report is overhead. Batch processing a fraud alert is too slow. Matchthe architecture to the decision window.

Stream recovery uses source offset, checkpoint, and exactly-once sink

Source offset, checkpointed state and an exactly-once sink each cover a different part of a restart, so losing one does not degrade recovery gently: either the history goes, or the aggregations reset, or the retry writes twice.

Stream recovery uses three mechanisms: source offset (where to restart), checkpointed state (what was computed), exactly-once sink (no duplicate writes). Apache Flink names this the savepoint trio; without all three, recovery silently double-counts or loses events.

Stream recovery uses source offset, checkpoint, and exactly-once sink Three cards left to right: Source offset (where to restart), Checkpointed state (what was computed, emphasised), Exactly-once sink (no duplicates). Verb arrows plus. A red-accent callout names what is lost without each. STREAM REPLAY · SAVEPOINT TRIO · APACHE FLINK 1Apache KafkaSource offsetRestart position in log2Apache FlinkCheckpointed stateAggregations + windows3Apache KafkaExactly-once sinkIdempotent or transactionalwrite ++ Without all three, recovery silently corrupts No offset: re-process from now and lose history. No checkpoint state: aggregations reset.Non-idempotent sink: duplicates on retry.

The overnight window that stopped being overnight

A retailer runs a nightly extract from its trading database into the warehouse. At 02:00 the job pulls yesterday, at 04:00 the reports rebuild, and by 07:00 the buying team has a clean picture of what sold. That picture was accurate at midnight and is already wrong by breakfast, because the shop never stopped selling.

For most of the reporting the staleness is harmless. Nobody makes a different decision about last quarter because the numbers are seven hours old. The damage sits in a narrow band of decisions: whether to show an item as in stock, whether to release the last unit in a warehouse to a click-and-collect order, whether to hold a delivery slot. Those decisions are taken in seconds and paid for in refunds, cancellations and lost trust.

The instinct is to make the whole pipeline faster. The disciplined move is narrower. Identify the handful of decisions where age of data changes the outcome, quantify what the wrong outcome costs, and buy freshness only for those. Everything else can stay on the overnight run, where it is cheap, restartable and quiet.

A retailer refreshes stock positions once a night. Online orders are taken every second. Where exactly does the harm from stale data land, and who pays for it?

Streaming is often introduced as the modern successor to batch, as though the industry simply moved on. That framing is unhelpful, because it hides the only question that matters in practice: how old is the data allowed to be at the moment somebody or something acts on it, and what is the organisation willing to pay to keep it younger than that. This module treats freshness as a purchase, then works through the machinery that delivers it: the log, change data capture, delivery guarantees, and the processing layer that turns a stream of events back into tables people can query.

Latency has a price list. Before choosing a technology, it is worth pricing the decisions that a shorter delay would actually change.

24.1 Freshness as a purchase, not a preference

A scheduled that runs once a night is the cheapest reliable thing in data engineering. It has one start time, one finish time and one failure mode. If it breaks, an engineer reruns it in the morning and the world is restored. There is no state to recover, no ordering to preserve and no consumer waiting mid-flight. The operating cost is close to the compute cost.

A continuously running stream has none of those comforts. It is always up, so it is always able to fail, and failure arrives at three in the morning rather than during office hours. It holds state between records, so recovery means restoring that state as well as restarting the process. It has consumers that are part-way through the data, so a restart has to answer the question of where to resume. Every one of those properties is engineering work that the nightly job never had to do.

That is why the useful question is not whether streaming is better. It is which decisions change when the data is five seconds old instead of twelve hours old, and what the improved decision is worth. Card fraud scoring, grid balancing, dynamic pricing, fulfilment promises and safety alarms all change materially. Monthly management reporting, regulatory returns, cohort analysis and most machine learning training sets do not change at all. Buying seconds of latency for the second group is pure cost.

Once the answer is known, write it down as a number rather than an adjective. A freshness target expressed as a gives the producing team a line they can be measured against and the consuming team a fact they can design around. "Near real time" is not a target. "The orders table is at most ninety seconds behind the source, measured at the ninety-fifth percentile over a rolling day" is a target, and it can be breached, alerted on and argued about with evidence.

Every streaming platform is built on the same primitive. Understanding the log explains most of the behaviour that surprises teams later.

24.2 The log, the topic and the consumer group

An is a sequence of records that is only ever added to at the end and never edited in place. It is therefore an ordered history of what happened rather than a snapshot of what is currently true. Current state is derived by replaying the records from a known position. This is not a streaming invention: it is the same structure a relational database uses for durability, which is why the two worlds join up so neatly later in this module.

An exposes that log to consumers. In , the unit consumers subscribe to is a topic, and each topic is split into partitions held on different brokers. Ordering is guaranteed within a partition and nowhere else. That constraint is easy to read past, because from the outside a topic looks like one stream. If every event about one customer must be processed in order, the customer identifier has to be the partition key, and the number of partitions becomes a design decision rather than a capacity setting.

A consumer group is how a set of processes share the work of reading a topic. Each partition is assigned to exactly one consumer in the group at a time, so the maximum useful parallelism of a group equals the partition count. Adding a tenth consumer to a six-partition topic buys nothing. Each group tracks its own position, called an offset, which is why several unrelated groups can read the same topic independently: the fraud engine, the warehouse loader and the alerting service each move through the same events at their own pace without interfering.

Events are retained for a configured period rather than deleted when they are read. Retention is what makes a stream recoverable. A consumer that has been writing corrupted output for six hours can be fixed, reset to an earlier offset and replayed, which is impossible with a queue that discards a message once it has been delivered. The retention window is therefore a governance decision as much as a storage one: it sets how far back a mistake can be undone, and it sets how long personal data sits in the platform.

Common misconception

Kafka guarantees that events arrive in the order they happened.

Kafka guarantees ordering within a partition, not across a topic. Two events written to different partitions have no defined relative order, and a topic with twelve partitions is twelve independent ordered logs. If a sequence matters, the events in that sequence must share a partition key so they land on the same partition. Teams that assume topic-wide ordering typically discover the gap during an incident, when an update is processed before the insert it depends on.

The primitives have been stable for a decade. What changed recently is the operating model around them.

24.3 What changed in Apache Kafka 4.0

For most of its life a Kafka cluster was two distributed systems, not one. Kafka held the data and Apache ZooKeeper held the cluster metadata: which brokers exist, which partitions live where, which configuration applies. Operators had to secure, patch, monitor and capacity-plan both, and a ZooKeeper problem became a Kafka problem.

Apache Kafka 4.0, released on 18 March 2025, was the first major release to operate entirely without ZooKeeper. mode, in which controller nodes inside Kafka manage cluster metadata as an internal log, became the default. The technical change is modest and the operational change is not: one distributed system to run instead of two, one security model, one upgrade path and one set of alerts.

The 4.0 release also introduced share groups, described in KIP-932 as queues for Kafka, as an early access feature. A share group behaves roughly like a durable shared subscription: records from a partition can be handed to several consumers cooperatively, so consumer count is no longer capped by partition count for work-queue style processing. The announcement marks the feature as early access with documented limitations, which is the correct way to read it. It is a signal about where the platform is going, not a foundation to place a production settlement pipeline on this year.

For a course on data rather than on Kafka administration, the point to carry forward is the direction of travel. Streaming infrastructure is consolidating: fewer moving parts, fewer specialist operators, and messaging patterns that used to require a separate broker now being absorbed into the log. That lowers the cost of the infrastructure and shifts the difficulty upwards, into modelling, semantics and delivery guarantees, which is exactly where the remaining sections sit.

Most organisations do not produce events. They produce database rows, and something has to turn those rows into a stream without rewriting the applications that wrote them.

24.4 Change data capture: read the log, do not poll the table

The traditional way to find out what changed in a source system is to ask it repeatedly. A job selects every row whose modified timestamp is later than the last run and loads the result. This is simple, it works, and it quietly loses data in four separate ways.

First, it depends on a modified timestamp that every writer maintains correctly, which is rarely true across an estate that includes batch scripts and vendor packages. Second, it cannot see deletes: a row that has gone leaves nothing behind for a query to find, so downstream copies keep records the source has removed. Third, it misses intermediate states. If a row changes three times between polls, the poll sees only the final value, and an audit trail built this way is fiction. Fourth, every poll is analytical load applied to an system that was sized for short transactions, so the freshness you gain by polling more often is paid for by the customers waiting on the application.

takes a different route. Relational databases already write every committed change to a durable log before applying it to the data files, because that is how they survive a crash. That log is a complete, ordered record of inserts, updates and deletes that the database maintains whether anyone reads it or not. Change data capture reads it and republishes each row-level change as an event.

is the open-source implementation most teams meet first. Its connectors read the source database log and emit an event per change, and only committed changes are published, so consumers never have to reason about work that was later rolled back. A connector normally begins with a consistent snapshot of the existing tables, then switches to reading the log from the position that snapshot corresponds to, so the stream is continuous from the first event. Deletes appear explicitly, which is what makes downstream retention and erasure work possible at all.

Three obligations come with the approach. Schema changes in the source now propagate into the stream, so someone has to decide what happens to consumers when a column is renamed. Personal data flows out of the operational estate into the platform, so the lawful basis and retention rules travel with it. And the managed services that cloud vendors offer remove the pipeline you would otherwise operate, but they do not remove the modelling, the quality rules or the contract. Fast access to badly understood tables is not progress.

Loading interactive component...

Once events are moving, the honest question is what happens to a record when something in the path restarts.

24.5 At-most-once, at-least-once and exactly-once

Networks drop packets, processes are killed mid-write, and brokers fail over. Every streaming design therefore has to state what happens to a record when the path between producer and sink is interrupted. There are three possible answers, and they differ in what they are willing to lose.

At-most-once means the producer does not retry. If an acknowledgement is lost, the record is gone. Nothing is ever duplicated and some things are simply missing. This is acceptable for high-volume telemetry where a gap changes no decision, and it is indefensible for anything financial.

At-least-once means the producer retries until it is sure the record landed. Nothing is lost and some records arrive twice, because a retry after a lost acknowledgement writes the same record again. This is the default behaviour of most systems and the right starting assumption when reading someone else's pipeline.

means each record affects the final result once, even though producers retry and consumers restart. It is assembled rather than switched on: producers must be able to detect and discard their own duplicate writes, the processing step must commit its state and its output together, and the sink must be able to participate in that commit. Kafka documents these three semantics directly and explains that its exactly-once processing rests on duplicate-suppressing producers and transactional writes.

The practical consequence is that is a property of the whole path, not of one component. A pipeline that enables transactional writes in the stream processor and then appends to a sink that cannot commit transactionally has not achieved it, whatever the configuration file says. The honest test is to walk every hop from source to final table and ask whether that hop can keep the promise.

There is a cheaper route that covers most real requirements. Run at-least-once and make the final write idempotent: upsert on a natural key rather than appending, so a duplicated record overwrites itself instead of double-counting. The effect at the point of use is exactly once, and the cost is a key and an upsert rather than a distributed transaction. Reserve the full guarantee for payments, billing and settlement, where a duplicate is a real financial event, and decline to pay for it on clickstream.

Common misconception

Exactly-once is a setting you turn on in the stream processor.

Configuration in the processor covers one hop. Exactly-once behaviour requires the producer to suppress its own duplicate writes, the processor to commit its state and its output atomically, and the sink to take part in that commit or to be idempotent. A path containing a plain HTTP call, a file append, or a warehouse insert with no key can retry, and a retry there duplicates the effect regardless of what the processor is configured to do.

Moving events is the easy half. The expensive half is remembering things across them and landing the result somewhere a query engine can read.

24.6 Stream processing and the streaming lakehouse

Kafka moves events. It does not compute over them. That job belongs to a engine such as Apache Flink, which treats the input as an unbounded sequence rather than a finished table, holds state between records, and produces results continuously. The arithmetic in these jobs is usually trivial. The engineering cost is the state: what has to be remembered, for how long, and how it is restored after a failure.

Time is the second cost. A streaming job orders work by the time events happened rather than the time they turned up, because a mobile device that was offline for an hour will deliver an hour-old event now. A is the marker carried along the stream that declares how far event time has advanced, which is how the job decides that a window is complete and emits its result. Flink documents a watermark at time t as the declaration that no further elements with a timestamp at or before t should arrive.

That definition contains the trade-off every streaming design has to face and state openly. Wait longer before closing a window and every result is later but more of the stragglers are included. Wait less and results are prompt but some are wrong. There is no configuration that removes the choice, and a job that has no stated policy for late data has made the choice by accident.

The output has to land somewhere. The streaming lakehouse pattern writes it into the same governed tables that batch jobs write and query engines read, using an such as to give files in object storage the behaviour of tables: atomic commits, schema changes that do not rewrite history, and queries against an earlier snapshot. That removes the old split in which streaming results lived in one store and batch results in another, and the two disagreed.

Two costs come with it. Streaming writes produce many small files, so compaction and snapshot expiry become scheduled operational work rather than optional tuning. And a table written by several engines at once makes the table format the interoperability contract for the whole platform, which is a strategic commitment rather than a storage detail.

the reliability and simplicity of SQL tables to big data, while making it possible for engines like Spark, Trino, Flink, Presto, Hive and Impala to safely work with the same tables, at the same time

Apache Iceberg project - Project overview

This is the sentence that makes a streaming lakehouse coherent. A streaming engine and a batch engine can write and read the same table because the format, not the engine, owns the definition of what the table currently contains. It is also why the choice of table format is the lock-in decision on a modern platform: the engines are replaceable and the tables are not.

24.7 Check your understanding

A finance team asks for the general ledger extract to move from a nightly batch to a streaming feed, because streaming is the modern approach. The ledger is used for monthly reporting and statutory accounts. What is the most defensible response?

A pipeline synchronises a customer table by selecting every row whose updated_at is later than the previous run. An audit finds that the downstream copy contains customers who were deleted from the source months ago. What is the underlying cause?

A team runs an at-least-once pipeline that writes payment events into a warehouse table with an INSERT. After a broker failover, the monthly revenue figure is overstated. Which change fixes the effect at least cost?

Loading interactive component...

Core distinctions

  • Batch and streaming are a purchase, not a fashion. Price the decisions that a shorter latency would change, and buy freshness only where it changes one.
  • A topic is a set of partitions and ordering holds within a partition only. Sequences that must stay in order have to share a partition key.
  • A consumer group can use no more parallelism than the partition count, and each group tracks its own offset, so unrelated consumers read the same events independently.
  • Apache Kafka 4.0, released on 18 March 2025, was the first major release to run entirely without ZooKeeper, with KRaft the default and share groups shipped as an early access feature.
  • Polling a source table cannot see deletes or intermediate states and loads the operational database. Change data capture reads the log the database already writes, and publishes only committed changes.
  • At-least-once is the usual default. Exactly-once is an end-to-end property assembled from duplicate suppression, atomic commits and a participating sink, and an idempotent upsert usually delivers the same effect for less.
  • Stream processing costs are dominated by state and by the watermark decision about how long to wait for late events. The streaming lakehouse lands the result in tables that batch engines share.

Standards and sources cited in this module

  1. Apache Kafka documentation

    Design, topics, partitions and consumer groups

    Primary source for topics as partitioned append-only logs, per-partition ordering, consumer offsets and retention behaviour.

  2. Apache Kafka documentation, message delivery semantics

    Semantics

    Sets out at-most-once, at-least-once and exactly-once semantics and explains what exactly-once processing depends on.

  3. Apache Kafka 4.0.0 release announcement

    KRaft by default and KIP-932 share groups

    Source for the 18 March 2025 release date, the first major release without ZooKeeper, and the early access status of share groups.

  4. Debezium

    Project overview and connectors

    Reference implementation of change data capture. Documents log-based capture, initial snapshots, and publication of committed changes only.

  5. PostgreSQL documentation, write-ahead logging

    Reliability and the write-ahead log

    Explains why a relational database already maintains a durable ordered log of changes, which is the structure change data capture reads.

  6. Apache Flink documentation, timely stream processing

    Event time and watermarks

    Definition of a watermark and the event-time model that determines when a window is complete and what counts as late data.

  7. Apache Iceberg

    Project overview

    Open table format underpinning the streaming lakehouse, allowing streaming and batch engines to work on the same tables safely.

Module 24 of 52 · Engineering and platforms