Chapter 25: Event Streaming

Event streaming platforms like Apache Kafka provide distributed, durable, ordered logs for event-driven architectures. Unlike traditional message queues, event streams enable replay, time-travel, and multi-consumer patterns at massive scale.

Interview Alert: Kafka questions are ubiquitous in system design interviews. Know partitioning, consumer groups, exactly-once semantics, and when NOT to use Kafka (simple RPC, transactional workflows with immediate consistency).

1. Event Streaming vs Message Queuing

Event streaming and message queuing solve different problems. Queues (RabbitMQ, SQS) deliver messages to consumers and delete them. Streams (Kafka, Pulsar) retain events as an immutable log, enabling replay and time-based queries.

Key Differences

Aspect Message Queue (RabbitMQ, SQS) Event Stream (Kafka, Pulsar)
Message Lifetime Deleted after consumption (ack) Retained by time/size (days/weeks)
Replay No replay (message gone after ack) Rewind to any offset, reprocess
Consumer Model Competing consumers (one gets msg) Consumer groups + broadcast (multiple)
Ordering Queue-level or no guarantee Per-partition ordering (strict)
Use Case Task distribution, RPC, work queues Event sourcing, analytics, CDC, logs
Throughput Lower (ack overhead, per-message) Higher (batching, sequential disk I/O)
Offset Management Broker tracks acks Consumer tracks offset (flexible)
When to Use Which: Use queues for task distribution (worker pattern, transient commands). Use streams for event sourcing, analytics, CDC, audit logs, or when multiple downstream consumers need the same events.

2. Kafka Architecture

Kafka is a distributed commit log. Producers append to topics, which are split into partitions. Brokers store partitions, and consumers read at their own pace. ZooKeeper (legacy) or KRaft (new) manage cluster metadata.

Core Components

Component Description Details
Broker Kafka server node Stores partitions, serves produce/fetch requests. Cluster = multiple brokers.
Topic Logical event stream Category/feed name (e.g., "orders", "clicks"). Divided into partitions.
Partition Ordered, immutable log Unit of parallelism. Messages appended sequentially. One leader, N replicas.
Segment Physical log file Partition split into segments (1GB or 7 days). Old segments deleted/compacted.
Offset Message position Sequential ID within partition (0, 1, 2...). Immutable.
ZooKeeper Cluster coordination (legacy) Leader election, broker membership. Being replaced by KRaft.
KRaft Kafka Raft (new) Built-in consensus (no ZooKeeper dependency). GA in Kafka 3.3+.
graph TB subgraph "Kafka Cluster" B1[Broker 1
Leader: P0, P2
Follower: P1] B2[Broker 2
Leader: P1
Follower: P0, P2] B3[Broker 3
Leader: none
Follower: P1, P2] end subgraph "Topic: orders (3 partitions)" P0[Partition 0
offsets 0-1000] P1[Partition 1
offsets 0-1500] P2[Partition 2
offsets 0-800] end Producer[Producer
key=user_id] CG1[Consumer Group A
C1 reads P0
C2 reads P1, P2] CG2[Consumer Group B
C1 reads P0, P1, P2] Producer -->|partition by key| P0 Producer --> P1 Producer --> P2 B1 --> P0 B2 --> P1 B1 --> P2 B2 -.replicate.-> P0 B3 -.replicate.-> P1 B3 -.replicate.-> P2 P0 --> CG1 P1 --> CG1 P2 --> CG1 P0 --> CG2 P1 --> CG2 P2 --> CG2
Interview Trap: "How does Kafka scale?" Answer: Partitions enable parallelism (more partitions = more consumers), and replication provides fault tolerance. But more partitions = higher ZooKeeper/KRaft load and longer rebalances.

3. Producers

Producers publish events to topics. Key decisions: partitioning strategy (which partition?), acknowledgment level (durability vs latency), batching (throughput vs delay), and idempotence (exactly-once).

Partitioning Strategies

