What Are Transactions in Kafka?

What Are Transactions in Kafka?

How Kafka makes a group of writes succeed or fail together: idempotent producers, transactional IDs, commit markers, read committed consumers, exactly-once processing and the limits you must respect in production.

Imagine you are moving money from your savings account to your checking account. Two things must happen: the savings balance goes down, and the checking balance goes up. If the bank’s computer crashes after the first step but before the second, your money has vanished. If it crashes and then retries, you might be charged twice. Nobody accepts “sometimes the money disappears” as an answer. The solution is a transaction: a bundle of steps that either all happen or none of them happen.

Apache Kafka faces the same problem. A program often reads a message, does some work, and writes results to several places. If it fails halfway, some results exist and others do not, and a retry may create duplicates. Kafka transactions solve this by letting a program write to many topics and partitions as one atomic unit. In this guide you will learn what transactions are, why they exist, how idempotent producers and transaction coordinators work, how consumers avoid seeing half-finished work, what “exactly-once” truly promises, and where the guarantees stop.

1What Transactions Mean in Kafka

A Kafka transaction is a group of writes, and optionally offset commits, that become visible together or not at all.

Start with the building blocks. A message is a small record of something that happened, such as “order 91 was paid”. A topic is a named log that stores messages in order. Each topic is split into partitions, which are separate ordered logs that let many machines share the work. A producer is a program that writes messages, and a consumer is a program that reads them. An offset is the position number of a message inside a partition, and consumers remember offsets to know where they stopped reading.

A Kafka transaction lets a producer say: “Begin. Write these messages to topic A, partition 2; these to topic B, partition 5; and record that I have finished reading up to offset 1200 of my input. Now commit.” Kafka then guarantees that all of those writes become visible to transaction-aware readers together. If the producer changes its mind or crashes, the whole bundle is thrown away as if it never happened.

Everyday analogy

Think of posting several letters in a single registered envelope. Either the envelope arrives with every letter inside, or it is returned unopened. The receiver never sees an envelope containing only half of the letters. A Kafka transaction is that sealed envelope for messages: the contents are only revealed to readers when the sender confirms the whole package is complete.

Where transactions came from

Transactions were added in Kafka version 0.11, released in 2017, together with the idempotent producer. The goal was to allow stream processing applications to produce correct results even when machines fail and messages are retried. Since then, transactions have become the base for exactly-once processing in Kafka Streams and in newer Kafka Connect features.

All or none
the core promise of a transaction
0.11
Kafka version that introduced transactions
Kafka only
the boundary of the guarantee
i
Remember this

A Kafka transaction makes writes atomic across partitions and topics. It does not make your database, your email service or your payment gateway part of the same all-or-nothing bundle. We return to this important limit later.

2Why Kafka Needs Transactions: Delivery Guarantees

Networks fail, machines crash and messages get retried. Delivery guarantees describe what the system promises when that happens.

Whenever two programs exchange messages, something can go wrong. A message may be lost on the wire. A confirmation may be lost, so the sender does not know if the message arrived. A program may crash between two steps. Systems describe their behavior in these situations using three standard levels of promise.

GuaranteeMeaningRiskTypical use
At most onceA message is delivered zero or one timesMessages can be lostLow value telemetry where loss is fine
At least onceA message is delivered one or more timesDuplicates are possibleMost pipelines, paired with duplicate-tolerant consumers
Exactly onceThe effect of each message happens onceExtra cost and complexityMoney, inventory, counts that must be right

How duplicates and losses appear in Kafka

Case 1

Producer retry

A producer sends a message, the broker stores it, but the acknowledgment is lost. The producer retries and the message is stored twice.

Case 2

Crash before commit

A consumer processes a message and writes output, then crashes before saving its position. On restart it reads the same message again and repeats the work.

Case 3

Partial multi-topic write

A program writes to two topics, and crashes between the two writes. One topic has the result and the other does not.

Case 4

Zombie instance

A slow program is replaced by a new copy, but the old copy wakes up and keeps writing, creating a second stream of results.

Everyday analogy

Consider a courier delivering a parcel that needs a signature. If the courier leaves the parcel without waiting for a signature, it may be lost (at most once). If the courier delivers and cannot confirm, so delivers a second identical parcel, you may receive two (at least once). Exactly-once is the courier and the recipient agreeing on a tracking number so that a repeat delivery is recognized and ignored, and the whole visit is either completed or cancelled.

?
Why not just handle duplicates in code?

You often can, and for many systems it is the simpler choice. But every consumer must then do the work, get it right and store extra state. Transactions move the burden into the platform, so pipelines that read, transform and write within Kafka do not need to invent their own duplicate handling.

3The First Building Block: The Idempotent Producer

Before transactions, Kafka added a smaller feature that removes duplicates caused by producer retries.

An operation is idempotent if doing it many times gives the same result as doing it once. Pressing the “call elevator” button ten times does not summon ten elevators. An idempotent producer is a producer whose retries never create duplicate messages in a partition.

How it works

1

The producer gets an identity

When it starts, the broker assigns it a unique number called a producer ID.

2

Every batch gets a sequence number

For each partition, the producer numbers its messages 0, 1, 2 and so on, increasing by one each time.

3

The broker remembers the latest number

For each producer ID and partition, the broker tracks the last sequence number it accepted.

4

Retries are recognized

If a retried batch carries a number the broker has already stored, the broker discards the copy and simply acknowledges it again. If a number is skipped, the broker reports an error, because something was lost.

Everyday analogy

Picture a cinema that numbers every ticket sold at the booth. If a customer shows the same numbered ticket twice, the usher lets them in once and politely ignores the copy. The producer ID is the booth, the sequence number is the ticket number, and the broker is the usher.

What it covers and what it does not

Covers

  • Duplicates from producer retries in one partition
  • Ordering preserved despite retries
  • Works for a single producer session

Does not cover

  • Atomic writes across several partitions or topics
  • Duplicates after the producer restarts with a new identity
  • Consumer side processing repeats
i
Good to know

In recent Kafka versions, the idempotent producer is turned on by default, and it works together with acknowledgment from all in-sync replicas. In older versions it had to be enabled explicitly, so check the setting for your version.

4Transactions Compared with Database Transactions

If you have used a database, Kafka transactions will feel familiar in spirit and different in the details.

Database transactions are often described by four properties, known as ACID: atomicity, consistency, isolation and durability. Kafka transactions provide some of these in a form that suits an append-only log.

PropertyDatabase meaningKafka equivalent
AtomicityAll changes happen or none doAll writes in the transaction are committed or aborted together, across partitions and topics
ConsistencyRules such as constraints stay trueNot enforced by Kafka; your application logic defines what a valid result is
IsolationUncommitted work is hidden from othersConsumers set to read committed hide uncommitted and aborted messages
DurabilityCommitted data survives crashesReplication and acknowledgment settings protect committed messages

Key differences to remember

Difference 1

No rollback of data

Kafka logs are append-only. Aborted messages are not removed; they stay in the log and are marked as aborted so readers skip them.

