Schema Evolution Lab
Add, rename, remove or retype a field, and watch which consumers break — including the ones that break without failing. Producers change schemas because a feature shipped; consumers find out because a number moved.
A contract is not a schema file. A schema says what shape the data has; a contract says who promised what to whom, what happens when the promise is broken, and where that is enforced. Without the last part, a schema registry is documentation — and a change that violates it still reaches production, because nothing was standing at the boundary.
The decision to make in advance, per hop, is what a violating batch does: rejected at the boundary, quarantined for inspection, or passed through with the damage. All three are defensible and only one of them is a decision if you make it during the incident.
The lab
Make a change to the producer's schema and see it propagate — through the serialization format, the raw layer, the transformation and the consumers.
Pay attention to the consumers that stay green. Those are the ones that will be wrong for months, because nothing in the platform is going to raise its hand about them.
| Change | No contract | Why |
|---|---|---|
| compatible | Readers that do not know the column ignore it; readers that do get nulls for old rows, which is what they should get. | |
| breaks loudly | Old rows have no value for it, so anything that enforces the requirement rejects the history it already holds. | |
| breaks loudly | Anything that selects it fails. The failure is loud, immediate, and lands on whoever reads it rather than whoever removed it. | |
| BREAKS SILENTLY | To a reader this is a drop and an add at the same time. Selects on the old name fail, and any SELECT * pipeline quietly starts carrying a new column full of the values the old one used to hold. | |
| compatible | Every old value fits in the new type. This is the direction evolution is meant to go. | |
| BREAKS SILENTLY | Values that fit continue to work. Values that do not are truncated or wrapped, and the row survives with a different number in it. | |
| BREAKS SILENTLY | The cast produces null rather than an error, so the rows survive, the row counts match, and every amount silently becomes nothing. | |
| BREAKS SILENTLY | The type is unchanged, the range is plausible, and every check passes. This is the change no schema system can catch, because the schema did not change. |
amount arrived as a string. The cast produced null rather than an error, so every row survived with no value in it.Four changes, ordered by how loudly they fail
The loud ones get reviewed. The silent ones are the ones still costing you something a quarter later.
Usually nothing — but a consumer doing SELECT * into a fixed-width structure, or asserting an exact column set, breaks immediately.
Every consumer that referenced it, at once, loudly.
Nothing visibly. The cast produces null rather than an error, so every row survives, row counts match, and the measure becomes nothing.
Nothing at any layer. The name is the same, the type is the same, and the number moves.