Strategy How It Works Use Case Trade-offs
Key-based hash(key) % partitions Ordering per key (e.g., user_id) Skew if keys unbalanced. Default for keyed messages.
Round-robin Cycle through partitions No key, max throughput No ordering guarantees. Balances load.
Sticky Batch to one partition, then switch High throughput (default no-key) Better batching than round-robin. Kafka 2.4+.
Custom Implement Partitioner interface Domain-specific (geo, tenant) Complexity. Must avoid hotspots.

Acknowledgment Levels (acks)

acks Durability Latency Risk
0 None (fire-and-forget) Lowest Data loss if broker fails before write. Use for metrics, logs.
1 Leader acknowledges Medium Data loss if leader fails before replication. Default.
all (-1) Leader + all ISR replicas ack Highest No loss (if min.insync.replicas=2+). Use for critical data.

Producer Configuration Essentials

Setting Default Purpose Tuning
batch.size 16KB Batch size in bytes Increase (64KB-1MB) for throughput. Adds latency.
linger.ms 0 Wait time to fill batch Set 5-100ms for throughput. Adds delay.
compression.type none Compress batches Use snappy (fast) or lz4. Saves network/storage.
enable.idempotence true (3.0+) Deduplication Prevents duplicates on retry. Requires acks=all.
max.in.flight 5 Unacked requests Lower (1) for strict ordering. Higher for throughput.
Idempotent Producer: Enabled by default in Kafka 3.0+. Assigns sequence numbers to messages. Broker deduplicates retries. Essential for exactly-once semantics. Requires acks=all, max.in.flight <= 5.

4. Consumers & Consumer Groups

Consumers read from topics at their own pace. Consumer groups enable horizontal scaling: each partition assigned to one consumer in the group. Multiple groups can read the same topic independently.

Consumer Group Mechanics

Concept Description Details
Consumer Group Logical subscriber group.id identifies group. Partitions split among group members.
Partition Assignment One partition → one consumer No two consumers in same group read same partition. Max parallelism = partition count.
Rebalancing Reassign partitions Triggered by consumer join/leave/failure. Stops consumption during rebalance.
Group Coordinator Broker managing group One broker per group. Handles heartbeats, rebalances, offset commits.
Offset Consumer position Stored in __consumer_offsets topic. Auto-commit or manual.

Rebalancing Strategies

Strategy How It Works Pros Cons
Eager (Range, RoundRobin) Stop all, reassign all Simple. Default. Stop-the-world. All consumers pause.
Cooperative (Incremental) Revoke only moved partitions Minimal downtime. Consumers keep unaffected partitions. Two-phase (revoke, assign). Kafka 2.4+.
Sticky Minimize partition movement Preserves assignments. Reduces cache misses. More complex. Works with eager or cooperative.

Offset Management

Mode How It Works Use Case Risk
Auto-commit Commit every N seconds Simple. enable.auto.commit=true. At-least-once (duplicates on crash). At-most-once if commit before process.
Manual (sync) commitSync() after processing At-least-once. Process then commit. Duplicates on crash. Blocks thread.
Manual (async) commitAsync() after processing At-least-once. Non-blocking. Duplicates on crash. May commit out of order.
Transactional Commit offset in transaction Exactly-once (with transactional producer). Requires isolation.level=read_committed. See section 6.

Consumer Lag

Consumer lag = (log end offset) - (consumer offset). High lag = consumer falling behind. Monitor via kafka-consumer-groups.sh, Burrow, or metrics (kafka.consumer:type=consumer-fetch-manager-metrics,partition=*).

Interview Question: "Consumer group has 10 consumers, topic has 5 partitions. What happens?" Answer: 5 consumers active (one per partition), 5 idle. Idle consumers become active on rebalance (if others fail).

5. Partitions Deep Dive

Partitions are the unit of parallelism, ordering, and replication. Messages within a partition are strictly ordered. Across partitions, no ordering guarantee. Choose partition count carefully (hard to increase later).

Ordering Guarantees

Scope Guarantee Caveat
Within Partition Total order Messages appended sequentially. Consumer reads in order (offset 0, 1, 2...).
Across Partitions No order Producer may write P0, P1, P0. Consumer sees interleaved.
Producer Retries Idempotent preserves order With enable.idempotence=true + max.in.flight ≤ 5. Else retries may reorder.

