Everything for Kafka in one place — pick a section below. 21 reviewed items across 4 content types.
Quick Answer
__consumer_offsets is the internal topic that stores every consumer group's committed offsets, so if it becomes under-replicated the cluster's ability to durably remember 'what has already been processed' is at risk, not just one application's data. It typically becomes under-replicated after adding brokers without ever reassigning this topic's replicas, since it was created with a small replication factor by default years earlier and never revisited.
Detailed Answer
Imagine a library that keeps one master card catalog recording exactly which books every patron has already returned. If that catalog is stored on index cards in a single filing cabinet with no backup copy, losing that cabinet doesn't just affect one book — it corrupts the library's memory of every patron's return status at once. __consumer_offsets plays that exact role for a Kafka cluster: instead of tracking application data, it tracks the read progress of every consumer group across every topic in the cluster.
Kafka creates __consumer_offsets automatically the first time any consumer group commits an offset, using the broker defaults offsets.topic.replication.factor (often left at the cluster default) and 50 partitions by default. This was a deliberate design choice to make offset storage itself just another partitioned, replicated Kafka topic rather than a separate external system — reusing Kafka's own replication and compaction machinery instead of building a bespoke offset store. The tradeoff is that this topic's health is now just as important as any user topic's, but because it's created automatically and rarely appears in a team's topic inventory, it's easy to forget it needs the same replication-factor and min.insync.replicas discipline as payments-events or order-created.
Internally, __consumer_offsets is a compacted topic (Kafka periodically removes older records for the same key, keeping only the latest offset per consumer-group/topic/partition combination) rather than a normal time-retained topic. If it was created back when the cluster had only one or two brokers, its replication factor may be stuck at 1 or 2 permanently — Kafka does not automatically increase replication factor when you add brokers later. So a cluster can look perfectly healthy on every application topic while this one internal topic silently sits under-replicated, one broker failure away from data loss on every consumer group's progress.
In production, engineers explicitly include __consumer_offsets in the same under-replicated-partition monitoring and alerting as user topics, and periodically run a replica reassignment against it just like any other undersized topic. Losing this topic's data (or a partition of it) doesn't crash consumers outright — it manifests subtly as consumer groups resetting to auto.offset.reset behavior (earliest or latest) and either reprocessing huge historical backlogs or skipping straight to the newest data, which looks like a data-correctness incident rather than an infrastructure one, making it much harder to diagnose.
The gotcha: because __consumer_offsets is compacted, not time-retained, simply waiting doesn't clean up an under-replication problem the way it might for a normal topic — you must explicitly run kafka-reassign-partitions.sh against it, and because it usually has 50 partitions, a reassignment against it touches every broker in the cluster simultaneously, so teams need to throttle the reassignment bandwidth or risk saturating inter-broker network links during business hours.
Code Example
# Confirm the internal offsets topic's current replication factor and ISR health kafka-topics.sh --bootstrap-server broker1:9092 --describe --topic __consumer_offsets | head -5 # Generate a reassignment plan to bring it up to replication-factor 3 across all brokers kafka-reassign-partitions.sh --bootstrap-server broker1:9092 \ --topics-to-move-json-file offsets-topic.json \ --broker-list "1,2,3,4" --generate # Execute the reassignment with a throttle to avoid saturating broker network links kafka-reassign-partitions.sh --bootstrap-server broker1:9092 \ --reassignment-json-file offsets-reassignment.json \ --throttle 20000000 --execute # Verify the reassignment completed and ISR now matches the full replica set kafka-reassign-partitions.sh --bootstrap-server broker1:9092 \ --reassignment-json-file offsets-reassignment.json --verify
Interview Tip
A junior engineer typically treats under-replicated partitions as a uniform problem to fix wherever it appears. For a senior or architect role, the interviewer wants you to recognize that internal topics like __consumer_offsets carry cluster-wide blast radius unlike a typical application topic, and that they're commonly forgotten because they're auto-created rather than explicitly provisioned. They're listening for you to describe the specific consequence of losing offset data — consumer groups falling back to auto.offset.reset and silently reprocessing or skipping data — and for you to know that fixing replication on a compacted, 50-partition internal topic requires a throttled reassignment plan, not just a config change, because it touches every broker at once.
◈ Architecture Diagram
┌────────────────────┐
│ __consumer_offsets │ 50 partitions, compacted
└────────────────────┘
│ tracks progress for ALL groups
↓
┌────────┐ ┌────────┐ ┌────────┐
│Group A │ │Group B │ │Group Z │
└────────┘ └────────┘ └────────┘
RF=1 on this topic -> single broker loss
-> ALL groups reset via auto.offset.reset💬 Comments
Quick Answer
Start by checking whether the consumer is even subscribed to the partitions the messages landed on, since a mismatched partition assignment or a paused consumer looks identical to 'missing messages' from the application's point of view. Then check retention (the messages may have already expired), and finally check whether the consumer's offset was manually reset or auto-reset past the messages in question.
Detailed Answer
This is like a courier confirming a package was delivered to the right building, while the recipient insists it never arrived — and the real answer turns out to be that it was delivered to the mailroom of the wrong floor, or it was delivered on time but the recipient's mail slot had already been cleaned out before they checked it. The delivery succeeded; the visibility into where it landed is what's broken.
In Kafka, a producer receiving an ack only confirms the message reached the partition leader (and, depending on acks configuration, was replicated to the ISR) — it says nothing about whether any particular consumer has read it yet. 'Missing' from a consumer's perspective can mean the message was never routed to a partition that consumer is responsible for, was already deleted by retention before the consumer got to it, or the consumer's offset was moved past it by a rebalance, a manual seek, or an auto.offset.reset event. Kafka was designed to decouple producer acknowledgment completely from consumer progress — intentionally, since coupling them would mean a slow or offline consumer could block producers, which defeats the purpose of a buffered log.
The diagnostic sequence starts by confirming the message actually exists on the topic at all: use kafka-console-consumer with --from-beginning against the specific partition (derivable by hashing the key the same way the producer does) to prove existence independent of any application code. Next, check the consumer group's current assignment and offsets with kafka-consumer-groups.sh --describe — if the consumer group was never assigned the partition holding the message (common right after a rebalance mid-incident), that fully explains the gap. Then check the topic's retention.ms and whether enough time has passed for the message to have been deleted, especially on topics with aggressive retention tuned for a different use case than what's currently consuming it.
In production, the most common real cause is an auto.offset.reset misconfiguration: if a consumer group's committed offset becomes invalid (for example, the offset was itself deleted due to offsets.retention.minutes expiring for an idle consumer group that hadn't committed in a long time), the consumer falls back to either 'earliest' or 'latest' depending on config, and 'latest' silently skips every message produced while the consumer group was inactive — with zero errors logged anywhere, because from Kafka's perspective this is completely valid, expected behavior, not a fault.
The gotcha that catches teams every time: offsets.retention.minutes (default 7 days in older versions, tied to broker uptime in newer ones) can expire a consumer group's committed offset even while the actual message data is still well within the topic's own retention window. So a consumer group that goes idle for a week-long holiday can come back, find its offsets gone, silently reset to 'latest,' and skip an entire week of data that was never deleted — the messages were never missing, the group's memory of where it was just vanished first.
Code Example
# Step 1: confirm the message exists on the topic independent of the consumer kafka-console-consumer.sh --bootstrap-server broker1:9092 \ --topic checkout-events --partition 4 --offset 128900 --max-messages 1 # Step 2: check whether the consumer group is even assigned that partition kafka-consumer-groups.sh --bootstrap-server broker1:9092 \ --describe --group checkout-worker-group # Step 3: check the topic's retention window against the message timestamp kafka-configs.sh --bootstrap-server broker1:9092 \ --describe --entity-type topics --entity-name checkout-events # Step 4: check whether the consumer group's committed offsets have expired kafka-consumer-groups.sh --bootstrap-server broker1:9092 \ --describe --group checkout-worker-group --verbose
Interview Tip
A junior engineer typically jumps straight to blaming the consumer application's code for dropping messages. For a senior role, the interviewer wants a systematic elimination sequence that treats producer acks, partition assignment, retention, and offset lifecycle as four independent, equally likely failure points before touching application logic. They're specifically listening for whether you know that offsets.retention.minutes can expire a consumer group's memory of its position even while the underlying data is still fully present in the topic, since this is the single most common cause of 'missing messages' that turn out to have been silently skipped rather than lost, and it requires zero errors or alerts to happen.
◈ Architecture Diagram
Producer ✓ acked → Partition 4 (data present)
│
↓
Consumer group offset expired
(offsets.retention.minutes)
│
↓
auto.offset.reset=latest
│
↓
Consumer resumes AFTER the data
→ looks like data loss, isn't💬 Comments
Quick Answer
Leading indicators include under-replicated partition count, request queue time, and ISR shrink/expand rate, because they reveal stress before clients notice anything. Lagging indicators like consumer lag spikes and producer timeout errors confirm client-visible impact has already begun, so a good alerting setup pages on the leading signals first and treats the lagging ones as escalation confirmation, not the initial trigger.
Detailed Answer
A building's fire alarm system has smoke detectors and sprinklers. Smoke detectors trigger on the earliest sign of trouble, before flames are even visible, giving people time to act. Sprinklers only activate once there's already real heat — by which point damage has started. Good Kafka monitoring needs both kinds of signal, but a mature operations team designs paging around the smoke detectors, not the sprinklers, because by the time the sprinkler-equivalent metric fires, users are already affected.
Kafka exposes broker-level JMX metrics that fall cleanly into these two categories. Under-replicated partition count and ISR shrink rate are 'smoke detector' metrics — they indicate replication is falling behind or brokers are struggling to keep up, well before any client-visible error occurs, because clients using acks=1 or even acks=all against a still-adequate ISR won't see failures yet. Request queue time and request handler idle ratio reveal broker-side saturation (the broker's thread pools are backed up) before latency percentiles blow out. By contrast, consumer lag spikes, elevated producer request timeouts, and NotEnoughReplicasException counts are 'sprinkler' metrics — real, but by definition something has already gone wrong for a client by the time these fire.
Internally, these metrics exist because Kafka brokers expose extensive JMX (Java Management Extensions, the standard way JVM applications publish internal metrics) counters for network request handling, log flush latency, replication fetcher lag, and controller state. A Prometheus JMX exporter sidecar scrapes these and feeds Grafana dashboards and Alertmanager rules. The design intent is that operators build layered alerting: cause-level alerts (under-replicated partitions, request queue time, network processor idle percentage below a threshold) feed into runbooks for platform engineers, while symptom-level alerts (elevated consumer lag, client error rates) are what pages the on-call for the affected application team, since that's the audience who can actually act on client-visible impact.
In production, the request handler idle ratio metric is one of the most predictive and least understood: it measures the percentage of time broker request-handling threads are idle, and a sustained drop below roughly 20-30% strongly predicts request queue buildup and latency spikes minutes before they show up in client-facing p99 latency. Teams that only alert on consumer lag or client error rate consistently get paged 5-15 minutes later than teams also watching broker-internal saturation metrics, and that gap is often the difference between a quiet mitigation and a customer-visible incident.
The non-obvious gotcha: under-replicated partition count can transiently spike to a nonzero value during completely normal operations — a rolling restart for a patch deploy, for instance — and a naive alert on 'any under-replicated partitions > 0' will page constantly during routine maintenance windows, training the on-call to ignore it. The fix experienced teams use is alerting on under-replicated partitions sustained for longer than the expected restart window (for example, more than 5 minutes outside a known maintenance window), which preserves the metric's predictive value without the noise.
Code Example
# Scrape broker JMX metrics via the Prometheus JMX exporter (running as a Java agent) # kafka-server-start.sh with: -javaagent:/opt/jmx_exporter/jmx_prometheus_javaagent.jar=7071:/opt/jmx_exporter/kafka-broker.yml # Query request handler idle ratio (leading indicator of saturation) in PromQL # kafka_server_kafkarequesthandlerpool_requesthandleravgidlepercent # Query under-replicated partitions, alerting only if sustained > 5m outside maintenance # kafka_server_replicamanager_underreplicatedpartitions > 0 (for: 5m) # Confirm current ISR health directly from the CLI as ground truth during an incident kafka-topics.sh --bootstrap-server broker1:9092 --describe --under-replicated-partitions
Interview Tip
A junior engineer typically lists consumer lag and error rate as 'the metrics that matter' for Kafka. For a senior or architect role, the interviewer wants you to separate leading indicators of broker-side saturation (request handler idle ratio, under-replicated partitions, ISR churn) from lagging indicators that confirm client impact has already started (consumer lag, timeouts). They're testing whether you've actually operated a cluster through an incident and learned that alerting only on lagging metrics means you're always reacting after users are affected, and whether you know how to tune sustained-duration thresholds to avoid paging on harmless transients like rolling restarts.
◈ Architecture Diagram
Leading (smoke detector) Lagging (sprinkler)
┌───────────────────┐ ┌───────────────────┐
│Request handler │ │Consumer lag spike │
│idle % dropping │──────▶│Producer timeouts │
│ISR churn rising │ │NotEnoughReplicas │
└───────────────────┘ └───────────────────┘
page platform team page app on-call💬 Comments
Quick Answer
Topic config automation must diff desired-state config against live broker state before applying anything, since some changes (like lowering partition count) are impossible and others (like changing a partitioning-relevant setting) have irreversible side effects on consumers. The automation itself must be idempotent — safe to re-run — and must gate destructive or irreversible operations like partition increases behind an explicit dry-run and approval step rather than applying them automatically on every pipeline run.
Detailed Answer
Think of topic configuration automation like a building permit process for renovating a house that's currently occupied. Some changes are reversible and low-risk — repainting a room (adjusting a retention value) can be done and undone freely. Others are irreversible the moment you do them — knocking down a load-bearing wall (increasing partition count, which reshuffles which key maps to which partition forever) can't be undone by just reversing the automation, because the house is still occupied while you do it and residents (consumers) get disrupted mid-change.
Kafka's AdminClient API and CLI tools (kafka-configs.sh, kafka-topics.sh --alter) expose topic configuration as declarative key-value pairs, which makes it tempting to manage them the same way as any other infrastructure-as-code resource — diff desired state against live state, apply the delta. This works cleanly for reversible settings like retention.ms, cleanup.policy, or min.insync.replicas. It does not work safely for partition count, because Kafka only supports increasing partitions, never decreasing them, and increasing partitions changes the hash-to-partition mapping for every existing key going forward, silently breaking any consumer logic that assumed a key always lands on the same partition it always has.
A safe automation pipeline therefore treats topic config changes as two distinct categories internally: mutable-safe settings get a standard plan-diff-apply flow, similar to Terraform, run automatically in CI on every merge. Partition count changes (and any other irreversible operation) get routed through a separate path that requires a human-reviewed dry-run showing exactly which topics and consumer groups are affected, and typically a scheduled maintenance window, because the blast radius touches every consumer of that topic simultaneously, not just the automation's direct target.
In production, idempotency for this kind of automation means the pipeline can be re-run safely after a partial failure without making things worse — for example, if a run to bump retention.ms on 40 topics fails after updating 22 of them, re-running it should update only the remaining 18, not error out or reapply to the already-correct ones. This requires the automation to always re-fetch live config immediately before diffing, never trust a cached 'last known state,' since another process or engineer could have changed the topic out of band between runs.
The gotcha experienced platform teams learn the hard way: kafka-configs.sh --alter with --add-config fully replaces the config map for the keys specified but silently leaves unspecified keys untouched — which sounds safe, but means an automation script that generates its config payload from a slightly stale template can silently fail to remove a deprecated setting that a previous manual change added, because the automation only ever adds and never explicitly reconciles the full config against a source of truth, leaving configuration drift that only shows up during the next incident when someone assumes a setting isn't there anymore.
Code Example
# Dry-run: diff desired config against live state before applying (safe, reversible settings) kafka-configs.sh --bootstrap-server broker1:9092 \ --describe --entity-type topics --entity-name order-events # Apply a reversible config change (retention increase) via automation kafka-configs.sh --bootstrap-server broker1:9092 \ --alter --entity-type topics --entity-name order-events \ --add-config retention.ms=1209600000 # Irreversible change (partition increase) — requires explicit approval gate, run manually kafka-topics.sh --bootstrap-server broker1:9092 \ --alter --topic order-events --partitions 48 # Verify no config drift remains after automation run kafka-configs.sh --bootstrap-server broker1:9092 \ --describe --entity-type topics --entity-name order-events
Interview Tip
A junior engineer typically treats all Kafka topic configuration as equally safe to automate with a standard apply-on-merge pipeline. For a senior or architect role, the interviewer wants you to distinguish reversible settings (retention, cleanup policy) from irreversible ones (partition count increases, which permanently reshuffle key-to-partition mapping) and to gate the irreversible category behind human review and a documented consumer-impact assessment. They're also listening for whether you understand that --add-config in Kafka's CLI only adds or overwrites specified keys rather than reconciling the full config, which is a common source of silent configuration drift in automated pipelines that assume a full-replace semantics that doesn't actually exist.
◈ Architecture Diagram
Desired config ──▶ diff ──▶ live broker config
│
↓
reversible settings irreversible (partition count)
(retention, min.isr) │
│ ↓
↓ dry-run -> human approval
auto-apply in CI ✓ -> scheduled window💬 Comments
Quick Answer
Kafka only guarantees ordering within a single partition, never across an entire topic, because strict ordering requires one writer and one reader lineage. When a consumer in a group fails mid-partition, the group coordinator triggers a rebalance, reassigns that partition to a surviving consumer, and processing resumes from the last committed offset — which can mean a handful of already-processed messages get reprocessed.
Detailed Answer
Think of a partition like a single checkout lane at a grocery store. If you want strict first-come-first-served order, you can only promise it within one lane. The moment the store opens more lanes to serve customers faster, two shoppers who arrived seconds apart might get served in a different order depending on which lane moves quicker. Kafka makes the same tradeoff deliberately: it wants both ordering and throughput, so it draws the ordering guarantee at the partition boundary, not the topic boundary.
A Kafka topic is split into partitions specifically so many consumers can read parts of the workload in parallel — parallelism is the entire reason to use a distributed log instead of a single queue. Every record appended to a partition gets a monotonically increasing offset (a per-partition sequence number), and Kafka writes records to a partition strictly in producer send order. This works because exactly one broker — the partition leader — owns the write path for that partition at any moment. Kafka's designers rejected global cross-partition ordering because it would require a global lock on every write, which would destroy the horizontal scalability the whole system is built around.
Internally, when a producer sends a keyed message (for example key=customer_id), Kafka hashes the key and deterministically routes every message with that key to the same partition — this is how per-key ordering is achieved without needing a global order. On the consume side, each consumer group has a group coordinator broker tracking which consumer owns which partition. If a consumer crashes or misses heartbeats past session.timeout.ms, the coordinator marks it dead and triggers a rebalance: it revokes that consumer's partitions and hands them to a live consumer in the group. The new owner does not start from zero — it reads the last offset committed to the internal __consumer_offsets topic and resumes exactly there.
In production, the offset-commit strategy determines what actually happens during failover. If a consumer processes a batch and crashes before committing offsets, the newly assigned consumer re-reads and reprocesses those same messages — this is at-least-once delivery, Kafka's default behavior. Engineers monitor consumer lag (the gap between the latest produced offset and the last committed offset) via kafka-consumer-groups.sh or a lag exporter feeding Prometheus, because widening lag right after a rebalance usually signals the new owner is struggling to catch up due to GC pauses or a hot key. Frequent rebalances themselves are a red flag — a 'rebalance storm' — usually caused by consumers taking longer than max.poll.interval.ms to process a batch, making the coordinator think they died even though they were just slow.
A subtle trap experienced engineers hit is assuming more consumers always fixes lag. Kafka can only actively use as many consumers as there are partitions — with 12 partitions and 20 consumers, 8 sit fully idle. An even sneakier gotcha: because ordering is per-partition, repartitioning a topic to scale out breaks order guarantees for existing keys, since a key's messages may now hash to a different partition than the older messages for that same key sitting in the original partition — anything relying on strict per-key order across a repartition event will silently see out-of-order processing with no error raised.
Code Example
# Describe consumer group lag and partition ownership for checkout-worker-group kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group checkout-worker-group # Simulate a consumer crash by deleting one pod in staging kubectl delete pod checkout-worker-7f9c8-abcde -n payments # Confirm the surviving consumer picked up the orphaned partitions after rebalance kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group checkout-worker-group --members # Verify no offset gap opened up across the rebalance kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group checkout-worker-group --offsets
Interview Tip
A junior engineer typically answers 'Kafka keeps messages in order,' treating ordering as a topic-wide guarantee. For a senior or architect role, the interviewer is actually testing whether you know ordering is scoped to a single partition, and that this is a deliberate tradeoff between consistency and horizontal scalability. They want you to reason about what happens to ordering when a topic is repartitioned (existing keys can hash to a different partition, breaking order relative to older messages), how offset-commit timing interacts with consumer crashes to produce at-least-once semantics, and how you'd design around it with idempotent consumers, dedup keys, or Kafka's transactional exactly-once semantics for cases like financial ledgers. Mentioning that adding consumers beyond the partition count buys nothing is a strong signal of hands-on experience.
◈ Architecture Diagram
┌─────────┐ key hash ┌────────────┐
│Producer │────────────▶│ Partition 0│
└─────────┘ └────────────┘
│ leader
↓
┌────────────────┐
│ Consumer A ✗ │
└────────────────┘
│ rebalance
↓
┌────────────────┐
│ Consumer B ✓ │
│ resumes offset │
└────────────────┘💬 Comments
Quick Answer
Give every consuming service its own consumer group so each gets a full independent copy of the stream, size partitions to the maximum parallelism any single group will ever need, and set replication factor 3 with min.insync.replicas=2 for durability. Getting partition count wrong is expensive because Kafka does not let you safely lower it and raising it later breaks per-key ordering for existing data.
Detailed Answer
Picture a company newsletter mailed to 50 different departments. Every department needs its own full copy delivered to its own mailbox (a consumer group), rather than 50 departments fighting over shared mail slots. But within a single department that splits reading across several clerks (consumer instances in one group) to keep up with volume, you only need as many mail slots (partitions) as clerks working that day.
In Kafka, a consumer group is the unit of independent consumption: every group gets its own copy of every message in the topic, tracked by its own offsets in __consumer_offsets. That is why 50 microservices reading the same event stream should be modeled as 50 separate consumer groups, not 50 consumers crammed into one group — cramming them into one group would split the stream 50 ways instead of replicating it 50 times, meaning most services would silently see only a fraction of the events. Kafka was designed this way so that adding a new downstream consumer of an existing stream requires zero changes to the topic or to other consumers — it's an additive, decoupled architecture, which is the entire value proposition over a traditional message queue with single-consumer semantics.
Internally, partition count sets a hard ceiling on parallelism per group: a group can have at most as many active consumers as there are partitions, because each partition can only be owned by one consumer within a group at a time. For 50 independent services with wildly different processing speeds, you pick the partition count based on the neediest consumer group's expected peak parallelism — commonly 24 to 48 partitions for a mid-size event stream — and set replication-factor=3 so the topic survives a broker or AZ failure. A schema registry (a separate service that stores and versions the Avro/Protobuf message schema) sits alongside the topic so all 50 services can evolve their event contract independently without a synchronized deploy.
At scale, the operational risks multiply with consumer count: 50 consumer groups on one topic means 50 independent lag curves to monitor, and a single slow consumer group doesn't block the others but can mask a real problem because dashboards aggregate lag by topic instead of by group. Retention (how long Kafka keeps messages before deleting them) has to be sized for the slowest consumer group plus a safety margin — if any one of the 50 services falls behind by more than the retention window, it silently loses data rather than erroring loudly. Engineers watch min.insync.replicas violations, under-replicated partition counts, and per-group max lag as the top three signals.
The gotcha almost everyone misses: you can increase partition count later, but doing so reassigns the hash-to-partition mapping for every key, so all 50 consuming services that relied on same-key-same-partition ordering will see a discontinuity in ordering exactly at the moment you repartition — old messages for a key sit in the old partition, new messages for the same key land in a different one. Because of this, teams almost always over-provision partition count up front rather than trying to right-size it later, even though excess partitions cost more open file handles and replication overhead on every broker.
Code Example
# Create a topic sized for 50 independent consumer groups with durable replication kafka-topics.sh --bootstrap-server broker1:9092 --create \ --topic order-events \ --partitions 32 \ --replication-factor 3 \ --config min.insync.replicas=2 \ --config retention.ms=604800000 # Each of the 50 microservices runs its own independent consumer group, e.g. checkout-worker kafka-console-consumer.sh --bootstrap-server broker1:9092 \ --topic order-events \ --group checkout-worker-group \ --from-beginning # Confirm each group is getting the full stream independently, not splitting it kafka-consumer-groups.sh --bootstrap-server broker1:9092 --list
Interview Tip
A junior engineer typically answers this as a partition-count sizing question — 'pick enough partitions for throughput.' For an architect role, the interviewer wants to see that you model consumer groups as the true unit of isolation between services, understand that partition count is a parallelism ceiling per group rather than a global one, and can explain the irreversible cost of getting it wrong — namely that repartitioning breaks per-key ordering for every one of the 50 downstream consumers simultaneously, not just one. Strong answers also mention schema registry for independent contract evolution and retention sizing driven by the slowest consumer, since that is what actually causes silent data loss at this scale rather than a loud outage.
◈ Architecture Diagram
┌────────────┐
│order-events│ 32 partitions, RF=3
└────────────┘
│ full copy replicated to each group
↓
┌────────┐ ┌────────┐ ┌────────┐
│Group A │ │Group B │ │Group Z │
│(svc 1) │ │(svc 2) │ │(svc 50)│
└────────┘ └────────┘ └────────┘💬 Comments
Quick Answer
Consumer lag grows when consumers process messages slower than producers write them, and the cause splits into two very different categories: overall under-capacity (too few consumers or slow per-message processing) versus a hot partition (one key receiving disproportionate traffic that no amount of horizontal scaling fixes). Distinguishing them requires looking at per-partition lag, not just the group total.
Detailed Answer
Imagine a restaurant with five equally staffed serving stations, but one station happens to be assigned every table from a bus that just arrived all at once. Adding more cooks to the other four stations does nothing for the overburdened one — the problem isn't total kitchen capacity, it's an uneven assignment of work. Kafka consumer lag has the exact same two failure modes: genuinely not enough total processing power, versus one partition getting far more traffic than the others because of a skewed partitioning key.
Consumer lag is formally the difference between a partition's log-end offset (the newest message written) and the consumer group's last committed offset (the newest message fully processed). Kafka exposes this number per partition specifically so operators can distinguish 'the whole group is behind' from 'one partition is behind.' This granularity was a deliberate design choice — early distributed queue systems that only reported aggregate backlog made it nearly impossible to tell a capacity problem from a data-skew problem, and teams would blindly add workers that never helped.
To diagnose it, run kafka-consumer-groups.sh --describe and look at the LAG column per partition rather than the sum. If lag is roughly even across all partitions and rising, that's a genuine capacity or slow-processing issue — check consumer-side metrics like max.poll.records, processing time per record, and whether downstream calls (a database write, an external API) are the bottleneck. If lag is concentrated on one or two partitions while others sit near zero, that's hot-partitioning — almost always because the message key (e.g., customer_id or tenant_id) is unevenly distributed, such as one enterprise customer generating 40% of all events.
In production, teams build alerting on both max per-partition lag and lag growth rate (the derivative, not just the absolute value), because a lag of 50,000 messages means something very different for a topic doing 10 msg/sec versus 50,000 msg/sec. Fixes differ by cause: capacity problems are solved by adding consumers up to the partition count, batching more aggressively, or moving expensive downstream work to an async queue; hot-partition problems require changing the partitioning key (adding a random suffix or sub-key to spread a hot tenant's traffic across multiple partitions) since scaling consumers cannot fix an assignment imbalance.
The gotcha that catches even experienced teams: adding partitions to fix a hot key doesn't help immediately, because the existing hot key still hashes to the same single partition it always did — only new keys benefit from the larger partition space, and the skew problem persists for that specific key until you explicitly change how it's partitioned (e.g., a custom partitioner or key-salting scheme), which usually requires a coordinated producer-side code change, not just a topic config change.
Code Example
# Describe per-partition lag for the group processing shipment-events
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--describe --group shipment-worker-group
# Look specifically at whether lag is concentrated on one partition (hot key)
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--describe --group shipment-worker-group | awk '{print $3, $6}'
# If hot-keyed, salt the key at the producer to spread load across partitions
# key = original_key + "-" + (hash(event_id) % 8)
# Verify the fix by re-checking lag distribution after redeploying the producer
kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group shipment-worker-groupInterview Tip
A junior engineer typically answers 'lag means consumers are slow, so add more consumers.' For a senior role, the interviewer is testing whether you separate two distinct root causes — even capacity shortfall versus partition-key skew — because the fixes are completely different and applying the wrong one wastes an incident. They want to hear that you'd check per-partition lag before touching the group total, and that you understand adding partitions doesn't retroactively fix a hot key already assigned to an existing partition. Mentioning lag growth rate as a leading indicator, not just an absolute threshold, signals you've actually tuned alerting for a real Kafka deployment rather than reciting textbook consumer-lag theory.
◈ Architecture Diagram
Partition lag comparison ┌───┐ ┌───┐ ┌───┐ ┌───┐ │P0 │ │P1 │ │P2 │ │P3 │ │low│ │low│ │low│ │HIGH│ ←── hot key └───┘ └───┘ └───┘ └───┘ Even lag → capacity issue One spike → hot partition
💬 Comments
Quick Answer
The ISR is the set of replicas that have fully caught up with the partition leader; with acks=all, a write is only acknowledged once every replica in the ISR has it, and min.insync.replicas sets the minimum ISR size Kafka will accept writes against. When the ISR shrinks below that minimum — for example after a broker crash — producers using acks=all immediately start receiving NotEnoughReplicasException and writes are rejected rather than silently under-protected.
Detailed Answer
Think of the ISR like a bank requiring at least two of three tellers to countersign a large withdrawal before it's finalized. If one teller goes home sick, the bank can still process withdrawals with the remaining two signatures. But if a second teller also leaves and only one remains, the bank stops processing withdrawals entirely rather than accepting a single signature on a transaction that's supposed to require two — because a single point of failure at that moment could lose the record of the withdrawal entirely.
In Kafka, every partition has one leader broker and some number of follower replicas. The ISR is the subset of followers (plus the leader) that are fully caught up — not lagging behind by more than replica.lag.time.max.ms. This exists because followers that fall behind (due to slow disks, network issues, or a restart) shouldn't be trusted to safely take over as leader without losing data, so Kafka tracks in real time which replicas are actually safe fallback candidates. min.insync.replicas is the operator's way of saying 'do not accept a write unless at least this many replicas, including the leader, have it,' which is what actually prevents data loss — acks=all by itself only means 'wait for the ISR,' and if the ISR has shrunk to one broker, acks=all offers no real protection unless min.insync.replicas blocks the write outright.
Internally, followers continuously fetch from the leader, and the leader tracks each follower's fetch progress. A follower is removed from the ISR the moment it falls too far behind, and re-added once it catches back up. With a topic set to replication-factor=3 and min.insync.replicas=2, the system tolerates exactly one broker failure: if one of the three replicas goes down, the ISR drops to two, which still satisfies min.insync.replicas=2, so writes continue. If a second broker fails simultaneously, ISR drops to one, falls below the minimum, and every producer using acks=all starts getting NotEnoughReplicasException on every write attempt until a replica rejoins the ISR.
In production, engineers monitor under-replicated partition count (a partition whose ISR is smaller than its replication factor) as a leading indicator of exactly this scenario, because a partition can run for a long time with a shrunk ISR without producers noticing if they're using acks=1 or acks=0. Dashboards should alert the moment ISR size drops, not just when writes start failing, because by the time NotEnoughReplicasException fires you're already one more broker failure away from an availability outage. Broker restarts during rolling upgrades are the most common trigger — restarting brokers too quickly, one right after another, can shrink the ISR transiently on many partitions at once if the previous broker hasn't fully rejoined and caught up.
The gotcha many engineers miss: acks=all does not mean 'wait for all replicas' — it means 'wait for the ISR,' which can legitimately be smaller than the full replica set. A team that assumes acks=all always means full multi-broker durability can be surprised to learn that if the ISR has silently shrunk to just the leader (which happens if min.insync.replicas isn't set and only relies on default configuration), acks=all writes succeed even though only one broker actually has the data — the safety net only exists because min.insync.replicas was configured, not because of acks=all alone.
Code Example
# Inspect the current ISR vs full replica set for the payments-ledger topic kafka-topics.sh --bootstrap-server broker1:9092 --describe --topic payments-ledger # Compare the Isr column against the Replicas column — they should match in a healthy cluster # Confirm the durability floor is actually enforced on this topic kafka-configs.sh --bootstrap-server broker1:9092 --describe --entity-type topics --entity-name payments-ledger # Simulate a broker failure and watch ISR shrink in real time kafka-topics.sh --bootstrap-server broker1:9092 --describe --topic payments-ledger --under-replicated-partitions # Producer config that actually gets protected by min.insync.replicas # acks=all # enable.idempotence=true
Interview Tip
A junior engineer typically says 'acks=all means the write is durable.' For an advanced or architect role, the interviewer wants you to explain that durability actually comes from the combination of acks=all and min.insync.replicas — acks=all alone only waits for whatever the current ISR happens to be, which can shrink to a single broker under failure. They're listening for you to connect replication-factor, min.insync.replicas, and ISR size into a coherent failure-tolerance model (RF=3, min.insync.replicas=2 tolerates exactly one broker down), and to describe the operational signal — under-replicated partition count — that should page someone before producers start seeing NotEnoughReplicasException, not after.
◈ Architecture Diagram
┌────────┐ RF=3, min.isr=2
│Leader L│ ISR={L,F1,F2} -> ack after 2 of 3
└────────┘
┌────────┐
│Foll. F1│ ✓ in ISR, caught up
└────────┘
┌────────┐
│Foll. F2│ ✗ fell behind -> dropped from ISR
└────────┘
ISR shrinks to {L,F1} -> still >=2 -> writes OK
ISR shrinks to {L} -> <2 -> NotEnoughReplicas💬 Comments
Quick Answer
A topic is a named log split into partitions; each message in a partition has a monotonically increasing offset.
Detailed Answer
Producers append to partitions; ordering is guaranteed only within a partition. Partitions enable parallelism and are distributed across brokers. Consumers track their position by committing offsets, so they can resume where they left off. Partition count sets the max consumer parallelism per group.
Interview Tip
Ordering is per-partition, not per-topic — a key point.
💬 Comments
Quick Answer
Partitions are distributed among consumers in a group so each partition is consumed by one member; adding consumers scales throughput up to the partition count.
Detailed Answer
Within a group, Kafka assigns each partition to exactly one consumer (rebalancing on membership changes), giving horizontal scaling and fault tolerance. Beyond the partition count, extra consumers sit idle. Offset commits plus idempotent producers/transactions enable effectively-once semantics.
Interview Tip
Max useful consumers = partition count.
💬 Comments
Quick Answer
Replication factor sets how many copies of each partition exist; acks controls how many replicas must confirm a write before it's acknowledged.
Detailed Answer
acks=all with min.insync.replicas>1 means a write is acknowledged only after replicating to the required in-sync replicas, surviving a broker loss. acks=1 acknowledges after the leader only (faster, can lose data on leader failure); acks=0 is fire-and-forget. Durability vs latency is the trade-off.
Interview Tip
acks=all + min.insync.replicas is the durable config.
💬 Comments
Quick Answer
Lag is the difference between the latest offset and the consumer's committed offset — how far behind processing is.
Detailed Answer
Growing lag means consumers can't keep up with producers, delaying downstream processing. Monitor it per partition/group (e.g. kafka_consumergroup_lag) and alert on sustained growth. Fixes include adding consumers (up to partition count), optimizing processing, or increasing partitions.
Interview Tip
Sustained lag growth is the alert-worthy signal.
💬 Comments
Context
Fintech processing 50K transactions/sec needs sub-second fraud detection.
Problem
Batch fraud system caught fraud 15 minutes after occurrence.
Solution
Kafka Streams app consumes transactions, enriches with user history from state store, applies rules in real-time, produces alerts. Legitimate transactions flow unblocked.
Commands
kafka-topics.sh --create --topic transactions --partitions 48 --replication-factor 3
Outcome
Detection latency dropped from 15 minutes to 200ms. Caught 94% of fraud before transfer.
Lessons Learned
Partition by customer_id for stateful processing without distributed lookups.
💬 Comments
Context
A payments-adjacent event platform on Kafka 3.x still running ZooKeeper mode: a 5-node ZK ensemble as an extra failure domain, controller failovers taking tens of seconds at their broker count, and the deprecation clock ticking toward Kafka 4.x (which removes ZK mode entirely).
Problem
ZooKeeper was the platform's least-understood component (two of its last three incidents were ZK session storms), metadata operations (partition creation, reassignment) slowed as the cluster grew, and staying on ZK mode meant being stranded on 3.x while support and CVE fixes moved on.
Solution
Executed the documented KRaft migration path on the staging fleet first, twice, with rollback rehearsed at each phase: provisioned a 3-node KRaft controller quorum, enabled migration mode (brokers dual-writing metadata to ZK and the quorum), watched the migration state machine complete metadata sync, moved brokers to KRaft mode rolling-restart-by-rack, then finalized (ZK writes cease — the commit point) and decommissioned the ensemble after a soak week. Every phase gated on health checks: URP=0, controller failover drill, metadata op latency benchmarks.
Commands
controller quorum: process.roles=controller, controller.quorum.voters=1@c1:9093,...
brokers: zookeeper.metadata.migration.enable=true -> watch kafka.controller:type=KafkaController,name=MigratingZkBrokerCount
verify: kafka-metadata-quorum.sh describe --status; URP==0 per phase
Outcome
Migration completed across three maintenance windows with zero consumer-visible interruption; controller failover went from ~30s (ZK session + election at their scale) to under 2s; partition-creation latency dropped 10x; and the ZK ensemble — plus its runbooks, monitoring, and 3am session-storm incidents — was retired.
Lessons Learned
Rehearsing rollback at each phase mattered psychologically as much as technically — teams commit to migrations they can reverse. The finalization step is the true point of no return; treat everything before it as reversible practice and everything after as a new platform.
💬 Comments
Context
An analytics pipeline whose Kafka retention was capped at 3 days purely by broker disk economics — while data-science teams kept asking for 30-day replay (backfills, model retraining) and got told to build a parallel S3 archive with its own consumers instead.
Problem
Broker-local retention couples compute and storage scaling: more history means more disks on every broker and slower rebalances (more data to move per reassignment); the parallel-archive workaround duplicated pipeline logic and drifted from the topics it mirrored.
Solution
Enabled Kafka tiered storage (remote log storage on S3): hot segments stay on broker disk (48h local retention), older segments transparently offload to object storage with topic-level total retention set to 30 days — consumers read historical offsets through the same protocol, brokers fetching remote segments on demand. Rollout per topic class: analytics topics first (latency-tolerant), then broader event topics; the parallel S3 archive was retired once replay tests confirmed byte-identical reads. Rebalance impact shrank alongside local data volume.
Commands
broker: remote.log.storage.system.enable=true; remote.log.storage.manager.class... (S3 plugin)
topic: kafka-configs --alter --topic events --add-config remote.storage.enable=true,local.retention.ms=172800000,retention.ms=2592000000
verify: consume from 25-day-old offset; compare checksums vs old archive
Outcome
Broker disk footprint dropped ~85% (48h local vs 3-day-everything before); 30-day replay became a consumer-group offset reset instead of a bespoke S3 pipeline; partition reassignments run 6x faster with less local data; and storage cost per retained TB fell to object-storage pricing. The duplicate archive pipeline was deleted.
Lessons Learned
Remote-fetch latency is real for cold reads — batch/replay consumers are fine, but latency-sensitive consumers should never read tiered offsets; topic classification up front prevented surprises. Monitor remote fetch error rates as a new failure domain: S3 issues now manifest as consumer lag on historical reads.
💬 Comments
Symptom
After deploying a new consumer version, the group enters continuous rebalancing with no messages processed.
Error Message
Member consumer-1 failed to heartbeat within session.timeout.ms
Root Cause
New consumer had longer initialization, exceeding session.timeout.ms. Each consumer starts, fails heartbeat, gets kicked, rejoins, triggering another rebalance.
Diagnosis Steps
Solution
Increased session.timeout.ms to 30s and max.poll.interval.ms to 600s. Switched to cooperative sticky rebalancing.
Commands
kafka-consumer-groups.sh --bootstrap-server broker:9092 --describe --group payments-consumer
Prevention
Set timeout values with headroom for startup. Use cooperative sticky assignor.
💬 Comments
Symptom
A rack loss takes down two of three replicas for a set of partitions; when power returns, some partitions elect the surviving-but-lagging replica as leader. Producers and consumers resume normally — but reconciliation jobs later find a gap: several thousand events acknowledged before the incident never arrive downstream.
Error Message
Broker logs at election time: 'Unclean leader election enabled for partition events-17: electing out-of-ISR replica 3 as leader; potential data loss'. Nobody was watching for that log line; the cluster-level unclean.leader.election.enable had been flipped to true during a past availability incident and never reverted.
Root Cause
During an old outage, someone enabled unclean leader election cluster-wide to restore availability ('temporarily'), converting a consistency guarantee into an availability preference — permanently, because the revert never happened. This incident's rack failure hit the exact scenario the setting governs: the only surviving replica for some partitions was out of ISR (lagging), and electing it as leader truncated the committed-but-unreplicated tail. acks=all had protected the write path, but min.insync.replicas=1 on the affected topics meant 'all' was one broker during the degraded window.
Diagnosis Steps
Solution
Quantified the loss from producer-side records (the upstream service's outbox retained the events — replayed into the topics), reverted unclean.leader.election.enable=false cluster-wide, raised min.insync.replicas=2 on all durability-tier topics (with topology-aware placement so a rack loss leaves an ISR member), and added alerting on both the config value itself (drift detection) and the unclean-election log/metric so any future occurrence is a page, not an archaeology finding.
Commands
kafka-configs --describe --entity-type brokers --all | grep unclean
JMX: kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec
kafka-configs --alter --entity-type topics --entity-name events --add-config min.insync.replicas=2
Prevention
Availability-vs-durability settings changed during incidents must carry expiry tickets — 'temporary' cluster configs are permanent without process. Alert on the settings, not just the symptoms: unclean.leader.election, min.insync.replicas, and the UncleanLeaderElectionsPerSec metric are all watchable. Durability claims need end-to-end reconciliation checks that would catch silent gaps within hours, not quarters.
💬 Comments
Symptom
A consumer group's lag begins climbing linearly on one partition while others drain normally; consumer pods crash-loop with deserialization exceptions, rebalance on each crash, and the same pod class dies again seconds later. The pipeline is effectively down for that partition's keyspace — and rebalance churn degrades the healthy partitions too.
Error Message
org.apache.kafka.common.errors.SerializationException: Error deserializing Avro message for id 4211 at offset 8837120 in events-9 — followed by container exit, restart, identical crash at the identical offset.
Root Cause
A producer deployed with a manually-registered schema that bypassed compatibility checks (a hotfix pushed with auto-registration against a misconfigured subject), writing records the consumers' expected schema couldn't decode. The consumer framework had no poison-pill handling: deserialization threw before any error handler saw the record, the offset never advanced, and at-least-once semantics faithfully re-delivered the same fatal message forever. One bad record halted the partition; crash-induced rebalances taxed the whole group.
Diagnosis Steps
Solution
Immediate: a one-off tool consumed the specific offset with a bypass deserializer, archived the raw bytes, and advanced the group's committed offset past the poison record — pipeline resumed in minutes. Durable: error-tolerant deserialization in the consumer framework (failed records route to a dead-letter topic with headers preserving origin partition/offset/error), DLT monitoring with replay tooling, and schema-registry compatibility enforcement locked down (no auto-registration from producers in prod; CI validates schema evolution against the subject's compatibility mode).
Commands
kafka-consumer-groups --describe --group pipeline # per-partition lag
kafka-console-consumer --partition 9 --offset 8837120 --max-messages 1 --value-deserializer ByteArrayDeserializer
framework: ErrorHandlingDeserializer -> DLT 'events.dlt' with origin headers
Prevention
Every consumer needs a poison-pill strategy before it needs one: deserialization failures must be catchable and routable (DLT), never process-fatal. Schema governance belongs in CI, not in producer runtime flags. Alert on single-partition lag divergence — it's the poison-pill signature and catches this class within minutes.
💬 Comments