Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Multi-Datacenter

Strategies for deploying Apache Kafka across multiple datacenters for disaster recovery and global distribution.


ModelRPORTOComplexityUse Case
Active-PassiveMinutesMinutesLowDisaster recovery
Active-ActiveNear-zero (bounded by replication lag)Near-zeroHighGlobal distribution
Stretch ClusterZero (with synchronous replication)SecondsMediumLow-latency DR

Primary datacenter handles all traffic. Secondary datacenter receives replicated data for failover.

Active-passive replication with MirrorMaker 2Active-passive replication with MirrorMaker 2Primary DCSecondary DC (Standby)ProducersKafka ClusterConsumersMirrorMaker 2Kafka ClusterConsumers(inactive)- Read-only replica- Consumers inactive until failover- Consumer offsets synchronizedproduceconsumereplicateproduce
mm2.properties
# Define clusters
clusters=primary,secondary
primary.bootstrap.servers=kafka-primary-1:9092,kafka-primary-2:9092
secondary.bootstrap.servers=kafka-secondary-1:9092,kafka-secondary-2:9092
# Replication flows
primary->secondary.enabled=true
primary->secondary.topics=.*
primary->secondary.groups=.*
# Exclude internal topics
primary->secondary.topics.exclude=.*[\-\.]internal,.*\.replica,__.*
# Replication settings
replication.factor=3
checkpoints.topic.replication.factor=3
heartbeats.topic.replication.factor=3
offset-syncs.topic.replication.factor=3
# Consumer offset sync (for failover)
sync.group.offsets.enabled=true
sync.group.offsets.interval.seconds=60
# Emit checkpoints for offset translation
emit.checkpoints.enabled=true
emit.checkpoints.interval.seconds=60
# Note: replication factors must not exceed the broker count in each target cluster.
  1. Detect failure - Monitor primary cluster health
  2. Stop MirrorMaker 2 - Prevent split-brain
  3. Translate offsets - Use checkpoint data
  4. Redirect producers - Update bootstrap servers
  5. Start consumers - Resume from translated offsets
Terminal window
# Translate consumer group offsets
kafka-consumer-groups.sh --bootstrap-server kafka-secondary:9092 \
--group my-consumer-group \
--reset-offsets \
--to-offset <translated-offset> \
--topic primary.my-topic \
--execute

The translated offsets are derived from the primary.checkpoints.internal topic emitted by MirrorMaker 2.


Both datacenters handle traffic. Bidirectional replication requires careful handling of data provenance to prevent infinite replication loops and enable correct data aggregation.

Active-active bidirectional replication with MirrorMaker 2Active-active bidirectional replication with MirrorMaker 2DC EastDC WestProducersKafka ClusterConsumersProducersKafka ClusterConsumersMirrorMaker 2(bidirectional)Topic naming:- east.orders (from east)- west.orders (from west)Prevents replication loops

In active-active replication, the system must track where each record originated. Without provenance tracking, records would replicate infinitely:

1. Producer writes to east.orders in DC East
2. MirrorMaker replicates to DC West as east.orders
3. Without provenance: MirrorMaker replicates back to DC East
4. Infinite loop of replication

MirrorMaker 2 solves this through topic prefixing—each replicated topic carries its origin datacenter in the name.

Topic prefixing that records the origin datacenterTopic prefixing that records the origin datacenterDC East ClusterDC West Clusterorders(local)west.orders(replicated from west)orders(local)east.orders(replicated from east)Consumers in DC East see:- orders (local writes)- west.orders (from DC West)Consumers in DC West see:- orders (local writes)- east.orders (from DC East)MirrorMaker 2adds "east." prefixMirrorMaker 2adds "west." prefix
Topic in DC EastOriginDescription
ordersDC EastLocally produced records
west.ordersDC WestReplicated from DC West
Topic in DC WestOriginDescription
ordersDC WestLocally produced records
east.ordersDC EastReplicated from DC East

MirrorMaker 2 never replicates prefixed topics, preventing loops:

  • east.orders in DC West is not replicated back to DC East
  • west.orders in DC East is not replicated back to DC West

Consumers that need a global view must subscribe to both local and replicated topics:

// Consumer in DC East wanting all orders globally
consumer.subscribe(Arrays.asList(
"orders", // Local DC East orders
"west.orders" // Replicated DC West orders
));
// Process records with origin awareness
while (true) {
ConsumerRecords<String, Order> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, Order> record : records) {
String origin = record.topic().startsWith("west.") ? "west" : "east";
processOrder(record.value(), origin);
}
}

For more granular provenance tracking, producers can add origin metadata to record headers:

