Core Concept

Data Serialization & Schema Evolution

How bytes move between services — JSON, Protobuf, Avro — and how backward/forward compatibility rules plus a schema registry let you deploy producers and consumers independently without breaking the pipeline.


1. What It Is

Every RPC and event carries a serialization format — JSON, Protobuf, Avro. We pick based on schema evolution needs, payload size, and whether consumers can change independently.

What:

The process of converting in-memory data structures into bytes for network transmission or disk storage, governed by an explicit or implicit schema that defines field names, types, and evolution rules.

Primary purpose:

Enable reliable inter-service communication and durable event logs where producers and consumers may run different code versions simultaneously during rolling deploys.

Usually used for:

Kafka event payloads, gRPC service contracts, API request/response bodies, database CDC streams, and cross-team integration boundaries.

2. Core Mental Model

Pick format by consumer diversity and volume. Public APIs → JSON + OpenAPI. Internal high-throughput pipes → Protobuf or Avro + schema registry. The hard part is not encoding — it is schema evolution without breaking old readers.

📜 Schema contract

The agreed shape of data — .proto file, Avro JSON schema, or OpenAPI component. Versioned and reviewed like code.

⬅️ Backward compat

New consumer reads old data — safe to deploy consumers first.

➡️ Forward compat

Old consumer reads new data — safe to deploy producers first (ignores unknown fields).

In the room

Protobuf/Avro for internal services (compact, typed, evolvable); JSON at the public API boundary. Forward and backward compatibility rules matter — never remove a field without a deprecation window; add optional fields with defaults.

3. Why It Matters in HLD

Serialization format choice affects compatibility, performance, and schema migration — plan evolution before production traffic. Three lenses:

Needed When:

Multiple teams own producers/consumers, Kafka topics retain data for days, or gRPC services deploy independently on rolling schedules.

Avoids:

Coordinated big-bang deploys, deserialization crashes on unknown fields, and silent data corruption from type mismatches.

Optimizes For:

Wire efficiency at Kafka scale, compile-time contract safety (Protobuf), and auditability of schema change history.

4. Architecture & Data Flow

Walk schema evolution as interview steps. Step 1 — Choose format: JSON for public APIs, Protobuf/Avro for internal high-throughput. Step 2 — Define schema: required fields, optional fields, defaults. Step 3 — Forward compat: new consumers read old data (add optional fields). Step 4 — Backward compat: old consumers read new data (never remove required fields). Step 5 — Registry: schema registry validates compatibility on publish (Confluent model).

Loading...

Protobuf safe evolution example

PROTOBUF
// v1 — original
message OrderEvent {
  int32 order_id = 1;
  string status = 2;
}

// v2 — backward + forward compatible add
message OrderEvent {
  int32 order_id = 1;
  string status = 2;
  optional string customer_email = 3;  // new optional field
  reserved 4;                          // never reuse removed field numbers
  reserved "legacy_field";
}

In the room

Say "add optional fields with defaults, never rename in place" — Avro/Protobuf schema registry is the production answer; JSON schema for public APIs with explicit versioning.

5. Key Characteristics

JSON vs Protobuf vs Avro — schema enforcement, size, and evolution rules we compare:

  • Format comparison — trade readability against wire efficiency and schema enforcement:
FormatEncodingProsCons
JSONHuman-readable text; UTF-8
  • Universal debugging
  • schema-less flexibility
  • browser-native
Verbose; no enforced schema; slow parse; float/key ordering ambiguities
Protobuf (Protocol Buffers)Binary; field-tagged wire formatCompact; fast; strong codegen; gRPC native
  • Not human-readable
  • breaking changes if field numbers reused
AvroBinary; schema embedded or via registry ID
  • Schema-in-payload or registry ref
  • excellent for Kafka data lakes
Reader/writer schema resolution rules must be understood
  • Compatibility modes — registry enforces one of these on every schema registration:
ModeRuleSafe Change Example
Backward compatibleNew schema can read old dataAdd optional field with default — old writers omit it, new readers default it
Forward compatibleOld schema can read new dataOld reader ignores unknown fields — new writers add fields old consumers skip
Full compatibleBoth directions
  • Only add optional fields with defaults
  • never remove or retype
  • Breaking changes (avoid): remove field without reserved, change field type, rename field number, change field number assignment.
  • Schema registry: central store (Confluent, AWS Glue); assigns monotonic schema IDs; runs compatibility check before accept; consumers fetch schema by ID at deserialize time.

6. Strategic Tradeoffs

Binary efficiency trades human readability and debugging ease — we articulate both:

BenefitCost
Independent deploys — roll out new producer fields without coordinated consumer releasesGovernance overhead — schema registry, compatibility CI checks, and deprecation policies
Compact wire format — Protobuf/Avro cut bandwidth and storage 3–10x vs JSON on high-volume Kafka topics
  • Operational tooling — cannot curl and read payloads
  • need schema-aware debuggers