Partition Count Selection

Factor Guidance Notes
Throughput partitions = target_throughput / partition_throughput Each partition: ~10-30MB/s producer, ~20-50MB/s consumer (disk-bound).
Consumer Parallelism Max consumers = partition count More partitions = more parallel consumers. Diminishing returns after ~100.
Latency More partitions = longer rebalance Rebalance time grows with partition count. 1000s of partitions = minutes.
Memory Each partition = buffer in producer/consumer Producer: batch.size per partition. Consumer: fetch.min.bytes.
File Handles Each partition = open files (log, index) Broker may hit ulimit with 10K+ partitions.

Replication & In-Sync Replicas (ISR)

Concept Description Details
Replication Factor Number of replicas RF=3 means 1 leader + 2 followers. Tolerates RF-1 failures.
Leader Partition owner All produce/fetch requests go to leader. One leader per partition.
Follower Replica Fetches from leader. Passive (no client requests). Becomes leader on failure.
ISR (In-Sync Replicas) Caught-up replicas Leader + followers within replica.lag.time.max.ms (default 10s). acks=all waits for ISR.
OSR (Out-of-Sync) Lagging replicas Removed from ISR. Cannot become leader (data loss risk). Must catch up to rejoin.
min.insync.replicas Min ISR for acks=all Topic config. Set 2 for RF=3 (leader + 1 follower). Producer fails if ISR < min.
graph LR subgraph "Partition 0 (RF=3)" Leader[Leader
Broker 1
offset 0-1000] F1[Follower
Broker 2
offset 0-1000
ISR] F2[Follower
Broker 3
offset 0-950
OSR] end Producer[Producer
acks=all] Producer -->|write| Leader Leader -.replicate.-> F1 Leader -.replicate.-> F2 F1 -->|ack| Leader F2 -->|lagging| Leader Leader -->|ack after ISR| Producer
Durability Setup: For zero data loss, use: replication.factor=3, min.insync.replicas=2, acks=all. Producer blocks if only 1 broker available (min ISR not met). Trades availability for consistency.

6. Exactly-Once Semantics

Exactly-once semantics (EOS) ensure each message is processed exactly once, even with failures. Requires idempotent producer (deduplication) + transactional API (atomic read-process-write).

Delivery Guarantees

Guarantee How Trade-offs
At-most-once Commit offset, then process Data loss on crash. Fast. Rarely desired.
At-least-once Process, then commit offset Duplicates on crash. Default. Requires idempotent downstream.
Exactly-once Transactional read-process-write No duplicates. Higher latency, complexity. Kafka-to-Kafka only.

Exactly-Once Components

Component Purpose Configuration
Idempotent Producer Deduplicates retries enable.idempotence=true (default 3.0+). Assigns PID + sequence number.
Transactional Producer Atomic multi-partition writes transactional.id (unique per producer). Enables transactions.
Transactional Consumer Read only committed messages isolation.level=read_committed. Skips aborted transactions.
Transaction Coordinator Manages transaction state Broker component. Writes to __transaction_state topic.

Read-Process-Write Pattern

// Exactly-once Kafka-to-Kafka processing
Properties props = new Properties();
props.put("enable.idempotence", true);
props.put("transactional.id", "my-app-txn-id-0");

producer.initTransactions();

while (true) {
    ConsumerRecords records = consumer.poll(100);
    producer.beginTransaction();

    for (ConsumerRecord record : records) {
        // Process
        ProducerRecord output = transform(record);
        producer.send(output);
    }

    // Commit offset in transaction
    producer.sendOffsetsToTransaction(
        consumer.position(), consumer.groupMetadata()
    );

    producer.commitTransaction(); // Atomic
}

Exactly-Once Limitations

