Chris Collins

Tools · infra

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

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_offsets shown 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 by linger.ms and batch.size, sent one batch per partition per request, retried with exponential backoff (KIP-580) until delivery.timeout.ms.

  • acks. acks=0 is never answered; acks=1 is answered after the leader’s write; acks=all checks the ISR against min.insync.replicas before 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_state partition abs(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 that read_committed consumers use.

  • Replication. Followers fetch from the leader; the leader tracks each follower’s position and "caught up" time exactly as Kafka’s Replica.updateFetchState does; the high watermark is the smallest log-end offset in the ISR. A follower not caught up within replica.lag.time.max.ms is 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.ms it 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 — unless unclean.leader.election.enable lets 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_offsets partition abs(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 with sendOffsetsToTransaction, KIP-447) is exactly-once.

  • Retention and compaction. Whole segments below the high watermark are deleted by retention.ms (largest timestamp) and retention.bytes; the log start offset moves up and consumers below it reset (auto.offset.reset). With cleanup.policy=compact the cleaner keeps the newest record per key outside the active segment once the dirty ratio reaches min.cleanable.dirty.ratio; tombstones survive one cleaning and go after delete.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.

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

example

A scenario (list below), with its own cluster; parameters given with it override the scenario’s

example=unclean

scenario

The same, without the scenario’s own settings being written into the link

scenario=rebalance

brokers

Brokers, 1–9

brokers=5

partitions

Partitions of the topic, 1–12

partitions=6

rf

Replication factor (≤ brokers)

rf=3

minIsr

min.insync.replicas

minIsr=2

acks

0, 1 or all

acks=all

idem

Idempotent producer (true/false; needs acks=all)

idem=false

txn

Transactional producer

txn=true

linger

linger.ms

linger=50

batch

batch.size in bytes

batch=16384

inflight

max.in.flight.requests.per.connection

inflight=1

unclean

unclean.leader.election.enable

unclean=true

consumers

Consumers in the group, 0–12

consumers=4

strategy

range, roundrobin, sticky, cooperative-sticky (classic) or uniform, server-range (KIP-848)

strategy=cooperative-sticky

protocol

classic or consumer (KIP-848)

protocol=consumer

commit

When offsets are committed: auto, before (at-most-once), after (at-least-once), txn (exactly-once)

commit=before

isolation

read_uncommitted or read_committed

isolation=read_committed

reset

auto.offset.reset: earliest or latest

reset=latest

cleanup

cleanup.policy: delete or compact

cleanup=compact

retentionMs

retention.ms (-1 = forever)

retentionMs=5000

retentionBytes

retention.bytes per partition (-1 = no limit)

retentionBytes=2000

segmentBytes

segment.bytes

segmentBytes=400

lagMax

replica.lag.time.max.ms

lagMax=3000

latency

One-way network latency in ms

latency=80

seed

Random seed (the sticky partitioner’s choices)

seed=7

speed

Playback speed: 0.25, 0.5, 1, 2, 4, 8 or 16

speed=2

hop

Hop time: how long each message takes to cross a wire at 1×, in seconds (0.25, 0.5, 1, 2, or anything from 0.1 to 5), or clock for the simulator’s own clock. Default 0.5

hop=1, hop=clock

Scenarios:

example= Shows

first-topic

Your first topic: partitions, leaders and followers, offsets, the high watermark and a consumer (open)

keys

Keys and partitions: murmur2, batching with linger.ms and batch.size, the sticky partitioner (open)

acks-all

acks=all with min.insync.replicas=2: followers die, writes stop with NOT_ENOUGH_REPLICAS, then resume (open)

lose-broker

Lose a broker: the KRaft controller fences it and elects new leaders from the ISR; nothing acknowledged is lost (open)

unclean

Unclean election loses data: an out-of-sync replica becomes leader and acknowledged records vanish (open)

isr

A slow follower leaves the ISR after replica.lag.time.max.ms and rejoins (open)

network-partition

A leader cut off from the cluster keeps acknowledging acks=1 writes that are later truncated; acks=all times out instead (open)

rebalance

Consumer group rebalance: eager range, a joining member, a crash (try &strategy=cooperative-sticky or &protocol=consumer) (open)

eos

Exactly-once with transactions: coordinator, markers, read_committed and the LSO, abort, a fenced zombie (open)

compaction

Compaction: the latest value per key, offsets kept, a tombstone removed after delete.retention.ms (open)

retention

Retention: segments roll and expire by time, then by size; a late consumer starts at the log start offset (open)

ordering

Ordering: per key within a partition, none across partitions, and retries reordering without idempotence (open)

too-many-consumers

Too many consumers: five consumers, three partitions, two idle (open)

idempotence

Idempotent producer: a lost ack makes a retry write a duplicate, unless the producer is idempotent (open)

playground

An empty cluster to experiment with (open)

Runs entirely in your browser; nothing you type leaves this page.