The Transaction Coordinator in Kafka

The Transaction Coordinator in Kafka

How Kafka makes a group of writes succeed together or fail together, how one broker acts as the careful referee, and how the whole system avoids duplicates, half-finished work, and confused old programs. Explained from zero, with everyday pictures, clear diagrams, and real-world lessons.

Imagine you are paying for a school trip. Money must leave your account and arrive in the school’s account. If the money leaves but never arrives, you are angry. If it arrives twice, the school is confused. The only acceptable results are “both things happened” or “neither thing happened.” Banks solved this problem long ago with the idea of a transaction, which means a group of actions that must succeed or fail as one unit.

Apache Kafka faces the same problem when a program reads messages, does some work, and writes new messages to several places. If the program crashes in the middle, some outputs may exist and others may not. Worse, when the program restarts, it may repeat work and write duplicates. Kafka’s answer is a feature called transactions, and the special helper that keeps them honest is the transaction coordinator.

In this guide you will learn what the transaction coordinator is, where it lives, how it remembers things, how it commits or cancels a group of writes, what it does when a broker or a client crashes, and how to run it well in production. You do not need any earlier knowledge of transactions, exactly-once processing, or Kafka internals. We begin with the very basics and build up slowly.

1
coordinator broker per transactional ID at any moment
2
possible endings for every transaction: commit or abort
50
default number of partitions of the internal transaction log

Why Transactions Matter: The Big Picture

Before any Kafka words, let us understand the simple problem that transactions solve.

Suppose a small program reads one message that says “customer Asha bought a phone.” The program must do three things: write a message to an “orders-confirmed” topic, write a message to a “stock-reduced” topic, and remember that it has finished reading that input message. If the program crashes after the first write but before the other two, the world is left in a strange state. Asha’s order is confirmed but the stock was never reduced, and after a restart, the program will read the same input message again and confirm it a second time.

Everyday Analogy

Think of a teacher handing back a set of exam papers. The teacher writes marks in the register, stamps the papers, and notes in a diary that this batch is done. If the teacher is called away halfway, the register may show marks that the papers do not, and the diary says nothing. A transaction is a rule that says: “Either everything for this batch is finished and recorded, or it is treated as if the batch never started.”

What a transaction promises

A Kafka transaction promises atomicity. That word means “all or nothing.” Either all the messages written inside the transaction become visible to readers, or none of them do. This holds even when the writes go to many different topics and partitions living on many different brokers, and even when the program or a broker crashes in the middle.

Who keeps the promise?

A single producer program cannot keep this promise alone, because it might crash at the worst moment. Something else must remember what the producer started, decide the final outcome, and finish the job even if the producer is gone. That “something else” is the transaction coordinator. It is not a separate machine. It is a role played by a normal Kafka broker.

i
Remember

The producer asks for the transaction, the coordinator keeps the official record, and the partitions hold the messages. Three actors, one clear job each.

Kafka Basics You Need First

A short tour of the Kafka words we will use again and again.

Storage

Broker

A Kafka server that stores messages on disk and serves producers and consumers.

Feed

Topic

A named stream of messages, such as “orders” or “payments”.

Scaling

Partition

A slice of a topic. Each partition is an ordered list of messages with numbered positions called offsets.

Safety

Replica

A copy of a partition kept on another broker so a failure does not lose data.

Leadership

Partition leader

The one replica that handles all reads and writes for a partition. Others copy from it.

Reading

Consumer group

A team of consumers that share the work of reading a topic and remember their progress.

Producers are programs that write messages. Consumers are programs that read them. A message is only added to the end of a partition, and it is never changed afterwards. Consumers remember how far they have read using a number called an offset, which Kafka stores in a special internal topic. This is important later, because committing that number is part of what a transaction can do.

Everyday Analogy

A partition is like a long roll of receipt paper that only grows at one end. Each receipt has a number printed on it. A consumer keeps a bookmark at the last receipt it has read. If the bookmark is lost, the reader may read some receipts again or skip some.

Internal topics

Kafka keeps some of its own working notes in topics too. They usually have names that start with two underscores. One holds consumer bookmarks. Another holds the transaction coordinator’s notes, which we will study closely in this guide. Using ordinary Kafka topics for its own memory is a favorite Kafka habit: it gets copying, durability, and recovery for free.

Delivery Guarantees: At-Most-Once, At-Least-Once, Exactly-Once

Three promises that a messaging system can make, and why the third is the hardest.

GuaranteePlain meaningWhat can go wrong
At most onceA message is delivered zero or one timeMessages can be lost, but never repeated
At least onceA message is delivered one or more timesNothing is lost, but duplicates can appear
Exactly onceEach message has its effect one time onlyHardest to build, and only true inside a defined boundary
Everyday Analogy

Imagine posting an important letter. At most once means you post it and never check, so it might vanish. At least once means you keep posting copies until you hear back, so the receiver may get several. Exactly once means the receiver gets one letter and knows it is the only one, even if you posted copies by mistake.

Why duplicates happen

A producer sends a message and waits for the broker’s reply. If the reply is lost in the network, the producer cannot tell whether the message arrived. The safe thing is to send it again. If the first message had in fact arrived, the partition now contains two copies. This is the classic origin of duplicates, and it happens even in a perfectly healthy system when the network hiccups.

Where “exactly once” is real

Kafka’s exactly-once feature is real inside Kafka: reading from Kafka topics, processing, and writing to Kafka topics. It cannot magically make an outside system, such as an email service or a database, act exactly once. For outside systems you need extra techniques, such as making the outside action harmless to repeat. We come back to this important limit later.

!
Watch Out

“Exactly once” is a promise about a boundary, not about the whole universe. Always ask: exactly once where? Inside Kafka topics, yes. Inside your bank’s database, only if you design it that way.

The Idempotent Producer: The Building Block

Transactions stand on the shoulders of a simpler idea that removes duplicates from a single partition.

Idempotent is a mathematical word that means “doing it twice has the same effect as doing it once.” Pressing an elevator button twice does not call two elevators. An idempotent producer works the same way: even if it sends the same message twice, the partition keeps only one copy.

1

The producer gets an identity

When it starts, it asks the cluster for a producer ID, a unique number, and an epoch, a version counter that starts small and can only go up.

2

Every message gets a sequence number

For each partition, the producer numbers its messages 0, 1, 2, and so on, like page numbers.

3

The broker checks the numbers

The partition leader remembers the latest sequence number from each producer. If a message arrives with an old number, it is a duplicate and is quietly ignored. If a number is skipped, the broker reports an error, because something was lost.

4

Retries become safe

The producer can now retry freely after a lost reply, and no duplicates result.

Everyday Analogy

A librarian who receives numbered pages from a courier checks each page number against the last one received. If page 7 arrives twice, the second copy goes in the bin. If page 9 arrives before page 8, she raises an alarm.

Where idempotence stops

Idempotence protects a producer only for one partition and only as long as the producer keeps the same identity. If the producer crashes and restarts, it usually gets a brand new producer ID, so the broker cannot recognize it as the same producer. And idempotence does nothing for a group of writes that must succeed together across many partitions. To fix these gaps, Kafka added the transaction coordinator, which gives a producer a stable identity that survives restarts and makes group writes atomic.

What Is a Transaction in Kafka?

A precise but friendly definition, with a picture of the most common use.

A Kafka transaction is a bundle of writes, possibly to several topics and partitions, that becomes visible to readers all at once or not at all. The producer opens the bundle, adds writes, and then either commits (makes everything visible) or aborts (makes everything invisible). Additionally, the bundle can include consumer bookmark updates, so that “I have finished reading up to here” is saved atomically with the output messages.

The classic pattern: consume, transform, produce

1

Consume

Read a batch of messages from an input topic.

2

Transform

Do your business work: check, enrich, calculate.

3

Produce

Write results to one or more output topics inside a transaction.

4

Save the bookmark

Include the input offsets in the same transaction, so progress and results are saved together.

5

Commit

End the transaction. Results and bookmark become real at the same instant.

If the program crashes anywhere before the commit, the outputs stay invisible and the bookmark stays where it was. After restart, the program reads the same input again and redoes the work, but because the earlier attempt was never committed, no visible duplicates exist. This is the heart of Kafka’s exactly-once processing.

Everyday Analogy

It is like a cashier who prepares a receipt, packs the goods, and marks the order as done, all placed in a sealed tray. The tray is only slid across the counter when everything inside is ready. If the cashier faints halfway, the tray never crosses the counter, and a colleague starts the order from scratch.

?
Good to Know