Difference 2

No locking of rows

Kafka does not lock messages or keys. It coordinates visibility, not access, so two transactions can run at once.

Difference 3

Scope is writes and offsets

A transaction bundles produced messages and consumer offsets. It does not run queries or joins.

Difference 4

Order stays by offset

Messages appear in the log in the order they were written, even if transactions commit in a different order.

!
Do not overpromise

Calling Kafka transactions “just like database transactions” leads to bad designs. There is no read-your-writes query, no cross-message locking and no multi-system commit. Treat it as a tool for atomic publishing, not as a general database.

5Architecture and Components

A transaction involves a producer, a special broker role called the coordinator, an internal log, and the partitions being written to.

flowchart LR
  CL["Client Requests via API Gateway and Load Balancer"] --> APP["Application with Transactional Producer"]
  APP --> TC["Transaction Coordinator on a Broker"]
  TC --> TL["Transaction Log Internal Topic"]
  APP --> PA["Output Topic A Partition 2"]
  APP --> PB["Output Topic B Partition 5"]
  APP --> OFF["Consumer Offsets Internal Topic"]
  TC --> PA
  TC --> PB
  TC --> OFF
  PA --> RC["Consumers with Read Committed"]
  PB --> RC
Figure 1: The main actors in a Kafka transaction.

The components in plain words

Client side

Transactional producer

A producer configured with a stable name called a transactional ID. It begins, sends and finally commits or aborts.

Broker role

Transaction coordinator

A module running inside a broker. It tracks every open transaction, decides commit or abort, and writes the final markers. Each transactional ID is assigned to one coordinator.

Storage

Transaction log

An internal replicated topic where the coordinator records the state of every transaction. If a coordinator fails, another broker reads this log and takes over.

Data

Data partitions

The ordinary topic partitions where the messages of the transaction are written, possibly many of them across many topics.

Progress

Consumer offsets topic

The internal topic where consumer positions are saved. In a read-process-write pipeline, the new positions are written here as part of the same transaction.

Readers

Read committed consumers

Consumers that hide messages of open or aborted transactions, so only finished, successful work is visible.

The transactional ID

The transactional ID is the name you give a producer so that Kafka can recognize it across restarts. It must be unique per logical producer instance and stable over time. If a program restarts with the same transactional ID, Kafka knows it is the same actor coming back, finishes or cancels whatever transaction the previous life left open, and blocks any older copy that may still be running. Choosing this ID well is the single most important design decision when using transactions.

Everyday analogy

The transactional ID is like an employee badge number. If a cashier goes on a break and a replacement takes the same till, the manager knows exactly whose unfinished sale must be closed or cancelled. Two people using the same badge at once would be detected and stopped. A different number for every restart would leave abandoned sales lying on the counter.

i
Design tip

The transaction coordinator chooses its home by hashing the transactional ID onto a partition of the transaction log. This spreads coordinators across brokers automatically, so adding more transactional producers does not overload one machine.

6The Life of a Transaction, Step by Step

Follow one transaction from the first call to the final commit and see what each broker and client does.

1

Initialize

When the producer starts, it registers its transactional ID with the coordinator. The coordinator hands back a producer ID and an epoch number, and cleans up anything the previous holder of that ID left unfinished.

2

Begin

The producer marks the start of a transaction. This is a local action; nothing is sent yet.

3

Register each new partition

The first time the producer writes to a partition inside this transaction, it tells the coordinator, so the coordinator knows every partition that will need a marker later.

4

Send messages

Messages are written to the data partitions immediately, flagged as belonging to an open transaction. They are stored, but hidden from read committed consumers.

5

Add offsets

If the program is reading and writing, the producer also sends the consumer positions to be committed with the transaction.

6

Commit or abort

The producer asks the coordinator to commit, or to abort. From this moment the coordinator, not the producer, drives the rest.

7

Markers are written

The coordinator writes a special commit or abort marker into every partition involved, and finally records that the transaction is complete.

The states of a transaction

Inside the coordinator, each transaction moves through a small set of states. It starts as empty, becomes ongoing while messages are being written, and moves to a prepare state when the producer asks to end it. A prepare state means the decision has been made and safely recorded, even though the markers are not yet in place. Once the markers are written everywhere, the state becomes complete. A transaction that runs too long is aborted by the coordinator.

StateMeaningWho moves it forward
EmptyNo transaction is open for this IDThe producer, by beginning work
OngoingMessages and offsets are being addedThe producer, by committing or aborting
Prepare commit or abortDecision is recorded; markers are being writtenThe coordinator
Complete commit or abortMarkers are in place; the outcome is finalThe coordinator
?
Why the prepare step matters

Once the decision is written to the replicated transaction log, it can never be lost. Even if the coordinator crashes half way through writing markers, its replacement reads the log and finishes the job. This is what makes the outcome reliable.

7Two-Phase Commit and Markers

Kafka uses a classic idea, the two-phase commit, adapted for a distributed log.

The challenge is that a transaction may touch partitions on many different brokers. How do you make all of them agree, when any machine can fail at any moment? The answer is to separate the decision from the announcement.

flowchart TD
  A["Producer asks coordinator to commit"] --> B["Phase 1: Coordinator writes prepare commit to transaction log"]
  B --> C["Decision is now durable and cannot be reversed"]
  C --> D["Phase 2: Coordinator writes commit markers to every involved partition"]
  D --> E["Coordinator writes complete commit to transaction log"]
  E --> F["Read committed consumers can now see the messages"]
Figure 2: Phase one records the decision, and phase two announces it.

What a marker is

A control record, also called a marker, is a tiny special message written into a partition to say “the transaction with this producer ID has committed” or “has aborted”. Applications never see markers. Consumers use them, behind the scenes, to know which earlier messages to reveal or skip. That is why a topic in which a lot of transactions run holds slightly more entries than the number of messages your code wrote.

Everyday analogy

Think of a wedding planner who collects signed approvals from all the vendors before sending out invitations. First she writes in her diary, “Wedding on. Decision final.” Only after that does she call each vendor to confirm. If she drops the phone halfway through, her assistant reads the diary and finishes the calls. The diary entry is phase one, and the phone calls are phase two.

i
Why writing to many partitions is safe

Even though markers reach different partitions at slightly different moments, readers only trust a transaction after its marker appears in a given partition. Each partition is therefore always consistent by itself, and the decision is never contradicted.

8The Consumer Side: Isolation Levels

A transaction is only useful if readers respect it. The consumer’s isolation level decides whether they do.

Every Kafka consumer has a setting called the isolation level. It has two values.

Isolation levelWhat the consumer seesLatencyUse when
Read uncommitted (default)Every message, including those of open and aborted transactionsLowestPipelines that tolerate duplicates or partial work
Read committedOnly non-transactional messages and messages of committed transactionsHigher; waits for commitsAny stage downstream of a transactional producer

The last stable offset

A read committed consumer never reads beyond a position called the last stable offset. This is the offset just before the earliest transaction that is still open. Messages after that point may belong to unfinished transactions, so the consumer waits. When the open transaction commits or aborts, the position moves forward and the waiting messages are either delivered or skipped.

