Kafka / projects
All modules
Kafka 4.3 · five projects

Projects we will work on

Five projects that give you the thing reading cannot: a system that went wrong and a story you can tell about it. Each project ends with a story in five parts.

Status of this page. No project here was run as a whole. Every command is copied from a module or from the capstone page, and every outcome is described, not predicted. Where a step uses something no module shows, it carries the tag unverified and the words "not run in the labs". Time estimates are guesses, not measurements. Numbers such as 5,000 partitions are targets to attempt. Your laptop may not reach them.

How to choose

Pick one project at a time. Start with C or B if this is your first. Project A takes the longest and teaches the most about time.

ProjectDifficulty (1 to 3)WeeksThe story it gives youCompose profile
A. Order-to-settlement capstone34 to 8A system that ran for weeks and failed in ways a one-hour lab does not.cdc and monitoring
B. Scale lab21Where a cluster starts to hurt, and how you measure it.none (monitoring helps for JMX)
C. Incident replay22Three incident reports in a fixed format.none (monitoring helps)
D. Lag monitor for Kafka 4.332A tool broke after an upgrade, and you built the replacement.monitoring
E. Failover rehearsal31 to 2A timed failover, with the real cost of a copy.none; start kafka-dr by name

Four of the five need only the main cluster. See the cheat sheet for the set-up used in the modules.

Rules for all projects

Log file format

2026-10-05 14:02  START   project X, step 3. Cluster: 3 brokers, all up.
2026-10-05 14:02  HEALTH  three checks: all empty (before)
2026-10-05 14:09  DID     <the exact command, copied>
2026-10-05 14:09  SAW     <the exact output or number, copied>
2026-10-05 14:31  GUESS   <what I expected before I ran it>   (write this BEFORE the run)
2026-10-05 14:50  HEALTH  three checks: <empty or the lines they printed> (after)

Helpers and health checks

Paste these into every new terminal. The J helper needs the monitoring profile, as in module 12.

k() { docker exec -i kafka-1 /opt/kafka/bin/"$@"; }
dr() { docker exec -i kafka-dr /opt/kafka/bin/"$@"; }
B=kafka-1:19092
D=kafka-dr:19092
d() { k kafka-topics.sh --bootstrap-server $B --describe --topic $1 | tail -1; }
J() { docker exec -i kafka-1 timeout 20 /opt/kafka/bin/kafka-jmx.sh \
  --jmx-url service:jmx:rmi:///jndi/rmi://localhost:9101/jmxrmi \
  --one-time true --report-format properties --object-name "$1"; }

The three health checks (module 09, step 1). If broker 1 is stopped, use another broker, as module 05 does.

k kafka-topics.sh --bootstrap-server $B --describe --unavailable-partitions
k kafka-topics.sh --bootstrap-server $B --describe --under-min-isr-partitions
k kafka-topics.sh --bootstrap-server $B --describe --under-replicated-partitions

Your progress

All projects 0 of 0

Your ticks are saved in this browser only.

Project board

Project A

Order-to-settlement capstone

Difficulty 3 of 34 to 8 weeks80 to 120 hours (estimate)
0 of 8

Purpose

This is the long project. You build one small order system on the lab cluster: a database, a connector that publishes events, a safe consumer, a Kafka Streams job, monitoring and a weekly failure drill. You keep it running for weeks. It is the only project here that teaches what a system does over time.

The full plan, requirements R1 to R11 and the starter files live on the capstone page. This card does not repeat them. It only tracks the eight weeks.

Spec

  • Read capstone.html and capstone/README.md first. Both say the starter files were not run end to end.
  • Needs the cdc and monitoring profiles together, as the capstone page says.
  • The spec, the acceptance tests and the design choices are on the capstone page. Use them there.

Eight milestones

Acceptance tests

Each week has its own tests on the capstone page. Tick a week here only when its tests pass and you have written down what you saw. The full test list is in capstone/acceptance-tests.md.

What to measure

Each capstone week lists what to measure. Examples from that page: time from a cold start to a ready cluster, delay from database commit to topic, replication slot size while the connector is stopped, and the highest rate with flat lag. Follow that page.

The story it produces

Eight weeks give about eight stories. The capstone page has a table of the five-part shape with an example from week 2. Use the same shape for every week. The template below is a blank copy.

Fill-in template for this story
Situation:         I was building ___ . What mattered was ___ .
What I saw:        ___ (output, numbers, symptoms; copy them from the log)
What I decided:    ___ . I rejected ___ because ___ .
What I got wrong:  ___ (the part interviewers trust most)
What I changed:    ___ , and I measured the change as ___ .
Project B