Transactions also help simple producers that are not reading anything. A producer may write a small group of related events to different topics, and use a transaction only to make them appear together.

Meet the Transaction Coordinator

The referee of every Kafka transaction.

The transaction coordinator is a component that runs inside every Kafka broker. For any given transaction, exactly one broker plays the coordinator. Its job is to remember which transactions are open, which partitions they touch, and what the final decision is. It makes sure that the decision is written down safely, and then it makes sure the decision is carried out on every partition involved.

Identity

Gives out identities

Hands each transactional producer a producer ID and an epoch, and keeps track of them.

Memory

Remembers the transaction

Records which partitions a transaction has touched and its current state.

Decision

Decides commit or abort

Writes the decision to its durable log before telling anyone.

Delivery

Delivers the decision

Sends special markers to every partition so readers know the outcome.

Safety

Fences old producers

Uses the epoch number to lock out an older copy of a producer that should no longer act.

Cleanup

Aborts stuck transactions

Cancels transactions that stay open too long, so they do not block readers.

Everyday Analogy

Think of a wedding registrar. The couple (the producer) arrives, the registrar records who is getting married and the witnesses (the partitions), writes the official decision in the register, and then sends the certificate to all the offices that need it. If the couple leaves the room, the register still says what was decided.

Which broker is the coordinator?

Any broker could be a coordinator, but for a given transactional ID only one broker is. That ID is a name chosen by the application, such as “payment-processor-7”. Kafka turns the name into a number, and the number decides which partition of the internal transaction log is responsible for it. The broker that currently leads that partition of the log is that producer’s coordinator. This is a neat trick: leadership of a log partition doubles as the coordinator election.

The Transaction Log: The Coordinator’s Notebook

A coordinator that forgets is useless, so its memory lives in a replicated Kafka topic.

The coordinator writes every important change of every transaction into an internal topic called the transaction state log. Its name is __transaction_state. By default it has fifty partitions and each partition is stored on three brokers, with at least two copies required to be in sync before a write is confirmed. The topic is compacted, which means Kafka eventually keeps only the newest record for each transactional ID, like a notebook where old entries for the same person are crossed out.

1

A name becomes a number

The transactional ID is put through a fixed mathematical function that produces a number. That number, divided by the count of partitions, leaves a remainder that picks one partition of the log.

2

The partition leader is the coordinator

Whichever broker currently leads that partition acts as the coordinator for that ID.

3

State is written before it is acted upon

The coordinator saves each state change in the log and waits for the copies to be safe, then continues.

4

Recovery is just a re-read

If the leader fails, a new leader is chosen from the copies, and it reads the log to rebuild the coordinator’s memory.

Everyday Analogy

Fifty numbered filing cabinets hold the notes, and each person’s file goes into the cabinet chosen by a rule based on their name. Each cabinet has a keeper, plus two spare copies stored in other buildings. If a keeper is absent, one of the spare-copy holders takes over and simply opens the cabinet.

Why the number of partitions is fixed

Because the mapping from name to partition depends on the partition count, the count must not change after the cluster begins using transactions. If it changed, names would suddenly point to different cabinets and the coordinators would lose track of open transactions. This is why the count is set once, at the beginning, and treated as permanent.

!
Watch Out

Do not try to change the number of partitions of the transaction log on a cluster that has been using transactions. Choose the value carefully at the start. The default is sensible for most clusters.

Architecture and Components

The whole picture: who talks to whom, and why.

flowchart TB
  P["Transactional Producer"]
  subgraph BR["Kafka Broker Fleet"]
    TC["Broker B - Transaction Coordinator"]
    TL[("Transaction log partition - led by Broker B")]
    L1["Broker A - leader of orders partition 0"]
    L2["Broker C - leader of stock partition 3"]
    GC["Broker D - group coordinator for offsets"]
  end
  CON["Read-committed Consumer"]
  P -->|"1 find coordinator and get identity"| TC
  P -->|"2 register partitions"| TC
  TC -->|"saves state"| TL
  P -->|"3 send messages"| L1
  P -->|"3 send messages"| L2
  P -->|"4 send consumer offsets"| GC
  P -->|"5 commit or abort"| TC
  TC -->|"6 write commit or abort markers"| L1
  TC -->|"6 write commit or abort markers"| L2
  TC -->|"6 write markers"| GC
  L1 -->|"only committed data"| CON
  L2 -->|"only committed data"| CON
    
Figure 1: The transaction coordinator sits between the producer and the partitions it writes to.

Read the figure from the left. The producer first talks to the coordinator to get an identity and to register the partitions it plans to write to. It then sends its messages directly to the partition leaders, without going through the coordinator, so the coordinator is not a bottleneck for data. When the producer is ready, it tells the coordinator to commit or abort. The coordinator writes the decision to its log and then sends small markers to every partition involved. Consumers that ask for committed data only see messages after they are marked committed.

The components in plain words

1

Transactional producer

The client program. It has a stable name, the transactional ID, and calls a small set of transaction commands.

2

Transaction coordinator

The broker-side referee described in this guide.

3

Transaction log

The compacted internal topic that stores every transaction’s state durably.

4

Partition leaders

Store the data messages and the commit or abort markers in their own logs.

5

Group coordinator

A different helper that stores consumer bookmarks. Bookmark updates can join a transaction.

6

Read-committed consumers

Readers that hide unfinished and cancelled transactions.

?
Good to Know

Two different coordinators exist in Kafka: the group coordinator for consumer groups and the transaction coordinator for transactions. They sound alike, but they do different jobs and keep different logs.

Idempotence Under the Hood: Sequence Numbers and Retries

The duplicate-catching machinery that transactions rely on every second.

Each partition leader remembers, for each producer ID, the last few sequence numbers it accepted. When a new batch of messages arrives, the leader compares its first sequence number with what it expects next. There are only three possible situations, and each has a clear response.

Expected

The next number in order

The batch is accepted and appended, and the expected number moves forward.

Old

A number already seen

The batch is a retry of something already stored. The leader replies “success” without storing a second copy.

Gap

A number that skips ahead

Something was lost in between. The leader reports an out-of-order error so the producer can react.

Why several batches can be in flight

To keep the network busy, a producer may send several batches without waiting for each reply. Kafka allows a small number of such in-flight batches per connection while still preserving order, because the sequence numbers let the leader detect any reordering after a failed send. This is why the producer’s order-preserving retries are safe when idempotence is on.

Everyday Analogy

Think of a factory conveyor belt where every box carries a running number. The packing table checks each number as boxes arrive. A repeat number is a duplicate to be discarded, and a missing number is a signal to stop and investigate.

How it connects to transactions

Transactions use the same producer ID, epoch, and sequence numbers that idempotence uses. What transactions add is the coordinator, which makes the producer ID survive restarts through the stable transactional ID, and which decides the final fate of all the numbered messages in a transaction. Idempotence is the foundation, and the coordinator is the roof.

?
Good to Know

Modern producers turn idempotence on by default. Even if you never use transactions, this quiet feature already protects your retries from creating duplicates within a partition.

Three Names: Transactional ID, Producer ID, and Epoch

These three labels are easy to mix up. Here is a simple way to keep them apart.

LabelWho chooses itHow long it lastsWhat it is for
Transactional IDYour applicationStable across restarts, until it expires from idlenessNames the logical producer so the coordinator can recognize it after a crash
Producer IDThe coordinatorTied to the transactional IDUsed inside messages so partitions can detect duplicates
EpochThe coordinatorGoes up each time a new instance takes overLets the system lock out older copies of the same producer
Everyday Analogy

Think of a school badge system. The transactional ID is a person’s permanent name, such as “Asha Verma.” The producer ID is the badge number printed on the badge. The epoch is the badge’s version sticker. When a new badge is issued because the old one was lost, the version sticker goes up, and any security guard who sees the old version knows to refuse it.

Why a stable name is required

When a producer crashes, a new instance starts and uses the same transactional ID. The coordinator sees a familiar name, gives it the same producer ID, and raises the epoch. In doing so, it also cleans up: if the earlier instance left a transaction half-finished, the coordinator aborts it before letting the new instance continue. This is how a restart avoids leaving stale work behind.

i
Tip

Choose transactional IDs that are stable and meaningful, tied to the job the producer does, for example the name of the task plus the input partition it handles. Random IDs generated at every start defeat the whole purpose.

The Life Cycle of a Transaction: States

Every transaction moves through a small set of named states, and the coordinator records each move.