flowchart LR
  M1["Offset 10 committed"] --> M2["Offset 11 committed"]
  M2 --> M3["Offset 12 in open transaction"]
  M3 --> M4["Offset 13 committed by another producer"]
  M4 --> M5["Offset 14 non transactional"]
  M2 -.-> LSO["Last stable offset stops before offset 12"]
Figure 3: A read committed consumer waits at the last stable offset until the open transaction finishes.

Notice that message 13 was committed, and yet the consumer cannot read it yet. Ordering by offset is preserved, so one slow open transaction can hold back the visibility of later messages in the same partition. This is called head-of-line blocking, and it is the main reason to keep transactions short.

Everyday analogy

Imagine a queue at a bank counter where one customer has taken a form and left to find a missing document, holding a place at the front. Customers behind them who are ready to finish cannot be served until the first customer returns or gives up. The last stable offset is the front of that queue.

!
A common surprise

Forgetting to set read committed on downstream consumers is the most frequent mistake. The producer uses transactions, but the consumer sees everything, including aborted data, and the guarantee silently disappears.

9Read, Process, Write: The Classic Pattern

The most important use of transactions is a program that reads from one topic, transforms the data, and writes to another.

Suppose a service reads payment events, calculates a fee, and writes fee events to another topic. Two things must be saved after processing a batch: the output messages, and the fact that the input has been consumed. In an ordinary program these are two separate steps. If the program crashes between them, either the output is written twice or it is never written.

flowchart TD
  IN["Input Topic"] --> R["Consumer reads a batch"]
  R --> P["Application processes the batch"]
  P --> B["Begin transaction"]
  B --> W["Write results to output topics"]
  W --> O["Add consumer offsets to the transaction"]
  O --> C{"Commit succeeds"}
  C -->|"Yes"| OK["Results visible and input marked as consumed"]
  C -->|"No"| AB["Abort: results hidden and input will be read again"]
Figure 4: Outputs and input offsets are committed together as one unit.

By including the input positions in the same transaction as the output messages, Kafka makes the two effects inseparable. Either both happen, and the program moves forward, or neither happens, and the program will reread the same input and produce the same output again. Because the first attempt was hidden by the abort, the retry does not create a duplicate. This is the heart of exactly-once processing in Kafka.

Production example: payment fees

A payments company reads completed payment events and writes fee ledger entries. With a transaction around each batch, a crash mid-batch cannot leave fee entries for half of the payments, and a restart cannot double-charge the merchant.

i
Batch size matters

Each transaction has fixed overhead. Grouping many input messages into one transaction spreads that cost, at the price of longer waits before results become visible. Tune the batch to your latency needs.

10Zombies and Fencing

A slow or disconnected program can wake up after it has been replaced. Kafka uses epochs to shut it out.

A zombie is an old instance of a program that the system believes is dead, but that is actually alive. It might have been paused by a long garbage collection, cut off by a network problem, or simply slow. Meanwhile, the system started a replacement. If both write to the same output, the results are duplicated or mixed. This is one of the hardest problems in distributed systems.

Kafka solves it with an epoch, a number attached to the producer ID. Each time a producer registers with a given transactional ID, the coordinator increases the epoch. Every write carries the epoch, and brokers refuse writes from an older epoch than the latest one they know. The zombie, still holding the old epoch, receives a “fenced” error and must stop.

flowchart TD
  A["Instance 1 starts with transactional ID X and epoch 1"] --> B["Instance 1 becomes slow or disconnected"]
  B --> C["Instance 2 starts with same transactional ID X"]
  C --> D["Coordinator raises epoch to 2 and aborts unfinished work"]
  D --> E["Instance 1 wakes up and tries to write with epoch 1"]
  E --> F["Broker rejects write: producer fenced"]
  F --> G["Instance 1 must shut down"]
Figure 5: Fencing lets the newest producer win and locks out the old one.
Everyday analogy

Think of a hotel key card. When a guest loses a card and receives a new one, the hotel reprograms the lock so that the old card no longer opens the door, even if someone finds it later. The epoch is the version number of the key card, and the broker is the lock.

Why the transactional ID must be stable

Fencing works only when the old and the new instance use the same transactional ID. If each restart picked a random name, the coordinator would treat them as unrelated, and the zombie would never be blocked. For stream processing, each unit of work, such as an input partition, is usually mapped to a fixed ID, so that when work moves from one machine to another, the new owner always takes over the same identity.

i
Modern improvement

Older designs needed one transactional producer per input partition to fence correctly, which used many connections. Since Kafka 2.5, the producer can pass the consumer group’s information into the transaction, and the broker fences zombies using the group’s generation instead. This lets one producer per thread do the job. It is the basis of the newer exactly-once mode in Kafka Streams.

11Timeouts and Hanging Transactions

A transaction that never finishes blocks readers. Timeouts are the safety valve.

Imagine a program begins a transaction, writes some messages, and then freezes forever. The messages are stored but never committed. Read committed consumers cannot pass the earliest open transaction, so the last stable offset stops moving. Every consumer of that partition is stuck.

Safety 1

Transaction timeout

The producer declares how long a transaction may stay open. If the time runs out without a commit or abort, the coordinator aborts it. The limit is capped by a broker maximum.

Safety 2

Fencing on restart

When the program comes back with the same transactional ID, the coordinator aborts the old transaction immediately, without waiting for the timeout.

Safety 3

ID expiry

A transactional ID that has been idle for a long time is eventually forgotten by the coordinator, freeing resources.

Safety 4

Operator tools

Recent Kafka versions include a command line tool that can list transactions and find hanging ones, so an operator can inspect and, if needed, abort them.

Choosing the timeout

Too short, and slow but healthy transactions are aborted and retried, wasting work. Too long, and a crashed producer blocks readers for a long time before the coordinator steps in. A good rule is to set the timeout to a small multiple of the longest normal transaction, and to keep normal transactions short in the first place, from a fraction of a second to a few seconds for streaming workloads.

!
Watch the lag

A read committed consumer that suddenly shows growing lag while the topic still receives data often points to a hanging transaction. Check for old open transactions before adding more consumers.

12Exactly-Once in Kafka Streams

Most people meet Kafka transactions through Kafka Streams, where a single setting turns the whole machinery on.

Kafka Streams is a library for building stream processing applications. It reads from topics, transforms data, keeps state such as counts and totals, and writes results. Internally it combines every one of the pieces described so far. When you switch on exactly-once processing, Streams wraps each processing cycle in a transaction that contains three things: the output messages, the updates to the internal state changelog topics, and the input offsets.

Guarantee settingBehavior after a crashCost
At least once (default)Some records may be processed again, so results may be counted twiceLowest latency and overhead
Exactly once (current version)Output, state and offsets are committed atomically; reprocessing does not double countSlightly lower throughput; results appear at commit time

What changes in behavior

1

Commit interval becomes short

With exactly-once turned on, the default commit interval drops to around a tenth of a second, so transactions are small and results appear quickly.

