Active-Active Multi-Region Design in Kafka
How to run Kafka in two or more regions at the same time, let applications read and write in every one of them, survive the loss of a whole region, and stay honest about what can and cannot be guaranteed when the data has to cross an ocean.
Picture an online shop with customers in Mumbai, Singapore and Frankfurt. On a normal day, every customer wants the page to load fast, the cart to save instantly, and the order to be safe. Now imagine that the building that holds the shop’s servers in Singapore loses power. If the shop lives only in Singapore, every customer everywhere sees an error page. If the shop lives in two places and one of them is just a quiet copy waiting for a disaster, the owner is paying for machines that do nothing on ordinary days, and nobody is really sure the copy will work when the disaster arrives.
There is a third way. Run the shop in every region at the same time. Every region takes orders, every region keeps its own data, and the regions keep telling each other what happened. If one region disappears, the others already know almost everything and simply carry on. This is called an active-active design. It is powerful, and it is also one of the trickiest things you can build, because the moment two places can change the same information at the same time, you must decide what “correct” means. This guide explains, from the very beginning, how to build it with Apache Kafka: the two main ways to spread Kafka over regions, the tools that copy data, how progress markers are translated, how conflicts are handled, what breaks, and how to test it before it breaks for real.
1What Is Active-Active Multi-Region Design?
The plain-English idea first. The machinery follows in later chapters.
A region is a geographic area that holds one or more data centers, such as Mumbai, Singapore or Frankfurt. Cloud providers sell regions as separate places with separate power, cooling and networks. Two regions are far enough apart that a flood, a fire or a bad software update in one is very unlikely to touch the other.
A multi-region design is any system that keeps running parts, or copies of its data, in more than one region. There are two big families. In an active-passive design, one region does all the work and the other waits with a copy of the data. In an active-active design, every region does real work at the same time: it accepts new data from local applications, serves local readers, and shares its data with the other regions.
Think of a chain of bakeries in different cities. In an active-passive chain, only the main bakery sells bread, and a second shop stays closed with the same recipes in case the main one burns down. In an active-active chain, every bakery sells bread every day, and every evening they phone each other to share what sold and what recipes changed. If one shop closes, customers walk into the next one, and it already knows the recipes. The hard part is the phone calls: what if two shops changed the same recipe on the same afternoon?
What Kafka brings to the picture
Apache Kafka is a system that stores streams of events, such as “order placed” or “payment received”, in an ordered log, and lets many programs read them. Kafka was designed to run inside one data center or one region, where machines are close together and the network is fast and reliable. Spreading it over regions means dealing with slower links, longer delays and the chance that the link between regions breaks while both sides stay alive.
That single fact, that the link between regions can be slow or broken while both regions are healthy, drives almost every decision in this guide.
RPO (recovery point objective) is how much recent data you can afford to lose in a disaster. RTO (recovery time objective) is how long you can afford to be down. Every multi-region choice is a trade between these two numbers, cost, speed and complexity.
2Kafka Basics You Need First
A short tour of the parts of Kafka that the rest of this guide keeps pointing at.
If you already know Kafka well, skim this chapter. If not, these few ideas are all you need. Each has a small card below, and the paragraphs after the cards show how the ideas fit together.
Broker
A machine that runs Kafka and stores data. A group of brokers working together is a cluster.
Topic
A named stream of events, such as orders or payments. Think of it as a labelled folder that only grows.
Partition
A topic is split into partitions so many machines can share the load. Order is guaranteed only inside one partition.
Offset
The number of a record inside a partition: 0, 1, 2 and so on. It is the record’s address.
Replication
Each partition is copied to several brokers inside one cluster, so a broker failure loses nothing.
Producer
A program that sends records to a topic. It chooses the partition, often by hashing the record’s key.
Consumer
A program that reads records and remembers how far it has read by saving an offset.
Consumer group
Consumers that share a topic’s partitions. Each group keeps its own saved offsets.
Replication inside a cluster versus between clusters
Kafka copies each partition to several brokers. One broker is the leader and takes all writes. The others are followers that copy the leader. The copies that are keeping up are called the in-sync replicas, or ISR. A producer can ask the leader to wait until all in-sync copies have the record before saying “done”. This setting is called acks=all, and together with a minimum in-sync count it is what makes a single cluster safe.
That copying is fast and tightly controlled because it happens inside one cluster over a fast network. Copying between clusters is a different job, done by a different tool, over a slower network, and with weaker promises. The whole art of multi-region Kafka is understanding this difference.
Offsets belong to a cluster
An offset only has meaning inside one partition of one cluster. Record number 5,000 in the orders topic of the Mumbai cluster has nothing to do with record number 5,000 in the copy of that topic in the Singapore cluster. They may hold different records, because the copying tool may skip, retry, or interleave data. This detail causes many of the surprises you will see later, especially when consumers have to move from one region to another.
Never assume that an offset saved in one cluster can be used as it is in another cluster. It usually needs to be translated, and the translation is approximate.
3Why Regions Matter: Speed, Survival and the Law
Three very different pressures push teams toward more than one region. Knowing which one is yours changes the design.
Nobody builds multi-region systems for fun. The cost in money and effort is high. Teams do it because one of three pressures leaves no better choice.
Pressure one: distance is slow
Light travels through fibre at roughly two thirds of its speed in a vacuum. A signal from Mumbai to Frankfurt cannot arrive in under about 60 milliseconds one way, no matter how much money you spend. If a customer in Frankfurt has to wait for a round trip to Mumbai on every click, the app feels sluggish. Serving that customer from a nearby region is the only cure, and that means the data must live nearby.
Pressure two: whole regions fail
Regions fail less often than single machines, but they do fail. A bad configuration change, a power problem, a cooling failure, a cut cable or a software bug in the cloud provider’s own control system can make a region unreachable for minutes or hours. Companies with strict uptime promises cannot say “our provider was down”. They need a second region ready to carry the traffic.
Pressure three: laws about where data lives
Some laws and contracts require that data about citizens of a country, or customers of a certain kind, stays inside a geographic boundary. This is called data residency. Multi-region designs must keep such data in the right regions and share only what is allowed, such as anonymous totals.
| Pressure | What you really need | Design direction |
|---|---|---|
| Speed for far-away users | Local reads and local writes | Active-active with regional ownership of data |
| Survive a region outage | A ready second region with recent data | Active-passive is often enough; active-active gives faster recovery |
| Data residency rules | Data that never leaves a boundary | Separate regional clusters with selective, filtered replication |
| All three together | Local speed, survival and legal control | Active-active with strict topic design and careful replication |
Production example: the global ride app
A ride-hailing company keeps a Kafka cluster in every major region so that a driver’s location updates stay close to the drivers and riders that use them. Trips that start in one region are finished in that region. Some data, such as pricing models and fraud signals, is copied between regions so that every region can make good decisions. When one region has trouble, traffic shifts to another region that already holds recent copies of what it needs. The lesson: most events have a natural home region, and the design gets far simpler when you let them keep it.
4Deployment Models: Single Region, Active-Passive, Active-Active
Three ways to lay out Kafka, from simplest to hardest, with what each one buys you.
Before choosing active-active, compare it fairly with the other layouts. Many teams discover that a simpler layout meets their real needs.
flowchart TB
subgraph SR["Single Region"]
A1["Apps"] --> K1["Kafka Cluster
3 zones"]
end
subgraph AP["Active-Passive"]
A2["Apps in Region A"] --> K2["Kafka A
active"]
K2 -- "one-way copy" --> K3["Kafka B
standby"]
A3["Apps in Region B
idle until failover"] -.-> K3
end
subgraph AA["Active-Active"]
A4["Apps in Region A"] --> K4["Kafka A"]
A5["Apps in Region B"] --> K5["Kafka B"]
K4 -- "copy" --> K5
K5 -- "copy" --> K4
end
The diagram shows the three layouts side by side. In the single-region layout, one cluster spreads its brokers across several zones inside a region. It survives the loss of a machine or a zone, but not the loss of the region. In the active-passive layout, a second cluster receives a one-way copy and stays quiet until a disaster. In the active-active layout, both clusters take writes, and each one copies to the other.
| Question | Single region | Active-passive | Active-active |
|---|---|---|---|
| Survives a machine or zone loss | Yes | Yes | Yes |
| Survives a region loss | No | Yes, after failover | Yes, often with little pause |
| Data lost in a region loss (RPO) | Everything since the last backup | Whatever was not yet copied | Whatever was not yet copied |
| Recovery time (RTO) | Hours or more | Minutes to an hour, needs a switch | Seconds to minutes, traffic already there |
| Speed for far-away users | Poor | Poor, all writes go to one region | Good, users write locally |
| Conflicts possible | No | No | Yes, must be designed for |
| Cost | 1x | About 2x | About 2x, but the second half does useful work |
| Operational difficulty | Low | Medium | High |
Many active-passive systems fail on the day they are needed, because nobody ever sent real traffic to the passive side. One quiet advantage of active-active is that both sides are proven every day.
5Option A: One Cluster Stretched Across Regions
The first way to use two regions: pretend they are one big cluster. It is elegant and it is expensive in delay.
In a stretch cluster, the brokers of a single Kafka cluster are placed in several regions or data centers. Kafka does not know or care that some brokers are far away. Each partition has replicas in different locations, and the normal replication rules keep them in step. Kafka’s own rack awareness feature lets you label each broker with its location, so that the replicas of a partition are spread across locations.
flowchart LR
subgraph R1["Location 1"]
B1["Broker 1
leader of P0"]
B2["Broker 2"]
end
subgraph R2["Location 2"]
B3["Broker 3
follower of P0"]
B4["Broker 4"]
end
subgraph R3["Location 3"]
B5["Broker 5
follower of P0"]
Q["Metadata quorum
voter"]
end
P["Producer"] --> B1
B1 -- "replicate" --> B3
B1 -- "replicate" --> B5
C["Consumer"] --> B1
What you gain
Replication is synchronous, meaning the producer is told “done” only after enough copies exist. If the leader’s location vanishes, another location already holds every acknowledged record, so RPO is zero for acknowledged writes. Applications see one cluster, one set of topics, one set of offsets. Consumers do not need offset translation, because the offsets are the same everywhere. There is no second tool to run, and no naming tricks.
What you pay
Every write with acks=all waits for followers in other locations. If the round trip between locations is 80 milliseconds, every write takes at least that long. Throughput per producer drops, and tail latency rises when the link is congested. Cross-region traffic is also billed by cloud providers, and a stretch cluster sends every byte across regions at least twice for replication.
There is a second, quieter cost: quorum. The cluster’s metadata controllers must agree by majority. With only two locations, losing the location that holds the majority stops the cluster from making decisions, even though the other location is healthy. That is why serious stretch clusters use three locations, where losing any one leaves a majority.
A stretch cluster is like a group of friends in three cities who must all say “yes” over the phone before anyone signs a contract. Nothing is ever lost, because everyone agrees first. But every signature takes as long as the slowest phone line, and if two of the three cities lose their connection, nobody can sign anything.
Benefits
- No data loss for acknowledged writes when one location fails.
- One set of topics and offsets, so no translation and no naming rules.
- Automatic failover using normal leader election.
- Simple for applications, since they see a single cluster.
Trade-offs
- Every write pays the cross-region round trip.
- Needs three locations for a safe quorum, and a very stable network between them.
- A network split can stop writes for the whole cluster.
- A cluster-wide software problem hits every region at once.
It fits best when the locations are close together, for example three data centers in one metropolitan area with a round trip of a few milliseconds. Across continents, it is almost always the wrong tool.
6Option B: Separate Clusters Connected by Replication
The second way: every region owns a full cluster, and a copying tool moves data between them. This is what most active-active systems use.
In the multiple-cluster design, each region runs its own independent Kafka cluster. Applications talk only to the cluster in their own region. A separate tool reads records from one cluster and writes them into the other. Because the copying happens after the local write has already succeeded, it is asynchronous: the local application is never slowed down by the distance.
flowchart LR
subgraph RA["Region A"]
PA["Producers A"] --> KA["Kafka Cluster A"]
KA --> CA["Consumers A"]
end
subgraph RB["Region B"]
PB["Producers B"] --> KB["Kafka Cluster B"]
KB --> CB["Consumers B"]
end
KA -- "replicator A to B" --> KB
KB -- "replicator B to A" --> KA
Why this is the common choice
Local writes stay fast, because a producer in Region A only waits for its own cluster. Failures are contained: a broken cluster in Region B does not stop Region A from taking writes. Each cluster can be upgraded on its own schedule, which limits the damage of a bad release. And each region can keep some topics private, replicating only what is allowed.
What you give up
Because copying is asynchronous, there is always a delay between the moment a record is written in Region A and the moment it appears in Region B. That delay is usually well under a second in a healthy system, but it can grow to minutes or hours during trouble. If Region A is destroyed, the records that had not yet been copied are lost. That is why RPO is greater than zero. You must also handle topic naming, offset translation, duplicates and conflicts yourself, which are the topics of the next chapters.
| Property | Stretch cluster | Separate clusters |
|---|---|---|
| Copy style | Synchronous inside Kafka | Asynchronous by an outside tool |
| Write latency | Includes the region round trip | Local only |
| Data loss on region failure | None for acknowledged writes | Data not yet copied |
| Offsets | Same everywhere | Different in every cluster |
| Failure blast radius | One cluster spans everything | Contained per region |
| Best for | Nearby sites, zero data loss | Far-apart regions, low latency |
7The Replication Tools: MirrorMaker 2, Cluster Linking and Others
Several tools can move data between Kafka clusters. They differ in what they copy, how faithfully, and who supports them.
A replicator is a program that reads records from a source cluster and writes them to a target cluster. It sounds simple, and at small scale it is. At large scale, it must also handle topic creation, partition counts, configuration, access rules, consumer positions, restarts and loop prevention. Over the years, several tools have grown to cover these jobs.
MirrorMaker 2
Part of Apache Kafka. Built on Kafka Connect. Copies data, topic settings, access rules and consumer group positions. The default choice for open-source setups.
Cluster Linking
A feature of some commercial Kafka platforms. The target cluster pulls data directly from the source and keeps the same offsets, producing read-only mirror topics.
Replicator connector
A commercial Kafka Connect based tool that copies topics between clusters and can translate consumer positions.
MirrorMaker 1
The first Kafka mirroring tool. Simple consume-then-produce. It has been replaced by version 2 and should not be used for new designs.
Company tools
Large companies have built their own, such as uReplicator at Uber and Brooklin at LinkedIn, to solve scale and stability limits they hit with early tools.
Your own app
A Kafka Streams or consumer-producer application can copy selected topics with filtering or masking. Useful for special cases, expensive to maintain.
The most important difference: offsets
Classic mirroring tools, including MirrorMaker 2, read records and write them again on the target. The target cluster assigns new offsets, so record 5,000 on the source may become record 4,982 on the target. Byte-for-byte linking tools copy the log itself and keep the offsets identical. Identical offsets make consumer failover far simpler, but they also mean the mirror topic is read-only and must be promoted before applications can write to it. That does not fit a design where both regions must write into the same topic name, so active-active designs still use separate topics per region, which the next chapters explain.
Ask three questions. Do you need open source only? Do you need identical offsets? Do you need to transform or filter records on the way? The answers point to the tool. This guide uses MirrorMaker 2 for its examples because it is part of Apache Kafka and its ideas apply to the others.
8Inside MirrorMaker 2
MirrorMaker 2 is a set of connectors that run inside Kafka Connect. Each connector has one small job.
Kafka Connect is a framework that runs connectors, which are plug-in programs that move data in and out of Kafka. It handles restarts, scaling across workers and storing its own progress. MirrorMaker 2 (MM2) is a family of three connectors that ride on top of this framework.
flowchart LR
subgraph SRC["Source cluster A"]
T1["orders topic"]
G1["Consumer group positions"]
TA["Topic configs and ACLs"]
end
subgraph MM["MirrorMaker 2 on Kafka Connect"]
MS["MirrorSourceConnector
copies records"]
MC["MirrorCheckpointConnector
translates group offsets"]
MH["MirrorHeartbeatConnector
sends heartbeats"]
end
subgraph TGT["Target cluster B"]
T2["A.orders mirror topic"]
OS["offset-syncs topic"]
CP["A.checkpoints.internal"]
HB["heartbeats topic"]
end
T1 --> MS --> T2
MS --> OS
G1 --> MC
OS --> MC --> CP
MH --> HB
TA --> MS
The three connectors
| Connector | Job | Why it matters |
|---|---|---|
| MirrorSourceConnector | Reads records from source topics and writes them to the target. Also creates matching topics, keeps partition counts in step, and can copy topic settings and access rules. | This is the data pipe. Its speed sets your replication lag. |
| MirrorCheckpointConnector | Reads consumer group positions from the source and, using the offset-sync records, works out the matching positions on the target. It can write them to a checkpoints topic and optionally apply them to groups. | This is what makes consumer failover practical. |
| MirrorHeartbeatConnector | Sends a small heartbeat record at regular intervals. | It proves the whole chain works and lets you measure end-to-end delay even when no real data flows. |
How offset syncs work
Every so often, the source connector records a pair of numbers: “record 5,000 on the source became record 4,982 on the target”. These pairs go into an internal topic. The checkpoint connector uses them like landmarks on a map. When it sees that a group has read up to source offset 5,010, it finds the nearest landmark at or before that point and computes a safe target offset. Because landmarks are sparse, the answer is slightly behind. That is deliberate: it is better to re-read a few records than to skip any.
Running it
MM2 can run in three ways: as a dedicated cluster using its own driver program, as connectors on an existing Kafka Connect cluster, or in a standalone mode for tests. For production, teams usually run a dedicated Connect cluster in the target region, so that the process reads across the long link and writes locally. Reading remotely and writing locally tends to be more forgiving, because a remote read that times out can simply be retried, while a remote write that times out may leave doubt about whether the record arrived.
MM2 is a courier company with three staff. One courier carries parcels between two warehouses. A second keeps a notebook that translates the address labels of one warehouse into the address labels of the other. A third sends a postcard every minute to prove the road is open. If the notebook keeper is late, customers can still get parcels, but they may not find their old place in the shelves.
9Internal Working: How the Source Connector Copies Records
A closer look at the engine room, since its behaviour explains most lag and duplicate stories.
The source connector splits its work into tasks. Each task is assigned a set of source partitions. A task acts like an ordinary consumer for those partitions and like an ordinary producer for the target. It has no group of its own that Kafka rebalances in the usual way. Instead, Kafka Connect hands out the partitions and stores each task’s progress in its own internal offsets storage.
Partition mapping
MM2 writes each record to the same partition number on the target that it came from on the source. That is why the partition counts of the source and mirrored topics must match: key-to-partition placement stays the same, and order inside every partition is kept. If someone adds partitions to the source topic later, the connector notices on its next refresh and adds partitions to the mirror. Records with the same key can then move to a different partition on the source side, and the mirror will follow, but old and new records for that key are split between partitions in both places.
Progress and restarts
The connector commits its progress at intervals. The commit says, “I have safely written everything up to source offset N”. If it crashes between writing a batch and committing, it restarts from the last commit and copies part of the batch again. This is the exact mechanism that produces duplicates. Shorter commit intervals make duplicates rarer and smaller, at the cost of more bookkeeping traffic.
Headers, keys and timestamps
The copier keeps the record’s key, value, headers and, in normal settings, its original timestamp. This is useful: a reader in the target region can compute how old a record is, and windowed jobs based on event time keep working. Log-append timestamps, set by the broker, are not preserved, because the target broker assigns its own.
Imagine a mail sorter who moves letters from one post office to another and writes the last letter number moved on a sticky note each hour. If the sorter faints halfway through the hour, the replacement reads the old sticky note and re-moves some letters. Nothing is lost, but a few letters arrive twice.
When replication falls behind, check first for under-sized workers or too few tasks, then for a slow or lossy network link with small buffers, and then for throttling by broker quotas on either cluster.
10Topic Naming and Preventing Infinite Loops
If both regions copy everything to each other, a record could bounce back and forth forever. One naming rule stops that.
Imagine Region A copies the orders topic to Region B, and Region B copies the orders topic back to Region A. A record written in Region A is copied to B. B’s copier sees a new record in its orders topic and copies it back to A. A’s copier sees it and copies it to B again. The record circles until the disks fill up. This is a replication loop, and it is the most dangerous mistake in active-active Kafka.
The prefix rule
MM2’s default answer is to rename topics when it copies them. A topic called orders in Region A appears in Region B as a.orders, where a is the alias of the source cluster. The copier in Region B is told to copy only topics that do not already carry a prefix from the other region. So a record created locally in A, copied to B as a.orders, is not copied back, because a.orders is recognised as a remote topic that came from A. Loops become impossible by design.
flowchart LR
subgraph A["Cluster A"]
AO["orders
local writes"]
ABO["b.orders
copy from B"]
end
subgraph B["Cluster B"]
BO["orders
local writes"]
BAO["a.orders
copy from A"]
end
AO -- "copied as a.orders" --> BAO
BO -- "copied as b.orders" --> ABO
ABO -. "not copied back" .-> X["stop"]
BAO -. "not copied back" .-> X
What this means for applications
Producers in every region write to the plain orders topic in their own cluster. To see the whole world, a consumer subscribes to a pattern that matches orders and every prefixed version of it. A consumer in Region A then reads its own orders plus b.orders. Each topic has a single writer region, which is a very important property: within any one topic, only one region ever writes. This makes ordering, ownership and debugging much easier.
MM2 offers a replication policy that keeps the topic name unchanged. It is tempting because applications do not need patterns. But it removes the automatic loop protection. Use it only for one-way copies, never for two-way active-active copying, unless you add your own protection.
Choose short, permanent cluster aliases such as the region code. The alias becomes part of every topic name, every dashboard and every consumer configuration. Renaming it later means renaming everything.
11Data Flow and Lifecycle: One Record Across Two Regions
Follow a single order from the moment it is written in Mumbai to the moment a reader in Singapore sees it.
sequenceDiagram participant P as Producer in Region A participant KA as Kafka A participant MM as MirrorMaker 2 participant KB as Kafka B participant C as Consumer in Region B P->>KA: 1. Write order 1001 to orders KA-->>P: 2. Acknowledge after local replicas agree MM->>KA: 3. Fetch new records KA-->>MM: 4. Return order 1001 MM->>KB: 5. Write order 1001 to a.orders KB-->>MM: 6. Acknowledge MM->>KB: 7. Record an offset sync pair C->>KB: 8. Fetch from a.orders KB-->>C: 9. Return order 1001
Local write
The producer sends the order to its own cluster. The cluster copies it to enough local brokers and answers. From the producer’s point of view, the job is done.
Pickup by the replicator
MM2 keeps fetching from the source topic like any consumer. It sees the new record within milliseconds when it is caught up.
Cross-region hop
The record travels over the long link. This is where most of the delay lives, and where cloud transfer charges start.
Remote write
MM2 writes the record into the prefixed topic in the target cluster. It gets a new offset there.
Progress is recorded
MM2 remembers how far it has copied and periodically stores an offset sync pair for later translation.
Remote read
Consumers in the target region read the mirrored topic as if it were any other. They see the record slightly after the original writer finished.
Where the record can go wrong
Between steps 3 and 5, the copier might crash after writing the record but before saving its progress. On restart, it copies the record again, and now the target holds it twice. This is the source of duplicates, discussed shortly. If the source cluster is destroyed between steps 2 and 3, the record is lost from the world even though the producer was told it was safe. This is the replication gap, the reason RPO is not zero.
12Consumer Offsets Across Regions
When a consumer moves to another region, it must find its place again. Offset translation is the map for that.
A consumer group remembers its progress as a saved offset for every partition. If a whole application moves from Region A to Region B, the saved offsets in A’s cluster are useless in B, because B’s copy of the topic has different offsets. Without help, the application has two bad choices: start from the newest records and skip data, or start from the beginning and re-process everything.
flowchart TD S["Consumer group reads a.orders
saved position in Cluster A"] --> CK["Checkpoint connector reads group position"] CK --> LM["Finds nearest offset sync landmark"] LM --> TR["Computes translated offset for Cluster B"] TR --> W["Writes checkpoint to the checkpoints topic"] W --> O{"Sync group offsets
enabled?"} O -- "yes" --> AP["Applies offsets to the same group name in Cluster B automatically"] O -- "no" --> MAN["Failover script reads checkpoints and sets offsets"] AP --> R["After failover the group resumes near its old place"] MAN --> R
Two ways to use the translated offsets
Automatic: newer Kafka versions can apply translated offsets straight into the target cluster’s group, as long as that group has no active members there. When the application starts in the target region, it simply resumes. Manual: the checkpoints topic is read by a failover script or by the application itself, which then sets its own starting positions. The manual route gives more control, and the automatic route gives less work at the worst possible moment.
Things that surprise people
- Translation only covers groups that consumed the mirrored topics. A group that reads the local topic in Region A is translated for the mirrored copy of that topic in Region B, which is where the group would go after failover.
- A group that is active in both clusters at once with the same name can fight the automatic sync. Keep group names clear about where they run, or make sure only one side is active for each group.
- The translated position lags behind the real one by up to one landmark gap. Expect a small burst of duplicates after failover, and design consumers so that repeats are harmless.
- Checkpoints are written on a timer. If the region dies just after a big burst of progress, the last checkpoint may be a few seconds old.
Starting a moved consumer at the latest offset feels quick and safe. It silently skips every record that was still being copied when the disaster struck. Use translated offsets, and accept the duplicates.
13Ordering, Duplicates and Delivery Guarantees
What Kafka promises inside one cluster is stronger than what you get across regions. Knowing the difference prevents nasty surprises.
Inside a single cluster, Kafka guarantees that records in one partition keep their order, and with the right settings, that acknowledged records are not lost when a broker fails. With transactions, a group of writes can succeed or fail together. Across regions, these promises weaken.
| Property | Inside one cluster | Across regions with async copying |
|---|---|---|
| Order within a partition | Guaranteed | Preserved for the mirrored topic of one source topic, since MM2 keeps the same partition mapping and order |
| Order across partitions | Not guaranteed | Not guaranteed |
| Order between regions | Not applicable | Not defined. Region A’s records and Region B’s records interleave differently in different places |
| Loss on failure | Not for acknowledged records with proper settings | Possible for records not yet copied |
| Duplicates | Avoidable with idempotent producers and transactions | Possible after copier restarts; the copy path is generally at-least-once |
| Transactions | Atomic groups of writes | Records are copied one by one; the atomic grouping is not preserved as a unit on the other side |
The consequence: make consumers idempotent
An operation is idempotent when doing it twice has the same effect as doing it once. Setting a customer’s address to a given value is idempotent. Adding 100 rupees to a balance is not. Since duplicates can appear on the far side, and since consumers moving between regions re-read a few records, the reliable defence is to make the consuming code tolerate repeats. The usual method is to give every event a unique event ID and to remember the IDs already handled, or to write results with an operation that overwrites by key.
Duplicates are like a letter that the post office delivers twice. If the letter says “the meeting is at 5 pm”, nobody minds. If it says “please send me 100 rupees”, the second copy is a problem. Writing a reference number on every letter lets the receiver say, “I already dealt with number 4711”.
Kafka’s exactly-once features protect work inside one cluster. They do not stretch over an asynchronous copy to another cluster. Plan for at-least-once delivery across regions and add idempotency at the edges.
14Conflicts: When Two Regions Change the Same Thing
The heart of active-active difficulty. Copying is easy. Agreeing is hard.
A conflict happens when two regions make different changes to the same piece of information before either has heard about the other’s change. Suppose a customer’s shipping address is updated from the phone app, handled in Region A, and at almost the same moment from the website, handled in Region B. Each region writes its new address locally. Then the replicators carry each change to the other side. Now each region holds both updates, but they arrive in opposite order. Which address wins?
sequenceDiagram participant A as Region A participant B as Region B Note over A,B: Both start with address Old A->>A: Set address to Street 1 at 10:00:00.100 B->>B: Set address to Street 2 at 10:00:00.200 A-->>B: Replicate Street 1 B-->>A: Replicate Street 2 Note over A: Sees Street 1 then Street 2 Note over B: Sees Street 2 then Street 1 Note over A,B: Without a rule, A ends with Street 2 and B ends with Street 1
Strategies for resolving conflicts
Single home region
Give every record one owner region. Only the owner may change it. Other regions send requests to the owner. There is nothing to conflict.
Last writer wins
Compare timestamps carried in the record and keep the newest. Simple, but depends on clocks and silently discards the other change.
Version numbers
Each change carries the version it was based on. A change based on an old version is rejected or merged, instead of blindly overwriting.
Mergeable data types
Use data whose changes can be combined in any order, such as counters that only add or sets that only grow. Also called CRDTs.
Manual review
Flag the conflict and let a person or a business rule decide. Suitable for rare, high-value cases.
The cleanest approach is always avoidance. If a record can only be changed in one region, no conflict can exist. The next chapter shows how to build that, because it is the pattern behind most successful active-active systems.
“Last writer wins” depends on comparing times from different machines. Clocks drift by milliseconds or more, and a misconfigured machine can be minutes off. Use it only where losing an occasional update is acceptable, and prefer logical version numbers where correctness matters.
15Key Ownership and Home-Region Routing
The most reliable way to avoid conflicts is to make sure every piece of data has exactly one place where it may change.
In a home-region design, each entity, such as a customer, an account or a device, is assigned to one region. That region is its home. All changes to that entity are written by applications running in the home region, into the home region’s local topic. Other regions receive those changes through replication and treat them as read-only facts.
flowchart TD U["User request arrives
at nearest region"] --> R{"Is this user's
home region local?"} R -- "yes" --> L["Handle locally
write to local orders topic"] R -- "no" --> F["Forward request to home region
or write to a request topic"] F --> H["Home region handles it
writes to its local topic"] L --> RP["Replicator copies to other regions
as prefixed topics"] H --> RP RP --> V["Every region builds its read view
from all regional topics"]
How the home is chosen
The home can be based on where the customer lives, which is the natural choice and gives the lowest delay for most requests. It can be based on a hash of the customer ID, which spreads load evenly but may put a customer far from their home. Or it can come from a small lookup table that says which region owns which customer range. Whatever the rule, it must be stable: if two regions disagree about a customer’s home, conflicts return.
What happens when the home fails
If a home region is down, its customers cannot change their data unless another region takes over. Takeover means updating the ownership table, so that another region becomes the home. This must be a deliberate, coordinated step, ideally through a small strongly consistent system such as a metadata store, and not a decision each region makes alone. Otherwise, two regions may both believe they are the new owner. Some teams accept a short read-only period for affected customers, rather than risk a split.
A big company has branch offices, and every customer file belongs to one branch. If you walk into a different branch, the clerk reads your file from a shared copy but phones your home branch to change anything. Nobody ever writes into the same file from two places. If your home branch closes, head office formally hands your file to another branch, and only then can it be changed.
Production example: banking and messaging
Large messaging and banking platforms commonly pin an account or conversation to a home region and route all writes there. Reads are served from the nearest replicated copy. The pattern gives fast local reads and safe single-writer updates, and accepts that a remote user’s write pays one extra hop. The lesson: it is usually far cheaper to route a write than to merge two writes.
16Consumer Patterns: Local, Aggregate and Global Views
Once data lives in several prefixed topics, consumers need a plan for what to read. Three plans cover most needs.
Local only
Read only the local topic. Simple and fastest, but the application sees only data created in its own region. Good for region-specific work such as local fulfilment.
Aggregate view
Read the local topic plus every remote prefixed topic and build a combined view. Every region ends up with a full picture, slightly delayed. The most common active-active reader.
Central aggregate cluster
Copy every region’s topics into one extra cluster and let global analytics read there. Keeps heavy readers away from the busy regional clusters.
Building the aggregate view
A consumer with the aggregate pattern subscribes to a name pattern that matches the local topic and every mirrored copy. All the records arrive in one stream, but they come from different partitions and different regions, so the consumer must not assume any global order. If it needs an order, it should sort by an event time or a version number carried inside the record, and accept that late records can still arrive.
The same-shape rule
The local and mirrored topics must contain the same kind of record, with the same schema. That means schemas need to be shared between regions and evolved carefully, which is covered in the supporting systems chapter. If one region starts writing a new field that others cannot read, every aggregate reader breaks at the same moment.
Put a small field in every event that names the region where it was created, plus a unique event ID and an event time. These three fields make debugging, deduplication and conflict handling far easier, and they cost almost nothing.
| Pattern | Freshness of remote data | Load on regional clusters | Best for |
|---|---|---|---|
| Local only | None, remote data not read | Lowest | Regional processes |
| Aggregate view | Delayed by replication lag | Medium | User-facing apps that need the whole world |
| Central aggregate cluster | Delayed by two hops | Low on regions, high on the central one | Analytics, reporting, machine learning |
17Kafka Streams and Stateful Applications in Active-Active
Programs that keep running totals hold their own state. In two regions, that state can drift apart unless you plan for it.
Kafka Streams is a library for building applications that read topics, transform events, keep state such as counts or balances, and write results. The state lives in local stores backed by internal Kafka topics. In an active-active design, a Streams application usually runs in every region, each with its own state stores and internal topics in its own cluster.
The drift problem
Suppose each region runs a job that counts orders per product. Each job reads its local orders topic and the remote prefixed topic. If both jobs read exactly the same records, they end with the same counts. But the two jobs read the records in different orders, at different times, and after a failover may re-read some records. If the counting logic is not designed for this, the two answers slowly disagree.
| Kind of logic | Order sensitive? | Safe across regions? |
|---|---|---|
| Counting distinct event IDs | No | Yes, if duplicates are removed by ID |
| Summing amounts with dedup by event ID | No | Yes |
| Keeping the latest value by event time or version | Handled by the rule | Yes, if the rule is deterministic |
| Running balance where each event depends on the previous state | Yes | Only if a single region owns each account |
| Detecting sequences such as “A then B” | Yes | Risky, arrival order differs between regions |
Practical rules
- Prefer logic where the result does not depend on arrival order.
- Remove duplicates by event ID inside the job, with a store that expires old IDs after a safe period.
- Give every application its own application ID in each cluster and never mix internal topics between regions.
- Do not replicate internal changelog and repartition topics as a substitute for running the job in both regions. Restoring from mirrored changelogs across different offsets is fragile.
- Where order matters, let one region own the key, and let the other regions only read results.
Kafka Streams’ exactly-once mode covers reading, state and writing inside one cluster. Once records cross to the other cluster by an asynchronous copy, that guarantee ends. Downstream jobs in the other region must protect themselves with idempotent design.
18Supporting Systems: Schemas, Connectors, Access Rules and Configs
Kafka is rarely alone. Everything around it must also work in more than one region, or the design has a hidden single point of failure.
Schema registry
A schema describes the shape of a record, such as its field names and types. A schema registry stores schemas and gives each one an ID that is embedded in every record. A reader looks up the ID to know how to decode the bytes. If Region B receives a record that carries schema ID 42 but its own registry has never heard of ID 42, the reader cannot decode it. Multi-region setups therefore need a registry whose IDs mean the same thing everywhere, either through a primary registry in one region with read-only followers elsewhere, or through a feature that links registries and keeps IDs aligned. Schema changes must be made in one place and be compatible in both directions.
Kafka Connect and source systems
Connectors that pull from databases or push to storage must also be planned. If both regions run the same connector against the same database, the same rows are published twice. Decide which region owns each source, run the connector only there, and let replication share the results. Sinks are similar: a sink that writes to a database in each region must write idempotently, since it will see duplicates.
Access rules and settings
Access control lists, called ACLs, say who may read or write which topic. Topic settings, such as retention and compaction, say how long data lives. MM2 can copy both, but you should review what it copies, since the mirrored topic name changes and some rules must be rewritten for it. Also decide whether topic settings and partition counts should match across regions. Matching partition counts is important because it preserves key-to-partition mapping.
Schemas
IDs must decode everywhere. Change them in one place. Keep changes backward and forward compatible.
Connectors
One owner region per source system. Avoid running the same source connector in two regions.
Idempotent writes
Sinks must tolerate repeated records, since copying between regions can deliver the same record twice.
ACLs
Grant applications rights on the local topic and read rights on mirrored ones. The replicator needs its own narrow identity.
Topic configs
Retention and compaction should suit each region’s needs. Keep partition counts equal for mirrored topics.
Metadata quorum
Each cluster runs its own. Never stretch one quorum across two far regions unless you have three.
List everything the application needs to run: registry, identity provider, secrets store, DNS, feature flags, databases. If any one of them lives in a single region, your active-active Kafka will still stop when that region stops.
19Failure Scenarios: Region Outage, Link Cut and Split Brain
A design is only as good as its behaviour on the worst day. Walk through the main failures before they happen.
flowchart TD
X["Alert: replication lag rising or region unreachable"] --> Q1{"Is the region itself
healthy for its own users?"}
Q1 -- "yes" --> Q2{"Is the link between regions broken?"}
Q2 -- "yes" --> LC["Link cut: both regions keep working
backlog grows on the copier"]
Q2 -- "no" --> SL["Copier too slow or under-sized
scale workers, check throttles"]
Q1 -- "no" --> Q3{"Confirmed by more than one
independent signal?"}
Q3 -- "yes" --> RO["Region outage: shift traffic
to the surviving region"]
Q3 -- "no" --> MON["Monitoring blind spot: verify before acting"]
LC --> HEAL["Link returns: backlog drains, conflicts resolved by rules"]
RO --> FB["Later: recover the region and fail back"]
| Failure | What happens | What you do |
|---|---|---|
| One broker fails | Kafka elects new leaders inside the cluster | Nothing in multi-region terms; normal repair |
| A zone fails | Replicas in other zones take over | Confirm partitions are in sync; nothing to fail over |
| Replicator crashes | Copying pauses, lag grows, then it resumes and may repeat some records | Restart, watch lag, confirm idempotent readers |
| Link between regions cut | Both regions keep serving locally; remote data goes stale | Keep serving; expect a burst of data and possible conflicts on return |
| Region completely lost | Data not yet copied is lost; the other region is the only truth | Shift traffic, take over ownership of the lost region’s entities, accept the gap |
| Bad deployment in one region | Wrong data may be written locally and replicated onward | Stop the replicator quickly, isolate, repair, replay |
| Poison or corrupt data | The bad record is faithfully copied everywhere | Quarantine, add filters, use dead-letter topics |
Split brain
A split brain happens when two regions each believe they alone are in charge and both act as the owner of the same data. It usually follows a network split, when each region thinks the other is dead. Home-region design plus a single authority for ownership changes prevents most of these. If the ownership rules are decided by a small strongly consistent service, only the side that can reach a majority of that service is allowed to take over.
Replication is not a backup. A bad deployment that writes wrong records, or an accidental topic deletion, is copied quickly to every region. Keep separate protection, such as longer retention, archived copies and the ability to stop the replicator in seconds.
20Failover and Failback Playbook
A written, rehearsed sequence of steps beats improvisation at three in the morning.
Failover means moving work from a failed region to a healthy one. Failback means moving it back after repair. In active-active, the surviving region is already running, so failover is mostly about redirecting users and taking over ownership. The steps below assume a home-region design with translated offsets.
Declare and confirm
Use more than one independent signal, such as health checks from outside and the heartbeat topic, and name one person who is allowed to make the call.
Redirect user traffic
Change DNS or the global load balancer so users reach the healthy region. Lower record lifetimes in advance, or the change takes hours to spread.
Take over ownership
Update the ownership table so the healthy region becomes home for the lost region’s entities. Do this once, through the single authority.
Move consumers
Start the applications that used to read from the lost cluster in the healthy region, using translated offsets, and confirm they resume near the right place.
Watch duplicates and gaps
Expect a burst of repeats. Check business numbers such as order counts against another source, and record the gap in data for later reconciliation.
Recover the failed region
When it returns, do not send it traffic yet. It still holds old data and possibly stale ownership. Bring the cluster up, let replication catch up in both directions, and compare.
Fail back deliberately
Move ownership and users back in small steps, with a hold period between steps. Failback is a second, planned migration and deserves the same care as any other.
Production example: the region that came back too eagerly
A team lost a region for two hours. When it returned, automation immediately put it back into service. It still believed it owned certain customers, while the surviving region had taken them over. For a few minutes both regions accepted changes for the same customers, and reconciliation took days. The fix was a rule that a returning region starts in read-only mode and needs a human to approve ownership return. The lesson: recovery is a risky moment, and it must be slower than failure.
21Performance, Scalability and Cost
Cross-region traffic is the slowest and most expensive part of the system. Most tuning is about moving fewer bytes, more efficiently.
Where the time goes
A replicated record pays for a fetch from the source, a trip over a long link, and a write on the target. The long link has high latency, so the copier must keep many bytes in flight at once. TCP connections with small buffers can leave a fast link mostly idle. Larger socket buffers, bigger batches and compression usually raise throughput far more than adding more copier tasks.
Compress
Compressing batches before they cross the link cuts transfer volume, and often cost, by half or more for text-like data.
Batch larger
Larger batches and a small linger delay let each request carry more, which matters more on a long link.
Buffers
Raise socket buffer sizes so the link stays full. A long, fast link needs more data in flight than a short one.
Tasks and workers
Add tasks up to the number of source partitions. More tasks than partitions do nothing.
Copy less
Replicate only the topics other regions need. Internal, temporary and region-private topics should stay home.
Follower fetching
Let local consumers read from a nearby replica, so read traffic does not cross zones or regions needlessly.
Cost is a design input
Cloud providers charge for data leaving a region. A design that replicates every topic in both directions to three regions multiplies transfer cost. Count it early: bytes per day per topic, times the number of destination regions, times the provider’s rate. Many teams are surprised to find that transfer costs exceed the cost of the brokers themselves.
Capacity planning for the survivor
When one region dies, the other must carry its traffic. If each region runs at 70 percent of capacity, the survivor needs 140 percent, and it will fail. Size each region to handle the whole load, or accept planned degradation, such as turning off non-critical features during an outage. The same holds for the replicator: after an outage, the backlog must drain at the same time as normal traffic, so it needs spare throughput.
22Growing the Design: Adding a Region and Migrating Safely
Systems grow. Adding a third region is easier when you plan the steps and keep every change reversible.
Adding a region is not just building another cluster. Every existing region must start copying to it, it must start copying to every existing region, and the ownership table must learn about it. With two regions there are two replication directions. With three there are six. With four there are twelve. The count grows with the square of the regions, and so do the number of dashboards, alerts and ways to make a mistake.
Build and test the empty region
Create the cluster, security, monitoring and schema access. Send test events to prove local operation before any data flows.
Copy in one direction first
Start replicating from existing regions into the new one. Let it catch up and check that lag is steady. Nothing depends on it yet.
Start read-only applications
Run readers in the new region against mirrored data. This proves schemas, permissions and offsets under real data.
Copy back out
Only now start replication from the new region to the others, with its local topics still empty or only test data.
Move a small slice of users
Assign a few home entities to the new region. Watch conflict counters, lag and error rates. Increase gradually.
Watch out for the quadratic growth
Some teams avoid the full mesh with a hub-and-spoke layout, where every region copies to and from a central hub cluster, not directly to every other region. This cuts links from the square to a straight line, but it adds an extra hop for data crossing between two spokes, and the hub becomes a critical component that must itself be highly available. Choose the mesh for small numbers of regions and low delay, and hub-and-spoke when the region count grows.
| Layout | Links for N regions | Delay between two regions | Main risk |
|---|---|---|---|
| Full mesh | N times (N minus 1) | One hop | Many links to run, secure and watch |
| Hub and spoke | 2 times N | Two hops | The hub is a shared dependency |
At each stage, know exactly how to undo it: stop the replicator, move users back, drop the mirrored topics. A migration you cannot reverse is a bet, not a plan.
23Configuration Reference
The settings that matter most, grouped by where they live. Names follow open-source Apache Kafka; check them against your version.
MirrorMaker 2 settings
| Setting | What it controls | Guidance |
|---|---|---|
| clusters and cluster aliases | Names for each cluster, used as topic prefixes | Short, permanent, region-based |
| Source-to-target enabled flags | Which direction copies run | Enable both directions for active-active, each with its own topic filters |
| topics and topics.exclude | Which topics are copied | Use explicit allow lists; exclude internal and already-mirrored topics |
| groups | Which consumer groups get translated offsets | Include the groups that may fail over |
| replication.policy.class | Naming rule for remote topics | Keep the default prefixing policy for two-way copying |
| replication.factor | Copies of each mirrored topic on the target | Same as local production topics, usually three |
| sync.topic.configs.enabled and sync.topic.acls.enabled | Copy topic settings and access rules | Review what is copied; rewrite rules for prefixed names |
| emit.checkpoints.enabled and emit.heartbeats.enabled | Write checkpoints and heartbeats | Keep both on |
| sync.group.offsets.enabled | Apply translated offsets to target groups automatically | Use where only one side is active per group |
| refresh.topics.interval.seconds | How often new topics are discovered | Lower it if new topics must appear fast |
| tasks.max | Parallel copy tasks | Up to the partition count of the busiest topics |
Producer, consumer and broker settings that matter
| Where | Setting | Why it matters |
|---|---|---|
| Local producers | acks=all, idempotence on | Keeps local writes safe before replication even starts |
| Broker and topic | min.insync.replicas of two with replication factor three | A local write is durable in the region before the far copy begins |
| Broker | unclean leader election off | Prevents silent local data loss that would then be replicated onward |
| Copier producer | compression, batch size, linger | Efficient use of the long link |
| Copier consumer | fetch size, receive buffer | Keeps the link full despite delay |
| Broker | rack labels and follower fetching | Cuts cross-zone read traffic |
| Local consumers | read committed for transactional topics | Avoids reading aborted data from local topics |
Store the replicator configuration in version control and review every change like code. A single mistaken topic filter can start a loop or stop replication silently.
24Security and Compliance
Data crossing regions crosses trust boundaries, legal boundaries and public networks. Each one needs a lock.
Protect the link
All cross-region traffic must be encrypted in transit with TLS, and both ends must prove who they are. Mutual TLS or a strong authentication mechanism, such as SASL with short-lived credentials, prevents an attacker from posing as a replicator. Prefer private connectivity, such as peering or dedicated links between regions, over the public internet.
Give the replicator the smallest rights
The replicator needs to read the topics it copies and write to the mirrored topics, and to manage its own internal topics and groups. It should not have administrator rights over the whole cluster. Separate identities per direction help: the identity that writes into Region B should not be able to write to local topics that only local applications own.
Respect data boundaries
If some data must stay in a country, do not replicate its topic, or replicate only a filtered, masked version. A common design keeps personal topics regional and publishes derived, anonymous events for global use. Test the filter, because a missing filter is a legal problem, not a bug.
Other important points
- Encrypt data at rest in every region with keys managed in that region, or with keys that each region can reach even when another region is down.
- Audit who reads mirrored topics; the copy in Region B is as sensitive as the original in Region A.
- Rotate credentials in both regions on a schedule, and rehearse it. A certificate that expires in one region can silently stop replication.
- Apply retention rules consistently. Deleting a customer’s data means deleting it in every region, including mirrors and backups.
Laws that require deleting a person’s data apply to every copy. Design a way to trace and remove data from mirrored topics, such as compacted topics with tombstone records, or per-customer encryption keys that can be destroyed.
25Monitoring, Logging and Metrics
You cannot manage what you cannot see. A few well-chosen signals tell you whether both regions are healthy and in step.
flowchart LR
subgraph RA["Region A"]
KA["Kafka A metrics"]
MMA["Replicator metrics"]
end
subgraph RB["Region B"]
KB["Kafka B metrics"]
MMB["Replicator metrics"]
end
HB["Heartbeat topic
end-to-end delay"]
KA --> DASH["Dashboards and alerts
hosted outside both regions"]
KB --> DASH
MMA --> DASH
MMB --> DASH
HB --> DASH
DASH --> ONC["On-call engineer"]
| Signal | What it tells you | Typical alert |
|---|---|---|
| Replication latency and record age | How late the far copy is, in time | Above your RPO target for several minutes |
| Replication throughput | Bytes copied per second | Drops to zero while producers are active |
| Heartbeat delay | End-to-end health even when traffic is quiet | Heartbeat older than a few intervals |
| Checkpoint latency | Freshness of translated consumer offsets | Much older than the expected interval |
| Copier task failures and restarts | Stability of the pipe | Repeated restarts |
| Consumer lag per region and per prefixed topic | Whether readers keep up with mirrored data | Growing lag on any aggregate reader |
| Under-replicated partitions in each cluster | Local cluster health | Any sustained value above zero |
| Cross-region bytes and cost | Money leaving the door | Sudden jump versus a baseline |
| Duplicate and conflict counters in applications | How often repeats and conflicts really occur | Sharp increase after a release or failover |
Measure RPO directly
Replication lag expressed in records is hard to interpret. Convert it into time: “the far region is 4 seconds behind”. That number is your live estimate of how much would be lost if the source region vanished right now. Track it, graph it, and alert when it exceeds the business target.
Logging that helps later
Log every ownership change, every failover step, and every conflict decision with the event ID and region. When a customer asks why their update disappeared, this log is the only way to answer.
Send a tiny test event from each region every few seconds and check that it appears in every other region. It proves the whole path, including permissions, naming and schemas, works right now.
26Design Patterns and Anti-Patterns
A few shapes recur in every successful design, and a few mistakes recur in every painful one.
Patterns worth using
Local write, remote read
Every application writes only to its local cluster, and other regions receive data by replication. Keeps writes fast and blame clear.
Home region per entity
One owner region per customer or account. Forward writes there. Conflicts disappear by design.
Idempotent consumer
Every event has an ID. Consumers remember or overwrite by ID so repeats do nothing.
Outbox
A service writes its database change and its event in one local step, and a relay publishes the event to Kafka. No lost or phantom events at the edge.
Aggregate cluster
A separate cluster gathers data from all regions for analytics, isolating heavy readers.
Regional kill switch
A control that can stop replication or reroute traffic in seconds when something bad is spreading.
Anti-patterns to avoid
What people do
They write to the same topic name in both regions and copy in both directions without prefixes.
Why it hurts
Records loop between the clusters, or two writers interleave in a topic that nobody can reason about. Ordering and ownership are lost.
Do this instead
Use one writer region per topic, prefixed remote topics, and readers that combine them.
What people do
They stretch a single cluster across continents to get zero data loss.
Why it hurts
Every write waits for a trip across an ocean, throughput falls, and a network split can stall everyone.
Do this instead
Use separate clusters and accept a small, measured RPO, or keep the stretch cluster inside one metropolitan area.
What people do
They assume that a consumer’s saved offset in Region A can be used unchanged in Region B.
Why it hurts
The consumer skips data or repeats a huge range, and nobody notices until numbers are wrong.
Do this instead
Use translated offsets, keep consumers idempotent, and rehearse failover.
What people do
They use “last writer wins” on wall-clock times for important data, such as balances or inventory.
Why it hurts
Clock differences silently discard real updates, and the loss is invisible.
Do this instead
Give the data a single owner, or use version numbers or mergeable data types.
What people do
They size each region for its own traffic only.
Why it hurts
When one region fails, the survivor is overloaded and fails too, turning a regional outage into a global one.
Do this instead
Plan for the survivor to carry the load, or define exactly what gets switched off.
27Real-World and Industry Examples
How well-known companies have described their multi-region streaming setups in public, in general terms.
These summaries are based on publicly shared talks and articles and are described in general terms. Details change over time and I did not check them against live sources. Treat them as illustrations of patterns, not as exact descriptions of any company’s current systems.
Uber
Has described running Kafka clusters in several regions with in-house replication tooling, aggregate clusters for global views, and a service that helps consumers resume in another region after failover. The pattern: regional clusters plus aggregation plus offset handling.
Created Kafka and has described mirroring data between data centers into aggregate clusters, later building its own replication service to handle scale and reliability. The pattern: local production, central aggregation.
Netflix
Has shared that its event pipelines run in multiple regions so that traffic can be moved away from a struggling region. The pattern: regional pipelines that can absorb each other’s load.
Financial platforms
Commonly keep each account in a home region and use idempotency keys everywhere, since duplicates are unavoidable. The pattern: single owner plus idempotent processing.
Global retailers
Often replicate catalog and price events to every region while keeping orders local to the customer’s region. The pattern: share reference data widely, keep transactions close to home.
Booking platforms
Need availability data everywhere but must avoid double booking. The pattern: single owner per inventory item, replicated views elsewhere.
What these stories have in common
None of them tries to make every region write every fact at will. They give data a natural home, replicate views of it elsewhere, treat duplicates as normal, and invest heavily in tools to observe lag and to move traffic. The technology is the smaller part of the work. The larger part is deciding who owns each piece of data.
28Best Practices and Common Mistakes
A checklist to review before going live, and a list of mistakes that show up again and again.
Do
- Decide the RPO and RTO in numbers before choosing a design.
- Give every entity a home region and route writes to it.
- Add event ID, origin region and event time to every record.
- Make every consumer and sink idempotent.
- Keep topic names prefixed and one writer region per topic.
- Monitor lag in seconds and alert against the RPO target.
- Run dashboards and alerting outside the regions they watch.
- Rehearse failover and failback on a schedule.
Do not
- Do not assume replication is a backup.
- Do not fail over automatically on a single signal.
- Do not reuse local consumer offsets in another cluster.
- Do not run the same source connector in two regions.
- Do not size regions only for their own traffic.
- Do not forget to plan the trip home after failover.
- Do not change schemas in two places at once.
- Do not depend on wall-clock timestamps for critical decisions.
Mistakes seen most often
- Treating asynchronous as synchronous. Teams tell the business that no data can be lost, then discover the far copy was seconds behind.
- Forgetting the small services. Kafka is multi-region, but the schema registry, identity provider or configuration store sits in one region.
- Testing only the happy path. Failover is tried once in a calm test, never under load, never with a partial network failure.
- Unbounded reprocessing. After failover, a consumer starts from the beginning of a topic and overloads downstream systems.
- Silent filters. A replicator topic filter that does not match a new topic means the data is never copied and nobody notices for weeks.
29Testing and Game Days
Confidence in a multi-region design comes from breaking it on purpose while everyone is watching.
A game day is a planned exercise in which the team injects a failure into a real or production-like environment and observes what happens. The point is not to prove that the design works. The point is to find what does not, while the cost is low.
Baseline test
Send known events through every path and confirm they arrive, once or with harmless repeats, with correct prefixes and schemas.
Slow the link
Add delay and packet loss between regions. Watch lag, alerts, and whether applications behave sensibly with stale remote data.
Cut the link
Block traffic entirely for a while, then restore it. Check the backlog drain and any conflicts that appear.
Kill the replicator
Stop and restart it mid-stream. Count duplicates and confirm that consumers absorb them.
Lose a region
Shut a region down under load. Time the traffic shift, offset translation and ownership takeover against your RTO.
Fail back
Restore the region and return traffic in stages. This step is skipped surprisingly often, and it is where surprises hide.
After each exercise, turn every surprise into a fix, an alert or a runbook step. A game day with no written findings is just a stressful afternoon.
30Worked Example: An Imaginary Shop Called CartCloud
All the ideas together in one made-up system. The company and numbers are invented for teaching.
CartCloud sells goods to customers in India, Southeast Asia and Europe. It runs three regions, which we call Mumbai, Singapore and Frankfurt. Its goals are simple: pages must feel fast everywhere, the shop must survive the loss of any one region, and customer data of European customers must stay in Europe.
flowchart TB
subgraph MUM["Mumbai"]
AM["Cart and order services"] --> KM["Kafka Mumbai"]
end
subgraph SIN["Singapore"]
AS["Cart and order services"] --> KS["Kafka Singapore"]
end
subgraph FRA["Frankfurt"]
AF["Cart and order services"] --> KF["Kafka Frankfurt"]
end
KM -- "catalog and price events" --> KS
KS -- "catalog and price events" --> KM
KM -- "catalog and price events" --> KF
KF -- "catalog and price events" --> KM
KS -- "catalog and price events" --> KF
KF -- "catalog and price events" --> KS
KM -- "anonymous order stats only" --> AGG["Global analytics cluster"]
KS -- "anonymous order stats only" --> AGG
KF -- "anonymous order stats only" --> AGG
Decisions and reasons
| Decision | Reason |
|---|---|
| Three separate clusters, no stretch cluster | Regions are far apart. Local writes must stay fast. |
| Each customer has a home region, chosen by where they signed up | Orders and carts have one owner, so no conflicts. European customers are homed in Frankfurt and their personal topics are never replicated out. |
| Catalog and price events are written by a central team in Mumbai, and mirrored to the others | Reference data with a single author needs no conflict handling. Other regions read the prefixed topic. |
| Consumers read local plus remote topics through name patterns | Every region builds a full catalog view. |
| Every event carries event ID, origin region and event time | Deduplication and debugging. |
| Order processing overwrites by order ID, and payment calls use idempotency keys | Repeats after failover do no harm. |
| Replication lag is reported in seconds; alert at 30 seconds, page at 120 | Business accepts up to two minutes of possible loss in a total region loss. |
| Each region is sized to carry 60 percent of global traffic; non-critical features switch off during a region loss | Balances cost against survival. |
A bad day: Singapore goes dark
Detection
External probes and the heartbeat topic both show Singapore missing. Two independent signals agree, and the on-call lead declares an outage.
Traffic shift
The global load balancer sends Singapore’s users to Mumbai. Short DNS lifetimes set in advance make this take minutes.
Ownership takeover
Singapore’s customers are reassigned to Mumbai through the ownership service. Mumbai starts accepting their changes.
Consumer resume
Jobs that read Singapore’s data start in Mumbai from translated offsets. A burst of repeats appears and is absorbed by order-ID overwrites.
Gap reconciliation
The last 40 seconds of Singapore orders, which had not yet been copied, are missing. Payment records from the payment provider are compared, and the gap is reconstructed.
Recovery and failback
Singapore returns in read-only mode. Replication catches up both ways. After review, ownership and traffic move back in three stages over a day.
31Choosing the Right Design: A Decision Guide
Answer a few honest questions and the right layout usually becomes obvious.
flowchart TD
S["Start: do you need more than one region?"] --> Q1{"Must the service survive
a whole region loss?"}
Q1 -- "no" --> SR["Single region across 3 zones"]
Q1 -- "yes" --> Q2{"Is zero data loss required
and are sites close together?"}
Q2 -- "yes" --> ST["Stretch cluster over 3 nearby sites"]
Q2 -- "no" --> Q3{"Do users in several regions
need fast local writes?"}
Q3 -- "no" --> AP["Active-passive with replication
and rehearsed failover"]
Q3 -- "yes" --> Q4{"Can each entity have
one home region?"}
Q4 -- "yes" --> AA1["Active-active with home regions
simplest and safest"]
Q4 -- "no" --> AA2["Active-active with merge rules
highest effort, use with great care"]
Choose active-active when
- Users in several regions need fast local writes.
- Recovery time must be seconds or minutes, not an hour.
- Data has a natural owner region, or changes can be merged safely.
- The team can run and rehearse a complex system.
Prefer something simpler when
- One region takes all writes and a short outage is acceptable.
- No clear ownership exists and merges are dangerous.
- The team is small and has no failover practice.
- Cross-region cost would outweigh the business value.
A safe path is to begin with active-passive, prove replication and failover, then make the second region active for selected topics one by one. Each step teaches the team something before the stakes rise.
32Frequently Asked Questions
Short answers to questions that come up in almost every design discussion.
33Summary and Key Takeaways
The whole guide in a few sentences.
Key Takeaways
- Two ways to span regions. A stretch cluster gives zero loss but pays the round trip on every write and needs three close sites. Separate clusters with asynchronous replication keep writes fast and accept a small, measurable loss window.
- One writer per topic. Use prefixed remote topics so each topic has a single writer region, loops are impossible, and readers combine local and remote topics.
- Avoid conflicts, do not merge them. Give every entity a home region and route writes to it. Use versions or mergeable data only where you truly cannot.
- Offsets need translation. Move consumers with translated offsets, expect a few duplicates, and never fall back on the latest position.
- Design for repeats. Carry event ID, origin and time in every record, and make every consumer and sink idempotent. Exactly-once does not cross the asynchronous copy.
- Watch lag in seconds and rehearse. Alert against the RPO target, keep monitoring outside the regions, and practise failover and failback on a schedule.
- Size and secure for the bad day. The survivor must carry the load, links must be encrypted and authenticated, and data boundaries must be enforced by filters you have tested.