stateDiagram-v2
  [*] --> Empty
  Empty --> Ongoing : first partition added
  Ongoing --> Ongoing : more partitions or offsets added
  Ongoing --> PrepareCommit : producer asks to commit
  Ongoing --> PrepareAbort : producer asks to abort or timeout expires
  PrepareCommit --> CompleteCommit : markers written to all partitions
  PrepareAbort --> CompleteAbort : markers written to all partitions
  CompleteCommit --> Empty : ready for next transaction
  CompleteAbort --> Empty : ready for next transaction
  Empty --> Dead : transactional ID expires from idleness
    
Figure 2: The main states the coordinator records for each transactional ID.
State 1

Empty

No transaction is open. The producer is registered and waiting to begin.

State 2

Ongoing

A transaction is open and collecting writes and offset updates.

State 3

Prepare Commit / Prepare Abort

The decision has been written to the log. It can no longer be changed, only carried out.

State 4

Complete Commit / Complete Abort

Every involved partition has received its marker. The transaction is finished.

State 5

Dead

The transactional ID has been idle so long that the coordinator forgot it.

State 6

Prepare Epoch Fence

A temporary state used while the coordinator locks out an older copy of the producer.

Everyday Analogy

Think of sending a parcel: “being packed” (Ongoing), “sealed and label printed” (Prepare), “delivered to every address” (Complete). Once the label is printed and the seal is on, the parcel cannot be re-packed; it can only be delivered.

Inside the Coordinator’s Notebook: What Is Stored for Each ID

A closer look at the small record that the coordinator keeps for every transactional ID.

For each transactional ID, the coordinator keeps one small record, both in memory and in the transaction log. Think of it as an index card. It is not big, but it holds everything needed to recover the transaction from any point.

Field on the cardWhat it holdsWhy it is needed
Transactional IDThe application-chosen nameThe key that finds the card
Producer IDThe number stamped on the producer’s messagesSo partitions can recognize the producer
Producer epochThe current version of the producerTo lock out older copies
Transaction timeoutThe limit the producer asked forTo know when to abort a stuck transaction
StateEmpty, Ongoing, Prepare, Complete, or DeadTo know what to do next after a crash
Partition listEvery partition touched by the open transactionTo know where to send markers
Start and last update timesWhen the transaction began and last changedTo apply timeouts and expire idle IDs
Everyday Analogy

A hospital keeps one chart per patient at the foot of the bed. The chart tells any new nurse the patient’s name, the current treatment stage, and what to do next. The nurse on the night shift needs no extra briefing, because the chart is complete.

Every change is a new card

The coordinator never edits a card in place. Each change, such as adding a partition or moving to a Prepare state, is written as a new version of the card at the end of the transaction log. Because the log is compacted, older versions of the same card are cleaned up over time. When a new coordinator loads its partition, it reads the cards in order, and the last version of each card wins.

Why the state names matter for recovery

After a crash, the state on the card tells the new coordinator exactly what remains to be done. An Ongoing card means “wait for the producer, or abort on timeout.” A Prepare card means “the decision is made, finish delivering markers.” A Complete card means “nothing to do.” This simple rule turns recovery from a puzzle into a lookup.

i
Tip

When you see confusing behavior after a broker restart, think of the card: which state was it in, and what does that state say the coordinator should do next? Most puzzles become simple.

The Whole Flow, Step by Step

One complete transaction from the first request to the final marker.

sequenceDiagram
  participant P as Producer
  participant TC as Transaction Coordinator
  participant TL as Transaction Log
  participant PL as Partition Leaders
  P->>TC: FindCoordinator for my transactional ID
  TC-->>P: I am the coordinator
  P->>TC: InitProducerId
  TC->>TL: Save producer ID and new epoch
  TC-->>P: Producer ID and epoch
  Note over P: beginTransaction is a local step
  P->>TC: AddPartitionsToTxn for orders-0 and stock-3
  TC->>TL: Save state Ongoing with partitions
  TC-->>P: OK
  P->>PL: Produce messages marked as transactional
  PL-->>P: Acknowledged
  P->>TC: EndTxn commit
  TC->>TL: Save PrepareCommit
  TC-->>P: Commit accepted
  TC->>PL: WriteTxnMarkers commit
  PL-->>TC: Markers written
  TC->>TL: Save CompleteCommit
    
Figure 3: The messages exchanged during a successful transaction.
1

Find the coordinator

The producer asks any broker “who is the coordinator for my transactional ID?” and gets an address.

2

Initialize

The producer asks the coordinator for its producer ID and epoch. Old unfinished work is cleaned up here.

3

Begin

The producer marks the start of a transaction. This step happens inside the client and needs no message to the broker.

4

Register partitions

Before writing to a new partition, the producer tells the coordinator, which records it in the log.

5

Write the data

Messages go straight to partition leaders, flagged as part of a transaction.

6

Ask to end

The producer asks the coordinator to commit or abort.

7

Decision then delivery

The coordinator writes the decision, replies, and then writes markers to every partition. Finally it records completion.

Everyday Analogy

Booking a group holiday through a travel agent: you register with the agent, tell the agent which hotels and flights you will use, book each one, and finally say “confirm all.” The agent writes the confirmation in her book first, then telephones every hotel and airline to finalize.

Step 1 and 2: Finding the Coordinator and Getting an Identity

The first conversation sets up everything that follows.

When the application starts, it creates a producer with a transactional ID and calls the initialization command. Behind the scenes, the client asks any broker to find the coordinator for that ID. The broker computes the partition of the transaction log that owns the name and answers with the address of that partition’s leader.

The producer then asks that broker for an identity. The coordinator looks in its memory for this transactional ID. If it has never seen the name, it creates a fresh producer ID and starts the epoch at zero. If it has seen the name before, it keeps the producer ID and increases the epoch by one. It writes the result into the transaction log, waits until the copies are safe, and replies.

New name

First time

A new producer ID and epoch zero. The state is Empty.

Known name

Restart or takeover

Same producer ID, higher epoch. Any earlier open transaction is finished off first.

Cleaning up the past

If the previous instance was in the middle of a transaction, the coordinator must finish it before answering. If the old transaction had already reached the “prepare commit” state, the coordinator completes the commit. If it was only ongoing, the coordinator aborts it. Only when the slate is clean does the new producer get its new epoch. This is how a restarted program never sees half-finished work from its earlier self.

Everyday Analogy

A new night guard arriving at a warehouse first gets a walk-through: “Here are the doors the last guard left open. We closed them before you took over.” Only then does the guard start the shift with a fresh badge.

i
Tip

Call the initialization command once per producer, right after creating it. Doing it again later in the same run is wasteful and can raise the epoch unnecessarily.

Step 3: Registering Partitions and Writing Data

The coordinator must know every partition a transaction touches, or it cannot finish the job later.

Suppose the producer wants to write to the “orders” topic, partition 0, for the first time within the current transaction. Before sending a single message, the producer tells the coordinator “I will write to orders-0.” The coordinator changes the transaction’s state to Ongoing if needed, records the partition in its list, saves the update in the log, and replies. Only then does the producer send the messages.

1

Register once per partition

The producer asks only the first time it touches a partition within a transaction. Later writes to that partition need no new registration.

2

Messages carry the identity

Every message is stamped with the producer ID, the epoch, and a sequence number, and it is flagged as transactional.

3

Partition leaders store them normally

The messages go into the partition log in order, replicated like any other. The messages exist on disk but are not yet visible to strict readers.

4

The coordinator keeps the list

The list of registered partitions is what the coordinator will later use to send its markers.

Everyday Analogy

Before you leave luggage at different hotels on a tour, you tell the tour manager the name of each hotel. When the tour ends, the manager knows exactly which hotels to inform that the trip is finished. A hotel that was never registered would never hear the news.

!
Watch Out

Newer Kafka releases add extra checks on the broker side, so a partition leader can confirm that a partition truly belongs to the transaction before accepting messages. This guards against rare delayed messages from a slow old request. Keep clients and brokers reasonably current to benefit.

Step 4: Committing, Kafka’s Two-Phase Approach

The coordinator decides first, and delivers second. That order is the secret of safety.

flowchart TD
  A["Producer asks to commit"] --> B["Coordinator saves PrepareCommit in the transaction log"]
  B --> C{"Saved on enough copies?"}
  C -->|"No"| B
  C -->|"Yes"| D["Point of no return - transaction WILL commit"]
  D --> E["Reply to producer - commit accepted"]
  D --> F["Send commit markers to every registered partition"]
  F --> G{"All partitions confirmed?"}
  G -->|"No - retry"| F
  G -->|"Yes"| H["Save CompleteCommit in the transaction log"]
  H --> I["Transaction finished - ready for next one"]
    