Scale lab: how many partitions can the lab hold?

Difficulty 2 of 31 week10 to 15 hours (estimate)
0 of 8

Purpose

Module 09 says too many partitions is a failure, but gives no feel for where "too many" starts. In this project you add partitions in steps and watch what each step costs: creation time, memory, restart time, failover time, and the size of the cluster metadata log. You will not find a universal limit. You will learn which number moves first and how to measure it.

Spec

  • Main three-broker cluster only. The monitoring profile is optional, and helps for JMX.
  • Create topics with replication factor 3 in steps of total partitions: for example 500, 1,000, 2,000 and 5,000. These are targets to attempt.
  • After each step: run the three health checks, record every measurement in the table below, then go on.
  • Run the restart test and the failover test at the smallest step and again at the largest step you reach.
  • Keep the Docker memory stop rule, below.

The loop

The topic command is the one from modules 01 and 03. The loop around it is yours to adapt.

# one step: add 5 topics of 100 partitions each (500 partitions in this step)
for i in $(seq 1 5); do
  k kafka-topics.sh --bootstrap-server $B --create --topic scale-a-$i \
    --partitions 100 --replication-factor 3
done
# next steps: change the topic names (scale-b-..., scale-c-...) and the counts.
# every partition has 3 copies, so 500 partitions mean 1,500 replicas in the cluster.

Stop rule. The 1,000 to 5,000 partitions are a target to attempt. Your laptop may not reach it. Watch the memory Docker Desktop shows. If Docker runs out of memory, a broker container exits, or a command stops answering, stop and write down the last step that worked. Do not push on. To clean up, use docker compose down and docker compose up -d in the main folder. Drills say this removes the container data.

Real hardware differs from a laptop in memory, disk speed and network. Never say "Kafka handles N partitions" from this project. Say "my three-container lab did or did not hold N".

Eight milestones

Acceptance tests

TestWhat you doWhat a pass looks like
B1 Every step ranCreate partitions in steps, for example 500, 1,000, 2,000 and 5,000 in total.You have one filled row for each step you reached. For a step you did not reach, the row says why you stopped.
B2 Clean after each stepRun the three health checks after each step.All three print nothing before you go on. If a check prints lines, you wait, write down how long, and only then go on.
B3 Restart timedStop one broker, start it, and poll the third health check.A time in seconds from the start command until the check prints nothing. You ran it at the smallest and the largest step.
B4 Failover timedFollow the module 04 recipe on a topic whose leader is on the broker you stop.A time in seconds until the leader column names another broker. You ran it at the smallest and the largest step.
B5 Quorum and metadata recordedRun the two kafka-metadata-quorum.sh commands and read the size of the metadata directory.Leader, epoch and log end offset at each step. The log grows as you add partitions; your numbers show how fast.
B6 One written conclusionWrite one page.It names the largest step that stayed healthy, the first number that moved, and the sentence "results will differ on real hardware".

What to measure

WhatHowSource
Time to createWrap the create loop with start and end times, for example date +%s before and after. Divide by the partitions created.Loop is yours. --create as in module 03. unverified not run in the labs: timing a loop
Memory of each brokerdocker stats --no-stream, after each step has settled. Note the memory limit in Docker Desktop settings too.unverified not run in the labs: docker stats
Restart time until under-replicated is 0Stop kafka-3, start it, then poll the third health check every 2 seconds. Count the seconds.Stop and start: module 04. Check: module 09, step 1. The polling loop is yours.
Leader failover timeModule 04, step 1: create a topic with --replica-assignment 3:1:2, run d on it, stop kafka-3, run d again until the leader changes.Module 04.
Quorum statuskafka-metadata-quorum.sh ... describe --status and describe --replication. Record leader, epoch, high watermark, log end offset.Module 05.
Metadata log sizeThe directory /tmp/kraft-combined-logs/__cluster_metadata-0 inside a broker. Read its size at each step.Directory: module 05. unverified not run in the labs: reading the size with du
JMX metricsThe five objects of module 12: under-replicated partitions, active controller count, offline partitions, request handler idle percent, raft metrics. Use the J helper.Module 12. The lab started the monitoring profile first; do the same.

The story it produces

The story is about a limit, not a success. A good one reads: "I expected X to break first. Y moved first. I changed how I measured, and found Z." Put real numbers in it.