Limitation Details
Kafka-to-Kafka Only EOS guarantees only for read-from-Kafka, write-to-Kafka. External systems (DB, API) not covered.
Performance Adds latency (2-phase commit). ~50% throughput vs non-transactional.
Zombie Fencing transactional.id must be unique. Two producers with same ID = one fenced (aborted).
Consumer Lag read_committed consumers lag by transaction duration (wait for commit/abort marker).
Interview Gotcha: "Does Kafka have exactly-once?" Yes, but ONLY for Kafka-to-Kafka workflows. If you write to Postgres after consuming, you need application-level deduplication (e.g., upsert by message ID).

7. Kafka Streams & ksqlDB

Kafka Streams is a Java library for stream processing (no separate cluster). ksqlDB is a SQL interface for streams. Both enable stateful transformations, joins, windowing on Kafka topics.

Kafka Streams Concepts

Concept Description Use Case
KStream Unbounded event stream Append-only log. Each record = new event. (clicks, orders)
KTable Changelog stream (latest per key) Upsert semantics. Key = primary key. (user profile, inventory)
GlobalKTable Replicated KTable (all partitions) Small reference data (lookup, broadcast join). Higher memory.
State Store Local RocksDB storage Stateful operations (aggregations, joins). Backed by changelog topic.
Windowing Group by time window Tumbling (non-overlapping), hopping (overlapping), session (gap-based).

Stream Processing Operations

Operation Type Description Stateful?
map, filter Stateless Transform/filter each record No
groupBy, aggregate Stateful Group by key, compute aggregates (count, sum) Yes (state store)
join (KStream-KStream) Stateful Join events within time window Yes (both sides buffered)
join (KStream-KTable) Stateful Enrich stream with table (latest) Yes (table cached)
join (KTable-KTable) Stateful Join two tables (foreign key) Yes (both cached)

ksqlDB

ksqlDB adds SQL interface for Kafka Streams. Run persistent queries (continuous) or pull queries (point-in-time). Backed by Kafka Streams under the hood.

-- Create stream
CREATE STREAM orders (order_id INT, user_id INT, amount DOUBLE)
  WITH (KAFKA_TOPIC='orders', VALUE_FORMAT='JSON');

-- Windowed aggregation
CREATE TABLE sales_per_minute AS
  SELECT user_id, COUNT(*) as order_count, SUM(amount) as total
  FROM orders
  WINDOW TUMBLING (SIZE 1 MINUTE)
  GROUP BY user_id
  EMIT CHANGES;

-- Stream-table join
CREATE STREAM enriched_orders AS
  SELECT o.order_id, o.amount, u.name, u.email
  FROM orders o
  JOIN users u ON o.user_id = u.user_id
  EMIT CHANGES;
Kafka Streams vs Flink: Streams = library (no cluster), tight Kafka integration, simpler ops. Flink = separate cluster, more features (CEP, batch), multi-source. Use Streams for Kafka-only pipelines.

8. Schema Registry

Schema Registry (Confluent component) stores schemas for Kafka messages. Producers/consumers validate against schemas. Enables evolution (add fields) without breaking consumers.

Schema Registry Features

Feature Description Details
Schema Storage Centralized schema repo REST API. Schemas stored in _schemas topic. Versioned per subject.
Schema Formats Avro, Protobuf, JSON Schema Avro most common (compact, fast). Protobuf for cross-language. JSON Schema for flexibility.
Schema ID Embedded in message 4-byte schema ID prefix. Consumer fetches schema from registry (cached locally).
Compatibility Checks Enforce evolution rules Registry validates new schemas against compatibility mode. Rejects incompatible changes.

Compatibility Modes

Mode Rule Use Case
BACKWARD New schema reads old data Default. Add optional fields, remove fields. Consumers upgrade first.
FORWARD Old schema reads new data Remove optional fields, add fields. Producers upgrade first.
FULL Backward + forward Only add/remove optional fields. Any upgrade order.
NONE No checks Disable validation. Risky (runtime errors).
BACKWARD_TRANSITIVE Compatible with ALL previous Check against all versions, not just latest.

Schema Evolution Example (Avro)

// Version 1
{
  "type": "record",
  "name": "Order",
  "fields": [
    {"name": "order_id", "type": "int"},
    {"name": "amount", "type": "double"}
  ]
}