Figure 4: Commit is decided first and then delivered to every partition.
1

Phase one: decide and record

The coordinator writes “PrepareCommit” to the transaction log. When enough copies confirm, the decision is permanent. Even if the coordinator broker dies a moment later, the next coordinator will read this entry and finish the commit.

2

Tell the producer

Now the coordinator can safely tell the producer that the commit was accepted. From this point the producer does not need to wait for the markers.

3

Phase two: deliver the markers

The coordinator sends a small commit marker to the leader of every partition on the registered list. Each leader appends the marker to its log.

4

Record completion

After every partition confirms, the coordinator writes “CompleteCommit.” The bookkeeping for this transaction is done.

Everyday Analogy

A judge writes the verdict in the court record first. Only then do clerks carry copies to every office that needs to act. If a clerk falls ill, another clerk picks up the record and delivers the copies. Nobody can argue about what the verdict was, because it is written down.

Why the order matters

If the coordinator delivered markers first and wrote the decision later, a crash in between could leave some partitions with “commit” and the coordinator with no memory of deciding. By writing first, the decision cannot get lost, and delivering the markers becomes a job that can always be repeated safely.

Aborting: The Same Path, the Opposite Result

When work must be cancelled, the coordinator follows the same careful steps.

A transaction can be aborted by the producer itself, for example when an error occurs in the business logic. It can also be aborted by the coordinator, for example when the transaction stays open longer than allowed, or when a new instance of the same producer takes over. The path mirrors the commit: the coordinator saves “PrepareAbort,” delivers abort markers to every registered partition, and finally saves “CompleteAbort.”

QuestionCommitAbort
Who asks?The producerThe producer, or the coordinator on timeout or takeover
Decision saved in the transaction log first?YesYes
Marker written to every registered partition?Yes, a commit markerYes, an abort marker
What do strict readers see?The transaction’s messagesNone of the transaction’s messages
Are the messages deleted from disk?Not applicableNo, they remain until normal cleanup, but they are skipped by strict readers
Everyday Analogy

Cancelling a group holiday works the same as confirming one, only backwards: the agent records the cancellation and telephones every hotel. The hotels do not throw away the paperwork; they mark it “cancelled” so nobody mistakes it for a live booking.

?
Good to Know

Aborted messages take up disk space until Kafka’s usual retention removes them. If your application aborts very often, this wasted space and the extra skipping work for readers become a real cost, so treat frequent aborts as a bug to fix.

Transaction Markers and Control Records

The tiny records that tell every reader whether a transaction won or lost.

A transaction marker is a special record placed in a partition’s log at the end of a transaction. It contains no business data. It only says “the transaction from producer ID so-and-so has been committed” or “…aborted.” Because it is a real record in the log, it occupies an offset number, which is why you may notice that offsets sometimes skip a number in transactional topics. Readers never see the markers as messages; they use them as signposts.

Data

Transactional data records

Your real messages, stamped with the producer ID, epoch, and a transaction flag.

Control

Commit marker

Says the earlier flagged messages from this producer are now official.

Control

Abort marker

Says the earlier flagged messages from this producer must be ignored by strict readers.

Everyday Analogy

In a book of receipts, a clerk stamps “VOID” or “PAID” on the last page of a batch. Readers do not read the stamp as a receipt, but they use it to know how to treat all the receipts before it.

Why markers live in each partition

A reader only talks to partition leaders, not to the coordinator. So the outcome must be written where the reader can see it: in the partition itself. The coordinator’s log holds the master truth, and the markers are the copies of the truth distributed to each place that needs it.

!
Watch Out

Programs that count messages by subtracting offsets will be off by a little in transactional topics because of these markers. Never assume that offsets are consecutive messages.

Reading Committed Data: Isolation Levels and the Stable Offset

The other half of the promise: readers must choose to respect transactions.

Consumers have a setting called the isolation level. In the default level, called read-uncommitted, a consumer sees every message as soon as it is written, including messages of open transactions and even messages of transactions that will be aborted later. In the strict level, called read-committed, the consumer sees only messages of committed transactions, plus ordinary messages that were not part of any transaction.

1

The last stable offset

Each partition leader tracks a position called the last stable offset. It sits just before the earliest transaction that is still open. Strict readers are never given anything beyond it.

2

An index of aborted transactions

When the broker sends a batch of messages, it also lists which transactions in that batch were aborted.

3

The client filters

The consumer library uses that list to drop the aborted messages before your code sees them.

4

Order is preserved

Messages appear in offset order, and a committed transaction becomes visible at the position of its data, not its marker.

Everyday Analogy

Imagine a printing press that lays out pages in order but keeps one page “under review.” Strict readers cannot go past the page under review, even if later pages are already finished. They wait until the review is done, then move forward together.

The cost of being strict

Because strict readers cannot go past the last stable offset, a single long-running open transaction can hold back everything after it in that partition, even messages from other, already committed transactions. This is why transactions should be short. A transaction that stays open for minutes creates minutes of delay for every strict reader of the partitions it touched.

i
Tip

Any consumer that is meant to see only reliable results, such as a downstream payment service, must use the strict setting. Otherwise it may act on results that are later cancelled.

A Worked Example: Offsets, Markers, and the Stable Offset

Let us watch one partition through a short story with real numbers.

Imagine one partition that is empty. Two transactional producers, Asha and Ravi, write to it at about the same time. We will follow the offsets one by one and see what a strict reader and a normal reader each see at every moment.

MomentWhat happens in the partitionLast stable offsetStrict reader seesNormal reader sees
1Asha writes three messages at offsets 0, 1, and 2 inside her transaction0Nothing yetOffsets 0 to 2
2Ravi writes two messages at offsets 3 and 4 inside his transaction0Nothing yetOffsets 0 to 4
3Asha commits. A commit marker is written at offset 53 (Ravi is still open)Offsets 0 to 2Offsets 0 to 4
4Ravi’s transaction is still open and time passes3Nothing new, even if more messages arrive afterEverything, including later messages
5Ravi aborts. An abort marker is written at offset 67 (nothing open)Offsets 3 and 4 are skipped as aborted; markers hiddenOffsets 3 and 4 still appear as ordinary messages
1

Ravi’s open transaction is the wall

Even though Asha’s work was committed, the strict reader cannot pass offset 3 while Ravi’s earliest message sits there without a marker. Asha’s messages before that wall are fine, but anything after it waits.

2

Markers use up offsets

Offsets 5 and 6 belong to markers. No business message lives there, which is why offsets in transactional partitions have small gaps.

3

The filter happens in the client

The broker hands the strict reader the messages up to the stable offset together with a list of aborted transactions. The library then drops the aborted ones.

Everyday Analogy

Two students are handing in homework to the same tray. The teacher only marks pages once each student’s stack is sealed. One student’s stack is sealed early, but the teacher cannot rush past the second student’s half-finished stack that sits in the middle of the tray, so marking pauses at that point.

!
Watch Out

This example shows why one slow producer can slow strict readers of a shared partition. If several transactional producers write to the same partition, the slowest open transaction sets the pace for everyone reading strictly.

Consume, Transform, Produce: Saving the Bookmark Inside the Transaction

This is where transactions deliver true exactly-once processing inside Kafka.

In an ordinary program, the consumer saves its bookmark in one step and the producer writes outputs in another. A crash between the steps causes either lost work or duplicated work. In a transactional program, the producer tells the transaction “please include these bookmark updates,” and the bookmark and outputs commit together or not at all.

1

Read a batch

The consumer reads messages, with automatic bookmark saving turned off and with the strict isolation level.

2

Begin and write

The producer begins a transaction and writes the results to the output topics.

3

Add the bookmark to the transaction

The producer tells the coordinator that the transaction includes bookmark updates for a certain consumer group. The coordinator registers the internal bookmark partition as part of the transaction.

4

Send the bookmark values

The producer sends the new offsets to the group’s coordinator, marked as part of the transaction, so they stay pending.

5

Commit

The transaction coordinator commits, and markers are written to the output partitions and the bookmark partition alike. The new bookmark and the outputs become real together.

Everyday Analogy

A warehouse worker takes items from an inbox, packs them into outgoing boxes, and moves the “done up to here” flag on the inbox. If all three actions are in one sealed tray, then a crash means the whole tray is restarted, never a half-tray.

?
Good to Know

The producer also passes the consumer group’s current membership details when saving the bookmark. This lets Kafka reject a bookmark update from a consumer that has already been removed from the group, which closes another door to duplicates.

