DBRaven
Failure Mode · configuration

Schema Version Mismatch Between Services

partial

Summary

When services sharing a data schema run at different versions at once, during a rolling deployment or a failed upgrade, a reading service encounters data written by a schema version it was not built to handle. The precise question in every such encounter is which of two distinct compatibility directions the change actually needs, and most incidents trace back to a change that only satisfied one of them.

Description

Schema evolution in distributed systems means every service sharing a schema must handle more than one version of it during the transition. There are two distinct compatibility directions, and they are not the same requirement. Backward compatibility means new code can read data written by old code: a newer service version encountering an older record must still work. Forward compatibility means old code can read data written by new code: an older service version, not yet upgraded, encountering a record a newer sibling already wrote must still work. A rolling deployment needs both simultaneously, because for its whole duration both old and new code are running and both are reading records the other version wrote. A schema change that is only backward compatible is not safe to roll out gradually; it is only safe once every reader has already upgraded.

In a rolling deployment of 10 instances, at any moment some run the new version and some run the old. Records written by new instances contain fields old instances have never seen; records read by new instances may have been written by old instances and lack fields the new version treats as required. Additive-only changes with safe defaults, the standard way to get both directions at once, are what make a change safe during this window: a new optional field with a default is backward compatible (old data still reads fine because the new reader falls back to the default) and forward compatible (new data still reads fine on the old reader because unknown fields are ignored) at the same time. A required field, or a semantic change to an existing field, breaks one direction or both.

How the failure manifests depends on the schema format. For JSON without a schema registry, the reading service's deserialization code accesses a field that does not exist and either throws a NullPointerException, returns a zero-value silently (Go structs), or applies an incorrect default. For Avro or Protobuf with a schema registry, compatibility checks catch changes that violate the configured direction before deployment, but a change that is technically compatible (an optional field with a default) can still be treated as required in application code and cause a logic error the registry cannot see, because the registry checks schema shape, not what the application code assumes about it. For database column additions, an old service reads a row without the new column and, on a subsequent update, may overwrite the new column's value with NULL if its UPDATE statement does not include that column.

The silent corruption variant is the most dangerous because both compatibility directions can look satisfied while application logic still breaks. Consider adding an is_verified boolean column to a users table, defaulting to false. The new service reads it and uses it correctly in authorization logic. The old service reads the user, does not see the column in its model, and later writes an update (changing last_seen). If the ORM constructs the UPDATE from only the fields it knows about, the column keeps its database value; that ORM behavior is what makes the change forward compatible in practice. But if the ORM maps every struct field including ones the old code never set, is_verified is silently overwritten with false, stripping verification status without either compatibility check ever firing, because the schema itself was fine, the ORM's write behavior was not.

Schema registries (Confluent Schema Registry for Kafka topics, Apicurio for OpenAPI) enforce the configured compatibility direction at publish time but cannot see application-level semantic assumptions layered on top of a technically compatible schema.

Characteristics

Propagationlinear
Time to detect1 to 10 minutes for hard errors (NullPointerException, deserialization failure) via error rate monitoring. Hours to days for silent corruption variants where data is written with incorrect default values and no exception is thrown. Schema registry compatibility violations are detected at deployment time, effectively zero-delay.
Blast radiusScope depends on the traffic share hitting mismatched version pairs. During a rolling deployment at 3 of 10 instances upgraded, roughly 30% of writes carry the new schema and 70% of reads by old instances encounter new-schema records. Data corruption can affect any record touched during the deployment window and persists in storage after deployment completes, potentially corrupting downstream caches, search indexes, and materialized views populated from the incorrect data.

Triggers

  • ·Rolling deployment of a service introducing a new required field without a multi-phase migration
  • ·Renaming a field (a breaking semantic change) via a technically compatible mechanism (add new field, deprecate old) without updating all consumers at once
  • ·Adding a new enum value that old consumers handle with a default case producing incorrect behavior
  • ·A database schema migration running before all application instances are updated

Detection Signals

error rate spikelog errorsalert

Mitigation Strategies

Schema registry with an explicit compatibility mode matching the deployment shapepreventscomplexity: medium

Use Confluent Schema Registry or AWS Glue Schema Registry for Kafka topic schemas, or OpenAPI validation middleware for REST. Set BACKWARD (or BACKWARD_TRANSITIVE) if only new readers must tolerate old data, FORWARD (or FORWARD_TRANSITIVE) if only old readers must tolerate new data, or FULL (or FULL_TRANSITIVE) for a rolling deployment, which needs both directions at once. Choosing BACKWARD alone for a rolling deployment is the most common version of this mistake: it protects the new code but not the old code still running alongside it.

Multi-phase schema migration with the expand-contract patternpreventscomplexity: medium

Phase 1 (expand): add the new field as optional with a safe default and deploy to all services, so every instance can read both old and new records regardless of which version wrote them. Phase 2 (migrate): backfill the field on existing data. Phase 3 (contract): make the field required and remove the old one, only once every instance is confirmed on the new version. No service ever encounters a version it cannot handle, because the schema stays additively compatible in both directions until the rollout is complete.

Strict ORM field selection to prevent unintentional column overwritescomplexity: low

Configure ORM UPDATE statements to modify only explicitly set fields (dirty-field tracking in Hibernate, SQLAlchemy, or ActiveRecord), so queries are column-specific rather than full-row. This is what makes a schema-compatible addition also safe in practice: without it, an old service can silently overwrite a column its schema never told it about, even though the schema change itself was technically forward compatible.

Recovery Steps

  1. 1.Stop the deployment rollout to prevent additional old-service instances from writing incorrect data
  2. 2.Identify the deployment time window to scope affected records
  3. 3.Query for records modified during that window with incorrect default values for the new field
  4. 4.Manually correct affected records using values derived from business logic or related data
  5. 5.Complete the deployment to the new version on all instances before resuming traffic
  6. 6.Verify downstream caches and materialized views are invalidated and rebuilt from corrected source data

Estimated recovery time: 30 minutes to 8 hours depending on the volume of corrupted records and available bulk-correction tooling. Hard errors (crashes) resolve immediately once the deployment completes. Silent corruption may require a multi-hour data audit and correction process.

Affected Systems

Patterns

event sourcingoutbox patterncqrswrite ahead log cdc

Technologies

kafkapostgresqlmongodbelasticsearch

Basis

Schema mismatch during rolling deployment is a well-documented distributed systems hazard; the backward/forward compatibility distinction is standard (Kleppmann, Designing Data-Intensive Applications, ch. 4; Confluent Schema Registry compatibility modes); the expand-contract pattern is established industry practice; the ORM column-overwrite and silent-NULL-defaulting failure modes are grounded in common ORM behavior.

Schema Version Mismatch Between Services: DBRaven