// Version 2 (BACKWARD compatible)
{
  "type": "record",
  "name": "Order",
  "fields": [
    {"name": "order_id", "type": "int"},
    {"name": "amount", "type": "double"},
    {"name": "currency", "type": "string", "default": "USD"} // Optional with default
  ]
}
Why Schema Registry? Without it, consumers break when producers change message format. Registry enforces contracts, enables evolution, and reduces message size (schema not embedded, just ID).

9. Kafka Connect

Kafka Connect is a framework for streaming data between Kafka and external systems (databases, object stores, APIs). Source connectors import data into Kafka. Sink connectors export from Kafka.

Kafka Connect Components

Component Description Details
Connector Logical job Config (source/sink, topic, transform). Manages tasks.
Task Worker thread Executes data movement. Multiple tasks for parallelism.
Worker Connect runtime Standalone (single process) or distributed (cluster). REST API for management.
Converter Serialization format JSON, Avro, Protobuf. Decouples connector from format.
Transform Single-message modification SMT (Single Message Transform). Mask, rename, route. No aggregations.

Popular Connectors

Connector Type Use Case Notes
Debezium (CDC) Source Capture DB changes (MySQL, Postgres, etc) Reads transaction log. Low-latency, no polling.
JDBC Sink Sink Write to relational DB Upsert, insert, delete. Auto-create tables.
S3 Sink Sink Archive to object storage Parquet, JSON, Avro. Partitioning by time/field.
Elasticsearch Sink Sink Index for search Bulk indexing. Schema mapping.
HTTP Source/Sink Both REST API integration Poll API (source), POST (sink).

Change Data Capture (CDC) with Debezium

Debezium captures row-level changes (insert, update, delete) from database transaction logs. Publishes to Kafka with before/after snapshots. Enables event sourcing, cache invalidation, analytics.

// Debezium MySQL connector config
{
  "name": "mysql-cdc",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "debezium",
    "database.password": "secret",
    "database.server.id": "184054",
    "database.server.name": "prod",
    "table.include.list": "inventory.orders",
    "schema.history.internal.kafka.topic": "schema-changes.inventory"
  }
}

// Output to topic: prod.inventory.orders
// Message: {"before": {...}, "after": {...}, "op": "u"}
Kafka Connect vs Custom Producer: Use Connect for common integrations (DB, S3, Elastic). Build custom producer for proprietary systems or complex logic. Connect provides exactly-once, restart, monitoring.

10. Operational Concerns

Running Kafka in production requires attention to retention, compaction, partition management, monitoring, and disaster recovery.

Retention Policies

Policy How It Works Configuration
Time-based Delete segments older than N retention.ms (default 7 days). Per topic or global.
Size-based Delete when partition exceeds size retention.bytes. Use for bounded storage.
Log Compaction Keep only latest per key cleanup.policy=compact. For changelog topics (KTable).

Log Compaction

Compaction retains the latest value for each key, deleting older versions. Guarantees each key appears at least once. Useful for changelog topics (user profiles, inventory). Background process merges segments.

Aspect Details
Use Case KTable backing, CDC snapshots, configuration topics
Retention Keeps latest per key forever (unless deleted via tombstone: null value)
Process Cleaner thread merges segments, rewrites without old values
Config cleanup.policy=compact, min.cleanable.dirty.ratio (default 0.5)

Partition Reassignment