2

Consumers use read committed

Streams reads its own input in read committed mode, so it never processes data from aborted upstream work.

3

State is protected

Changes to the state stores are recorded in changelog topics inside the same transaction. After a failure, state is restored to match the last committed point.

4

Zombies are fenced

Ownership of a piece of work moves cleanly between instances, and any old owner is locked out.

Production example: counting money

A retailer counts sales per store per minute and feeds the numbers to finance reports. With at-least-once, a crash could cause a batch to be counted twice and inflate revenue. With exactly-once processing, the same crash leads to the aborted batch being redone with no visible duplication.

?
Version note

Kafka Streams offers an older exactly-once mode and a newer, more efficient one that relies on the improvements from Kafka 2.5. Use the newer mode when your brokers and libraries support it, and read the upgrade notes for your version.

13The Edge of the Guarantee: External Systems

Kafka can make its own writes atomic. It cannot reach into other systems and make them part of the same bundle.

Suppose a consumer reads an order, sends a confirmation email, and writes a result to Kafka inside a transaction. If the transaction aborts and is retried, the email is sent again. The email service knows nothing about Kafka transactions. The same is true for writes to a relational database, calls to a payment gateway, or updates to a search index.

Target of the writeCovered by a Kafka transaction?What to do
Kafka topics and partitionsYesUse a transactional producer
Consumer offsets in KafkaYesSend offsets within the transaction
Relational databaseNoIdempotent writes or the outbox pattern
Email or push notificationNoDeduplicate using a unique key
External payment APINoPass an idempotency key with each call

Patterns for the outside world

Pattern 1

Idempotency keys

Attach a unique identifier, such as the message key and offset, to each external call. The receiving system ignores a second request with the same identifier.

Pattern 2

Outbox pattern

Write the business change and an “event to publish” row in one database transaction. A separate process reads the outbox and publishes to Kafka, so the database and Kafka never disagree about what happened.

Pattern 3

Idempotent consumer

Store the identifiers of processed messages in the target system and skip any that have been seen.

Pattern 4

Sink connectors with upserts

Write to the target by primary key, so repeating a write replaces the same row instead of creating a new one.

flowchart LR
  SVC["Order Service"] --> DB["Database with Orders and Outbox Table"]
  DB --> CDC["Outbox Reader or Change Data Capture"]
  CDC --> KT["Kafka Topic"]
  KT --> PROC["Transactional Stream Processor"]
  PROC --> OUT["Output Topic"]
  OUT --> SINK["Sink with Upserts to Warehouse"]
Figure 6: The outbox pattern links a database and Kafka without a distributed transaction.
Everyday analogy

Your bank can guarantee that a transfer between two of its own accounts is all-or-nothing. It cannot guarantee what a shop does after you show the payment receipt. For the shop, you need another safeguard, such as a receipt number that the shop records so it never fulfils the same order twice.

i
Newer connector support

Recent Kafka versions also allow certain source connectors to write exactly-once into Kafka. Support depends on the connector and the version, so check each connector’s documentation before relying on it.

14What Transactions Do Not Guarantee

Misunderstandings cause more incidents than bugs. Learn the limits before they teach you.

Myth 1

“Exactly once means the code runs once”

Your processing code may run more than once. What is guaranteed is that its committed effects on Kafka appear once.

Myth 2

“It covers my database”

Only Kafka topics and offsets are covered. Other systems need their own protection.

Myth 3

“Consumers see all or nothing automatically”

Only consumers set to read committed respect transactions. Default consumers see everything.

Myth 4

“Aborted data is deleted”

Aborted messages stay in the log until normal retention removes them. They are hidden, not erased.

Myth 5

“It works across clusters”

A transaction lives inside one Kafka cluster. Copying data to another cluster is a separate step with its own guarantees.

Myth 6

“It makes ordering global”

Ordering exists per partition. Transactions do not add ordering across partitions.

!
Say it precisely

When you describe your system to others, say “exactly-once processing within Kafka” rather than “exactly-once everywhere”. The extra words prevent teams from assuming protections that do not exist.

15Configuration Cheat Sheet

A handful of settings control nearly all transactional behavior. Names are shown so that you can find them in the documentation.

SettingWherePurpose
transactional.idProducerStable name that enables transactions and fencing
enable.idempotenceProducerRemoves duplicates from retries; required for transactions
acksProducerShould be all so a message counts only when all in-sync replicas have it
transaction.timeout.msProducerHow long a transaction may stay open before the coordinator aborts it
isolation.levelConsumerSet to read_committed to hide open and aborted data
enable.auto.commitConsumerTurn off when offsets are committed through the transaction
transaction.max.timeout.msBrokerUpper limit of the timeout a producer may request
min.insync.replicasBroker or topicMinimum copies that must acknowledge a write
transaction.state.log.replication.factorBrokerCopies of the transaction log; use three in production
processing.guaranteeKafka StreamsSwitches Streams to exactly-once processing
i
Check the defaults

Defaults have changed between versions. Always read the documentation for the exact version you run before assuming a value.

16Producer Reliability Settings Explained

Transactions sit on top of the ordinary producer safety settings. If those are weak, the whole structure is weak.

A producer sends messages to a partition leader, which copies them to follower replicas. The producer chooses how much confirmation it wants before it considers a message safely stored. This choice is controlled by a setting called acks. With acks set to zero, the producer does not wait for any reply and may lose messages silently. With acks set to one, the leader confirms once it has written the message, but a crash of the leader right after can lose it before any follower copies it. With acks set to all, the leader confirms only after every in-sync replica has the message, which is the safe choice and the one transactions require.

Setting valueWait forRisk
acks zeroNothingMessages lost without any error
acks oneThe leader onlyLoss if the leader fails before followers copy
acks allAll in-sync replicasSlightly higher latency; safest

Retries and ordering

When a send fails for a temporary reason, the producer retries. Without idempotence, a retry can reorder messages: batch one fails, batch two succeeds, and then batch one is retried and lands after batch two. With the idempotent producer, sequence numbers let the broker put batches in the right order and drop duplicates, even with several requests in flight at once. The recommended limit of in-flight requests with idempotence is five or fewer.

The minimum in-sync replicas rule

Acks all means “all replicas currently in sync”, which could shrink to just the leader if followers fall behind. The topic setting min.insync.replicas sets a floor: if fewer than the required number of replicas are in sync, the leader refuses the write instead of accepting it with weak protection. The common production pairing is three replicas with a minimum of two, so that one broker can fail without losing data or stopping writes.

Everyday analogy

Think of signing an important contract. Acks zero is shouting the terms across a room and hoping someone heard. Acks one is one witness nodding. Acks all is every required witness signing the page. The minimum in-sync rule says the contract is not valid unless at least two witnesses are actually present.

!
Idempotence needs the right partners

The idempotent producer expects acks all and retries enabled. Settings that contradict these can cause the producer to refuse to start, which is a good thing, because a half-safe configuration is worse than a failure.

17How Consumers Filter Aborted Data

