Skip to content
About
Main content

Versioned schemas and migration

Why do you migrate the schema rather than the data, and what invariant follows?

Why this is different from application migrations

In an application database you can usually migrate the data: run a script that updates every row. In a large analytical table, you usually cannot. There is too much of it, it is immutable, and old files remain in the table indefinitely. So the reader must resolve a set of schemas simultaneously, forever.

Two strategies

StrategyWhat it doesBenefitCost
Migrate the datarewrite the files under the new schemaone schema at read time; simple readerscost proportional to table size; it changes file identity and invalidates every cache keyed on file name; viable for small or hot tables
Migrate only the schemaresolve old files against the current schema at read timefree at change timeimposes an invariant: every past schema must be reachable from the current one using the safe operation set

The invariant is why the operation set must be closed

That invariant is the real reason the allowed operations must form a closed set. You are never making one change. You are maintaining the composition of every change ever made. The requirement is:

Transitive compatibility is exactly that invariant, stated as a setting.

Migration scripts and the last escape

Migration scripts here alter and may start a . Both must be idempotent and restartable, because they will be interrupted.

The remaining escape is : carry a version field in the data and branch on it in the reader.

def read_shipment(rec):
    v = rec.get("schema_version", 1)
    if v == 1:
        return {"price_cents": rec["price"]}              # old: price meant cents
    elif v == 2:
        return {"price_cents": rec["price_dollars"] * 100}
    else:
        raise ValueError(f"unknown schema_version {v}")

It handles changes the algebra cannot express, at the permanent price of dual-path reader code. It is ugly, honest, and the only thing that works when a change is genuinely not a widening.

You migrate the schema, not the data, so the current schema must remain a legal reading of every schema you have ever had.