JSON at the edge, binary on the bus is a common pattern: REST/GraphQL APIs expose JSON for client debuggability; internal Kafka topics use Avro/Protobuf for throughput. Concept #32 API Contract governs the JSON surface; this concept governs the pipeline bytes underneath.

7. Failure / Bottleneck Awareness

Breaking schema changes, unknown field drops, and double serialization — we volunteer:

💥 Schema Registry Rejection at Deploy

Problem: Engineer retypes int32 amount to string amount — registry rejects; producer cannot publish; pipeline stalls.

Mitigation: CI compatibility check against registry; add new field amount_v2 as string, deprecate old field; never change types in place.

🗄️ Tombstone Field Number Reuse

Problem: Field 3 removed in v2; engineer reassigns field 3 to a new meaning in v3 — old data deserializes into wrong semantics.

Mitigation: Protobuf reserved 3; permanently; Avro aliases for renames; document deprecation in schema comments.

🐢 JSON Hot Path Bottleneck

Problem: 50K events/sec Kafka topic with JSON payloads — CPU spent in parse/serialize dominates consumer lag.

Mitigation: Migrate to Avro with schema registry; or Protobuf for gRPC-native services; keep JSON only on low-volume control planes.

8. Common HLD Usage

Kafka CDC pipelines, gRPC services, and event sourcing depend on schema evolution:

Production SystemFormatRationale
Kafka + Confluent Schema RegistryAvro with BACKWARD_TRANSITIVE
  • Consumers always read latest registered schema
  • producers auto-register on publish.
gRPC microservicesProtobuf .proto contracts
  • Codegen in Go/Java/TS
  • breaking changes caught at compile time if field numbers respected.
Public REST APIsJSON + OpenAPI spec
  • Human debuggable
  • versioning via URL prefix or Accept header (concept #32).

9. Decision Signals

Discuss serialization when multiple services share an event stream or API over time:

🎯 Use schema registry + Avro/Protobuf when:
  • Kafka topic has multiple consumer teams and retained history (concept #18).
  • Deploy order cannot be synchronized — consumers and producers roll independently.
  • Payload size at MB/sec scale — binary encoding saves broker disk and network.
  • Data lake ingestion — Avro + schema evolution feeds Spark/Presto with stable column mapping.
⏭️ JSON is fine when:
  • Public REST API with human debugging needs (pair with OpenAPI — concept #32).
  • Low-volume internal APIs where schema flexibility outweighs wire cost.
  • Prototyping — migrate to Protobuf before production Kafka load.

11. Deep Dive (Optional)

Avro Reader/Writer Schema Resolution

Avro separates writer schema (embedded in each message or referenced by ID) from reader schema (what the consumer expects). On deserialize, Avro resolves differences: missing fields get defaults; extra fields are skipped; field aliases map renames. This is why Avro dominates Kafka data lakes — Spark jobs written against schema v1 still read v5 data if evolution rules were followed.

Confluent Schema Registry Compatibility Levels

LevelRuleTypical Use
BACKWARDNew schema can read data written by previous schemaDeploy consumers before producers
FORWARDPrevious schema can read data written by new schemaDeploy producers before consumers
FULLBoth backward and forwardMaximum deploy flexibility
BACKWARD_TRANSITIVENew schema can read ALL prior versionsKafka default for most teams

Protobuf vs JSON Size Example

// Same logical message:
JSON:   {"order_id":12345,"status":"SHIPPED","items":[{"sku":"ABC","qty":2}]}  → ~72 bytes
Proto:  [field tags + varint encoding]                                         → ~18 bytes

// At 100K events/sec:
JSON:   ~7.2 MB/s wire + parse CPU
Proto:  ~1.8 MB/s wire + faster zero-copy decode

JSON Schema Evolution (Weaker Guarantees)

Without a registry, JSON evolution relies on convention: only add optional keys, never remove keys consumers depend on, use null for absent vs missing distinction carefully. OpenAPI additionalProperties: false catches unexpected fields in strict mode but breaks forward compatibility. For public APIs (concept #32), version via /v1/ URL prefix when breaking changes are unavoidable.

CDC + Schema Registry Pipeline

Debezium captures row changes from Postgres/MySQL WAL, converts to Avro with a schema derived from table DDL. Schema registry stores one schema per table topic. Downstream Spark Structured Streaming reads by schema ID — column adds in the source DB propagate as compatible schema bumps without stopping the pipeline. Ties directly to concept #18 retention and consumer group offset management.

Interview Checklist

  1. Name the format (JSON edge, Avro/Proto bus).
  2. State compatibility mode (usually BACKWARD_TRANSITIVE for Kafka).
  3. List safe changes: add optional field with default, add new message type.
  4. List forbidden changes: retype, renumber, delete without reserved.
  5. Mention schema registry CI gate before production deploy.

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...