Kafka, from first principles

Apache Kafka is a durable, replayable log that many programs can write to and read from at their own pace. It's how Prayog gets every trade from the exchange to the post-trade, leaderboard and market-data services without slowing the exchange down. This page assumes no prior Kafka knowledge.

1. The problem

When a trade happens, several other parts of Prayog care: post-trade updates positions and P&L, the leaderboard re-ranks, the web terminal shows the fill. The exchange could call each of them directly, but then:

Kafka sits in between. The exchange writes each event once and moves on. Each service reads at its own speed, and a service that was down simply continues from where it stopped.

2. A business example: the numbered order register

A wholesale market keeps a big bound register at the front desk. Every sale is written on the next numbered line, in ink. Nobody tears out pages.

The register is a topic, the three registers are partitions, line numbers are offsets, and bookmarks are committed offsets.

3. Topics, partitions and offsets

Topic prayog.exchange.events.v1 partition 0partition 1partition 2 new records appended here offsets 0, 1, 2 ... per partition. Reading never removes a record; old records are deleted only by retention (time or size).
Look familiar? A partition is a journal: append-only segment files, numbered records, sequential disk writes. Kafka is built on the same ideas as Prayog's own journal (journaling), exposed over the network to many readers.

4. Producing: keys decide the partition

The producer picks the partition from the record's key: hash(key) mod partitions. Same key, same partition, so records with the same key stay in order. Records with different keys may land in different partitions and be read in any relative order.

Prayog keys exchange events by symbol. All INFY events are in order relative to each other; INFY and TCS events can interleave. That's exactly the guarantee consumers need: an INFY position is built from INFY trades in order.

Producer settingMeaning
acks=allThe write counts only when every in-sync replica has it. The safe setting, and the default since Kafka 3.0.
enable.idempotence=trueRetries can't create duplicates or reorder within a partition. Also default since 3.0.
linger.ms, batch.sizeWait a moment to send records in batches: more throughput, a little latency. Group commit again.

5. Consumer groups

Consumers that share a group.id form a consumer group. Kafka gives each partition to exactly one member of the group, so the members split the work:

When a member joins, leaves or crashes, the group rebalances: partitions are reassigned. The new owner starts from the group's committed offset for that partition, the bookmark, stored in Kafka's internal topic __consumer_offsets.

A different group (say, the leaderboard) reads the same records independently, with its own bookmarks. That's how one topic feeds many services.

6. Delivery guarantees

The question is when the consumer commits its bookmark, relative to doing the work:

GuaranteeHowAfter a crash
At most onceCommit, then processA record committed but not processed is lost
At least onceProcess, then commitA record processed but not committed is processed again
Exactly once (effectively)At least once + an idempotent consumer, or Kafka transactions for Kafka-to-Kafka pipelinesDuplicates arrive but have no effect

Prayog uses at-least-once with idempotent consumers (BUILD_PLAN 16.2 #18). Every event carries eventId = the exchange's event sequence number. The plan for post-trade (S16): store the last applied id per partition in the same database transaction as the position update, and skip anything at or below it. A redelivered trade is recognised and ignored, so P&L is never counted twice.

7. Try it: produce, consume, crash

Three partitions, keyed by symbol. Produce some trades, set how many consumers the group has, and let them work. Then crash one between "processed" and "committed" and watch the redelivery.

INFYTCSHDFCRELIANCEfaded = before the committed offset

8. Brokers, replicas and KRaft

Prayog runs one node that is both broker and controller (KAFKA_PROCESS_ROLES=broker,controller), with replication factor 1. There's no redundancy, which is fine for a laptop, and the 3 partitions still make consumers deal with real partitioning.

9. Listeners: the most common setup mistake

A client connects in two steps:

  1. It connects to a bootstrap address and asks for metadata: "which broker leads which partition, and how do I reach it?"
  2. The broker answers with its advertised address, and the client opens new connections to that address for all real traffic.

So the advertised address must be reachable from wherever the client runs. Containers reach the broker as kafka; your Mac can't resolve that name, but can reach localhost:9094. Prayog therefore has one listener per network:

ListenerPortAdvertised asUsed by
INTERNAL9092kafka:9092Services in Docker (post-trade, exchange in the app profile)
EXTERNAL9094localhost:9094Tools and services run on the host during development
CONTROLLER9093not advertisedKRaft quorum traffic only
Symptom of a wrong advertised address: the client "connects" (bootstrap works) and then times out on every send, because step 2 points somewhere it can't reach.

10. Kafka in Prayog

WhatValueWhy
Topicprayog.exchange.events.v1Versioned name: a breaking format change gets a new topic
Partitions3, keyed by symbolPer-symbol order; up to 3 consumers per group in parallel
FormatJSON validated against JSON Schema in contracts/Readable by Python and TypeScript consumers; SBE stays inside the engine
ProducerA downstream pipeline stage of the exchange (S15), after the journalNever on the order path; if Kafka is down, the journal still has everything and the publisher catches up
DeliveryAt least once; eventId = event seq; idempotent consumersNo lost events, no double-counted trades
Topic creationkafka-init job; auto-create offA typo fails loudly instead of creating a topic

11. Questions

Why not put Kafka on the order path instead of the journal?

A network round trip to a broker (and its replicas, with acks=all) costs far more than an append to a local file, and Prayog's invariants forbid network calls on the order path. The journal makes the order durable; Kafka distributes the results afterwards.

Kafka or RabbitMQ?

RabbitMQ is a message broker: messages are routed to queues and removed once acknowledged. Kafka is a log: records stay for the retention period and any number of groups can read or re-read them. For "every service needs every trade, and must be able to catch up or replay", the log model fits.

Can I change the number of partitions later?

You can add partitions, but hash(key) mod partitions then sends some keys to a different partition, breaking per-key order across the change. Choose the count up front; that's why Prayog fixes it at 3 in the init job.