Zombies and Fencing: Locking Out Old Copies

What if an old instance of your program wakes up after a new one has already taken over?

Programs sometimes freeze for a long time because of a network pause, a heavy garbage collection, or a stuck machine. Meanwhile, the orchestration system may decide the program is dead and start a replacement. Later the frozen program wakes up and continues as if nothing happened. Such a revived old instance is called a zombie. If two copies write the same outputs, duplicates and confusion follow.

1

The replacement starts

It uses the same transactional ID and asks for an identity. The coordinator raises the epoch, say from 4 to 5, and aborts any leftover transaction of the old one.

2

The zombie wakes up

It still believes it has epoch 4 and tries to add partitions or send messages.

3

Everyone compares epochs

The coordinator and partition leaders know the newest epoch is 5. A request stamped with 4 is refused with a “producer fenced” error.

4

The zombie must stop

The client treats this error as fatal and shuts down that producer, leaving the replacement in charge.

Everyday Analogy

A security guard finds that the old key card no longer opens any door because the building issued a new card version. The former employee can walk up to every door and press the card, but none opens. Only the holder of the newest version passes.

Why this is such a strong protection

Fencing does not depend on clocks or on guessing who is alive. It depends on a number that only goes up and that every part of the system checks. This idea, used across many distributed systems, is one of the most reliable ways to prevent two active copies from causing damage.

!
Watch Out

Never run two live instances with the same transactional ID on purpose. If you do, they will fence each other again and again, and neither will make progress. Each running producer needs its own ID, or a clear takeover relationship.

Timeouts and Expiration: Stopping Stuck Transactions

Every open transaction is a small promise to finish soon. The coordinator enforces that promise.

Setting ideaWhere it livesMeaningTypical default
Transaction timeoutProducerHow long the producer allows a transaction to stay open before the coordinator may abort itAbout one minute
Maximum transaction timeoutBrokerThe largest timeout any producer is allowed to requestAbout fifteen minutes
Abort cleanup intervalBrokerHow often the coordinator looks for timed-out transactionsEvery few seconds
Transactional ID expirationBrokerHow long an idle transactional ID is remembered before it is forgottenAbout seven days

These are typical defaults and may vary by version, so check the documentation for yours. The important idea is that the coordinator does not trust producers to finish. If the producer disappears with a transaction open, the coordinator aborts it after the timeout, which releases the readers who were waiting behind it.

Everyday Analogy

A restaurant holds a table for a party for fifteen minutes. If nobody arrives, the host releases the table so the queue can move. Without this rule, one no-show would block all the customers behind them.

Choosing the timeout

Set the transaction timeout comfortably longer than your longest normal transaction, but not much longer. Too short, and healthy but slow transactions get aborted. Too long, and a crashed producer can hold back strict readers for a long time. If your transactions normally take a second, a timeout of several seconds to a minute is usually plenty.

Forgetting idle IDs

If a transactional ID is unused for the full expiration time, the coordinator forgets it to save space. When the ID returns later, it is treated as new. This matters for rare batch jobs that run less often than the expiration time. They should not depend on remembered state between runs.

When the Coordinator Broker Fails: Failover and Recovery

A referee who can collapse must have a replacement who knows exactly where the game stood.

Because the coordinator’s memory lives in a replicated log, losing a coordinator broker never loses the transactions. When the broker that leads a partition of the transaction log fails, the cluster’s own leader election picks a new leader for that partition from the copies that are fully caught up. In modern Kafka, the active controller of the KRaft controller quorum performs this choice and records it in the metadata log, exactly like any other partition.

1

The coordinator broker goes silent

Its heartbeats to the controller stop. After the session timeout, the controller declares the broker unavailable.

2

A new leader is chosen for the log partition

The controller picks a fully caught-up copy and records the change. The chosen broker becomes the new coordinator for all transactional IDs stored in that partition.

3

The new coordinator loads its state

It reads the transaction log partition from the start of the compacted data and rebuilds an in-memory table: every ID, its producer ID, epoch, state, and registered partitions.

4

Unfinished decisions are completed

Any transaction found in a Prepare state is finished: the markers are sent again to every registered partition. Repeating a marker is harmless.

5

Producers reconnect

Producers that receive a “not the coordinator” error ask again, find the new coordinator, and continue their work.

Everyday Analogy

If a chess referee has to leave, a new referee takes the official scoresheet, reads what has happened, and continues. If the old referee had just announced “checkmate” and written it down, the new referee simply completes the ceremony.

Why loading takes a moment

Rebuilding memory from the log is not instant. For a coordinator responsible for a large number of transactional IDs, loading can take noticeable time, during which requests for those IDs receive a “coordinator loading” answer and the producer retries. Good clients handle this automatically. Keeping the number of transactional IDs reasonable and the log compacted keeps loading fast.

i
Tip

Test broker failure during a busy period in a staging environment. Watch how long producers stall and confirm that no committed data is lost and no transaction is left half-delivered.

Failure Scenarios, One by One

A design is only as good as the way it behaves when things go wrong.

Scenario 1: The producer crashes before committing

The transaction stays open until its timeout passes, or until a new instance with the same transactional ID starts. Either way, the coordinator aborts it. Abort markers are written, strict readers skip the messages, and the new instance redoes the work from the saved bookmark.

Scenario 2: The producer crashes right after asking to commit

If the coordinator had already saved the decision, the commit will be completed even though the producer is gone. If the request never arrived, the transaction is aborted later. Both outcomes are clean and consistent.

Scenario 3: The coordinator crashes after saving PrepareCommit

The new coordinator reads the log, sees the decision, and sends the markers again. The transaction commits exactly as promised.

Scenario 4: The coordinator crashes before saving the decision

There is no decision to complete. The producer’s commit request fails with a retriable error, the producer finds the new coordinator, and it asks again. If it can no longer be sure, it aborts and redoes the batch.

Scenario 5: A partition leader is unavailable when markers are sent

The coordinator keeps retrying until a new leader for that partition is elected. The transaction stays in the Prepare state during that time. Strict readers of that partition wait, but no data is lost.

Scenario 6: A network split isolates a zombie producer

When it returns, its old epoch is rejected. It is fenced and must stop. The replacement continues cleanly.

Scenario 7: Too few in-sync copies of the transaction log

If fewer than the required number of copies are in sync, the coordinator cannot safely save its state, and transaction operations for those IDs fail until the copies recover. This is a deliberate choice: safety first.

!
Watch Out

Setting the transaction log’s minimum in-sync copies to one to “improve availability” quietly removes the safety of the whole feature. Keep the defaults of three copies with two required in sync on production clusters.

Hanging Transactions: The Quiet Troublemaker

A transaction that neither finishes nor gets cleaned up can freeze readers for a long time.

A hanging transaction is a transaction whose data messages are in a partition, but whose marker never arrived. Strict readers cannot move past it, because the last stable offset sits in front of it. Normally the coordinator’s timeout prevents this. In rare cases, such as certain software bugs, unusual failure sequences, or very old client versions, a transaction can be left hanging even though the coordinator no longer remembers it.

1

Notice the symptom

Strict consumers on a partition stop making progress, and their lag grows, while ordinary readers of the same partition keep going.

2

Look for open transactions

Kafka ships an administration tool that can list transactions, describe a partition’s active producers, and search for hanging ones.

3

Confirm it is really stuck

Check the transaction’s age against the timeout. A young transaction is probably just working.

4

Resolve it carefully

The same tool can ask for a stuck transaction to be aborted. Do it only after you are sure that the owning producer is gone.

Everyday Analogy

A “reserved” sign is left on a table long after the customers left. The queue builds up outside because the host does not know the sign is stale. Someone has to walk over, check that nobody is sitting there, and remove the sign.

i
Tip

Newer Kafka releases add extra protection to shrink the chance of hanging transactions. Keeping clients and brokers up to date is the best prevention, and a short runbook for finding and aborting hanging transactions is the best cure.

Exactly-Once in Kafka Streams, Flink, and Kafka Connect

Most teams never call the transaction commands directly. They use them through bigger tools.

Streams

Kafka Streams

One configuration switch turns on exactly-once processing. The library handles transactional IDs, reading strictly, saving bookmarks in the transaction, and committing on a short timer. Its newer mode uses one producer per processing thread instead of one per task, which greatly reduces the number of transactional IDs.

Flink

Apache Flink

Flink’s Kafka sink can write with transactions that commit when a checkpoint completes, so output becomes visible only when the state of the whole job is safely saved.

Connect

Kafka Connect

