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).
Protobuf safe evolution example
// 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:
| Format | Encoding | Pros | Cons |
|---|---|---|---|
| JSON | Human-readable text; UTF-8 |
| Verbose; no enforced schema; slow parse; float/key ordering ambiguities |
| Protobuf (Protocol Buffers) | Binary; field-tagged wire format | Compact; fast; strong codegen; gRPC native |
|
| Avro | Binary; schema embedded or via registry ID |
| Reader/writer schema resolution rules must be understood |
- Compatibility modes — registry enforces one of these on every schema registration:
| Mode | Rule | Safe Change Example |
|---|---|---|
| Backward compatible | New schema can read old data | Add optional field with default — old writers omit it, new readers default it |
| Forward compatible | Old schema can read new data | Old reader ignores unknown fields — new writers add fields old consumers skip |
| Full compatible | Both directions |
|
- 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:
| Benefit | Cost |
|---|---|
| Independent deploys — roll out new producer fields without coordinated consumer releases | Governance 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 |
|
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:
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.
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.
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 System | Format | Rationale |
|---|---|---|
| Kafka + Confluent Schema Registry | Avro with BACKWARD_TRANSITIVE |
|
| gRPC microservices | Protobuf .proto contracts |
|
| Public REST APIs | JSON + OpenAPI spec |
|
9. Decision Signals
Discuss serialization when multiple services share an event stream or API over time:
- 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.
- 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
| Level | Rule | Typical Use |
|---|---|---|
| BACKWARD | New schema can read data written by previous schema | Deploy consumers before producers |
| FORWARD | Previous schema can read data written by new schema | Deploy producers before consumers |
| FULL | Both backward and forward | Maximum deploy flexibility |
| BACKWARD_TRANSITIVE | New schema can read ALL prior versions | Kafka 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 decodeJSON 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
- Name the format (JSON edge, Avro/Proto bus).
- State compatibility mode (usually BACKWARD_TRANSITIVE for Kafka).
- List safe changes: add optional field with default, add new message type.
- List forbidden changes: retype, renumber, delete without reserved.
- Mention schema registry CI gate before production deploy.
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.