Schema Drift: Beginner
What you will be able to do
The Producer Added a Column Problem
Recognize the producer-added-a-column failure mode and name the three reactions a loader can have to an unexpected field.
What Actually Breaks
| Symptom | Cause Upstream | Where It Surfaces |
|---|---|---|
| Schema mismatch error on load | A new column appeared in the source payload | Loader rejects rows because the target table lacks the column |
| Silent type coercion | A field's type widened from int32 to int64 | Values truncate or wrap around without an error |
| Empty column downstream | A field was renamed and the pipeline still reads the old name | Dashboards show NULL for what used to be populated |
| Failed downstream join | A foreign key column was dropped | Joined fact table is missing rows or fans out incorrectly |
A Concrete Failure
- ▸Reject: refuse to process rows with the unexpected field, fail loudly
- ▸Silent drop: ignore the new field, lose the data, no error raised
- ▸Accept: store the field in a schemaless column, decide later what to do with it
Why Producers Cannot Be Asked to Stop
Schema drift is a property of the world, not a bug in the upstream team. A pipeline that assumes the schema is stable is a pipeline that has not yet broken in the way it inevitably will.
Forward vs Backward Compatibility
Distinguish backward and forward compatibility, and classify a proposed schema change against both.
The Two Definitions, Plain
| Term | Plain Definition | Who It Protects |
|---|---|---|
| Backward compatible | A new schema can read data written under the old schema | The consumer that upgrades first while old data is still in flight |
| Forward compatible | An old schema can read data written under the new schema | The consumer that has not yet upgraded when the producer ships first |
| Full compatible | Both directions hold at the same time | Either side can upgrade first without coordination |
- Adding a new optional field with a default value
- Widening a numeric type from int32 to int64
- Adding a new enum value at the end of a list
- Old rows read fine after the new schema is deployed
- Renaming a field that downstream code reads
- Removing a field that downstream code references
- Narrowing a type from string to fixed-length char(8)
- Old rows fail or produce wrong results under the new schema
A Worked Pair
When Compatibility Breaks
- ▸Add an optional field with default: full compatible, safest change
- ▸Add a required field with no default: backward incompatible for old writers
- ▸Rename a field: incompatible in both directions, forces coordinated deploy
- ▸Remove a field: forward incompatible (new readers may still want it)
- ▸Change a type: usually incompatible, depends on the type system
Most schema registries enforce one of these modes by default. Confluent Schema Registry, for example, ships with BACKWARD as the default compatibility check, which means a new schema must be readable by the most recent reader. Other modes include FORWARD, FULL, and NONE. The choice tells the team which deploy ordering is safe.
Schema evolution stays safe when a compatibility check sits between producer and topic: adding a column is backward-compatible and passes; a rename or type change is breaking and is caught before it reaches consumers.
Adding Is Safe, Renaming Is Not
Classify a proposed schema change as additive, destructive, or coordinated, and name the safe rename pattern.
Why Adding Is the Safe Default
Why Renaming Is the Dangerous Default
| Change | Risk Level | Typical Outcome If Unannounced |
|---|---|---|
| Add an optional column | Low | Pipelines keep working; new column is ignored until adopted |
| Add a required column with default | Low | Old rows get the default; new rows carry the value |
| Add an enum value at the end | Low to medium | Old readers may misclassify; new readers handle correctly |
| Widen a numeric type | Low | Old values still fit in the wider type |
| Drop a column | High | Any consumer reading that column produces NULL or fails |
| Rename a column | High | All consumers reading the old name break simultaneously |
| Narrow a type | High | Some old values no longer fit; data is silently truncated |
| Reorder positional fields | Critical | Type coincidences mask wrong data flowing into the wrong column |
The Stripe Example
- ▸New fields are added; existing fields are never repurposed
- ▸Removals happen only after a deprecation window with deprecation flags in the schema
- ▸Renames are treated as a remove plus an add, with both fields populated during the migration window
- ▸Type changes happen by adding a new column with the new type and migrating off the old one
- Default to additive changes for any cross-team schema
- Treat renames as add-then-drop with a deprecation window
- Document each new field's meaning before downstream teams start using it
- Drop or rename a column without naming every downstream consumer first
- Reorder positional fields in serialized formats; the cost is invisible breakage
- Narrow a type in place; add a new column with the narrower type instead
What Late Data Means
Recognize late data, distinguish event time from processing time, and name the three reactions a pipeline can have to a late event.
The Two Timestamps That Matter
| Timestamp | What It Records | Owned By |
|---|---|---|
| Event time | When the event actually happened in the world | The producer (or the user's device) |
| Ingestion time | When the pipeline first received the event | The pipeline |
| Processing time | When the pipeline acted on the event in a transform | The processing engine |
Why Lateness Happens
A Concrete Late Event
- ▸Include it in its event-time bucket and update history (correct, but mutates yesterday)
- ▸Include it in the bucket of when it arrived (wrong, but cheap and stable)
- ▸Drop it (wrong, but predictable and bounded if lateness is rare)
- Records when the event happened in reality
- Stable property of the event
- Required for accurate historical reports
- Forces the pipeline to handle out-of-order arrivals
- Records when the pipeline saw the event
- Depends on broker and consumer lag
- Easy to compute, monotonic by construction
- Drifts from event time when the system is under load
- Tuesday late event lands in Tuesday's bucket
- Reruns produce the same totals over time
- Reflects what happened in the world
- Forces idempotent writes and partition overwrites
- Tuesday late event lands in Thursday's bucket
- Reruns can produce different totals as lag changes
- Reflects what the engine saw, not what occurred
- Cheap to implement; misleading to read
Lateness is not an exception condition. It is the expected behavior of any distributed system that includes mobile clients, intermittent networks, or producer batching. The question is not whether late data appears, but how the pipeline handles it.
Late Data: Rerun Last 7 Days
Apply a daily-rerun window as the simplest workable defense against late data in a batch pipeline.
Why a Rerun Window Works
| Window Size | Catches | Misses | Daily Compute Cost |
|---|---|---|---|
| 1 day (today only) | Events arriving the same day they were produced | Anything later than processing time | 1x baseline |
| 3 days | Most mobile retry traffic and brief producer outages | Long tail of stuck SDKs and weekend outages | 3x baseline |
| 7 days | Nearly all real-world lateness in event-driven systems | Multi-week outages and audit-grade backfills | 7x baseline |
| 30 days | Even uncommon long-tail late arrivals | Truly unbounded lateness | 30x baseline; rarely worth it for routine running |
The Pattern in Code
- ▸An idempotent write: re-running must produce the same answer, not duplicates
- ▸Partition-level overwrite (not append): yesterday's row gets replaced, not added to
- ▸Source data retained for at least the window length: raw events from 7 days ago must still be queryable
- ▸Compute budget that absorbs the window: 7 days of work, every day
When the Window Is Not Enough
- Pick a window size based on observed lateness, not on a hunch
- Make the rerun idempotent at the partition level: overwrite, not append
- Retain raw source data for at least the window length
- Append late events to the bucket of when they arrived; this hides the late-data problem
- Skip the rerun on weekends; producer outages happen on weekends too
- Set the window to one day and assume the pipeline handles late data; it does not
> A subscription company runs a nightly batch pipeline that aggregates checkout events from a Kafka topic into a Snowflake daily revenue table. Two issues land in the same week. On Monday a backend engineer adds a promo_code field to the checkout event, and on Wednesday the on-call engineer notices that Tuesday's revenue total quietly grew by 1.4 percent overnight. The new data engineer is asked to design the simplest set of changes that keeps both problems from recurring.
Data shapes shift and events arrive out of order; pipelines must absorb both without breaking
- Category
- Pipeline Architecture
- Difficulty
- beginner
- Duration
- 25 minutes
- Challenges
- 0 hands-on challenges
Topics covered: The Producer Added a Column Problem, Forward vs Backward Compatibility, Adding Is Safe, Renaming Is Not, What Late Data Means, Late Data: Rerun Last 7 Days
Lesson Sections
- The Producer Added a Column Problem (concepts: paSchemaEvolution)
Pipelines do not own the data flowing through them. The teams that produce events, write to operational databases, or push files into shared buckets own the upstream shape. Those teams ship code on their own cadence. Sooner or later, one of them adds a field, renames a column, or changes a type, and a pipeline that has been running fine for months suddenly fails. The producer-added-a-column problem is the most common variant of this story. It is so common that every senior data engineer has a pe
- Forward vs Backward Compatibility (concepts: paSchemaEvolution)
Two terms appear in nearly every conversation about schema change: backward compatible and forward compatible. They sound interchangeable. They are not. The distinction matters because it tells the producer and the consumer who can upgrade first without breaking the other. Confusing the two is the source of half the schema-related production incidents in event-driven systems. The Two Definitions, Plain Backward compatibility says the new code reads the old data. Forward compatibility says the ol
- Adding Is Safe, Renaming Is Not (concepts: paSchemaEvolution)
The compatibility framework above implies a practical rule that holds for almost every real-world schema change. Adding things is usually safe. Removing or renaming things is almost never safe without coordination. This is not a deep theoretical claim. It is an observation about the asymmetry between adding new information and removing or relabeling information that downstream code already depends on. The asymmetry is so reliable that it shows up as a default in serialization formats, in version
- What Late Data Means (concepts: paLateData)
Schema drift is one half of the lesson. The other half is late data. Events do not always arrive in the order they were produced. A click happens on a phone with patchy reception on Tuesday morning, the SDK queues the event locally, and the event is uploaded Thursday afternoon when the phone reconnects to wifi. The event is timestamped Tuesday. It arrives Thursday. Every batch and streaming system in the industry has to decide what to do with that event. The Two Timestamps That Matter These thre
- Late Data: Rerun Last 7 Days (concepts: paLateData)
The simplest workable fix for late data in a batch pipeline is also the most common: every day, do not compute today alone; also recompute the last several days. The size of the window depends on how late events tend to arrive. Seven days is a typical default because it covers nearly all mobile SDK retry tail behavior without making the daily run prohibitively expensive. Why a Rerun Window Works If today's run also recomputes the last seven days, then any event whose event_time was within the la