Aborted messages stay in the log. The consumer library quietly removes them before your code sees anything.

When a read committed consumer fetches data, the broker sends the raw messages along with a short list of aborted transactions in the range that was fetched. Each entry names the producer ID that aborted and the offset where its transaction began. Brokers keep an index file for each log segment to answer this quickly, so they do not need to scan every message.

1

Fetch

The consumer asks for messages starting at its current offset, up to the last stable offset.

2

Receive data with the aborted list

The response holds the messages plus the list of producer IDs whose transactions were aborted in that range.

3

Track aborted producers

As the consumer reads forward and reaches the start offset of an aborted transaction, it starts skipping messages from that producer ID.

4

Stop skipping at the marker

When the consumer reaches the abort marker for that producer, it stops skipping and continues normally.

5

Deliver clean data

Your application receives only messages that are safe to use.

Because this filtering happens inside the client library, your code stays simple. The cost is a little extra memory and bookkeeping in the consumer, which grows when many transactions are open at once. A workload with a very high abort rate makes consumers do more filtering, so abort rates should stay low in healthy systems.

Everyday analogy

Picture a library returns desk where a clerk receives a cart of books together with a slip listing “these three books were returned damaged and must not go back on the shelves”. The clerk quietly sets those aside while shelving the rest. Readers only ever see the shelved books. The slip is the aborted transactions list.

18Offsets, Markers and Compaction

Markers use up offsets and aborted data lingers. Both surprise people who look closely at their topics.

Gaps in offsets

Every commit or abort marker takes one offset in each partition it is written to. If a partition holds ten messages and one transaction marker, the offsets run from zero to ten, with the marker at the end. A consumer never receives the marker, so it sees a gap in offsets where the marker sits. Tools that compute lag by subtracting offsets, and any code that assumes offsets increase by exactly one, must allow for these gaps. A partition full of very small transactions has many gaps.

Retention and compaction

Aborted messages are removed by normal cleanup. Under time or size based retention, whole old segments are deleted, including their aborted messages and markers. Under log compaction, where Kafka keeps the latest value for each key, the cleaner also removes aborted data during its work and eventually cleans up the markers themselves. Until that cleanup happens, aborted data takes disk space, which is another reason to keep abort rates low.

Effect 1

Offset gaps

Lag numbers can look slightly off. Do not treat a small permanent difference as an error.

Effect 2

Extra disk use

Markers and aborted messages occupy space until retention or compaction removes them.

Effect 3

Tools that count messages

Counting by offset difference overestimates the number of visible messages when markers are present.

Effect 4

Compacted topics

The latest committed value per key is what remains, so state topics stay correct after cleanup.

i
Practical advice

When comparing message counts between two topics, count the messages that consumers actually receive, not the difference between the first and last offsets.

19Migrating an Existing Pipeline to Exactly-Once

Most teams do not start with transactions. They add them to a pipeline that already runs. Do it in a careful order.

1

Confirm the cluster supports it

Check broker and client versions, the replication settings of the transaction log, and the minimum in-sync replicas. Fix weak settings before anything else.

2

Move the downstream readers first

Switch every consumer of the topics you will write transactionally to read committed. This is harmless while writes are still non-transactional, and it protects readers in advance.

3

Enable the idempotent producer

This alone removes duplicates from retries and is a safe first improvement.

4

Choose transactional IDs

Design stable names tied to units of work, and grant ACLs for them.

5

Turn on transactions in one service

Start with the least critical pipeline. Watch commit latency, abort rate and lag.

6

Test failure on purpose

Kill instances and brokers, and verify that outputs are correct. Fix what you find.

7

Roll out gradually

Move the remaining services one by one, keeping dashboards for open transactions and fencing errors.

Production example: a careful first step

A retailer moved its order pipeline to exactly-once by first switching all consumers to read committed on a quiet Tuesday, then enabling transactions on the inventory service alone. Only after a week of clean metrics and a planned broker failure test did it enable the remaining services.

!
Mind the mixed period

During a migration some services write transactionally and others do not. Consumers set to read committed handle both kinds of writers correctly, so the reader-first order is what keeps the mixed period safe.

20Sizing the Transaction Log

The transaction log is small compared with your data topics, but it sits on the critical path of every commit.

The log is an internal, compacted, replicated topic with a fixed number of partitions, fifty by default. Each transactional ID is mapped to one of these partitions by hashing, and the broker that leads that partition is the coordinator for the ID. The number of partitions is chosen when the cluster first creates the log, and it should not be changed later, because changing it would remap IDs to different coordinators.

ItemGuidanceWhy
Partitions of the logLeave at the default unless you have measured a needFifty spreads coordinators well; changing later is risky
Replication factorThreeCoordinator failover depends on replicas
Minimum in-sync replicasTwoPrevents accepting decisions with weak protection
Idle ID expiryKeep the default of about seven days, or set to match your idle patternsFrees state for IDs that are no longer used
Number of transactional IDsPlan for units of work times environmentsEach ID holds a small amount of state
Disk speedUse fast storage on brokersEvery state change is a write on the commit path
Everyday analogy

A hotel front desk holds a small ledger of which guest has which room key. The ledger is tiny compared with the hotel, but if it is lost or slow, nobody can check in. The transaction log is that ledger: small, and vital.

i
Set it before the first transaction

Configure the log’s replication and minimum in-sync settings before the cluster receives its first transactional request, because the log is created lazily at that moment using the settings of that time.

21Failure Scenarios and Recovery

The best way to trust a transaction system is to know exactly what it does in every bad situation.

FailureWhat Kafka doesWhat you should see
Producer crashes before commitCoordinator aborts the open transaction after the timeout, or immediately when the same ID returnsAborted messages hidden; input reread and reprocessed
Producer crashes after asking to commitCoordinator completes the commit on its ownMessages become visible together
Coordinator broker failsA replica takes over and reads the transaction logShort pause, then normal completion
Partition leader fails during a transactionNew leader elected; the producer may retry the batchRetries with sequence numbers cause no duplicates
Network partition isolates the producerTimeout aborts the transaction; a replacement fences the old producerOld producer receives a fenced error when it reconnects
Consumer crashes after processingOffsets not committed; on restart, the same input is read againOutput from the first attempt was aborted, so no duplicates
Log segments deleted by retention before readingConsumer offset out of rangeFollow your offset reset policy; a data gap is possible

Reading the table

Notice how each row has the same shape: something goes wrong, Kafka relies on a durable record such as the transaction log or the epoch, and the outcome is either a hidden abort followed by a clean retry, or a completed commit. No row leaves half of a transaction visible. That consistent shape is the entire purpose of the design.

i
Use this table as a test plan

Every row of this table can be turned into a test: cause the failure on purpose in a staging cluster and confirm that the observed result matches the last column.

22A Decision Guide: Do You Need Transactions?

Transactions are valuable, not free. Use this guide to decide pipeline by pipeline.

