Kafka, simulated
An animated, step-by-step Kafka cluster in your browser: partitions as replicated logs, the ISR and high watermark, acks and min.insync.replicas, broker failures and KRaft leader elections (clean and unclean), idempotence and transactions, consumer-group rebalances, compaction and retention — with guided scenarios.
Added
Works with
- System design interviews Interview prepwhere a log like Kafka sits in feed, metrics and payment designs, and a Kafka-like queue designed as an interview question
How to use it
A Kafka cluster in miniature that runs entirely in this page: three KRaft controllers, a few brokers, one topic whose partitions are replicated logs, producers and a consumer group. Every message, fetch, acknowledgement and metadata update is a dot moving between them, and every rule — who may be leader, when a record counts as committed, what a consumer may read, what is lost when — follows Apache Kafka’s (KRaft-era, 3.x and 4.x). Nothing here talks to a real cluster; to work with one, use the Kafka client.
How to use it
Pick a scenario and press Start. Each step sets something up — sends records, kills a broker, adds a consumer — then plays for a few simulated seconds and pauses so you can read what happened and why. Next step moves on, Back rewinds (the simulator is deterministic, so it replays the earlier steps), ⏭ jumps to the end of the step and Step ⇥ advances to the next single thing that happens. Space plays and pauses; → is the next step. The Playground scenario has no script.
Speed and Hop time set the pace. Speed scales everything. Hop time is how long a message takes to cross a wire at 1× (half that at 2×): while a drawn message is on its way the simulated clock slows, so the log cell fills, the high watermark moves and the ack starts back only when it lands, and quiet stretches with nothing on the wire still pass at full speed. The default is ½ s. Sim clock turns the slowing off (a hop is then the network latency — 40 simulated ms, about 0.16 s at 1×). Hop time changes only how the run is shown, never what happens: the same events happen at the same simulated times. With reduced motion set in your system, messages jump instead of travelling and there is no slowing. Step ⇥ and ⏭ always jump.
⛶ Full screen (or F) gives the simulator the whole screen — the browser’s full screen where it
is allowed, otherwise a full-window layer (iPhone). The diagram fills the space and is redrawn as
the window changes; on a wide screen the controls and panels sit in a column beside it, and
◫ Panels hides them so the diagram goes edge to edge. Escape leaves.
-
The diagram. An architecture diagram of the whole system, drawn from the simulator’s state: producers on the left, the brokers in the middle as tall cards, the consumer group on the right, and the KRaft controller quorum above (★ marks the active controller). Inside each broker every partition replica is drawn as its log — one cell per offset, coloured by key, with offsets underneath; faded cells are above the high watermark (written but not yet committed), the black bar is the high watermark and the dashed slot the log-end offset. The leader’s row is outlined, an in-sync follower’s plain and a replica out of the ISR dashed red. The same partition sits on the same lane in every broker, so replication runs straight across. Wires show who talks to whom: produce requests from each producer to the partition leaders (orange), followers fetching from their leader (blue), leaders feeding the consumer that owns each partition (green), brokers and controllers exchanging metadata (violet), consumers and the group coordinator (dashed yellow, with
__consumer_offsetsshown in its card) and transactional producers and the transaction coordinator (dashed pink). Messages travel the wires as labelled packets — a record’s key, the offsets a fetch carries,ack @offset, a commit — and a cell lights up as it is appended. A dead broker is crossed out and its wires go grey; a network partition cuts wires (✕); a new leader is announced by a badge travelling from the old leader’s row; records truncated away flash red; a rebalance moves the consumer wires and shows revoked partitions struck through. Consumer cards show each assigned partition’s position and lag. With reduced motion set in your system, messages jump to where they are going instead of travelling. Tap a broker, controller, producer or consumer to inspect it and act on it. On a phone the diagram stacks top to bottom. -
Delivery counters. Sent, acknowledged, in flight, failed, acked but lost (acknowledged to the producer but no longer in the current leader’s log), duplicates written by retries, processed, processed twice and skipped.
-
Partition logs. Every replica of every partition, one cell per offset, coloured by key. Cells above the high watermark are faded (written but not committed); the bar is the HW and the number on the right the log-end offset. Segment boundaries, transaction markers (C/A), aborted transactions, tombstones (∅), compacted gaps and deleted segments are drawn too.
-
Consumer group. The protocol, generation (or group epoch), state and coordinator; each member’s partitions; and per partition the end offset, the committed offset and the lag.
-
Do something. Send records (with a key, a value, or as a tombstone), steady traffic, change acks and idempotence, lose the next request or ack on the network; kill, restart, slow the disk of, or cut off any broker; kill the active controller; add, close, crash or restart consumers; begin, commit and abort transactions.
-
Event log and internal logs: the metadata log (
cluster_metadata), committed offsets (consumer_offsets) and transaction state (__transaction_state). -
Settings rebuild the cluster: brokers, partitions, replication factor, min.insync.replicas, acks, idempotence, transactions, batching, unclean election, consumers, protocol, assignment strategy, commit timing, isolation level, retention and compaction.
The other tabs: Keys → partitions computes the Java client’s partition for any key (and librdkafka’s CRC32 one, which differs); Assignors compares range, round-robin, sticky and cooperative-sticky for any group and shows what moves when a consumer leaves; Throughput is a simple model of partitions against consumer parallelism and hot keys.
What it models
-
Topics and logs. Partitions are append-only logs of offsets, stored in segments that roll at
segment.bytes/segment.ms. Replicas are placed as Kafka’s rack-unaware assignment does (with its random start fixed at 0). -
Producers. Keys go to
toPositive(murmur2(key)) % partitions, the same murmur2 as the Java client (checked against Kafka’s own test vectors); null keys use the uniform sticky partitioner (KIP-794). Records are batched per partition bylinger.msandbatch.size, sent one batch per partition per request, retried with exponential backoff (KIP-580) untildelivery.timeout.ms. -
acks.
acks=0is never answered;acks=1is answered after the leader’s write;acks=allchecks the ISR againstmin.insync.replicasbefore writing (NOT_ENOUGH_REPLICAS), and answers once the high watermark passes the batch (NOT_ENOUGH_REPLICAS_AFTER_APPEND if the ISR shrank meanwhile) — see acks. -
Idempotence (on by default since Kafka 3.0, KIP-679): producer id, epoch and per-partition sequence numbers; the leader answers a duplicate batch with its original offset and rejects a gap with OUT_OF_ORDER_SEQUENCE_NUMBER; it remembers the last five batches per producer.
-
Transactions (KIP-98): the transaction coordinator is the leader of
__transaction_statepartitionabs(transactionalId.hashCode()) % 50; InitProducerId bumps the epoch and fences zombies; AddPartitionsToTxn, EndTxn, PrepareCommit/PrepareAbort, COMMIT/ABORT control markers, the last stable offset (LSO) and the aborted-transaction index thatread_committedconsumers use. -
Replication. Followers fetch from the leader; the leader tracks each follower’s position and "caught up" time exactly as Kafka’s
Replica.updateFetchStatedoes; the high watermark is the smallest log-end offset in the ISR. A follower not caught up withinreplica.lag.time.max.msis removed from the ISR, and added back once it reaches the HW, through AlterPartition requests to the controller (KIP-497); the controller never adds a fenced broker (KIP-841). Followers detect divergence through their last leader epoch and truncate (KIP-101, KIP-320). -
KRaft. Brokers heartbeat to the active controller; after
broker.session.timeout.msit fences a silent broker, removes it from every ISR and elects new leaders from the ISR at a new leader epoch, writing PartitionChangeRecords to the metadata log that every broker replays (KIP-500, KIP-631). If the last ISR member is lost the ISR keeps it and the partition goes offline — unlessunclean.leader.election.enablelets an out-of-sync replica lead. The controllers form a Raft quorum (KIP-595): without a majority nothing changes. A restarted broker registers again and stays fenced until it has caught up with the metadata. -
Consumer groups. The group coordinator is the leader of
__consumer_offsetspartitionabs(groupId.hashCode()) % 50. The classic protocol runs JoinGroup / SyncGroup with a client side assignor: eager (range, round-robin, sticky — every partition revoked) or cooperative (cooperative-sticky, KIP-429 — only moved partitions, then a follow-up rebalance). The next-generation consumer protocol (KIP-848, generally available in Kafka 4.0,group.protocol=consumer) computes the assignment on the coordinator (uniform or range) and lets each member reconcile through heartbeats with no group-wide barrier. Commit timing decides delivery: before processing is at-most-once, after processing and auto-commit are at-least-once, and transactional (offsets committed atomically with the results, as withsendOffsetsToTransaction, KIP-447) is exactly-once. -
Retention and compaction. Whole segments below the high watermark are deleted by
retention.ms(largest timestamp) andretention.bytes; the log start offset moves up and consumers below it reset (auto.offset.reset). Withcleanup.policy=compactthe cleaner keeps the newest record per key outside the active segment once the dirty ratio reachesmin.cleanable.dirty.ratio; tombstones survive one cleaning and go afterdelete.retention.ms; offsets never change (log compaction).
Limits
-
One topic per cluster, one consumer group, up to three producers. Internal topics (
consumer_offsets,transaction_state) are not replicated logs here: their coordinators move with broker availability and their state survives. -
Timeouts are about a tenth of Kafka’s defaults so failures play out in seconds (each setting shows Kafka’s default); latency is a fixed one-way delay, disks a fixed write time.
-
A "kill" is a process crash: the log files survive (Kafka does not fsync each write by default, so a machine crash could lose more). No rack awareness, no preferred-leader election, no tiered storage, no quotas, no compression, no follower fetching (KIP-392), and no Eligible Leader Replicas (KIP-966).
-
The sticky assignors follow Kafka’s algorithm for equal subscriptions; where Kafka iterates a hash map, members are taken in sorted order. KIP-848’s uniform assignor is approximated by the same sticky, balanced algorithm.
-
The throughput tab is a back-of-the-envelope model, not a benchmark.
Related: Kafka client (REST proxy), latency numbers, graph algorithms.
Linking to an example
Links can be written by hand. Add a tab after #: sim (the default), keys, assign or
throughput. A link’s settings replace this page’s saved settings (a button restores yours).
| Parameter | Meaning | Example |
|---|---|---|
|
A scenario (list below), with its own cluster; parameters given with it override the scenario’s |
|
|
The same, without the scenario’s own settings being written into the link |
|
|
Brokers, 1–9 |
|
|
Partitions of the topic, 1–12 |
|
|
Replication factor (≤ brokers) |
|
|
|
|
|
|
|
|
Idempotent producer ( |
|
|
Transactional producer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Consumers in the group, 0–12 |
|
|
|
|
|
|
|
|
When offsets are committed: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
One-way network latency in ms |
|
|
Random seed (the sticky partitioner’s choices) |
|
|
Playback speed: |
|
|
Hop time: how long each message takes to cross a wire at 1×, in seconds ( |
|
Scenarios:
example= |
Shows |
|---|---|
|
Your first topic: partitions, leaders and followers, offsets, the high watermark and a consumer (open) |
|
Keys and partitions: murmur2, batching with linger.ms and batch.size, the sticky partitioner (open) |
|
acks=all with min.insync.replicas=2: followers die, writes stop with NOT_ENOUGH_REPLICAS, then resume (open) |
|
Lose a broker: the KRaft controller fences it and elects new leaders from the ISR; nothing acknowledged is lost (open) |
|
Unclean election loses data: an out-of-sync replica becomes leader and acknowledged records vanish (open) |
|
A slow follower leaves the ISR after replica.lag.time.max.ms and rejoins (open) |
|
A leader cut off from the cluster keeps acknowledging acks=1 writes that are later truncated; acks=all times out instead (open) |
|
Consumer group rebalance: eager range, a joining member, a crash (try |
|
Exactly-once with transactions: coordinator, markers, read_committed and the LSO, abort, a fenced zombie (open) |
|
Compaction: the latest value per key, offsets kept, a tombstone removed after delete.retention.ms (open) |
|
Retention: segments roll and expire by time, then by size; a late consumer starts at the log start offset (open) |
|
Ordering: per key within a partition, none across partitions, and retries reordering without idempotence (open) |
|
Too many consumers: five consumers, three partitions, two idle (open) |
|
Idempotent producer: a lost ack makes a retry write a duplicate, unless the producer is idempotent (open) |
|
An empty cluster to experiment with (open) |
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.