Book Deep-Read · Fundamentals of Data Engineering · Chapter 5
Fundamentals of Data Engineering · Ch 5 · Data Generation in Source Systems · Reis & Housley · 2022
You place an order online and it lands in the shop's database. You open a page and a server writes a line to a log. Your phone reports its location every few seconds. These are the places where data is born. This chapter is about where data is born, what shape it is born in, and how the people who want to build reports from it can take it out without causing trouble.
An order goes from "placed" to "paid" to "shipped": three changes. But a report that copies the database once a night only ever sees the last one. Whatever happened in between is simply gone. Worse, if the order was deleted, the copier never learns it existed.
If the data is small and one report a day is enough, a nightly copy is fine. If you need full history or minute-level freshness, follow the diary. One honest cost: following the diary is more accurate and more real-time, but it is one more system to build and keep running.
Data engineering starts upstream, in systems you do not control: understand how the data is born, take it in a way that does not hurt the source, and agree on the rules with whoever owns it.
Want OLTP and ACID, batch versus log-based CDC, event time versus processing time, partitions and delivery guarantees in queues and streams, plus evidence from Databus, Maxwell and Debezium? → Switch to the deep read
You think data engineering starts at "ingestion"; in fact it starts in an upstream system you do not own and cannot control. The chapter's point: source systems are built for the business, not for analytics. A data engineer must understand how they produce data (databases, APIs, logs, messages and streams), extract it in a way that neither hurts the source nor loses history (log-based CDC first), and agree on change rules with the system's owners up front.
The first chapter of Part II, "The Data Engineering Lifecycle in Depth", covering the first stage of the Chapter 2 lifecycle: generation. Chapter 6 (storage) and Chapter 7 (ingestion) follow. In practice it is the upstream half of "get the data out of the application database, the SaaS API, the event logs and Kafka".
Data engineers consume source systems but do not own them. The authors keep insisting that you should know a source system as well as your own: how it generates data, how often, at what volume, whether its structure may change without warning. Otherwise everything downstream is built on sand. Not knowing the source typically fails in three ways:
Data is first split into analog data (things happening in the real world, such as speech or handwriting) and digital data (converted from analog or native to a system, such as an online order or a sensor reading). Once digital, the source systems a data engineer meets fall into five kinds:
OLTP (online transaction processing) databases hold the application's current state: balances, inventory, orders. They are built for high concurrency, low latency, and a few rows per read or write. Most support ACID: atomicity (a transaction happens entirely or not at all), consistency (the book defines it as: any read returns the last written version of the item), isolation (concurrent transactions don't interfere), durability (once committed, it survives a power cut). By contrast, OLAP (online analytical processing) systems are built for large scans and aggregations. Running analytics directly on OLTP is slow and hurts the business, which is why you extract the data, or at least go through a read replica.
How a source table is written decides what you can get:
CDC means extracting every change (insert, update, delete) from a database. The book describes two kinds.
Batch-oriented CDC: add an updated_at column and periodically query "rows changed since last time". Simple to build, but you only see each row's final state at query time: a row changed three times between two queries shows up once, as its third version, and physically deleted rows never appear at all.
Continuous CDC: read the database's write-ahead log directly (PostgreSQL's WAL, MySQL's binlog) and turn every committed change into an event, usually written to a streaming platform such as Kafka. Every intermediate state and every delete is captured, latency can drop below a second, and reading the log does not scan tables the way a big query does. It shares its roots with replication: replicas have always kept up with the primary by replaying the log.
CDC is not free either: reading the log still uses the source database's CPU, disk and network. In practice two more things need managing. In PostgreSQL, for example, a replication slot that nobody consumes makes the log pile up until the disk fills. And CDC only gives you changes, so a first connection needs a full snapshot that is then stitched onto the log.
A log records events in a system: who (a user or system account), what (the event and its metadata), and when (a timestamp). By encoding there are three kinds: binary (such as database logs; compact but need special parsing), semistructured (such as JSON; machine-readable and the most common), and plain text (console output; the hardest to parse). You also care about resolution (how much is recorded: just a primary key, or the full row before and after) and latency (flushed in batches or pushed in real time). Database logs matter so much precisely because they underlie both CDC and replication.
The book separates the two: a message is a piece of data passed between two systems and is usually removed from the queue once consumed; a stream is an append-only log of event records that are retained for a period, so several consumers can each read from different positions and even replay.
Streaming platforms such as Kafka group events into topics, and split topics into partitions processed in parallel. Events are routed by a partition key (such as user ID): the same key always lands in the same partition, so ordering is guaranteed only within a partition. Pick a poor key, say one big customer generating half of all events, and you get hotspotting: one partition is crushed while the rest sit idle.
Know your delivery guarantee too: at-least-once can produce duplicates, so downstream must be idempotent; exactly-once is achievable but depends on the platform and the consumer cooperating, at a cost in throughput and complexity.
That leads to the chapter's three kinds of time: event time (when it happened at the source), ingestion time (when it entered the ingestion system), and processing time (when the pipeline processed it). The gaps between them are latency. The authors' advice is plain: timestamp every one of them, and compute business metrics by event time from the start, or a phone that was offline for hours will land its data on the wrong day.
Beyond relational databases the book names several NoSQL families: key-value stores (very fast reads and writes by key, often caches and sessions), document stores (nested JSON-like documents, flexible structure), wide-column stores (such as Cassandra, for massive write volumes), graph databases (nodes and relationships), search engines (full-text retrieval), and time-series databases (time-ordered metrics). For a data engineer the questions never change: how do I get the data out, will extraction hurt the source, and is there a CDC-like change feed.
On APIs: REST is the most common, fetching by resource and usually requiring you to handle pagination and rate limits; GraphQL lets the caller declare exactly which fields it wants in one request; a webhook is a "reverse API" where the source pushes events to you as they happen, so your receiver must not drop them; gRPC uses efficient binary transport and suits internal service-to-service calls. There is also data sharing (cloud warehouses granting direct read access without copying) and third-party data (purchased external data whose quality and refresh cadence need extra checking).
The chapter's most practical part is working with source-system stakeholders. Data engineers should agree with system owners on SLAs/SLOs for availability and freshness, how much notice a schema change gets, and whom to call when things go wrong. The cross-cutting undercurrents apply to sources too: security (credentials in a secrets manager, VPN or private links, read-only access), data management (who is accountable for data quality), DataOps (monitor the source's availability and changes, not only your own pipeline), and orchestration (align extraction cadence with when the source actually produces data).
Table 1 · Extracting from a database: four approaches
| Approach | Good for | Cost |
|---|---|---|
| Full snapshot | Small tables and dimensions, once a day | Slow and heavy on the source as tables grow; no view of changes |
| Batch CDC (updated_at) | Medium tables, hourly freshness, final state is enough | Misses intermediate states and physical deletes; relies on upstream always maintaining the column |
| Continuous CDC (log) | Full history, sub-minute freshness, many downstream subscribers | One more streaming platform and connector; snapshot stitching, log buildup and schema evolution to manage |
| Query a read replica | Complex queries without touching the primary | Replication lag; still only final state |
Table 2 · How source tables are written
| Pattern | Benefit | Cost |
|---|---|---|
| CRUD (update in place) | Small tables, fast current-state reads, natural for apps | History is lost by default; analytics must recover it via CDC |
| Insert-only | History kept naturally; auditable and replayable | Tables keep growing; current state means finding the latest row |
Table 3 · Message queue vs event-streaming platform
| Type | Good for | Cost |
|---|---|---|
| Message queue (RabbitMQ, SQS) | Decoupling systems, distributing tasks; deleted once consumed | No replay; a message usually serves one kind of consumer |
| Event streaming (Kafka, Kinesis) | CDC pipelines, many subscribers, replay | Partitions, retention and consumer offsets to operate; ordering only within a partition |
Rule of thumb: first ask whether downstream needs intermediate states and deletes; if so, use log-based CDC. Then ask how fresh it must be; daily freshness is fine with snapshots or batch increments. Finally ask whether it will hurt the source; whatever the approach, never full-scan the primary at peak.
A common interview and design-review question: "How do you sync the order database into the warehouse and the search index?" A passing answer is a scheduled SELECT ... WHERE updated_at > last_run. A good answer names its flaws (missed deletes, missed intermediate states, load on the primary), moves to CDC from the binlog or WAL into Kafka, then explains how the initial snapshot is stitched on, how the partition key keeps each order's events in sequence, and how consumers stay idempotent. Another frequent trap is the dual write: the application writes to the database and to a message queue, and if either fails the two disagree. Log-based CDC exists precisely to remove the dual write.
Evidence from industry and open source
① Data engineering starts in source systems you don't own: know their output, cadence, volume and quirks as well as your own.
② Five kinds of source: files, APIs, application databases, logs, messages and streams. The core is the OLTP database: concurrent, low-latency, ACID, and wrong for direct analytics.
③ CRUD keeps only current state; insert-only keeps history at the cost of growing tables. The write pattern decides what you can get.
④ Batch CDC sees only final state and misses deletes; continuous CDC reads the WAL or binlog, captures every change, and is the standard cure for dual writes.
⑤ CDC has costs: snapshot stitching, log buildup, load on the source, schema evolution.
⑥ Messages vanish once consumed; streams are append-only and replayable, parallel by partition, ordered only within a partition; a bad key causes hotspots.
⑦ At-least-once demands idempotent consumers; separate event, ingestion and processing time and timestamp them all.
⑧ Agree with source owners on SLAs, schema-change notice and contacts; read-only access and credentials in a secrets manager.