Newer versions can run source connectors with exactly-once delivery by writing the source data and the connector’s progress notes in one transaction.

Custom

Your own services

Any program that consumes, transforms, and produces can use the transactional client directly for the same effect.

Everyday Analogy

You do not need to know how a car engine’s fuel injection works to drive to work. Streams and Flink are the car. The coordinator is the injection system beneath the hood, working quietly every time you press the accelerator.

How the commit timer affects everything

With exactly-once processing turned on, Streams commits often, by default about every hundred milliseconds instead of every thirty seconds. Each commit is a full transaction. A shorter interval reduces delay for strict readers but increases the number of transactions per second, so it costs more coordinator work. A longer interval does the opposite. This one setting is the main dial for trading latency against overhead.

?
Good to Know

Because these tools hide the details, problems often appear as “consumers are lagging” or “processing is slow” rather than as an obvious transaction error. Knowing about the coordinator helps you connect the symptom to the cause.

The Limits of Exactly-Once: Outside Systems

Kafka transactions protect what happens inside Kafka. Everything outside needs extra care.

Suppose a program reads an order from Kafka and also sends a confirmation email. The email service is not part of the Kafka transaction. If the program sends the email and then crashes before committing, the restart repeats the work and sends the email again. Kafka cannot take back an email.

TechniqueHow it helpsAnalogy
Make the outside action idempotentGive each action a unique key so a repeat is ignoredA ticket number that a cinema accepts only once
Outbox patternWrite the intended action into Kafka inside the transaction, and let a separate reliable worker perform itLeaving a note in an out-tray for the postman
Transactional sink connectorsStore the result and the position together in the target databaseStamping the receipt and the register in one motion
ReconciliationCompare both sides regularly and fix differencesAn end-of-day cash count
!
Watch Out

Do not tell business colleagues that “Kafka guarantees exactly once” without explaining the boundary. Money and messages leaving Kafka need their own protection.

Performance and Scalability

Transactions add safety, and safety costs a little work. Here is where the cost goes.

Extra calls

Coordinator round trips

Each transaction needs a few extra messages: registering partitions and ending the transaction. These are small but add up when transactions are tiny and frequent.

Extra writes

Log and marker writes

The coordinator writes several entries to its log per transaction, and every involved partition receives a marker.

Latency

Visibility waits for commit

Strict readers see data only after the commit. Shorter transactions mean lower delay.

Batching

Bigger batches, fewer transactions

Doing more work per transaction spreads the fixed cost across more messages, which raises throughput.

Finding a balance

Very small transactions, such as one message each, waste effort: the coordinator work dwarfs the data. Very large transactions hold back strict readers and risk timeouts. A good rule is to group as many messages as fit within your acceptable delay, often somewhere between tens of milliseconds and a few seconds.

How the design scales

The coordinator’s work is spread across the fifty partitions of the transaction log, and therefore across many brokers. The data itself never passes through the coordinator; it goes straight to partition leaders. So adding brokers and partitions increases capacity without making one central machine a bottleneck. The main things that grow with scale are the number of transactional IDs and the number of partitions touched per transaction.

Everyday Analogy

A courier who takes one letter per trip spends the whole day on the road. A courier who collects a bag of letters makes the same trip but delivers hundreds. Batching is the same idea for transactions.

i
Tip

Measure before you tune. Record the transaction rate, the commit time seen by producers, and the lag of strict consumers. Then change one setting, such as the commit interval, and measure again.

Sizing the Transaction Log and the Coordinator Load

How much memory, disk, and attention does the coordinator need?

The coordinator keeps a small entry in memory for every transactional ID it manages, containing the producer ID, the epoch, the state, and the current list of partitions. Even a large number of IDs uses modest memory. The transaction log on disk is compacted, so it holds roughly one latest entry per ID, plus recent history not yet cleaned.

FactorWhat growsWhat to do
Number of transactional IDsMemory and loading time after failoverReuse stable IDs, avoid random new IDs per run, and let idle ones expire
Transactions per secondCoordinator requests and log writesBatch more work per transaction
Partitions per transactionMarkers to send and state to storeAvoid touching hundreds of partitions in one transaction
Transaction log partition countSpread of coordinator workChoose it once at the start, and never change it
Replication settings of the logDurability and availabilityKeep three copies with two required in sync
Everyday Analogy

A hotel front desk keeps a small card for each guest. A hotel with more guests needs more cards and more desk clerks, but it does not need a bigger building for the cards. What matters is how fast guests check in and out.

A special case: many short-lived IDs

An anti-pattern is to create a new transactional ID for every request or every job run. The coordinator then accumulates a huge number of entries that each live for a week before expiring. This wastes memory, slows failover, and adds no benefit. Design a small, stable set of IDs instead.

Advantages, Disadvantages, and Trade-Offs

An honest look at what transactions give you and what they cost.

Advantages

  • All-or-nothing writes across topics and partitions
  • No duplicates from retries or restarts inside Kafka
  • Bookmarks and outputs saved together
  • Old copies of a producer are locked out automatically
  • No separate service to run; the coordinator lives in the brokers
  • State survives failures because it is stored in a replicated log

Disadvantages

  • Extra latency, because readers wait for the commit
  • Extra coordinator work per transaction
  • Long or stuck transactions can hold back strict readers
  • Guarantee stops at the edge of Kafka
  • More settings to understand and monitor
  • Aborted data still uses disk until retention removes it
Correctness vs speed

Strict readers wait

You get clean results, but you accept a delay of about one transaction length.

Safety vs availability

Log needs enough copies

If the transaction log cannot be safely written, transactions pause instead of guessing.

Simplicity vs control

Use a framework or call directly

Frameworks hide details and reduce mistakes. Direct use gives control but needs more care.

Throughput vs latency

Batch size

Bigger transactions raise throughput but delay visibility. Smaller ones do the opposite.

Safe results are worth a short wait.

Versions and Improvements Over Time

Kafka’s transaction feature has been refined many times since it first appeared.

1

The first release (around version 0.11)

Idempotent producers, transactions, and the coordinator arrived together, based on the original design proposal known as KIP-98.

2

Simpler exactly-once for Streams (around 2.5)

A follow-up change allowed one producer per thread instead of one per task, using consumer group information to keep fencing safe.

3

Better tooling (around 3.0)

An administration tool arrived to list, describe, and clean up transactions, including finding hanging ones.

4

Stronger server-side defenses (3.x to 4.0)

Later proposals, known as KIP-890, add checks on the broker so that delayed or stray requests cannot corrupt a transaction, and reduce the chance of hanging ones.

Everyday Analogy

Think of a bank’s payment system. The first version worked, but over the years the bank added better receipts, faster checks, and extra guards at the doors. The idea stayed the same, but the walls got thicker.

!
Watch Out

Exact version numbers and defaults change over time. Always confirm behavior in the release notes and documentation for the versions you run, and keep clients and brokers close to each other in version.

Configuration Checklist for a Production Cluster

The settings that matter, grouped by who owns them and described by their meaning.

OwnerSetting ideaRecommended approach
BrokerNumber of partitions in the transaction logKeep the default unless you have a very large cluster, and never change it later
BrokerReplication factor of the transaction logThree copies for production
BrokerMinimum in-sync copies of the transaction logTwo, so a write is safe on at least two brokers
BrokerMaximum transaction timeoutSet an upper limit that matches your longest legitimate transaction
BrokerTransactional ID expirationLonger than the gap between runs of your slowest job
ProducerTransactional IDStable, unique per logical producer
ProducerTransaction timeoutA little longer than your longest normal transaction
ProducerAcknowledgmentsWait for all in-sync copies
ProducerIdempotenceOn, which is required for transactions
ConsumerIsolation levelStrict, for anything that must see only committed data
ConsumerAutomatic bookmark savingOff in consume-transform-produce programs, since the transaction saves bookmarks
StreamsProcessing guaranteeUse the newest exactly-once mode available in your version
Everyday Analogy

Before opening a new restaurant you check the fire exits, the fridge temperature, the staff schedule, and the cash register. Each item is simple, but forgetting one causes trouble on opening night.

Small cluster warning

Small development clusters with fewer than three brokers cannot honor the default replication of the transaction log. Some people lower the settings in development to make things start. That is fine for learning, but never carry those reduced settings to production, because the safety of the whole feature depends on them.

!
Watch Out

The setting names and defaults vary between versions. Verify each one in the documentation for your release, and store your final settings in version control so changes are visible and reviewable.

Security of Transactions

A producer that can run transactions can influence what many readers see, so its rights deserve care.