Move partitions between brokers for load balancing, broker decommission, or rack awareness. Use kafka-reassign-partitions.sh or Cruise Control (LinkedIn's auto-rebalancing tool).

Monitoring Metrics

Metric What It Measures Alert On
Under-Replicated Partitions Partitions with ISR < RF kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions > 0
Consumer Lag Messages behind kafka.consumer:type=consumer-fetch-manager-metrics,partition=*,topic=*,attribute=records-lag-max
Request Latency Produce/fetch time kafka.network:type=RequestMetrics,name=TotalTimeMs,request={Produce|Fetch} p99 > 100ms
Active Controllers Controller count kafka.controller:type=KafkaController,name=ActiveControllerCount != 1
Offline Partitions Partitions with no leader kafka.controller:type=KafkaController,name=OfflinePartitionsCount > 0

Disaster Recovery

Strategy How RTO/RPO
Cross-Cluster Replication MirrorMaker 2.0 (Kafka Connect) RPO: seconds (async). RTO: minutes (DNS/client switch).
Backup to S3 S3 Sink Connector RPO: hours (batch). RTO: hours (restore + rebuild).
Active-Active Bidirectional MirrorMaker + conflict resolution RPO: zero. RTO: zero (if app can handle both clusters).
Common Outage Causes: 1) Under-replicated partitions (slow disk, network). 2) Consumer lag explosion (bad code, GC pause). 3) ZooKeeper failure (upgrade to KRaft). 4) Disk full (monitor retention, compaction). 5) Too many partitions (rebalance storm).

11. Alternatives to Kafka

Kafka dominates event streaming, but alternatives exist for specific use cases: Pulsar (multi-tenancy, tiered storage), Redpanda (no JVM, low latency), managed services (Kinesis, EventHubs).

Kafka vs Alternatives

Platform Strengths Weaknesses When to Use
Kafka Mature, ecosystem, throughput, Kafka Streams JVM (GC pauses), ZooKeeper (legacy), ops complexity Default choice. Event sourcing, CDC, analytics.
Pulsar Multi-tenancy, tiered storage (S3), geo-replication, functions Smaller ecosystem, fewer managed options Multi-tenant SaaS, infinite retention (tiered), geo-active.
Redpanda C++ (no JVM), low latency, Kafka API compatible, simpler ops Newer (less mature), smaller community Performance-critical, want Kafka API without JVM.
AWS Kinesis Fully managed, auto-scaling, AWS integration Proprietary API, shard limits (1MB/s write), higher cost AWS-native, small scale, don't want ops.
Azure EventHubs Fully managed, Kafka API support, Azure integration Kafka API limited (no admin ops), cost Azure-native, want managed Kafka-like.
NATS JetStream Simple, low latency, lightweight, K8s-native Simpler feature set, smaller ecosystem Microservices messaging, edge, IoT.

Pulsar Architecture Highlights

Feature How It Differs from Kafka
Layered Architecture Serving layer (brokers) + storage layer (BookKeeper). Decoupled. Kafka = monolithic broker.
Tiered Storage Auto-offload old segments to S3/GCS. Infinite retention without broker disk. Kafka requires add-on.
Multi-Tenancy Namespaces, quotas, isolation built-in. Kafka = single tenant or manual ACLs.
Geo-Replication Built-in async replication across clusters. Kafka requires MirrorMaker.

When to Choose What

Scenario Best Choice
Event sourcing, CDC, stream processing Kafka (mature, Kafka Streams, ecosystem)
Multi-tenant SaaS (isolated tenants) Pulsar (namespaces, quotas)
Low-latency trading, gaming Redpanda (C++, no GC) or Pulsar
Infinite retention (years of data) Pulsar (tiered storage) or Kafka + S3 sink
AWS-only, small scale, minimal ops Kinesis
Microservices pub/sub (simple) NATS JetStream or RabbitMQ
Interview Question: "Why not use a database for event streaming?" Answer: Databases optimize for updates/deletes (random I/O). Event streams append-only (sequential I/O = 100x faster). Also, Kafka provides consumer groups, replay, partitioning out-of-the-box.

Benchmarks & Production Patterns

Kafka Performance Benchmarks

Configuration Hardware Throughput (msgs/sec) Throughput (MB/sec) P99 Latency (acks=all)
3-broker cluster m5.2xlarge, EBS gp3 800K msgs/sec 800 MB/sec 15ms produce
Single partition write m5.xlarge ~10K msgs/sec ~10 MB/sec 5-10ms
Single partition read m5.xlarge ~30K msgs/sec ~30 MB/sec 1-5ms fetch

Production Scale Examples