// Producer adds provenance headers
ProducerRecord<String, Order> record = new ProducerRecord<>("orders", order.getId(), order);
record.headers()
.add("origin-dc", "east".getBytes())
.add("origin-timestamp", Long.toString(System.currentTimeMillis()).getBytes())
.add("origin-producer", producerId.getBytes());
producer.send(record);

Consumers can then extract provenance regardless of topic name:

Header originHeader = record.headers().lastHeader("origin-dc");
String originDc = new String(originHeader.value());
PatternImplementationUse Case
UnionSubscribe to orders + west.ordersGlobal view of all orders
Local-firstSubscribe to orders onlyDC-local processing
Kafka StreamsMerge streams with origin trackingComplex aggregations

Kafka Streams aggregation example:

// Merge streams from both origins
KStream<String, Order> localOrders = builder.stream("orders");
KStream<String, Order> remoteOrders = builder.stream("west.orders");
KStream<String, Order> allOrders = localOrders.merge(remoteOrders);
// Process with origin awareness using headers
allOrders.foreach((key, order) -> {
// Origin available in record headers
});
mm2-active-active.properties
clusters=east,west
east.bootstrap.servers=kafka-east-1:9092,kafka-east-2:9092
west.bootstrap.servers=kafka-west-1:9092,kafka-west-2:9092
# East to West replication
east->west.enabled=true
east->west.topics=orders,events
# West to East replication
west->east.enabled=true
west->east.topics=orders,events
# Prevent replication loops
replication.policy.class=org.apache.kafka.connect.mirror.DefaultReplicationPolicy
# Topic naming (default adds source cluster prefix)
# east.orders in west cluster
# west.orders in east cluster
StrategyDescriptionTrade-off
Topic prefixingDifferent topic names per DCConsumers must aggregate
Key partitioningRoute keys to owning DCRequires consistent routing
Last-write-winsAccept all writes, latest winsPotential data loss
Application mergeApplication-level conflict resolutionComplexity

Single Kafka cluster spanning multiple datacenters with synchronous replication.

Stretch cluster spanning two datacenters and a witnessStretch cluster spanning two datacenters and a witnessStretch ClusterDC1DC2DC3 (Witness)Broker 1Broker 2Broker 3Broker 4Broker 5(controller only)- Single cluster namespace- Synchronous replication- Requires low-latency network- Zero RPOsync replicationsync replication
# Rack awareness for cross-DC placement
broker.rack=dc1
# Minimum ISR spans DCs
min.insync.replicas=2
default.replication.factor=3
# Replica placement
replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector
RequirementThreshold
Network latencyTypically < 10ms RTT between DCs
Network bandwidthSufficient for replication traffic
Broker countOdd number for controller quorum

AspectActive-PassiveActive-ActiveStretch Cluster
RPOMinutesNear-zeroZero
RTOMinutesNear-zeroSeconds
Latency impactNoneNoneCross-DC latency
Network requirementAsync-capableAsync-capableLow-latency
Topic namespaceSeparateSeparate (prefixed)Single
Failover complexityManual/automatedMinimalAutomatic

MirrorMaker 2 synchronizes consumer group offsets using checkpoints.

MirrorMaker 2 checkpoint emission for consumer offset translationMirrorMaker 2 checkpoint emission for consumer offset translationPrimaryMirrorMaker 2Secondarytopic__consumer_offsetsCheckpointEmitterprimary.topicprimary.checkpoints.internalMaps primary offsetsto secondary offsetsfor failoverreplicatereademit
Terminal window
# View checkpoint topic
kafka-console-consumer.sh --bootstrap-server kafka-secondary:9092 \
--topic primary.checkpoints.internal \
--from-beginning \
--property print.key=true

MetricDescriptionAlert Threshold
kafka.connect.mirror.record-countRecords replicatedSudden drops
kafka.connect.mirror.record-age-msReplication lag> 60000 ms
kafka.connect.mirror.checkpoint-latency-msCheckpoint delay> 120000 ms
kafka.connect.mirror.replication-latency-msEnd-to-end latency> 30000 ms
Terminal window
# Check MirrorMaker 2 status
curl http://connect:8083/connectors/mirror-source-connector/status
# Check replication lag
kafka-consumer-groups.sh --bootstrap-server kafka-secondary:9092 \
--group mirror-source-connector \
--describe

PracticeRationale
Test failover regularlyEnsure procedures work
Monitor replication lagDetect issues early
Use rack awarenessDistribute replicas across DCs
Document failover proceduresReduce MTTR
Automate where possibleReduce human error