Kafka’s access rules treat the transactional ID as a resource, just like a topic. A producer needs permission to use its own transactional ID, permission to write to the topics it touches, and, when it saves consumer bookmarks inside the transaction, permission to read the consumer group. Idempotent writes also require a cluster-level right on older versions.

1

Grant the transactional ID right narrowly

Give each application permission only to its own transactional IDs, using a prefix such as the application name. One application should never be able to use another’s ID.

2

Grant write access only to needed topics

Limit the topics a transactional producer may write to. A compromised producer could otherwise commit unwanted data anywhere.

3

Encrypt and authenticate connections

Use encrypted connections and strong identities between clients and brokers, and among brokers, since the coordinator sends markers between brokers.

4

Protect the internal log

Nobody except the brokers themselves should be able to read or write the transaction log topic directly.

5

Restrict administrative tools

The tool that can abort transactions is powerful. Limit who may run it and record every use.

Everyday Analogy

In a company, only a few people are allowed to sign cheques, and each person can sign only for their own department. A transactional ID is like a signing authority: it should be personal to the job and never shared casually.

!
Watch Out

Giving every application permission for all transactional IDs “to make it work” lets one faulty program fence, abort, or disturb the transactions of all the others.

Monitoring, Logging, and Metrics

What to watch so you notice trouble before your users do.

Monitoring transactions comes down to four questions: Are transactions completing? Are they fast? Are any stuck? Are the coordinators healthy? Kafka exposes measurements for each, though exact names differ between versions, so treat this as a guide to the ideas.

What to watchWhere it appearsWarning sign
Time to begin, commit, and abortProducer metricsCommit time creeping up
Rate of aborted transactionsApplication metrics and logsA rising share of aborts
Producer fenced errorsApplication logsRepeated fencing, which suggests two instances share an ID
Lag of strict consumers versus normal consumersConsumer metricsStrict readers far behind on a partition
Age of the oldest open transactionAdministration tool or custom checkOlder than your transaction timeout
Transaction log partition healthBroker metrics: under-replicated or offline partitionsAny non-zero value
Marker delivery queue sizesBroker metrics for the transaction marker channelGrowing queues, meaning markers are not being delivered
Coordinator loading and not-coordinator errorsClient and broker logsLong or frequent bursts

Logs that tell the story

Coordinator logs mention state changes, timeouts, and epoch bumps for transactional IDs. Producer logs mention fencing, coordinator changes, and retries. Read them together in time order. A pattern such as “coordinator moved, loading, producer retried, committed” is normal. A pattern such as “fenced, restarted, fenced, restarted” points to two copies of the same program.

i
Tip

Add an alert on the age of the oldest open transaction. It is the earliest warning for hanging transactions, and it usually fires long before anyone complains about lag.

Tools and Day-to-Day Operations

A short map of what you will use, described by what each thing does.

Inspect

Transactions administration tool

Lists transactional IDs, describes their state and open partitions, shows active producers on a partition, and searches for hanging transactions.

Repair

Abort option

The same tool can abort a specific hanging transaction. Use it only after confirming the owner is gone.

Read log

Log dump tool

Shows the raw contents of a log, including markers, useful for deep investigation.

Topics

Topic tools

Confirm the transaction log topic’s partition count, copies, and health.

Groups

Consumer group tools

Show bookmarks and lag, which help compare strict readers with normal ones.

Producer

Client metrics

Show the time spent in each transaction step from the application’s point of view.

A daily and weekly routine

1

Daily glance

Check the abort rate, the oldest open transaction age, and strict consumer lag on the main pipelines.

2

After every broker restart

Confirm there are no under-replicated partitions in the transaction log and that producers reconnected.

3

Weekly review

Look for growth in the number of transactional IDs, and for applications with unusual fencing or abort patterns.

4

Quarterly rehearsal

Kill a coordinator broker in a test environment and measure recovery. Practice the hanging-transaction procedure.

Everyday Analogy

Keeping a car healthy means glancing at the dashboard every day, checking the oil weekly, and going for a service now and then. Transactions need the same gentle, regular attention.

Adopting Transactions in an Existing Pipeline

A gentle path from a simple at-least-once program to a transactional one.

1

Decide if you need it

List what would go wrong with duplicates or half-finished outputs. If the answer is “little,” simpler idempotent handling may be enough.

2

Upgrade the platform first

Bring clients and brokers to recent versions with the newer safety checks, and confirm the transaction log has three copies.

3

Turn off automatic bookmarks

Move progress-saving into the transaction, so the program controls when it happens.

4

Design the transactional IDs

Create a naming rule that stays stable across restarts and gives each running copy its own name.

5

Wrap the work loop

Read a batch, begin, write, add bookmarks, commit. On an ordinary error, abort and retry. On a fencing error, stop the producer.

6

Switch downstream readers to strict

Change consumers that depend on these results to see only committed data.

7

Test the failures

Kill the program mid-transaction, freeze it until it is replaced, and restart a broker. Check for no duplicates and no losses.

8

Roll out and watch

Start with one low-risk pipeline. Watch commit time, abort rate, and strict lag before moving to the next.

Everyday Analogy

Switching a shop from handwritten receipts to a till system is not done in one afternoon. You test it with a few customers, watch for errors, and only then use it for the whole shop.

i
Tip

Write a test that deliberately crashes the program at each step of the loop, before the write, after the write, and just before the commit. If your outputs are correct after every crash, your design is sound.

Best Practices and Design Patterns

Habits that keep transactions calm and predictable.

Identity

Stable, unique transactional IDs

One per logical producer, the same across restarts, never shared by two live instances.

Length

Keep transactions short

Commit within seconds. Short transactions mean small delays for strict readers and small losses if aborted.

Readers

Strict readers where it matters

Consumers that act on results should read only committed data.

Safety

Strong producer settings

Wait for all in-sync copies, allow retries, and keep idempotence on.

Log

Default log replication

Three copies with two in sync for the transaction log.

Timeouts

Sensible transaction timeout

A little more than your longest normal transaction, and never more than needed.

Errors

Handle errors properly

Abort and retry on ordinary errors, and shut down cleanly on fencing errors.

Boundary

Plan for the outside world

Design idempotent or outbox-style steps for anything that leaves Kafka.

Useful design patterns

Decide first, deliver second

The coordinator writes the decision durably before telling anyone. This pattern, found in many careful systems, makes recovery a simple matter of reading the record and finishing the job.

Fencing token

A number that only goes up and is checked everywhere. It removes the need to guess whether an old process is really dead.

Log as memory

Store the coordinator’s state in a replicated log. Recovery is replay, and there is no separate database to manage.

Timeout as a safety valve

Never let one silent participant block everyone else. A time limit turns an endless wait into a clean abort.

Anti-Patterns to Avoid

Three popular mistakes and the better ways to work.

ANTI-PATTERN 1Avoid
What people do

They create a new random transactional ID every time the program starts, or for every request.

Why it hurts

The coordinator cannot recognize the returning producer, so it cannot clean up leftovers or lock out zombies. Memory fills with useless entries, and failover slows down.

Better approach

Use a small set of stable IDs that describe the job, such as application name plus task or input partition.

ANTI-PATTERN 2Avoid
What people do

They hold a transaction open while waiting for slow outside calls, like a web service or a human approval.

Why it hurts

The last stable offset is stuck, strict readers stall, and the timeout may abort the work anyway.

Better approach

Finish the outside call first, then open a short transaction just to write the results. Or use the outbox pattern.

ANTI-PATTERN 3Avoid
What people do

They read with the default isolation level downstream of a transactional pipeline and assume the data is always final.

Why it hurts

Those readers can see messages of transactions that later abort, and can act on data that never becomes official.

Better approach

Configure downstream consumers as strict readers wherever correctness matters.

Common mistakes in short

  • Sharing one transactional ID between two running programs
  • Setting the transaction log’s in-sync requirement to one
  • Changing the transaction log’s partition count after use
  • Committing single-message transactions at very high rates
  • Ignoring aborted-transaction rates until disk use grows
  • Assuming exactly-once extends to email, payments, or databases

Troubleshooting Playbook

From symptom to likely cause to first check.

