Data replication strategies including primary-backup, multi-primary, chain replication, and quorum-based replication
Scope: Primary-backup, multi-primary, chain replication, quorum replication, trade-offs Lines: ~270 Last Updated: 2025-10-27 Format Version: 1.0 (Atomic)
Activate this skill when:
Approach: One primary handles writes, backups handle reads
Client writes → Primary → Replicate → Backups
Client reads → Primary or Backups
Implementation:
class PrimaryBackupStore:
"""Primary-backup replication"""
def __init__(self, is_primary=False):
self.is_primary = is_primary
self.data = {}
self.backups = []
def write(self, key, value):
"""Write (primary only)"""
if not self.is_primary:
raise NotPrimaryError()
self.data[key] = value
# Replicate to all backups
for backup in self.backups:
backup.replicate(key, value)
return True
def replicate(self, key, value):
"""Receive replication (backup only)"""
if self.is_primary:
return
self.data[key] = value
def read(self, key):
"""Read (any node)"""
return self.data.get(key)
# Usage
primary = PrimaryBackupStore(is_primary=True)
backup1 = PrimaryBackupStore(is_primary=False)
backup2 = PrimaryBackupStore(is_primary=False)
primary.backups = [backup1, backup2]
primary.write('key1', 'value1') # Replicated to backups
print(backup1.read('key1')) # 'value1'
Pros:
Cons:
Approach: Multiple nodes accept writes, sync with each other
Client A writes → Primary A ←─┐
↓ │ Sync
Primary B ←──┘
↑
Client B writes ──────┘
Implementation:
class MultiPrimaryStore:
"""Multi-primary with conflict resolution"""
def __init__(self, node_id):
self.node_id = node_id
self.data = {} # key → (value, vector_clock)
self.peers = []
def write(self, key, value):
"""Write to local node"""
# Update with new vector clock
vc = self._increment_vector_clock(key)
self.data[key] = (value, vc)
# Async replicate to peers
for peer in self.peers:
peer.replicate_async(key, value, vc)
def replicate_async(self, key, value, vector_clock):
"""Receive replication from peer"""
if key not in self.data:
self.data[key] = (value, vector_clock)
else:
# Merge with conflict resolution
self._merge(key, value, vector_clock)
def _merge(self, key, new_value, new_vc):
"""Merge concurrent updates"""
old_value, old_vc = self.data[key]
if self._causally_before(old_vc, new_vc):
# New value supersedes old
self.data[key] = (new_value, new_vc)
elif self._causally_before(new_vc, old_vc):
# Old value supersedes new
pass
else:
# Concurrent - keep both (siblings) or use LWW
# For simplicity, use LWW based on node ID
if new_vc > old_vc: # Simplified comparison
self.data[key] = (new_value, new_vc)
Pros:
Cons:
Approach: Nodes in a chain, writes flow through chain
Client write → Head → Node 2 → ... → Tail → Ack to client
Client read ← Tail (strongly consistent)
Implementation:
class ChainNode:
"""Node in chain replication"""
def __init__(self, node_id):
self.node_id = node_id
self.data = {}
self.next_node = None
self.is_tail = False
def write(self, key, value):
"""Write (head only)"""
self.data[key] = value
if self.next_node:
# Forward to next node
self.next_node.write(key, value)
elif self.is_tail:
# Tail: acknowledge to client
return True
def read(self, key):
"""Read from tail only (strongly consistent)"""
if not self.is_tail:
raise MustReadFromTailError()
return self.data.get(key)
# Setup chain
head = ChainNode('head')
middle = ChainNode('middle')
tail = ChainNode('tail')
head.next_node = middle
middle.next_node = tail
tail.is_tail = True
# Write flows through chain
head.write('key1', 'value1')
# Read from tail (has all committed writes)
print(tail.read('key1')) # 'value1'
Pros:
Cons:
Approach: Write to W nodes, read from R nodes where W+R > N
Write: Ack when W replicas confirm
Read: Query R replicas, return latest
Implementation:
class QuorumStore:
"""Quorum-based replication"""
def __init__(self, nodes, w, r):
self.nodes = nodes
self.w = w # Write quorum
self.r = r # Read quorum
def write(self, key, value):
"""Write to W nodes"""
version = self._generate_version()
acks = 0
for node in self.nodes:
try:
if node.put(key, value, version):
acks += 1
if acks >= self.w:
return True
except:
continue
raise QuorumNotMetError(f"Only {acks}/{self.w} acks")
def read(self, key):
"""Read from R nodes"""
responses = []
for node in self.nodes:
try:
value, version = node.get(key)
responses.append((value, version))
if len(responses) >= self.r:
break
except:
continue
if len(responses) < self.r:
raise QuorumNotMetError(f"Only {len(responses)}/{self.r} responses")
# Return value with highest version
return max(responses, key=lambda x: x[1])[0]
Pros:
Cons:
def sync_write(primary, backups, key, value):
"""Wait for all backups to acknowledge"""
primary.write(key, value)
for backup in backups:
backup.replicate(key, value) # Blocks until complete
return True # All replicas updated
Pros: Strong consistency Cons: Higher latency, availability depends on all nodes
import threading
def async_write(primary, backups, key, value):
"""Don't wait for backups"""
primary.write(key, value)
for backup in backups:
threading.Thread(target=backup.replicate, args=(key, value)).start()
return True # Don't wait for replication
Pros: Low latency, high availability Cons: Eventual consistency, data loss risk
Primary
/ | \
Backup Backup Backup
Node A → Node B → Node C → Node A
Node A ←→ Node B
↕ ↕
Node C ←→ Node D
| Strategy | Consistency | Availability | Write Perf | Read Perf | Complexity | |----------|-------------|--------------|------------|-----------|------------| | Primary-Backup | Strong | Low | Low | Medium | Low | | Multi-Primary | Eventual | High | High | High | High | | Chain | Strong | Medium | Low | Medium | Medium | | Quorum | Tunable | Medium | Medium | Medium | Medium |
-- On primary
CHANGE MASTER TO
MASTER_HOST='primary_host',
MASTER_USER='repl_user',
MASTER_PASSWORD='password';
START SLAVE;
from pymongo import MongoClient
client = MongoClient('mongodb://host1,host2,host3/?replicaSet=myReplicaSet')
# Write to primary
db = client.mydb
db.collection.insert_one({'key': 'value'})
# Read from secondary
db.collection.find_one({'key': 'value'}, read_preference=ReadPreference.SECONDARY)
from cassandra.cluster import Cluster
from cassandra import ConsistencyLevel
cluster = Cluster()
session = cluster.connect('keyspace')
# Write to quorum
session.execute(query, consistency_level=ConsistencyLevel.QUORUM)
distributed-systems-consensus-raft - Consensus-based replicationdistributed-systems-eventual-consistency - Consistency modelsdistributed-systems-conflict-resolution - Handling conflictsdistributed-systems-partitioning-sharding - Data partitioningLast Updated: 2025-10-27