Skip to content
Back to Blog

Schema Evolution Is Still the Hardest Problem in Data Engineering

Prateek SinghOctober 24, 20268 min read88 views
Schema Evolution Is Still the Hardest Problem in Data Engineering

Adding a column is easy. Renaming one silently breaks three dashboards and a compliance report. Why schema evolution is data engineering's hardest problem.

Every data platform eventually hits the same wall. A source system renames a field. A product team changes an enum. An upstream API quietly drops a nullable column and starts sending nulls instead of omitting the key. None of these events trigger an alarm. They propagate silently downstream, and you discover them three weeks later when a revenue dashboard shows a number that is off by exactly the amount that should have come from that column.

Schema evolution is the process of changing the structure of data over time without destroying the systems that depend on it. The industry has spent years building tooling around it, and yet it remains one of the most reliable sources of production incidents in mature data platforms. That gap — between the tooling we have and the incidents we still suffer — is worth examining carefully.

What the Lake Formats Actually Solved

Apache Iceberg, Delta Lake, and Apache Hudi all provide transactional schema evolution on top of Parquet. They handle the cases that used to require a full table rewrite: adding a nullable column, dropping a column, reordering columns. Iceberg in particular has a clean model — columns are identified by a stable integer ID, not by name or position, so a rename at the metadata level does not require touching a single data file.

This is genuinely useful. It means that a write job can add a column and a read job can safely ignore it. It means that column reordering in the schema does not corrupt readers that rely on positional access. For the class of additive, backward-compatible changes, modern lake formats are a solved problem.

The mistake is believing that additive changes are the common case. They are the easy case. The hard cases are everywhere else.

The Cases That Still Break Everything

Consider a column rename. In Iceberg, you can rename a column at the metadata layer without rewriting files. The column ID stays the same; only the human-readable name changes. From the storage engine's perspective, this is clean. From the perspective of every SQL query that references the old name by string — in a dbt model, a Looker LookML file, a Jupyter notebook, a Spark job someone wrote eighteen months ago — it is a silent breaking change. The lake format did its job. The broader system did not.

Type widening is subtler. Promoting an INT to a BIGINT is technically backward-compatible in most formats. But a downstream system that serializes that column to JSON and sends it to a third-party API may now produce numbers that exceed the API's expected range. A BI tool that inferred a formatting rule from the column type may now render the values differently. The type change was safe at the file layer and dangerous everywhere above it.

Semantic drift is the hardest case of all, because no schema format can capture it. A column called revenue might mean gross revenue in January and net revenue after a definitional change in March. The name is the same. The type is the same. The values are different in a way that no automated system will detect unless someone explicitly recorded the change. Dashboards built before March will silently produce wrong numbers after it.

Why the Problem Compounds at Scale

A single table with ten downstream consumers is manageable. A data platform with hundreds of tables, each with dozens of consumers spread across SQL files, notebooks, BI tools, ML feature pipelines, and operational applications, is not. The dependency graph is rarely documented, almost never complete, and constantly changing.

The standard response is lineage tooling — systems that track which queries read which tables and which columns. Lineage is necessary but not sufficient. It tells you what was reading a column as of the last time the lineage crawler ran. It does not tell you about the Airflow DAG that runs monthly, the ad-hoc notebook that gets re-executed during audits, or the external partner who pulls data via a shared view. Lineage gives you a lower bound on impact, not an upper bound.

The other compounding factor is ownership ambiguity. In most organizations, the team that owns the source table is not the team that owns the downstream consumers. The upstream team makes a change that is locally reasonable — cleaning up a column name, fixing a type — and has no visibility into the blast radius. The downstream teams have no mechanism to express what they depend on or to be notified when it changes. The schema is a shared interface with no contract.

Data Contracts Are the Right Frame, But Implementation Is Hard

The data contracts movement — treating the schema and semantics of a dataset as an explicit, versioned, enforceable agreement between producer and consumer — is the correct conceptual response to this problem. A contract specifies not just the column names and types but the nullability guarantees, the expected value distributions, the business definitions, and the versioning policy. Breaking a contract requires a deliberate process, not just a commit.

The challenge is that contracts require discipline from both sides of the interface, and that discipline is culturally expensive. Producers have to treat schema changes as a release process, not a migration script. Consumers have to declare their dependencies explicitly rather than querying whatever columns happen to exist. Both sides have to agree on a compatibility model — backward, forward, or full — and actually enforce it.

