Command Palette

Search for a command to run...

Unit 2.3 · Kafka and Messaging

Replication, ISR, Retention, Compaction, and Running Kafka

How Kafka survives broker failure (replication factor, ISR, min.insync.replicas, unclean election), retention vs compaction, sizing partitions, schemas, and operating Kafka with Strimzi or MSK.

Advanced 60 min 3 lab steps 3 failure drills

Start here

The mental model

Every partition is copied to several brokers (the REPLICATION FACTOR, usually 3). One copy is the leader; the followers keep up by fetching from it. The followers that are fully caught up form the IN-SYNC REPLICA set (ISR). A write with acks=all is only confirmed once every ISR member has it, so if the leader dies, any ISR member can take over with nothing lost.

The safety catch is min.insync.replicas: 'refuse writes unless at least this many copies are in sync'. With RF=3 and min ISR=2, you can lose one broker and keep writing safely; lose two and Kafka stops accepting acks=all writes rather than pretend they're safe.

Go deeper

How it works inside

01ISR, high watermark, and leader election

A follower drops out of the ISR if it hasn't caught up within replica.lag.time.max.ms (30 s). Consumers only see records up to the HIGH WATERMARK, the offset replicated to all ISR members, so they never read data that could disappear in a failover.

If the leader dies, the controller elects a new leader from the ISR. If NO ISR member is alive, there are two bad choices: wait (the partition is unavailable) or allow UNCLEAN leader election, promoting an out-of-sync replica and losing the records it missed. unclean.leader.election.enable=false (the default) chooses availability loss over data loss, which is right for business data.

ISR, high watermark, and leader electiondiagram
Rendering diagram…

02Retention vs compaction

DELETE retention (default) removes whole segments older than retention.ms or beyond retention.bytes per partition. Good for event streams: 'keep 7 days of orders'.

COMPACTION (cleanup.policy=compact) keeps at least the LATEST record for every key and removes older ones in the background, forever. A compacted topic is a changelog/table: 'the current price of every product', 'the latest profile of every user'. Deleting a key needs a TOMBSTONE (a record with that key and a null value). Kafka's own __consumer_offsets is compacted. The two policies can be combined (compact,delete).

03Sizing partitions and brokers

