skip to content

Active-Active Bidirectional Replication

Two-way replication with prefixed topics, local plus aggregate consumption, and the write-conflict and dedup problems it creates. Interviewers ask because active-active sounds simple and is not.

part ofApache Kafkaoverview, primer and where to startread it →
on this pageshow

questions

5

In an active-active bidirectional MirrorMaker 2 setup between two clusters, how does the remote-topic naming convention prevent replication loops, and what would happen if you turned it off?

level: juniorimportance: must knowfreq 70%

answer

  1. DefaultReplicationPolicy: source-alias prefix
  2. A.orders, B.orders
  3. prefix = origin tag = loop guard
  4. IdentityReplicationPolicy loops bidirectionally
  5. consumers subscribe local + remote

basics

~20 s

MM2 prefixes replicated topics with the source cluster's alias (e.g. topic 'orders' from cluster A becomes 'A.orders' on cluster B). Because the prefix marks where data came from, MM2 won't replicate a topic back to the cluster it originated from, so records don't loop forever.

solid answer

~50 s

MirrorMaker 2 renames each replicated topic by prepending the source cluster alias using the DefaultReplicationPolicy: 'orders' on cluster A appears as 'A.orders' on cluster B, and 'orders' on B appears as 'B.orders' on A. The prefix is the loop-prevention mechanism: MM2 inspects the topic name and refuses to replicate a topic whose name indicates it already came from the destination (it won't take 'A.orders' on B and copy it back to A as 'A.A.orders'). If you flatten names with IdentityReplicationPolicy in a bidirectional setup without other safeguards, a record written to 'orders' on A replicates to 'orders' on B, which MM2 then sees as a local topic on B and replicates back to 'orders' on A, creating an infinite loop and unbounded duplication. The trade-off: prefixed names mean consumers must subscribe to both 'orders' and 'B.orders' (or use a regex/aggregate pattern) to see all data.

go deeper

for a junior

Know that MM2 renames replicated topics with a cluster-name prefix and that this prefix is what stops records from bouncing back and forth forever.

for a middle

Explain DefaultReplicationPolicy vs IdentityReplicationPolicy and why identity naming loops in a two-way setup; know consumers must read local + remote topics.

for a senior

Discuss the ReplicationPolicy.topicSource mechanism, distinct-alias requirement, multi-hop name accumulation, and the consumer-subscription regex / aggregate-cluster consequence.

for a principal

Reason about when to override the policy, custom loop guards for identity naming, alias governance across many clusters, and the operational cost of prefix-aware consumer contracts org-wide.

## The problem: replication loops In active-active replication, **both** clusters accept writes and **both** directions of MirrorMaker 2 (MM2) are running: A→B and B→A. The naive danger is a loop: a record produced to topic `orders` on cluster A is copied to `orders` on B; the B→A flow then sees `orders` on B as a topic it should replicate and copies it back to `orders` on A; A→B copies it again, and so on forever. Each pass duplicates the record. ## How MM2 prevents it: remote-topic prefixing MM2's `MirrorSourceConnector` does not keep topic names identical. By default it uses the **`DefaultReplicationPolicy`**, which renames a replicated topic as `<source-alias>.<topic>`. So with cluster aliases `A` and `B`: - `orders` produced on A becomes **`A.orders`** on B. - `orders` produced on B becomes **`B.orders`** on A. The alias prefix is metadata baked into the topic name that says *"this data originated at A."* MM2 uses it for loop prevention: the source connector calls `ReplicationPolicy.topicSource(...)` / `isInternalTopic` style checks and **will not replicate a topic that the policy says already originated from the downstream cluster**. Concretely, the B→A connector sees `A.orders` on B, recognizes via the policy that its origin is A (the destination), and skips it. So `A.orders` never becomes `A.A.orders` back on A. Loop broken. ## Local vs. remote topics This yields two kinds of topics on each cluster: - **Local topics**: produced directly to that cluster (`orders` on A). - **Remote topics**: mirrored in, carrying a prefix (`B.orders` on A). A consumer in region A that wants *all* orders globally subscribes to both `orders` and `B.orders` — often via a regex like `(^|.*\.)orders$` or by reading from a dedicated **aggregate cluster**. ## What if you disable prefixing? MM2 ships an **`IdentityReplicationPolicy`** that keeps names identical (`orders`→`orders`). It exists for one-directional migration scenarios where you're moving off legacy MirrorMaker 1 and want unchanged names. In a **bidirectional** setup it is dangerous: with identical names and no other origin-tracking, MM2 cannot tell a re-replicated record from a fresh local write, so you get the infinite loop and exploding duplication described above. If you must use identity naming bidirectionally you have to add your own loop guard (e.g., record headers identifying origin and a filtering SMT, or only replicate disjoint topic sets per direction). ## Edge cases - **Custom aliases collide**: if both clusters use the same alias, prefixing no longer disambiguates origin — keep aliases distinct. - **Multi-hop topologies** (A→B→C): names accumulate context; `DefaultReplicationPolicy` handles `topicSource` so `A.orders` on B replicates to C, where consumers still see it traces back to A. - **Consumers must be prefix-aware**: a poorly written consumer subscribing only to `orders` in region A silently misses everything written in region B.

  • A consumer in region A only subscribes to 'orders' and complains it's missing data written in region B. What's wrong and how do you fix it?
    Data from B arrives on A as the prefixed remote topic 'B.orders', not 'orders'. The consumer must also subscribe to 'B.orders' — typically via a regex subscription matching both local and remote forms, or by consuming from an aggregate cluster. The 'local + aggregate consumption' pattern exists precisely so apps see both local and mirrored data.
  • When is IdentityReplicationPolicy actually appropriate?
    For one-directional flows — e.g., migrating off MirrorMaker 1 or a single source→target replication where you want topic names unchanged and there's no return flow to loop with. Using it bidirectionally without an external loop guard causes infinite replication.

saying these in an interview costs you the question

  • Claiming MM2 prevents loops with offset tracking or consumer-group state rather than the topic name prefix
  • Saying you should always use IdentityReplicationPolicy because 'prefixes are ugly' — that breaks loop prevention in active-active
  • Thinking the prefix is purely cosmetic / for human readability
  • Forgetting that consumers must subscribe to both local and remote (prefixed) topics to see all data

context

open as a page

In active-active, both regions accept writes to the same logical entity. What write-conflict and ordering hazards arise across regions, and how do idempotency keys and dedup help?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Two regions can write conflicting updates to the same entity concurrently, and MM2 gives no cross-region ordering or conflict resolution — it just copies records. You handle conflicts at the application layer: attach idempotency keys so re-delivered or duplicate records are deduped, and use last-writer-wins, CRDTs, or entity-region affinity to resolve concurrent updates.

open as a page

Explain the per-region local + aggregate consumption pattern in an active-active deployment. How should a globally-aware consumer read all data, and what are the aggregate-cluster trade-offs?

level: middleimportance: should knowfreq 55%

basics

~20 s

Each region has local topics (written there) plus remote mirrored topics from other regions. A region-local consumer reads just local topics; a globally-aware consumer reads both local and remote (prefixed) topics — via a regex subscription or by consuming a separate aggregate cluster that holds all regions' data in one place.

open as a page

How do you actually configure bidirectional MM2 between two clusters (connectors, flows, prefixes), and how do consumers fail over between regions?

level: middleimportance: should knowfreq 45%

basics

~20 s

Define both clusters and enable replication in both directions in the MM2 config (A->B and B->A). MM2 runs three connectors per flow: MirrorSourceConnector (copies records, applies the prefix), MirrorCheckpointConnector (translates consumer offsets), and MirrorHeartbeatConnector (liveness). For failover, consumers use the translated checkpoint offsets to resume on the other cluster.

open as a page

As an architect, when would you choose active-active bidirectional MM2 over alternatives like a stretched cluster or active-passive, and what consistency limits must you communicate to product teams?

level: principalimportance: should knowfreq 35%

basics

~20 s

Choose active-active MM2 when both regions must serve low-latency local writes and tolerate eventual, asynchronously-replicated cross-region data. Avoid it when you need strong global consistency or strict ordering — a stretched cluster gives synchronous consistency (at WAN-latency cost), and active-passive gives simpler failover without write conflicts. The key limit to communicate: it's eventually consistent, at-least-once, with no global ordering or conflict resolution.

open as a page