Some teams implement contracts through schema registries, which work well for streaming systems where every message passes through a broker that can validate it against a registered schema. For batch lake workloads, the enforcement point is less obvious. You can validate on write, on read, or as a separate quality gate in your pipeline. Each has tradeoffs. Write-time validation catches problems early but requires the producer to know the contract. Read-time validation protects consumers but adds latency and complexity. A separate quality gate is flexible but creates a window where bad data is already in the lake.

What a Practical Schema Governance Model Looks Like

The teams that handle schema evolution well tend to share a few practices that are less about tooling and more about process:

  • Schema changes go through the same review process as code changes. A pull request that modifies a table schema triggers the same review, testing, and approval workflow as a pull request that modifies application logic. The schema is not a side effect of a migration; it is a first-class artifact.
  • Breaking changes require a deprecation period. Old column names are aliased, not immediately removed. Old types are maintained in parallel with new ones for a defined window. Consumers have time to migrate before the old interface disappears.
  • Semantic changes are documented at the column level. A column comment is not documentation. A versioned changelog that records when the definition of revenue changed, why it changed, and what the impact on historical data is — that is documentation. It is tedious to maintain and essential to have.
  • Consumers declare their dependencies explicitly. Whether through a formal contract system or simply a maintained registry of which pipelines read which columns, the dependency graph needs to exist somewhere that is not just implicit in the query text.

The Deeper Issue: Schemas Are Interfaces, Not Implementation Details

The root cause of most schema evolution pain is a category error. Engineers treat schemas as an implementation detail of storage — something you define once and update whenever the data changes. They are actually interfaces between systems, with all the versioning and compatibility obligations that implies.

In software engineering, changing a public API without a deprecation process is considered a serious mistake. In data engineering, changing a table schema without one is routine. The difference is not that data schemas matter less. It is that the consequences are slower to surface and harder to attribute, so the discipline has not developed the same way.

Modern lake formats have made the mechanical parts of schema evolution safer. The hard part was never mechanical. It was organizational: who owns the contract, who enforces it, and what happens when someone breaks it. Until data teams treat schema changes with the same rigor they apply to API changes, the silent breakages will keep happening — and the dashboards will keep showing numbers that are off by exactly the amount that used to live in a column that no longer exists.

References & Citations

Subscribe to new posts from theaivibe.org

No spam — just new posts. One-click unsubscribe.
Share this article

Related Posts

DuckDB GPU Acceleration After 2.0: Where It Wins, Where It Doesn't, and Why the Mac Is Different
Data Engineering14 min read

DuckDB GPU Acceleration After 2.0: Where It Wins, Where It Doesn't, and Why the Mac Is Different

DuckDB 2.0 turned DuckDB into a server, AWS is buying the company behind it, and the GPU question is back. DuckDB 2.0 also changed the query format my GPU extension reads, and gpudb 0.7.1 adapts to it: on an Apple M4 Max, 18 of 22 TPC-H SF10 queries run on the GPU under DuckDB 2.0, 3.7x faster end to end, with every answer identical to DuckDB's. Here is where a GPU helps DuckDB, how gpudb decides, and why the Mac is the interesting case.

44 views
Read
DuckDB vs SQL Server vs PostgreSQL for Analytics: SQL Server Standard's Ceilings, What They Cost, and What a Lakehouse Does Instead
Data Engineering27 min read

DuckDB vs SQL Server vs PostgreSQL for Analytics: SQL Server Standard's Ceilings, What They Cost, and What a Lakehouse Does Instead

SQL Server ships a real analytical engine and then fences Standard edition off from it: batch mode capped at 2 threads, a 32 GB columnstore cache, no batch mode on rowstore, no SIMD, no adaptive joins, all Enterprise at 3.83x the price per core. On one Apple M4 Max, DuckDB 2.0 ran six TPC-H-style queries over 60 million rows 18x to 47x faster than PostgreSQL 18, and DuckDB held to 2 threads still beat PostgreSQL on 8 workers by 3.7x to 8.6x. Where PostgreSQL's row store runs out, how a DuckDB lakehouse starts, what MotherDuck costs, and where DuckDB sits in AWS.

134 views
Read
Apache Arrow and Apache DataFusion Now List ArrowMetal: Arrow Compute and a DataFusion Optimizer Rule on the Apple Silicon GPU
Data Engineering10 min read

Apache Arrow and Apache DataFusion Now List ArrowMetal: Arrow Compute and a DataFusion Optimizer Rule on the Apple Silicon GPU

ArrowMetal is now on Apache Arrow's Powered By page and on Apache DataFusion's integrations list. What the two listings mean, and what the listed thing does as of 0.5.0, with one real run on each side.

216 views
Read