Fill-in template for this story
Situation:         I wanted to know what many partitions cost on a 3-broker lab.
What I saw:        at ___ partitions: create ___ s, broker memory ___, restart ___ s,
                   failover ___ s, metadata log ___ . (copy from the table)
What I decided:    I stopped at ___ because ___ . I did not claim a real-world limit because ___ .
What I got wrong:  I expected ___ to move first, but ___ did. Or: I measured too soon after the step.
What I changed:    I waited ___ before measuring / changed the step size to ___ .
Project C

Incident replay: three incidents, three reports

Difficulty 2 of 32 weeks12 to 20 hours (estimate)
0 of 8

Purpose

Staff-level interviews ask how you handle a bad day. In this project you pick three incidents from the table, break the lab on purpose, observe, repair, and then write a post-incident report for each. The report is the point. Writing a timeline from your own log shows you what you missed.

Spec

  • Pick three incidents from the table. Pick at least one that touches replication or the controller, and one from the client side.
  • Use the matching drill on drills.html for the commands. Do not skip its questions.
  • For each incident: reproduce, observe, repair, then write the report. Use the template below.
  • As a reading step, open case-studies.html and compare each incident with a published case.
  • Run the three health checks before and after each incident.

Choose from these lab-supported incidents

IncidentModuleDrillHow the lab reproduces it
Leader failure and epochs043Stop the leader broker, write under the new leader, read the leader-epoch file.
Unclean election and data loss044Topic with minimum in-sync 1, stop brokers in the module 04 order, force the unclean election, read the truncation warning.
Lost controller majority055Stop two of three brokers. Metadata changes time out. Start them and read the quorum status.
Hanging transaction068Kill a transactional producer with pkill -9. Compare read_uncommitted and read_committed.
Poison record and stuck partition037Drill 7 stands in for a poison record with a stopped group and lag. unverified not run in the labs: a consumer that crashes on one bad record.
Connector task failure and dead-letter topic089Send a line that is not JSON to a strict sink connector. Then add a dead-letter topic.
Noisy client and quotas0911Quota on a client id, two performance runs. A short run misleads.

Post-incident report template

POST-INCIDENT REPORT: <title>
Date and who ran it:
Timeline     (clock times from your log; first sign, detection, action, recovery)
Impact       (what a user or a downstream system would have seen; numbers)
Cause        (the one mechanism, in plain words; point at the module section)
Fix          (the exact commands you ran, and the output that proved it worked)
Prevention   (a setting, an alert or a habit; name who would own it)
What the monitoring showed  (which metric or alert moved, how fast, or "nothing")
What I would still not know in production

Eight milestones

Acceptance tests

TestWhat you doWhat a pass looks like
C1 ReproducedFor each of three incidents, run the break step.The symptom named in the drill appears. If it does not, you wrote down what appeared instead.
C2 Observed with numbersBefore you repair, collect the output the drill tells you to read.Copied output and at least one number in the log, for example a leader epoch, an offset or a message count.
C3 Repaired and verifiedRun the repair step. Then run the three health checks.All three print nothing, or you wrote down the one leftover and why. Drills say the topic no-quorum is a known leftover after drill 5.
C4 Monitoring recordedNote what an alert or metric showed. The monitoring profile helps.One line per incident: what moved, or "no alert would have fired".
C5 Report completeFill the template for each incident.Seven headings filled, no heading says "n/a".
C6 Compared with published casesRead the published cases and pick the closest one for each incident.One paragraph per incident: what matched, what differed, what the case adds that the lab cannot.

What to measure

  • Time from the break to the first sign you noticed, and to the first alert if one fired.
  • Time from the repair command to clean health checks.
  • The number the drill asks for: epoch, message count, lag, or throughput.
  • How many of your predictions were wrong. This is the most useful number.

The story it produces

You will have three stories. Choose the one where your prediction was most wrong. Speak it aloud in under two minutes.

Fill-in template for this story
Situation:         I replayed ___ (incident) on a 3-broker lab.
What I saw:        timeline ___ ; first sign at ___ ; numbers ___ .
What I decided:    I repaired by ___ and rejected ___ because ___ .
What I got wrong:  I predicted ___ . The lab showed ___ .
What I changed:    I would add ___ (alert, setting or runbook line) and check it by ___ .
Project D

Lag monitor for Kafka 4.3

Difficulty 3 of 32 weeks15 to 25 hours (estimate)
0 of 8

Purpose

