A vendor ships an API update on a Tuesday. They renamed company_id to entity_id. No notification. Their changelog mentions it in the third bullet of the 2.4.0 release notes under "minor field updates."
Your pipeline still runs. Depending on how your ingestion handles unknown fields, one of three things happens: the field is silently dropped, the pipeline throws a KeyError and fails, or the field passes through under the new name that doesn't match your dbt model column reference. Option 2 is the best outcome. You find out immediately and can fix it. Options 1 and 3 are how you end up looking at wrong dashboard numbers three weeks later trying to figure out when they changed.
What Makes Schema Drift Distinct from a Pipeline Bug
A pipeline bug fails immediately and visibly. Schema drift fails slowly and invisibly. The ingestion job runs without errors. The data lands in your warehouse. Transformations execute. Your model references a column. But that column is full of NULLs where there used to be values, or the record count is off because a renamed field was treated as a new field and a different field was treated as deleted.
The insidious dynamic: schema drift often doesn't affect active metrics immediately. If the drifted field is not referenced in any currently running model or dashboard, the drift sits undetected until someone builds a new model that needs it. At that point, your historical data for that field is partially missing and the gap is hard to backfill because you don't know exactly when the change occurred.
Four Types of Schema Drift
Understanding which type of drift you're dealing with determines the right response.
Field rename: company_id becomes entity_id. This is the most common type. Usually caught if you have schema validation at ingestion. The fix is an alias map update. If you're running HyperNorm, this produces an unmatched-field alert: the new name doesn't appear in the alias map, and the old name produces no data.
Type change: a field changes from integer to string. Common example: IDs becoming UUIDs when a vendor migrates to a distributed ID scheme. This causes downstream type mismatches in any model that casts the field to integer for joins.
New required field: the vendor adds a field they consider required. If your ingestion specifies columns explicitly, no effect. If it uses wildcard column selection, your schema gains a new column you didn't plan for, and any downstream model with a strict column list now fails.
Semantic drift: the field name stays the same but the meaning changes. A status field that previously accepted values [active, inactive] now accepts [active, suspended, deleted, pending]. No pipeline error. No type error. Your model's business logic that treats inactive as the terminal state is now wrong because deleted exists and is different. This type of drift is the hardest to detect automatically.
Three Detection Approaches That Work
Schema Fingerprinting
Hash the list of field names and types at each pipeline run. Compare to the previous run's hash. Any structural change triggers an alert before data lands in the warehouse.
import hashlib
import json
def schema_fingerprint(fields: dict) -> str:
normalized = {k: str(v) for k, v in sorted(fields.items())}
return hashlib.sha256(
json.dumps(normalized, sort_keys=True).encode()
).hexdigest()
prev_fingerprint = load_from_run_metadata("vendor_karbon")
curr_fingerprint = schema_fingerprint(inbound_schema)
if curr_fingerprint != prev_fingerprint:
alert("Schema change detected for vendor_karbon")
Fingerprinting catches all structural changes (rename, add, remove, type change). It does not catch semantic drift.
Coverage Monitoring
Track the percentage of your expected canonical fields that have non-NULL data in each pipeline run. A drop in coverage for a specific canonical field is a strong signal that the source field was renamed or removed. This approach catches drift even when the raw schema looks unchanged to the fingerprint check (for example, when a vendor adds a new field and removes an old one simultaneously, the fingerprint changes but field count stays the same).
Coercion Error Tracking
If a field that was previously clean now produces type coercion errors, the source type probably changed. A sudden appearance of coercion errors on a field that had a clean history is a reliable signal. Combine this with the coverage metric: a field with increasing coercion errors and declining coverage is likely undergoing a type change mid-migration.
Versioning Vendor Schemas
The cleanest long-term solution is treating vendor schemas as versioned artifacts. When a schema change is detected:
- Record the existing schema as version N, set its
valid_untilto today - Record the new schema as version N+1, set its
valid_fromto today - Keep alias mappings for both versions active
- Tag each pipeline run with the schema version in effect
schema_versions:
vendor_karbon:
v1:
valid_from: "2025-01-01"
valid_until: "2025-09-15"
fields:
company_id: integer
company_name: string
v2:
valid_from: "2025-09-16"
fields:
entity_id: integer
company_name: string
This versioning approach means you can reproduce any historical pipeline run against the schema version that was in effect at that time. It also gives you a precise record of when each vendor change occurred, which is useful for investigating why a specific metric changed on a specific date.
Handling Drift in Production Without Auto-Adapting
This is worth drawing a clear line around: detecting schema drift is not the same as handling schema drift. Detection is automated and should produce an alert. Handling requires a human decision about whether to update the canonical alias map, mark the old field as deprecated, or add a new canonical field.
Auto-adaptation (silently updating the canonical schema when a vendor changes theirs) seems convenient but creates a different problem. Your canonical field map quietly grows every time a vendor adds a field. Your downstream models accumulate columns no one explicitly decided to include. The semantic contract between your pipeline and your models erodes.
The right model is: detection fires an alert, your team reviews the change, someone makes an explicit config update. The turnaround time for that review should be measured in hours, not days, for critical fields. Non-critical fields can queue and batch-review weekly.
Vendor Schedules Are Not Your Schedules
One thing worth accepting early: vendors change their schemas on their own release cadence, not yours. Many enterprise data vendors provide change notifications, but they're often buried in release notes rather than delivered as proactive API version headers. Some vendors version their APIs, meaning you can pin to v1 while they develop v2. Others don't, and every API call returns the current schema regardless.
The practical assumption to build on: schema drift will happen. How often depends on the vendor. The cost of an undetected drift scales with how long it goes undetected, not with how severe the change was. A minor rename caught within hours is nearly free to fix. The same rename undetected for three weeks while your model trains on incomplete data is expensive, because you now have a data quality gap in your historical dataset that affects anything trained or computed in that window.
Build detection as a first-class concern in your ingestion layer, and define a response workflow for schema alerts before you need it.