QuestionIf yesIf no
Is a duplicate result harmful, such as a double charge or inflated count?Use transactions or idempotent sinksAt-least-once is likely enough
Does one input produce writes to several topics that must agree?Use transactions for atomic publishingA plain idempotent producer may do
Does the pipeline keep running totals or other state?Use exactly-once in Kafka StreamsSimple stateless steps rarely need it
Are the results consumed only by Kafka readers you control?Read committed can be enforced everywhereOutside readers need extra protection
Can the team run and monitor the extra moving parts?Proceed with runbooks and alertsStart with simpler duplicate handling
Is very low latency more important than exactness?Consider at-least-onceTransactions fit well

If most answers point toward transactions, adopt them for that pipeline and keep the rest simple. There is no reward for turning them on everywhere. A focused use on the few paths where exactness matters gives the benefits while keeping overhead and operational effort small.

?
Start small

Pick the single pipeline where a duplicate would hurt most, make it exactly-once, learn from running it, and only then decide about the next one.

23Advantages, Disadvantages and Trade-offs

Transactions buy correctness and pay for it with some speed and some complexity. Most teams should use them only where the trade is justified.

Advantages

  • Atomic writes across many partitions and topics
  • Duplicates from retries and crashes are avoided within Kafka
  • Input offsets and outputs are committed as one unit
  • Zombie producers are fenced automatically
  • Downstream readers can rely on committed data only
  • Kafka Streams offers all this through a single setting

Disadvantages

  • Extra latency, since results are visible only after commit
  • Lower throughput than plain at-least-once, especially with tiny transactions
  • Open transactions can block readers
  • Guarantee stops at the edge of Kafka
  • More settings to understand and operate
  • Aborted data still occupies storage until retention removes it

The main trade-offs

Trade-off 1

Latency versus overhead

Small transactions commit quickly but pay the fixed cost often. Large transactions are efficient but delay visibility.

Trade-off 2

Correctness versus simplicity

Exactly-once removes duplicate handling from application code but adds operational concepts to learn.

Trade-off 3

Timeout length versus safety

Short timeouts recover quickly from crashes but abort slow healthy work. Long timeouts are tolerant but risk long blockage.

Trade-off 4

Read committed versus freshness

Hiding uncommitted data protects consumers and makes them wait for the last stable offset.

i
A question to ask first

Ask: “What is the cost of a duplicate or a lost result?” If the cost is small, at-least-once with idempotent consumers is often simpler. If the cost is money, legal risk or customer trust, transactions earn their place.

24Performance and Scalability

Transactions add work, but a well-tuned system can handle very high volumes.

The extra cost of a transaction comes from a few fixed steps: registering partitions with the coordinator, writing state changes to the transaction log, and writing markers to each partition. These steps are the same whether a transaction holds ten messages or ten thousand. That is why the size of the transaction matters so much for efficiency.

FactorEffectHow to tune
Messages per transactionMore messages spread the fixed costBatch reasonably, guided by latency needs
Partitions per transactionEach partition needs registration and a markerAvoid touching more partitions than necessary
Number of transactional producersEach has its own state at the coordinatorUse one producer per thread where supported
Commit interval in StreamsShort interval means more, smaller transactionsRaise it if latency allows
Read committed readsConsumers must filter aborted dataKeep aborts rare
Transaction log loadEvery state change is a writeSpread across enough log partitions; use fast disks
Everyday analogy

Sending parcels by courier costs a pickup fee for each visit. Sending one parcel per visit is expensive per item. Bundling fifty parcels in one visit costs the same pickup fee, spread thin. The trade-off is that the earliest parcel waits until the courier arrives. A transaction commit is the courier visit.

Production example: high volume stream with modest overhead

Confluent and other teams have published benchmarks showing that when transactions carry a sensible number of messages, the drop in throughput compared with at-least-once is modest, often only a few percent. Very small transactions, for example one message each, show much larger overhead. Treat published numbers as guidance and measure your own workload.

!
Do not transact per message

Wrapping every single message in its own transaction is the most common performance mistake. Group messages into batches that match your latency target.

25High Availability and Reliability

A transaction system must survive broker failures, network splits and application crashes without changing its answer.

flowchart TD
  F["Coordinator broker fails during commit"] --> L["New coordinator elected from replicas of the transaction log"]
  L --> R["New coordinator reads log and finds prepare commit"]
  R --> M["It finishes writing commit markers"]
  M --> D["Transaction completes with the original decision"]
Figure 7: A replacement coordinator finishes what the failed one started.
Replication

Three copies of everything

Set the transaction log and data topics to three replicas, with at least two required to acknowledge each write.

Acknowledgment

Wait for all in-sync replicas

Producers should require acknowledgment from all in-sync replicas, so a committed message survives one broker failure.

Recovery

Coordinator failover

Because the log is replicated, another broker can take over as coordinator and finish any transaction in progress.

Client side

Retry with a fresh transaction

On most errors, the application aborts the transaction and retries the batch. On a fencing error, it must shut down.

Handling errors correctly

Errors fall into three groups. Retriable errors, such as a temporary network problem, are handled by the client library. Abortable errors mean the current transaction cannot complete, so the application aborts it and starts over with the same input. Fatal errors, such as being fenced by a newer instance, mean the producer must close and stop writing. Confusing these groups, for example retrying after a fatal error, can create the very duplicates transactions try to prevent.

Production example: planned broker maintenance

A financial platform restarts brokers one at a time for upgrades. Because transaction logs and data topics are fully replicated, producers see brief pauses, some transactions abort and retry, and consumers reading committed data see no gaps or duplicates.

26Security

Transactions add new resources that need protection. Treat the transactional ID like a credential.

Identity

Authenticate producers

Require each application to prove who it is, for example with TLS certificates or SASL, so that only known programs can start transactions.

Authorization

ACLs on transactional IDs

Kafka has an access control resource for transactional IDs. Grant each application permission only to its own IDs, so that one service cannot fence or abort another.

Topics

Least privilege on data

Give write access only to the topics an application must produce to, and read access only to those it must consume.

In transit

Encrypt connections

Use TLS between clients and brokers and between brokers, so that messages and coordination traffic cannot be read or altered.

Internal topics

Guard the internal logs

The transaction log and offsets topic are internal. Normal applications should not read or write them directly.

Naming

Predictable but private IDs

A structured naming scheme helps operations. Combine it with ACLs so that guessing a name does not grant power over it.

!
A subtle risk

Anyone allowed to use a transactional ID can start a new session with it and thereby fence, and stop, the legitimate producer. Protect transactional IDs as carefully as you protect write access to the data itself.

27Monitoring, Logging and Metrics

A few signals reveal almost every transactional problem before customers notice.

SignalWhat it tells youTypical action
Consumer lag of read committed groupsWhether readers are stuck behind an open transactionLook for hanging transactions before scaling out
Age of the oldest open transactionVery old open transactions block the last stable offsetInvestigate the producer; consider aborting
Abort rateHow often transactions fail and are retriedCheck timeouts, errors and downstream failures
Fencing errorsInstances competing for one transactional IDCheck for duplicate deployments or unstable membership
Commit latencyTime from commit request to completionLook at broker load and replication
Transaction log healthUnder-replicated partitions in the internal logTreat as urgent; the commit path depends on it
Producer batch size and transaction sizeEfficiency of each transactionIncrease batch size if transactions are tiny

