Loading learning content...
We've explored a spectrum of synchronization approaches: from strongly consistent 2PC to non-blocking 3PC, from consensus protocols to async replication. Each has its strengths and trade-offs. The question now is: how do you choose the right one for your system?
This isn't an academic exercise. The synchronization strategy you select fundamentally shapes your system's behavior under load, during failures, and across geographic distances. Choose wrong, and you'll either pay unnecessary latency costs or face data integrity issues in production.
This page provides a structured framework for making this critical decision.
By the end of this page, you will have a decision framework for evaluating synchronization approaches, understand the key questions to ask about your requirements, and be able to select and justify the appropriate strategy for different scenarios.
Before diving into specific approaches, understand that every synchronization choice is a trade-off. There is no universally optimal solution.
The CAP Theorem Reminder:
During a network partition, you must choose between:
The PACELC Extension:
Even without partitions (normal operation), there's a trade-off:
Strong consistency typically requires coordination, which adds latency.
| Approach | Consistency | Availability | Latency | Durability |
|---|---|---|---|---|
| 2PC | Strong | Low (blocking) | High | High |
| 3PC | Weak (partition) | Medium | Higher | High |
| Paxos/Raft | Strong (majority) | High (majority) | Medium | Majority |
| Async Primary-Replica | Eventual | Very High | Very Low | Risk of loss |
| Semi-Sync | Eventual | High | Low-Medium | Configurable |
| Multi-Primary | Eventual | Very High | Very Low | Conflict risk |
Notice that no approach excels in all dimensions. The goal isn't to find the 'best' approach—it's to find the approach whose trade-offs align with your requirements.
Before selecting a synchronization approach, systematically answer these questions:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
// Requirement Scoring Framework interface SystemRequirements { // Consistency requirements (1-5 scale) consistencyNeeded: 1 | 2 | 3 | 4 | 5; // 1=eventual, 5=strong // Availability requirements (1-5 scale) availabilityNeeded: 1 | 2 | 3 | 4 | 5; // 1=can tolerate downtime, 5=must always work // Latency requirements maxWriteLatencyMs: number; // Geographic distribution deploymentScope: 'single-dc' | 'multi-dc-same-region' | 'multi-region' | 'global'; // Failure tolerance tolerateNodeFailure: boolean; tolerateDatacenterFailure: boolean; tolerateNetworkPartition: boolean; // Write characteristics writesPerSecond: number; writeConflictLikelihood: 'low' | 'medium' | 'high';} function recommendSyncApproach(req: SystemRequirements): string { // Strong consistency, single DC → 2PC or consensus if (req.consistencyNeeded >= 4 && req.deploymentScope === 'single-dc') { if (req.availabilityNeeded >= 4) { return 'Consensus (Raft) - Strong consistency with majority availability'; } return '2PC - Strong consistency, acceptable blocking risk'; } // Strong consistency, multi-region → Consensus with geographic awareness if (req.consistencyNeeded >= 4 && req.deploymentScope !== 'single-dc') { return 'Geo-distributed Consensus (Spanner-style) - Accept latency for consistency'; } // High availability, eventual consistency OK → Async replication if (req.availabilityNeeded >= 4 && req.consistencyNeeded <= 2) { if (req.deploymentScope === 'global' && req.writeConflictLikelihood === 'low') { return 'Multi-Primary with LWW - Global writes, eventual consistency'; } return 'Async Primary-Replica - Simple, fast, eventually consistent'; } // Balanced requirements → Semi-sync if (req.consistencyNeeded === 3 && req.availabilityNeeded === 3) { return 'Semi-Sync Replication - Balance of durability and latency'; } // Default return 'Evaluate specific trade-offs - requirements are complex';}Use this decision tree to narrow down your options:
START
│
▼
┌─────────────────────────────┐
│ Do you need STRONG │
│ consistency (serializable)? │
└─────────────┬───────────────┘
│
┌───────────┴───────────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ Geographic │ │ Write conflicts │
│ distribution? │ │ possible? │
└───────┬──────┘ └────────┬─────────┘
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ │ │ │
Single Multi YES NO
DC Region │ │
│ │ ▼ ▼
│ │ Multi-Primary Async Primary-
│ │ + Conflict Replica
│ │ Resolution
▼ ▼
2PC or Spanner-
Raft style
(Paxos +
TrueTime)
| Approach | Best For | Avoid When |
|---|---|---|
| 2PC | Distributed transactions within single DC | High availability needed, WAN latency |
| Consensus (Raft) | Replicated state machines, leader election, metadata stores | Very high write throughput (leader bottleneck) |
| Primary-Replica Async | Read-heavy workloads, analytics replicas | Zero data loss required |
| Semi-Sync | Balance of durability and latency | Global distribution (latency too high) |
| Multi-Primary | Global writes, offline-first apps | High conflict rate, complex merge logic |
In practice, systems often combine multiple approaches for different data types:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
// Hybrid Synchronization Strategy Example interface DataClassification { type: 'critical' | 'important' | 'best-effort'; syncStrategy: SyncStrategy;} const dataClassifications: Record<string, DataClassification> = { // Critical: Strong consistency required 'account_balance': { type: 'critical', syncStrategy: 'consensus' }, 'payment_transactions': { type: 'critical', syncStrategy: '2pc' }, 'inventory_count': { type: 'critical', syncStrategy: 'consensus' }, // Important: Durability required, eventual consistency OK 'order_history': { type: 'important', syncStrategy: 'semi-sync' }, 'user_profile': { type: 'important', syncStrategy: 'semi-sync' }, // Best-effort: Availability over consistency 'product_reviews': { type: 'best-effort', syncStrategy: 'async' }, 'page_views': { type: 'best-effort', syncStrategy: 'async' }, 'recommendations': { type: 'best-effort', syncStrategy: 'async' },}; class HybridDataStore { private consensusStore: RaftCluster; private asyncStore: AsyncReplicatedDB; private twoPhaseCoordinator: TwoPhaseCommitCoordinator; async write(table: string, data: Record<string, any>): Promise<void> { const classification = dataClassifications[table]; switch (classification?.syncStrategy) { case 'consensus': await this.consensusStore.write(table, data); break; case '2pc': await this.twoPhaseCoordinator.execute([ { table, operation: 'insert', data } ]); break; case 'semi-sync': await this.asyncStore.writeSemiSync(table, data); break; case 'async': default: await this.asyncStore.writeAsync(table, data); break; } }}The first step in choosing synchronization is classifying your data. Ask: 'What's the cost of this data being wrong or lost?' Financial data: very high. Cached recommendations: very low. Match the protection level to the cost.
We've covered the complete spectrum of data synchronization patterns in distributed systems:
| If you need... | Consider... |
|---|---|
| Distributed transactions, single DC | 2PC or Saga pattern |
| Replicated state machine | Raft consensus |
| Global strong consistency | Spanner-style (Paxos + TrueTime) |
| Maximum availability | Async replication + eventual consistency |
| Global writes without conflicts | CRDTs or careful multi-primary |
| Balanced durability/latency | Semi-synchronous replication |
Congratulations! You've completed the Data Synchronization Patterns module. You now understand the full spectrum of distributed synchronization approaches—from classical protocols to modern consensus and async replication. This knowledge is foundational for designing any distributed system that manages data across multiple nodes.