Module 12 found that the lab exporter could not read consumer groups from Kafka 4.3, so the lab has no lag metric and no lag alert. Real teams meet this: a tool stops working after an upgrade. You will build a small replacement in a language you know. It reads lag from the command-line tool, turns it into metrics, and raises one useful alert.

This card gives the spec and the tests only. You write the tool.

Spec

  • Run kafka-consumer-groups.sh --describe --all-groups (module 03) every 30 seconds. Run it through docker exec on a broker, as the modules do.
  • Parse the columns GROUP, TOPIC, PARTITION, CURRENT-OFFSET, LOG-END-OFFSET and LAG for every group.
  • Compute lag in records for each partition. Check it against the LAG column.
  • Estimate lag in time: lag in records divided by the end-offset rate (records per second, measured over a window you choose). Decide what to publish when the rate is zero.
  • Publish the numbers where Prometheus can read them: a text file or a small HTTP endpoint. Name the metrics yourself and label them with group, topic and partition.
  • Write one alert rule: a partition's committed offset has not moved for a time you choose, while its end offset has moved.
  • Survive broker restarts. If one broker is down, try another, as module 05 does with kafka-2.
  • Say what happens when a partition has no committed offset yet. unverified not run in the labs: the output for a group with no commit on a partition. Run it and record what you see.

What the labs do and do not show. Module 12 shows the Prometheus rules and metric queries on port 9095, and the rule file format in monitoring/alerts.yml (alert, expression, wait time, labels, annotations). The labs do not show a text-file or endpoint source added to Prometheus. unverified not run in the labs: adding a scrape target to monitoring/prometheus.yml, reloading Prometheus, and letting the Prometheus container reach a program on your laptop. Finding a working way, and writing it down, is part of the project. Note that the lab mounts both files read-only inside the container; you edit your copy on disk.

Eight milestones

Acceptance tests

TestWhat you doWhat a pass looks like
D1 Parse checkRun the module 03 command once. Save its real output to a file. Write a test that feeds the file to your parser.Every partition row becomes one record with group, topic, partition, current offset, end offset and lag. Lines that are not rows (a header, a blank line, "has no active members") are skipped without an error.
D2 Stuck group fires the alertStop all consumers of a group. Keep writing to the topic with the performance tool.The alert fires after the wait time you chose, and its text names the group, topic and partition. The lab stand-in for a stuck consumer is a stopped group, as in drill 7. A real stuck consumer needs code, and no lab has one.
D3 Healthy group stays quietRun a consumer with a group and a producer at a steady low rate for at least 10 minutes.The alert never fires. The lag in records is small and steady. Report how many false alerts you saw.
D4 Idle topic stays quietStop the producer. The group has caught up.No alert: the committed offset has not moved, but the end offset has not moved either.
D5 Survives a cluster restartStop all three brokers, wait, start them. Keep the tool running the whole time.The tool does not crash. It shows a clear "cannot read the cluster" state. It does not publish stale lag as if it were fresh. A separate alert says the tool is not reporting.
D6 Time estimate is honestCompare your lag in time with a real measure: write a record, time how long until the group commits past it.Your estimate and the measure are written side by side. You list when the estimate is wrong, for example when the write rate is zero.

What to measure

  • Seconds your tool takes to run one poll, and whether it finishes inside the 30-second gap.
  • Time from the stop of a consumer to the alert firing. Include the wait time you chose.
  • False alerts in a 24-hour run with a healthy group. Write down the count, even if it is zero.
  • Lag in time estimate against the real measure in test D6.
  • Time for the tool to recover after the cluster restart in D5.

The story it produces

The strong story is about the gap between what a metric says and what is true: lag in records looked small while the group was stuck, or the estimate was wrong at a zero rate. Tell what you saw in the numbers.

Fill-in template for this story
Situation:         The lab exporter had no lag metric on Kafka 4.3, so ___ .
What I saw:        my first version showed ___ ; the stuck group showed ___ ; idle showed ___ .
What I decided:    I chose a wait time of ___ s because ___ ; I rejected ___ .
What I got wrong:  ___ (a false alert, a stale number, a parse error)
What I changed:    ___ , and the false alerts went from ___ to ___ .
Project E

Multi-cluster failover rehearsal with MirrorMaker 2

Difficulty 3 of 31 to 2 weeks12 to 20 hours (estimate)
0 of 8

Purpose

Module 10 says a copy is not the original. Offsets differ, transactions are not preserved the way you hope, and failback is harder than failover. In this project you rehearse a real failover with a runbook and a stopwatch, then count what it cost. You also reproduce one problem module 10 reports and test its proposed fix.

