advanced 2 min answer

You must remove a field from a protobuf message used by dozens of deployed services. Walk through the safe sequence.

grpcprotobufschema-evolutioncompatibilitygoogle
Show the full answer Hide the answer

What is being tested

Whether you understand that a field's wire identity is its number, not its name, and that deployed clients cannot be upgraded in lockstep.

The sequence

1. Find out who uses it. Not who you think uses it. Instrument reads of the field if you can, or search every consumer repository. If you cannot answer this, stop — you are not ready to remove anything.

2. Mark it deprecated in the schema and communicate a date. The deprecation annotation surfaces in generated code, so consumers see a compiler warning rather than a changelog entry nobody read.

3. Stop populating it — after every consumer has stopped reading it. This ordering matters. If you stop writing before they stop reading, they receive the type's default value (empty string, zero) and usually cannot distinguish that from a legitimate value. Silent wrong behaviour, not an error.

4. Remove the field from the schema and immediately reserve both its number and its name.

reserved 7;
reserved "legacy_status";

5. Never reuse that number. This is the permanent obligation. If number 7 is later assigned to a new field of a different type, an old client still sending the old field produces a value that deserialises into the new field. That is silent, typed data corruption in production, and it is extremely hard to attribute.

Reserving the name too prevents someone reintroducing the same name with a different number, which confuses everyone reading the schema even though it is wire-safe.

Why the ordering is not negotiable

At no point can you assume every client runs the same schema version. Deployed mobile clients, partner integrations, and services on an old release all coexist. Every intermediate state must be safe for every version in the field, which is the same constraint that governs zero-downtime database migrations and for the same reason.

The design rules that would have made this easier

  • Wrap parameters in a message even when one field would do. A method taking a bare string can never gain a second parameter compatibly.
  • Additive change only as the default posture: new fields optional, unknown fields ignored.
  • Instrument per-consumer field usage from the first version, because you cannot deprecate what you cannot measure — and that instrumentation cannot be added retroactively to history.

What a strong answer adds

Noting that "remove the field" is often the wrong goal. If the cost of removal exceeds the cost of carrying a deprecated, unpopulated field, carrying it is the correct engineering decision. Schema tidiness is not a requirement; the reserved number stays reserved either way.