Company Volume Infrastructure Use Case
LinkedIn 7 trillion messages/day 100+ Kafka clusters, 100K+ topics. Largest cluster: 3000 brokers. Activity streams, metrics, logs, CDC, messaging
Netflix 700 billion events/day Multiple Kafka clusters globally. Keystone pipeline (custom). Playback events, A/B test logging, personalization signals, operational metrics
Uber 1+ trillion messages/day Multi-region Kafka clusters, custom uReplicator (MirrorMaker fork) Surge pricing, ETA calculation, fraud detection, dispatch events

Partition Count Formula

Rule of thumb: partitions = max(throughput / partition_throughput, consumer_count)

Scenario Target Throughput Partition Throughput Partition Count
Single partition write limit - ~10 MB/sec write Disk-bound (sequential I/O)
Single partition read limit - ~30 MB/sec read Network-bound (page cache)
Example: 100 MB/sec write 100 MB/sec 10 MB/sec per partition 100 MB / 10 MB = 10 partitions
Example: 20 consumers - - Min 20 partitions (1 per consumer)

Kafka vs Pulsar vs Redpanda Concrete Comparison

Metric Kafka Pulsar Redpanda
P99 Latency ~15ms (acks=all) ~7-10ms (2x lower tail latency) ~1-5ms (10x lower P99, no JVM GC)
Storage Architecture Monolithic (broker = storage) Tiered (BookKeeper + S3 offload) Monolithic (C++ Raft, local disk)
Operational Complexity High (ZooKeeper/KRaft, JVM tuning) Higher (BookKeeper + ZooKeeper + brokers) Low (single binary, no ZooKeeper)
Ecosystem Largest (10x more connectors, libraries) Smaller (Pulsar Functions, built-in geo-replication) Smallest (Kafka API compatible, growing)
Multi-Tenancy Manual (ACLs, quotas) Native (namespaces, tenant isolation) Manual (via Kafka API compatibility)
Infinite Retention Requires S3 sink connector Built-in (tiered storage to S3/GCS) Tiered storage (beta, v22.2+)

Real-World Architecture: Netflix Keystone Pipeline

Use case: 700 billion events/day for playback telemetry, personalization, and A/B testing.

Component Technology Details
Ingestion Kafka (custom build) Multi-region clusters. 100K+ partitions across topics.
Stream Processing Flink (not Kafka Streams) Real-time aggregations, windowing. Chosen for richer feature set than Kafka Streams.
Storage S3 + Parquet Cold storage for analytics. S3 sink from Kafka via custom connector.
Real-Time ElasticSearch + Druid Kafka → ES for search, Kafka → Druid for time-series analytics.
Monitoring Consumer lag, under-replicated partitions Custom tooling. Alert on lag > 1M messages or ISR < min.insync.replicas.
Production Numbers Matter: Kafka 3-broker cluster (m5.2xlarge) = 800K msgs/sec, 800 MB/sec, P99 15ms (acks=all). Single partition: ~10 MB/sec write, ~30 MB/sec read. LinkedIn: 7 trillion msgs/day, 3000-broker clusters. Netflix: 700B events/day. Pulsar has 2x lower tail latency (tiered storage = less GC). Redpanda has 10x lower P99 (no JVM GC). Kafka has 10x larger ecosystem. Partition formula: partitions = throughput / 10MB/sec (write-bound) or consumer_count (parallelism).

Summary

Event streaming platforms enable distributed, durable, replayable event logs at massive scale. Kafka remains the dominant choice, with partitioning for parallelism, consumer groups for load distribution, and exactly-once semantics for correctness. Pulsar offers multi-tenancy and tiered storage; Redpanda removes JVM overhead. Choose based on scale, latency, ops complexity, and ecosystem maturity.

Final Exam: Design a real-time analytics pipeline ingesting 1M events/sec. Answer: Kafka with 100 partitions (10K/sec each), consumer group with 100 instances, Kafka Streams for aggregation, ksqlDB for SQL interface, S3 sink for archival, Schema Registry for evolution. Monitor consumer lag, under-replicated partitions. Use acks=all + RF=3 + min.insync.replicas=2 for durability.