Log the transactional ID, the epoch and the outcome of each transaction at a low level of detail, so that when something looks wrong, you can trace a specific run. Add an alert when a transaction stays open for longer than a few times your normal duration, since this is the earliest warning of trouble.

i
A useful habit

Keep a runbook page for hanging transactions: how to find them, how to decide whether to wait or abort, and who to notify. In an incident, a written procedure is worth far more than memory.

28Worked Example: A Money Transfer Pipeline

Walk through a realistic service, first without transactions and then with them, and see exactly what changes.

A bank publishes transfer requests to a topic. A service reads each request and writes two events: a debit event to a topic named “debits” and a credit event to a topic named “credits”. Each request should produce exactly one debit and one credit.

Without transactions

The service reads request 55, writes the debit event, and then its machine loses power before it writes the credit. On restart, the service has not saved its position, so it reads request 55 again. It writes the debit again, and then the credit. The account holder is debited twice and credited once. Alternatively, if the service saved its position before writing, a crash would lose the transfer entirely.

With transactions

1

Read request 55

The service polls the input with the read committed setting.

2

Begin a transaction

The transactional producer starts a new transaction under its stable ID.

3

Write debit and credit

Both events go to their partitions, flagged as part of the open transaction and hidden from readers.

4

Add the input offset

The new position, just after request 55, joins the transaction.

5

Commit

The coordinator records the decision and writes commit markers on the debits partition, the credits partition and the offsets topic.

Now consider the same power failure. If it happens before the commit, the coordinator aborts the transaction after a timeout or as soon as the service restarts with the same ID. The debit and credit are hidden, the input position was never advanced, and the service reads request 55 again and repeats the work. Readers of the debit and credit topics never saw the aborted events. If the failure happens after the commit decision is recorded, the coordinator finishes writing the markers. Either way, the account holder sees exactly one debit and one credit.

Crash momentWithout transactionsWith transactions
After debit, before creditDebit exists alone; retry may duplicate itBoth events hidden and later redone
After both writes, before saving positionRequest is reprocessed; both events duplicatedAborted; only one pair ever becomes visible
During commitNot applicableNew coordinator completes the commit
Old instance wakes up laterOld instance writes duplicatesOld instance is fenced and stopped
!
Do not forget the readers

The guarantee only holds if the systems reading the debits and credits topics use read committed. A reader in the default mode would still see the hidden events from aborted attempts.

29Testing Transactional Applications

Failures are rare in production, so you must create them on purpose in testing.

1

Happy path

Send a known set of inputs and check that outputs appear exactly once for a read committed reader.

2

Kill the application mid-transaction

Stop the process after writing but before committing, restart it, and confirm that no duplicates or partial results are visible.

3

Kill a broker

Restart the broker that acts as coordinator while a transaction is in flight, and verify the outcome is still consistent.

4

Create a zombie

Pause an instance long enough to be replaced, then resume it, and confirm it is fenced and shuts down.

5

Check the readers

Read the output with both isolation levels and compare the results. The difference shows what aborted data would have leaked.

6

Soak and load test

Run for hours at production volume and watch commit latency, abort rate and lag.

Kafka Streams also ships with a test driver that runs a topology in memory, which is excellent for checking logic. However, the driver does not reproduce real broker failures, so it complements, but cannot replace, tests against a real cluster with deliberate crashes.

Everyday analogy

A fire drill does not prove that a building will never burn. It proves that people know the exits and that the alarms work when they are needed. Crash tests for transactions serve the same purpose: they show that the recovery path works before a real failure demands it.

30Design Patterns and Anti-patterns

A few proven shapes solve most needs. A few tempting shortcuts cause most of the outages.

Patterns that work

Pattern

Consume, transform, produce

Wrap each batch of input in one transaction with its outputs and offsets. This is the standard exactly-once loop.

Pattern

Transactional outbox

Publish database changes to Kafka via an outbox table, so the database and the topic never diverge.

Pattern

Idempotent sinks

End every pipeline with a sink that writes by primary key, so any leftover duplicates collapse into one row.

Pattern

Stable IDs by work unit

Derive the transactional ID from the unit of work, not from the machine, so ownership can move without confusion.

Anti-patterns to avoid

AP-01Avoid
What people do

Use a random transactional ID on every start.

Why it hurts

The coordinator cannot recognize the returning program, so unfinished transactions linger and zombies are never fenced.

Do this instead

Use a stable ID tied to the unit of work.

AP-02Avoid
What people do

Produce transactionally but leave downstream consumers on the default isolation level.

Why it hurts

Readers still see aborted and open data, so the guarantee is lost without any error message.

Do this instead

Set read committed on every consumer that reads transactional output.

AP-03Avoid
What people do

Call an external API inside the transaction and assume a retry is safe.

Why it hurts

An aborted and retried transaction repeats the external call, sending a second email or charging a card twice.

Do this instead

Use idempotency keys, or the outbox pattern, for anything outside Kafka.

AP-04Avoid
What people do

Keep transactions open for a long time, for example while waiting on a slow service.

Why it hurts

The last stable offset stalls and every read committed consumer of those partitions waits.

Do this instead

Do slow work before beginning the transaction, and keep the transaction itself short.

31Best Practices and Common Mistakes

A short checklist gathered from production systems. Follow it and you avoid most transaction pain.

1

Decide whether you need transactions

Use them where duplicates or partial results are costly. Otherwise, prefer simple at-least-once plus idempotent consumers.

2

Use stable transactional IDs

One ID per unit of work, the same across restarts.

3

Set read committed downstream

Every consumer of transactional output must use it.

4

Keep transactions short but not tiny

Batch enough messages to spread the fixed cost, and stay well below the timeout.

5

Replicate everything

Three replicas, acknowledgment from all in-sync replicas, and a sensible minimum for in-sync copies.

6

Handle errors by type

Retry retriable errors, abort and retry abortable ones, and stop on fatal ones such as fencing.

7

Protect the edges

Use idempotency keys or the outbox pattern for every system outside Kafka.

Mistakes seen again and again

Mistake

Forgetting read committed

The most frequent cause of “exactly-once did not work” reports.

Mistake

Auto-committing offsets

Leaving automatic offset commits on lets the consumer save its position outside the transaction.

Mistake

One transaction per message

Fixed overhead dominates and throughput collapses.

Mistake

Duplicate deployments

Two copies running with the same ID fence each other in a loop.

Mistake

Weak replication of the log

A transaction log with one replica makes the whole feature fragile.

Mistake

Assuming external effects are covered

Databases, email and payments need their own protection.

32Transactions Versus Other Approaches

Kafka transactions are one tool among several. Knowing the alternatives helps you pick the simplest one that works.

