Design question: across many teams with long-retention and compacted topics, how would you choose between non-transitive and transitive compatibility modes, and what failure does the wrong choice cause?
answer
- Compatibility is NOT chained across versions
- Compaction = V1 bytes live forever → need transitive
- Replay / offset reset re-exposes oldest schemas
- Non-transitive failure = latent, weeks later
- Default FULL_TRANSITIVE for shared/compacted topics
basics
~20 sUse a _TRANSITIVE mode when old-schema data persists in topics (long retention or compaction), so every schema is checked against all prior versions. Non-transitive only guarantees adjacent versions, so version 3 might fail to read version 1's data even though each step passed.
solid answer
~50 sThe choice hinges on whether old-schema data still needs to be read. Non-transitive modes (BACKWARD, FORWARD, FULL) validate a new schema only against the immediately previous version. That is fine if old records age out before a schema drifts further — but with long retention, compacted topics (which keep the latest record per key indefinitely), offset resets, or replay, version 3 may have to read records written by version 1. Non-transitive compatibility is not chained: V2-compatible-with-V1 and V3-compatible-with-V2 does NOT imply V3-compatible-with-V1. So I default to FULL_TRANSITIVE (or at least BACKWARD_TRANSITIVE) for any topic with non-trivial retention or compaction, accepting stricter evolution rules in exchange for a guarantee that any current reader can handle any historical writer. The wrong choice surfaces as intermittent deserialization failures on old or replayed records, often long after the schema change shipped and passed its register-time check.
go deeper
Understand that transitive checks against all old versions, non-transitive only the latest.
Connect retention/compaction/replay to the need for transitive guarantees.
Construct a concrete passing-but-breaking version sequence and pick modes per topic type.
Set org-wide policy (default FULL_TRANSITIVE for shared topics), enforce centrally, and frame the flexibility-vs-latent-failure trade-off.
## The core property: compatibility is not transitive by default 'Compatible' under a non-transitive mode is only a pairwise, adjacent-version guarantee. Formally, if V2 is BACKWARD-compatible with V1 and V3 is BACKWARD-compatible with V2, it does **not** follow that V3 is BACKWARD-compatible with V1. Each step is checked against only the latest version at the time. Over several evolutions the cumulative drift (e.g. a field added in V2 with a default, then the default's semantics relied upon, then another change in V3) can leave V3 unable to read V1's bytes. ## When old data still matters The register-time check is about *future* readers vs *past* writers. It only bites you in production if old-schema data is still being read. That happens when: - **Long retention**: `retention.ms` set high (or `-1` infinite) keeps old records around for months. - **Compacted topics** (`cleanup.policy=compact`): the latest record per key is retained **indefinitely**; a key written once under V1 may never be overwritten, so V1 bytes live forever. These are exactly the topics (state, config, changelogs) where transitive guarantees are essential. - **Offset reset / reprocessing**: a consumer group reset to earliest, a new consumer, or a Kafka Streams app rebuilding state replays the whole topic, re-exposing the oldest schemas. - **Disaster recovery / replay** from tiered storage or backups. ## What goes wrong with the non-transitive choice The schema change passes the register-time 409 gate (it's compatible with the latest version), ships, and works for live traffic. Weeks later a consumer reprocesses, hits a V1 record, and throws a deserialization exception — a **latent**, hard-to-trace failure decoupled from the deploy that caused it. This is the classic argument for transitive modes: shift the failure left to register time for the *entire* history. ## Choosing a policy across many teams - **Default to FULL_TRANSITIVE** for shared/event-sourced/compacted topics. It guarantees any reader reads any writer in both directions, so cross-team deploy ordering and replay are both safe. Cost: only add/remove defaulted fields are allowed — strict but predictable. - **BACKWARD_TRANSITIVE** when you control deploy ordering (consumers first) but must read full history (the common pick for general event topics with retention). - **FORWARD_TRANSITIVE** when producers lead and a long tail of old consumers/readers must keep reading new data. - **Non-transitive** only for short-retention, ephemeral topics where old data provably ages out before further evolution. - **NONE** essentially never for shared infrastructure; if used, compatibility must be enforced by an external governance process. ## Operational guardrails that pair with the policy - Disable `auto.register.schemas` and register through CI running the `/compatibility` (all-versions) check. - Enforce mode centrally via `/config` so individual teams can't weaken it on a shared subject. - Treat schema changes as reviewed artifacts (PR + compatibility test) rather than app-side side effects. ## The trade-off framed for leadership Transitive modes trade **flexibility of schema change** for **elimination of a class of latent production failures**. For platforms where replay/compaction is normal, that trade strongly favors transitive; for truly disposable short-lived streams, the looser non-transitive mode reduces friction without real risk.
- Give a concrete sequence where non-transitive BACKWARD passes every register but still breaks in production.V1 has field A. V2 removes A (BACKWARD-OK vs V1). V3 adds a new required-ish field handled relative to V2 (BACKWARD-OK vs V2). A V3 consumer replaying a compacted topic hits a V1 record; the V3 schema was never validated against V1, so resolution can fail — a latent error the register checks never caught.
- Why are compacted topics the strongest case for transitive modes?Log compaction retains the latest record per key indefinitely, so records written under the oldest schema can persist forever and must remain readable by every future reader.
saying these in an interview costs you the question
- Claiming pairwise adjacent compatibility implies full-history compatibility.
- Ignoring compaction/retention when judging whether transitive is needed.
- Treating the register-time pass as proof old data will always decode.
- Recommending NONE for shared/event-sourced topics.
- Assuming a single non-transitive mode is universally safe across teams.