Partitions = max(target throughput ÷ per-partition producer throughput, target throughput ÷ per-consumer throughput, the consumer parallelism you'll need), with headroom. You can add partitions later but that remaps keys (breaking per-key order during the change), and you can't reduce them. Too many partitions per broker means slower failover and more open files and memory; with KRaft, clusters handle far more than ZooKeeper-era limits, but thousands per broker still need care.

Spread replicas across availability zones with RACK AWARENESS (broker.rack) so losing a zone never loses all copies of a partition, and consumers can fetch from the closest replica (client.rack) to cut cross-AZ data transfer costs, which on AWS can exceed the cost of the brokers themselves.

04Schemas, Connect, and the ecosystem

Producers and consumers evolve independently, so the record format is a contract. SCHEMA REGISTRY (Confluent, Apicurio, AWS Glue) stores Avro/Protobuf/JSON schemas and enforces COMPATIBILITY rules (e.g. BACKWARD: new consumers can read old data, so only add optional fields). KAFKA CONNECT moves data in and out without custom code: Debezium streams database changes (CDC) from Postgres's WAL into Kafka, and sink connectors write to S3, OpenSearch, or warehouses. Stream processors (Kafka Streams, Flink) join, aggregate, and transform streams.

05Operating Kafka

MSK (and MSK Serverless) or Confluent Cloud remove broker operations. On Kubernetes, STRIMZI is the standard operator: Kafka, KafkaTopic, and KafkaUser CRDs, so topics and ACLs live in Git (GitOps course). The metrics that matter: under-replicated partitions (should be 0), under-min-ISR partitions (0), offline partitions (0), active controller count (exactly 1), request latency p99, disk usage per broker, consumer lag per group, and network throughput. Rolling restarts go one broker at a time, waiting for under-replicated partitions to return to 0 in between.

Do it

Hands-on lab

  1. 1

    A three-broker cluster with Strimzi

    On a kind cluster, install the Strimzi operator, then declare the cluster. Node pools define broker and controller roles.

    kafka-cluster.yamlwhole fileyaml
    apiVersion: kafka.strimzi.io/v1beta2
    kind: KafkaNodePool
    metadata:
      name: dual
      labels: { strimzi.io/cluster: shop }
    spec:
      replicas: 3
      roles: [controller, broker]
      storage: { type: persistent-claim, size: 5Gi, deleteClaim: false }
    ---
    apiVersion: kafka.strimzi.io/v1beta2
    kind: Kafka
    metadata:
      name: shop
    spec:
      kafka:
        listeners:
          - { name: plain, port: 9092, type: internal, tls: false }
        config:
          default.replication.factor: 3
          min.insync.replicas: 2
          unclean.leader.election.enable: false
          auto.create.topics.enable: false
      entityOperator: { topicOperator: {}, userOperator: {} }
    ---
    apiVersion: kafka.strimzi.io/v1beta2
    kind: KafkaTopic
    metadata:
      name: orders
      labels: { strimzi.io/cluster: shop }
    spec:
      partitions: 6
      replicas: 3
      config: { retention.ms: 604800000 }
    terminal
    $ kubectl create ns kafka && kubectl -n kafka apply -f https://strimzi.io/install/latest?namespace=kafka
    kubectl -n kafka apply -f kafka-cluster.yaml && kubectl -n kafka wait kafka/shop --for=condition=Ready --timeout=600s
    kubectl -n kafka exec shop-dual-0 -- bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders | head -3
    ── expected output ──
    kafka.kafka.strimzi.io/shop condition met
    Topic: orders PartitionCount: 6 ReplicationFactor: 3 Configs: min.insync.replicas=2,retention.ms=604800000
    Topic: orders Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
    Topic: orders Partition: 1 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
  2. 2

    Kill a broker, keep writing

    Delete one broker pod while producing. Leadership moves, the ISR shrinks to two, and acks=all writes keep succeeding. When the pod returns it catches up and rejoins the ISR.

    terminal
    $ kubectl -n kafka delete pod shop-dual-1
    kubectl -n kafka exec shop-dual-0 -- bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders --under-replicated-partitions
    ── expected output ──
    Topic: orders Partition: 1 Leader: 2 Replicas: 1,2,0 Isr: 2,0
    Topic: orders Partition: 4 Leader: 0 Replicas: 1,0,2 Isr: 0,2
  3. 3

    A compacted topic as a table

    Product prices keyed by SKU. After compaction runs, only the last price per SKU remains. Low segment settings make compaction happen quickly in the lab.

    terminal
    $ kubectl -n kafka exec -i shop-dual-0 -- bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic prices \
    --config cleanup.policy=compact --config segment.ms=10000 --config min.cleanable.dirty.ratio=0.01
    printf 'sku-1:499\nsku-2:120\nsku-1:449\nsku-1:399\n' | kubectl -n kafka exec -i shop-dual-0 -- bin/kafka-console-producer.sh \
    --bootstrap-server localhost:9092 --topic prices --property parse.key=true --property key.separator=:
    # ~1 minute later, after a new segment rolls:
    kubectl -n kafka exec shop-dual-0 -- bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic prices --from-beginning --property print.key=true --timeout-ms 5000
    ── expected output ──
    sku-2 120
    sku-1 399
    Processed a total of 2 messages

Operate it

Knobs that matter

SettingDefaultWhat it doesWhen to change it
default.replication.factor1Copies of each partition for new topics.3 in production, with brokers in 3 AZs.
min.insync.replicas1Minimum in-sync copies for acks=all writes.2 with RF=3: tolerate one broker loss without risking data.
unclean.leader.election.enablefalseAllow an out-of-sync replica to become leader.Keep false for business data; true only where availability beats completeness (some metrics).
cleanup.policydeleteTime/size retention or per-key compaction.compact for changelog/state topics; compact,delete to also age out keys.
broker.rack / client.rackunsetAZ awareness for replica placement and nearest-replica fetching.Set to the AZ ID on every broker and consumer on AWS to survive zone loss and cut cross-AZ cost.
log.retention.bytes-1Per-partition size cap.Set as a disk-full safety net alongside time retention.

3am practice

Failure drills

Each drill is a real failure mode. Read the scenario and the output, decide what's wrong, then reveal the diagnosis.

Drill #1

Producers fail with NotEnoughReplicas

Two of three brokers are unhealthy after a bad node upgrade. Checkout's event publishing starts failing.

terminal
$ kubectl logs deploy/checkout | tail -1
── what you'll see ──
org.apache.kafka.common.errors.NotEnoughReplicasException: Messages are rejected since there are fewer in-sync replicas than required.

Drill #2

Broker disk 100% full

A team switched a high-volume topic's retention to 30 days 'just in case'. A week later one broker crashes and won't start.

terminal
$ kubectl -n kafka logs shop-dual-2 | grep -m1 -i 'no space'
── what you'll see ──
ERROR Error while appending records to clicks-3 in dir /var/lib/kafka/data (org.apache.kafka.storage.internals.log.LogDirFailureChannel) java.io.IOException: No space left on device

Drill #3

Consumers break after a producer deploy

The orders team renames a field in their JSON event. Three downstream services start failing to process orders.

terminal
$ kubectl logs deploy/stock-consumer | tail -1
── what you'll see ──
KeyError: 'total_amount'

The bigger picture

Connects to

Prove it

Interview questions

01

Explain ISR and min.insync.replicas.

02

When would you use a compacted topic?

03

What Kafka metrics do you alert on?

0/4 · 0%