ApproachHow it worksStrengthWeakness
Kafka transactionsAtomic writes and offsets inside one clusterExactly-once for Kafka to Kafka pipelinesDoes not extend to outside systems
Idempotent consumersConsumer remembers processed IDs and skips repeatsWorks with any target systemEvery consumer must implement it and store state
Outbox patternDatabase change and event stored together, published laterKeeps database and Kafka alignedExtra table and a publishing process
Saga patternA chain of local steps with compensating actions to undo themWorks across many independent servicesComplex logic; states are visible mid-way
Distributed two-phase commitA coordinator locks and commits across several databasesStrong atomicity across systemsSlow, blocking and rarely supported with Kafka
Everyday analogy

Booking a holiday needs a flight, a hotel and a car. A saga is like booking each separately and cancelling the earlier ones if a later one fails. Kafka transactions are like a travel agent who can guarantee that all the bookings made through the agent’s own office succeed or fail together, but who cannot control other companies’ systems.

i
Combine, do not choose

Real systems often mix approaches: transactions inside the Kafka pipeline, the outbox pattern at the entrance, and idempotent sinks at the exit. Each covers the part of the path where it is strongest.

33Real-World and Industry Examples

Transactions appear wherever a wrong or duplicated result costs money or trust.

Kafka began at LinkedIn to move activity data, and transactions were added to support stream processing that businesses could trust with critical numbers. Public talks and articles from many companies describe patterns like the ones below. The internal designs of specific companies change over time, so treat these as illustrations of the technique rather than exact blueprints.

Banking: ledgers and transfers

Banks publish debit and credit events and rely on atomic writes so that an outage cannot leave half a transfer visible. Downstream reporting and fraud systems read only committed events.

Payments: fee and settlement calculation

Payment platforms compute fees, refunds and settlements from streams of payment events. Exactly-once processing keeps merchant balances correct even when machines restart during heavy traffic.

E-commerce: inventory and orders

Online retailers reserve stock when orders arrive. Writing the reservation and the order status together prevents the same item from being promised twice after a retry.

Ride-hailing: trip billing

Ride platforms turn trip events into fares. A transaction around each calculation stops a restart from charging a rider twice for the same trip.

Advertising: spend tracking

Ad platforms track spend against budgets in near real time. Kafka Streams with exactly-once processing keeps running totals from being inflated by reprocessing after failures. Pinterest has publicly described using Kafka Streams for budget related processing.

Data platforms: enrichment pipelines

Pipelines that join, clean and enrich events before loading a warehouse use transactions so that aborted batches never reach the warehouse loader.

i
Pattern to notice

The common thread is money or counts that feed decisions. Where a duplicate is harmless, teams usually skip transactions and keep the pipeline simple.

34Glossary of Terms

A quick reference for the vocabulary used throughout this guide.

TermPlain meaning
TransactionA group of writes that succeed or fail together
IdempotentSafe to repeat; repeating gives the same result as doing it once
Producer IDA number the broker assigns to identify a producer session
Sequence numberA per-partition counter used to detect duplicate or missing batches
Transactional IDA stable name for a producer that enables transactions and fencing
EpochA version number that increases each time a transactional ID is re-registered
Transaction coordinatorThe broker module that manages the state of transactions
Transaction logThe internal replicated topic where transaction states are stored
Control record or markerA hidden message that marks a transaction as committed or aborted
Isolation levelThe consumer setting that decides whether uncommitted data is visible
Last stable offsetThe position before the earliest open transaction; the limit for read committed readers
ZombieAn old instance that keeps running after being replaced
FencingBlocking a zombie so that it cannot write any more
Exactly-once processingEach input affects the output once, within Kafka
Outbox patternStoring an event in the database with the change, then publishing it separately

35Frequently Asked Questions

Quick answers to the questions that come up most when teams first use Kafka transactions.

Q1What is a transaction in Kafka?
A group of message writes, and optionally consumer offset commits, that become visible together to read committed consumers, or are all discarded if the transaction is aborted.
Q2What is the difference between an idempotent producer and a transactional producer?
An idempotent producer removes duplicates caused by its own retries within a partition. A transactional producer builds on that and adds atomic writes across several partitions and topics, plus offset commits and zombie fencing.
Q3Does exactly-once mean my code runs only once?
No. Your code may run more than once after a failure. The guarantee is that the committed effects on Kafka appear once.
Q4What is the transactional ID for?
It gives a producer a stable identity across restarts, so Kafka can clean up its unfinished work and lock out older copies. It should be unique per unit of work and stay the same each time.
Q5What does read committed do?
It makes a consumer skip messages from aborted transactions and wait for open transactions to finish, showing only committed data.
Q6What is the last stable offset?
The position just before the earliest open transaction in a partition. Read committed consumers do not read beyond it until the transaction completes.
Q7What happens to messages of an aborted transaction?
They stay in the log, marked as aborted, and are hidden from read committed consumers. They are removed later by the normal retention process.
Q8What is a zombie producer and how is it stopped?
A zombie is an old instance still running after a replacement started. The coordinator raises the epoch when the replacement registers, and brokers reject writes with the older epoch.
Q9Do Kafka transactions cover my database?
No. Only Kafka topics and offsets are covered. For a database, use idempotent writes, or the outbox pattern to keep it in step with Kafka.
Q10How long should a transaction be?
Long enough to carry a reasonable batch of messages and short enough to stay far below the timeout. For streaming workloads, this usually means fractions of a second to a few seconds.
Q11Why are my read committed consumers lagging?
A hanging transaction may be holding back the last stable offset. Look for old open transactions on the affected partitions, and address the producer or abort the transaction.
Q12How do I enable exactly-once in Kafka Streams?
Set the processing guarantee to the current exactly-once mode in the Streams configuration. The library then handles transactions, offsets, state and fencing for you, provided your brokers support the required version.
Q13Do transactions work across two Kafka clusters?
No. A transaction lives inside a single cluster. Replication between clusters is a separate step with its own guarantees.

36Summary and Key Takeaways

Kafka transactions make a bundle of writes succeed or fail as one, so that pipelines can stay correct through failures.

Key Takeaways

  • A transaction is an atomic bundle. Writes to many partitions and topics, plus consumer offsets, are committed or aborted together, and only inside one Kafka cluster.
  • Idempotence is the foundation. Producer IDs and sequence numbers remove duplicates from retries, and transactions build on that with a coordinator, a replicated log and commit or abort markers.
  • Readers must cooperate. Only consumers set to read committed hide open and aborted data, and they read up to the last stable offset, so open transactions can hold them back.
  • Stable transactional IDs and epochs fence zombies. The newest producer wins, and older copies receive an error and must stop.
  • Exactly-once processing means committed effects appear once. Your code may still run more than once, and everything outside Kafka needs idempotency keys, the outbox pattern or idempotent sinks.
  • Tune for short, sensible transactions. Batch enough to spread the fixed cost, avoid one transaction per message, keep timeouts realistic, and never hold a transaction open while waiting on slow work.
  • Operate it with care. Replicate the transaction log, protect transactional IDs with ACLs, alert on old open transactions and fencing errors, and rehearse failures before they happen.