Schema drift is what happens when the structure of data you receive from a source changes without any notice to your pipeline. A column gets renamed. A field that was always present stops appearing in some records. A type changes from integer to string because someone upstream decided to store enum values as text rather than numeric codes.
The insidious thing about schema drift is that it rarely causes an immediate visible failure. Your pipeline continues to run. Queries continue to execute. The dashboard continues to update. But the numbers are wrong, because the column your revenue aggregation depended on is now NULL for all records added after the schema change, or because the join key that looked like an integer now contains strings that your warehouse implicitly cast to zero.
Detection approaches vary significantly in implementation cost and in the classes of drift they catch. Here are four, ordered from least to most implementation effort.
Approach one: column checksum comparison
What it catches: column additions, column removals, column renames.
What it misses: type changes, nullability changes, value encoding changes.
Implementation cost: low. One query per source table per run.
The simplest drift detection approach is to compute a checksum of the column list for each source table and compare it against the checksum from the previous run. In practice, this means querying your source's information schema or equivalent catalog table, sorting the column names alphabetically, concatenating them into a string, and hashing the result.
SELECT MD5(STRING_AGG(column_name ORDER BY column_name))
FROM information_schema.columns
WHERE table_name = 'orders'
AND table_schema = 'public';
Store this hash on every ingestion run. When it changes, halt the pipeline and alert. The alert does not tell you what changed, only that something changed. You then query the information schema directly to get the diff.
The limitation is obvious: this catches structural changes (column additions and removals) but not semantic ones. A type change from INTEGER to VARCHAR on a column that was carrying numeric IDs will not change the column list checksum. Your pipeline will continue to run and your numeric ID field will silently become a string field downstream.
Despite this limitation, column checksums are worth implementing first because they catch the most common class of drift. Most schema changes involve adding or removing columns, and most of those happen because an upstream team changed a table structure without notifying their downstream consumers.
Approach two: column-level type and nullability comparison
What it catches: column additions and removals, type changes, nullability changes, precision and scale changes.
What it misses: value encoding changes within a compatible type.
Implementation cost: moderate. Per-column metadata storage and comparison logic.
Instead of a checksum over column names, store a per-column schema snapshot: column name, data type, character maximum length, numeric precision, numeric scale, and is_nullable. Compare the snapshot on the current run against the snapshot from the previous run and produce a structured diff.
A structured diff gives you actionable information. Instead of "the schema changed," you know "the column amount changed from NUMERIC(10,2) to NUMERIC(18,4)." This tells you whether the change is breaking (type incompatibility, removed column) or additive (new column, precision increase on an existing numeric column).
In Nava Labs, we capture this per-column snapshot at every source sync and expose the diff as an observable event in the observability layer. When a type change is detected, we route it to alerting and also tag the downstream pipelines that reference the changed column so impact assessment is immediate.
The limitation here is that type metadata does not tell you about encoding conventions. If a column named currency_code stores ISO 4217 codes as three-character strings, and the upstream system starts storing them as numeric currency codes instead, the schema metadata shows no change. The column is still a three-character string column. But the values are now numbers, not alphabetic codes, and your downstream join to a currency reference table will produce no matches.
Approach three: value distribution sampling
What it catches: encoding changes within a compatible type, value range violations, unexpected null rates, cardinality changes in low-cardinality columns.
What it misses: changes that happen to maintain the same distribution profile.
Implementation cost: higher. Requires statistical comparison over a sample of data values.
Value distribution sampling moves from structural schema checks to semantic content checks. Instead of only comparing column metadata, you compute summary statistics over the values in each column and compare them against a baseline.
For numeric columns: min, max, mean, median, and null rate. For string columns: min length, max length, mean length, null rate, and for low-cardinality columns the distinct value set. For timestamp columns: min, max, and whether the value distribution has gaps that fall outside normal expected intervals.
These statistics surface the currency code encoding change described above. The mean value of currency_code was previously around 1.5 characters of alphabetic content. After the encoding change, the mean value becomes a three-digit number. The distribution has shifted dramatically even though the column type did not.
The practical challenge with distribution sampling is setting useful baselines. Data distributions are not static. Revenue values increase over time. User counts grow. Event frequencies follow weekly patterns. A naive baseline comparison will produce false positives during periods of normal business growth.
The approach that works best is to compare against a rolling baseline of the same time-of-week from the trailing four weeks, rather than against an absolute baseline. This handles business-driven distribution shifts while remaining sensitive to sudden structural changes.
Approach four: data contract validation
What it catches: everything the previous three approaches catch, plus semantic constraint violations, referential integrity violations, and business rule violations that the source should never have produced.
What it misses: nothing, by design. Contract violations are caught and quarantined.
Implementation cost: high. Requires contract authoring and maintenance discipline.
Data contracts formalize the expectations your pipeline has about a data source. A contract for an orders table might specify: the order_id column is a non-null string, always present, unique within the table; the amount column is a non-null positive decimal; the status column is a non-null string with values drawn from the set {placed, paid, shipped, completed, refunded}; and the created_at column is a non-null timestamp not in the future.
Contract validation runs these checks on every record before it is written to the destination. Records that fail any contract assertion are quarantined rather than written. The quarantine table holds the raw record plus the assertion that failed, which makes debugging the source issue fast.
The limitation of data contracts is the ongoing maintenance cost. Contracts need to be updated when legitimate source schema changes happen. If the set of valid status values is extended with a new state, the contract needs updating before the pipeline accepts records with the new state. Teams that define contracts but do not maintain them end up with contracts that quarantine valid records, which is its own kind of failure.
The teams we see get the most value from data contracts are the ones that treat contract authoring as part of their deployment process for any pipeline that touches a production data surface. Contracts get updated at the same time as the downstream transform that consumes the new field. The discipline cost is real, but the resulting pipeline is meaningfully more reliable than one that runs unguarded against source changes.
Choosing the right level for your situation
Column checksums and column-level type comparisons are worth implementing for every source, regardless of team size or data volume. The implementation cost is low and the detection value is high for the most common class of drift.
Value distribution sampling is worth adding for columns that carry semantic meaning that the type system does not capture: currency amounts, status codes, geographic identifiers, or any column where the downstream semantics depend on the value space rather than just the type.
Full data contracts are worth the investment when the pipeline feeds a production surface where silent data quality failures have direct business consequences. Revenue reporting, billing calculations, and compliance reporting are the obvious cases.
See cross-source queries in action
Nava Labs connects your databases, warehouses, and event streams. Run SQL across all of them without moving data.
Get Early Access