Spec

  • Copy topics from main to kafka-dr with MirrorMaker 2, using the module 10 part B steps. Start the second cluster with docker compose up -d kafka-dr.
  • Translate group offsets (module 10 shows this with sync.group.offsets.enabled).
  • Stop the main cluster. Start consumers on the standby from the translated offsets. Switch producers to the standby.
  • Measure copy delay and repeated records.
  • Reproduce the aborted-records problem from module 10, then test a read_committed copying consumer.
  • Module 10 marks this fix unverified unverified: it did not rerun the lab. Your job is to find the right property name, set it, and record what you find. Do not trust a name from memory. Do not trust the module. Write down where you found it and what happened.
  • Write the runbook before the rehearsal, and time the rehearsal against a target of 15 minutes. The target is a number to attempt. No module measured a failover time.

Runbook template

FAILOVER RUNBOOK: main -> kafka-dr
Decision rule   (what exact condition makes us fail over? e.g. main unreachable for ___ minutes)
Who decides     (a named role and a named backup; who tells the others)
Stopwatch       start ___ : ___ ; target 15 minutes total; stop ___ : ___
1. Stop the copy        (command; confirm the copy process has ended)
2. Record last offsets  (source end offsets if readable; copy end offsets; translated group offsets)
3. Switch producers     (new address; which topic name on the standby; who changes the settings)
4. Start consumers      (group name; the offsets they start from; how you check them)
5. Verify               (health checks on the standby; sample records; count of repeats)
6. Rebuild the old site (what to start; what is stale; how to resync; who confirms)
Known losses    (records not copied; repeated records; aborted records visible?)

Failback (return to the main cluster) is harder than failover. Module 10 explains why. Write down what you would do, and mark each step you did not run. unverified not run in the labs: any failback.

Eight milestones

Acceptance tests

TestWhat you doWhat a pass looks like
E1 Copy existsModule 10 part B, step 1 and 2: start kafka-dr and MirrorMaker 2, then compare end offsets of source and copy.Topics named main.orders, main.events and main.payments exist on kafka-dr. Offsets are equal or close, and you explain any difference (module 10 explains transaction markers).
E2 Copy delay measuredModule 10 part B, step 3, repeated at least five times.Five delays in seconds, with the smallest and largest. The module saw about 2 seconds once; yours may differ.
E3 Translated offsets seenModule 10 part B, step 4: describe the same group on both clusters.The group on kafka-dr shows current offsets lower than on main. You can explain why (the copy is not the original).
E4 Failover done and timedFollow your runbook: stop main, switch, start consumers on the standby from translated offsets.Consumers read from kafka-dr and make progress. The stopwatch time is written down against the 15-minute target. unverified not run in the labs: starting a consumer on kafka-dr with a group copied from main.
E5 Repeats and losses countedCompare what the old consumer had processed on main with what the standby consumer read.A number of repeated records and a number of records never copied. Both written, with how you counted.
E6 Aborted-records problem reproducedRepeat the module 06 hanging-transaction lab on payments, copy it, read the copy.The copy contains records that the aborted transaction wrote, because markers are not copied (module 10 evidence: 73 data records, 38 aborted).
E7 The fix testedChange the copying consumer to read only committed records, rerun E6 on a new topic name.Either the aborted records are gone from the copy, or you wrote down exactly what happened instead. Both count as a pass if recorded.

What to measure

  • Copy delay in seconds, at least five samples, and the range.
  • Failover time on the stopwatch, step by step. Which step took longest?
  • Repeated records after the switch, and how you counted them.
  • Records not copied at the moment you stopped main.
  • The count of aborted records visible on the copy before the fix, and after it.
  • Time to rebuild the old site, and what you still could not tell.

The story it produces

The best story here is the gap between your runbook and your rehearsal: the step that took longer than planned, the number of repeats you did not predict, or the fix that behaved differently from the module. State what you found, not what you expected.

Fill-in template for this story
Situation:         The standby cluster had to take over, and the target was 15 minutes.
What I saw:        copy delay ___ s; failover took ___ min ___ s; ___ repeated records; ___ uncopied.
What I decided:    I moved consumers to translated offsets and accepted ___ repeats because ___ .
What I got wrong:  ___ (a runbook step, a topic name, an offset I trusted)
What I changed:    the runbook now says ___ ; the read_committed setting I found was ___ and it ___ .

Where to go next