SymptomLikely causeFirst thing to check
Producer keeps getting “fenced”Two live instances share one transactional ID, or a restart loopHow the ID is generated and how many copies are running
Strict consumers stall on one partitionA long-running or hanging transactionAge of the oldest open transaction on that partition
Commit calls are slowSlow coordinator log writes, busy brokers, or too many partitions per transactionBroker disk and network health, and partitions touched per transaction
Errors saying “not coordinator” or “coordinator loading”The coordinator recently movedRecent broker restarts; these usually clear by themselves
Transactions time outTimeout too short, or the producer is stuckReal transaction duration compared with the timeout
Transaction operations fail cluster-wideTransaction log lacks enough in-sync copiesUnder-replicated partitions of the transaction log
Disk use grows on output topicsMany aborted transactionsAbort rate and the cause of aborts
Duplicates seen downstreamReaders are not strict, or the duplicate arises outside KafkaConsumer isolation setting, and idempotency of outside steps
i
Tip

Start with the simplest question: is one specific transaction stuck, or is everything slow? A single stuck transaction points to an application or a hanging case. Everything being slow points to brokers or the transaction log.

Real-World and Industry Examples

The same ideas appear in many well-known systems.

Stream processing at scale with Kafka Streams

Companies that build payment, fraud, and inventory pipelines on Kafka Streams turn on exactly-once processing so that each event affects the results once. Behind the scenes, this is the transaction coordinator committing outputs and bookmarks together many times per second.

Apache Flink jobs writing to Kafka

Flink’s Kafka sink can tie a Kafka transaction to each checkpoint. Output becomes visible only when the whole job’s state is safe, which is a classic two-phase commit design that maps naturally onto the Kafka coordinator.

Kafka Connect exactly-once sources

Source connectors that pull rows from a database or read files can, in newer versions, write records and their own progress notes in one transaction, so a crash never causes a gap or a repeat.

Financial and order-management systems

Banks, online retailers, and ride-hailing platforms are typical Kafka users where duplicates or half-finished updates cost real money. They usually combine Kafka transactions inside the event backbone with idempotent writes and reconciliation at the edges.

The wider pattern: two-phase commit

Relational databases, distributed databases, and messaging brokers all use variations of “record the decision, then deliver it.” Kafka’s approach is a streamlined version built on its own log.

?
Good to Know

Details of how specific companies configure their systems change over time and are often not public. Treat these examples as patterns rather than statements about particular deployments, and check current documentation.

A Day in the Life: QuickBite Payments

Everything you learned, told as one small story.

QuickBite is a food-delivery company. When a customer pays, a service called payment-processor reads a “payment-requested” event, checks the customer’s balance, and writes three things: a “payment-confirmed” event, a “ledger-entry” event, and a “notification-request” event. It also saves its bookmark on the input topic. All four must succeed together.

1

Morning: startup

The service starts with the transactional ID “payment-processor-3”. The coordinator recognizes the name, keeps its producer ID, and raises the epoch from 6 to 7. Nothing was left open from yesterday, so the state is clean.

2

Lunch rush: steady transactions

Every few hundred milliseconds the service batches a group of payments and commits them in one transaction. Strict downstream readers see confirmed payments only, a little after each commit.

3

Afternoon: a machine freezes

The service’s machine is overloaded and stops responding for a minute. The orchestration system starts a replacement with the same transactional ID. The coordinator raises the epoch to 8 and aborts the frozen instance’s open transaction.

4

The zombie wakes up

The old instance tries to continue with epoch 7. Every request is refused as fenced, and it shuts itself down. The replacement continues from the saved bookmark, so no payment is lost and none is confirmed twice.

5

Evening: a broker fails

The broker acting as the coordinator crashes. The controller elects a new leader for that partition of the transaction log. The new coordinator loads the state, finishes one commit that was already decided, and answers producers again within a few seconds.

6

Night: the email step

The notification service reads only committed events and sends emails using a unique request key, so a repeated event would not send a second email. The boundary of exactly-once is respected.

i
Tip

Notice how many failures the story contains, and how little the customers noticed. That is the point of the coordinator: to turn crashes into small delays instead of wrong money.

Glossary: Every Term in One Place

A quick reference for the words used in this guide.

TermSimple meaning
TransactionA group of writes that all become visible together or not at all
AtomicityThe all-or-nothing property
Transaction coordinatorThe broker-side referee that tracks and finishes transactions
Transactional IDA stable name chosen by the application for a logical producer
Producer IDA number the coordinator assigns to identify a producer in messages
EpochA counter that goes up when a new instance takes over, used to lock out old ones
Sequence numberA per-partition counter used to detect duplicate or missing messages
Idempotent producerA producer whose retries never create duplicates in a partition
Transaction logThe internal compacted topic that stores the coordinator’s state
CompactionKeeping only the newest record for each key
CommitMaking a transaction’s messages visible
AbortCancelling a transaction so its messages are ignored by strict readers
Transaction markerA special record that marks a transaction as committed or aborted in a partition
Control recordA record that carries a system signal, not business data
Isolation levelA consumer setting that decides whether uncommitted data is visible
Last stable offsetThe position before the earliest open transaction, beyond which strict readers cannot read
FencingRefusing requests from an outdated producer instance
ZombieAn old instance that wakes up after being replaced
Hanging transactionAn open transaction whose outcome was never delivered to a partition
Group coordinatorThe broker role that manages consumer groups and their bookmarks
Consume-transform-produceThe pattern of reading, processing, and writing results with a saved bookmark
Outbox patternRecording an outside action in Kafka first and performing it later by a reliable worker
Exactly onceEach input has its effect one time, inside a defined boundary

Frequently Asked Questions

Quick answers to the questions beginners ask most often.

Q1Is the transaction coordinator a separate server?
No. It is a role inside every broker. For each transactional ID, the broker that leads the matching partition of the transaction log plays the coordinator.
Q2Does my data pass through the coordinator?
No. Messages go directly to partition leaders. The coordinator handles only small control messages, which keeps it from becoming a bottleneck.
Q3What happens to messages of an aborted transaction?
They stay in the log until normal retention removes them, but strict consumers skip them.
Q4Do consumers need special settings?
Yes. To see only committed data, consumers must use the strict isolation level. The default level shows everything, including data of open or aborted transactions.
Q5What if my program crashes in the middle of a transaction?
The transaction is aborted, either by timeout or when a new instance with the same transactional ID starts. The restarted program redoes the work from the saved bookmark.
Q6Why must the transactional ID stay the same after a restart?
So the coordinator can recognize the returning producer, clean up its leftovers, and lock out any older copy by raising the epoch.
Q7Can a transaction span two Kafka clusters?
No. A transaction lives inside one cluster. Copying data between clusters needs separate techniques.
Q8Does a transaction lock the data so nobody else can write?
No. Other producers can write to the same partitions at the same time. A transaction controls when its own messages become visible, not who may write.
Q9How do transactions relate to Kafka Streams?
Streams uses them internally when exactly-once processing is turned on, handling transactional IDs, bookmarks, and commits for you.
Q10What is the difference between idempotence and transactions?
Idempotence removes duplicates for one producer session and one partition. Transactions add atomic writes across many partitions, stable identity across restarts, and atomic bookmark saving.
Q11Are transactions slow?
They add a small, predictable cost. With sensible batching, the impact on throughput is modest, and the main visible effect is a short delay before strict readers see data.
Q12Can I change the number of partitions of the transaction log later?
You should not. The mapping from names to partitions depends on the count, so changing it would confuse coordinators. Choose it once at the start.
Q13What role does the KRaft controller play?
It does not run transactions, but it elects the leader of each transaction log partition, and therefore decides which broker is the coordinator after a failure.
Q14Do I need transactions for every pipeline?
No. If duplicates are harmless or your consumers are idempotent, simpler at-least-once processing with idempotent handling can be enough and cheaper. Use transactions where atomic multi-topic results or strict duplicate avoidance matter.

Summary and Key Takeaways

The whole story in a few short lines.

Key Takeaways

  • A role, not a machine: the transaction coordinator is a component inside brokers, chosen per transactional ID by the leader of one partition of the internal transaction log.
  • Decide first, deliver second: the coordinator saves the commit or abort decision in its replicated log before sending markers to any partition, so no crash can lose or split the outcome.
  • Identity plus epoch: a stable transactional ID, a producer ID, and a rising epoch let Kafka clean up after crashes and lock out zombie producers.
  • Readers must opt in: only consumers with the strict isolation level hide open and aborted transactions, and they are held back by the last stable offset.
  • Bookmarks join the transaction: saving consumer offsets inside the transaction gives exactly-once processing from Kafka to Kafka.
  • Keep transactions short and stable: small, quick transactions, stable IDs, default log replication, and sensible timeouts prevent most problems, including hanging transactions.
  • Know the boundary: exactly-once holds inside Kafka only. Outside systems need idempotent actions, outbox patterns, or reconciliation.