Custom Conflict Resolution Policies
Complete the full lesson to earn 25 points — 50 with Pro
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks, the wait, and see fewer ads — read each lesson on a single page with Pro
Designing and Implementing Custom Conflict Resolution Policies in Multi-Region Architectures
Introduction: The Challenge of Distributed State
In a globalized digital economy, applications are expected to be available 24/7 with minimal latency for users regardless of their geographic location. To achieve this, engineers frequently deploy database clusters across multiple regions. This architectural pattern allows a user in Tokyo to read from a local data center while a user in New York interacts with a local node on the East Coast. However, this convenience introduces the "CAP theorem" trade-off: when you prioritize availability and partition tolerance, you must eventually deal with the reality that data can be modified simultaneously in two different parts of the world.
When two users update the same record in two different regions at nearly the same time, the system faces a conflict. If the database simply overwrites one change with the other, data loss occurs. If it locks the entire system to ensure a strict sequence of events, latency spikes and the benefits of multi-region deployment vanish. This is where custom conflict resolution policies become essential. A conflict resolution policy is the logic that determines the final state of a record when concurrent updates occur. Understanding how to design, implement, and test these policies is a fundamental skill for any engineer working on large-scale distributed systems.
Understanding the Nature of Data Conflicts
Before diving into custom implementation, we must understand why conflicts happen. In a multi-region setup, data is typically replicated asynchronously to ensure that local writes remain fast. If Region A receives a write and Region B receives a different write for the same row before they have synchronized, the database enters an inconsistent state.
Categories of Conflicts
Conflicts generally fall into three categories that dictate how you might approach resolution:
- Write-Write Conflicts: Two users attempt to update the same field in the same document. For example, a user updates their profile bio in London while simultaneously updating it in Singapore.
- Delete-Write Conflicts: One user deletes a record while another user attempts to update it. The system must decide if the update should be ignored or if it should resurrect the record.
- Add-Add Conflicts: Two users create the same unique entity (e.g., two users trying to claim the same username). This requires semantic conflict resolution rather than simple timestamp comparison.
Callout: The "Last Write Wins" Fallacy Many developers default to "Last Write Wins" (LWW) because it is simple to implement. LWW uses wall-clock timestamps to determine the winner. However, wall-clock time is notoriously unreliable in distributed systems due to clock skew—the phenomenon where servers' internal clocks drift apart. Relying solely on LWW often leads to data loss where the "latest" update is actually the one that arrived at a server with a faster clock, not the one that happened later in human time.
Designing Custom Resolution Policies
When the default behavior of your database is insufficient, you must design a custom resolution policy. The goal is to move from a blind overwrite approach to a semantic or deterministic approach that preserves the intent of the user.
1. Vector Clocks and Versioning
Instead of relying on timestamps, you can use logical clocks, such as Vector Clocks or Version Vectors. These structures track the causality of events. Each node maintains a counter for every other node in the system. When a change occurs, the version vector is updated. This allows the system to determine if one update strictly follows another, or if they are concurrent.
2. Semantic Merging (CRDTs)
Conflict-free Replicated Data Types (CRDTs) are data structures designed to be merged automatically without conflicts. For instance, if you are building a counter, you can use a G-Counter (Grow-only Counter). Each region keeps its own count, and the final value is simply the sum of all regional counts. This eliminates the need for conflict resolution entirely because the merge operation is commutative, associative, and idempotent.
3. Application-Level Resolution
Sometimes the database cannot resolve the conflict because it lacks context. In these cases, you implement a "Conflict Handler" in your application code. When the database detects a conflict, it stores both versions of the record (or a "tombstone" for deletions) and flags the object as conflicted. The next time the application reads this object, it triggers a function to resolve the conflict based on business rules.
Implementation Strategies with Code Examples
Let's look at how to implement a custom resolution policy in a hypothetical environment using a "Versioned Merge" strategy.
Example: Resolving Profile Updates
Imagine a user profile with a bio and a status field. We want to ensure that if a user updates the bio in one region and the status in another, both updates are kept.
/**
* Custom Conflict Resolver
* @param {Object} localRecord - The record existing in the local database
* @param {Object} remoteRecord - The record received from another region
* @returns {Object} - The resolved record
*/
function resolveProfileConflict(localRecord, remoteRecord) {
// Check if the records have different versions
if (localRecord.version === remoteRecord.version) {
return localRecord; // No change needed
}
// Merge logic: Combine fields if they were updated independently
const resolved = {
id: localRecord.id,
version: Math.max(localRecord.version, remoteRecord.version) + 1,
bio: localRecord.lastModifiedBio > remoteRecord.lastModifiedBio
? localRecord.bio
: remoteRecord.bio,
status: localRecord.lastModifiedStatus > remoteRecord.lastModifiedStatus
? localRecord.status
: remoteRecord.status
};
return resolved;
}
Explanation of the Code
In this implementation, we move away from a single "record version" and instead track the last modified time for individual fields. By comparing lastModifiedBio and lastModifiedStatus independently, we allow the user to modify different parts of their profile simultaneously without losing data. The version increment ensures that the resulting record is treated as a new, authoritative state in the replication stream.
Tip: Use Deterministic Logic Always ensure your conflict resolution logic is deterministic. If two nodes receive the same two conflicting versions, they must arrive at the exact same result. If your resolution function relies on non-deterministic data (like
Math.random()or current system time), your cluster will diverge, creating permanent inconsistencies.
Step-by-Step Implementation Workflow
Implementing custom resolution is a multi-stage process. Follow these steps to ensure safety:
- Define the Conflict Detection Window: Identify which datasets are prone to contention. You do not need complex resolution for every table. Focus on high-traffic, multi-user entities like shopping carts, user settings, or session data.
- Choose the Data Structure: Decide if you can model the data as a CRDT (like a set or a counter) or if you need to store metadata (like timestamps or vector clocks) alongside your data.
- Implement the Resolver: Write the resolution function. It should be a pure function: it takes two inputs and returns one deterministic output.
- Register the Resolver: Configure your database (e.g., Cassandra, DynamoDB, or Riak) to use your custom resolver. Most distributed databases have a hook for "Read Repair" or "Conflict Resolution" that triggers your code.
- Test for Convergence: Create a simulation script that generates random concurrent writes in two regions and verifies that both regions eventually arrive at the same record state.
Best Practices for Multi-Region Consistency
1. Minimize Conflict Scope
The best way to handle conflicts is to avoid them. You can use "data sharding" or "regional ownership" to ensure that a specific user's data is only ever written in one region. For example, a user's session could be pinned to the region closest to them. If the user travels, you initiate a "handoff" process to move the data ownership to a different region.
2. Use Logical Clocks
Avoid system wall-clock time whenever possible. Use Hybrid Logical Clocks (HLC) if you need to maintain a readable timestamp that still respects causality. HLCs combine the benefits of physical clocks (for ordering events) with logical clocks (for tracking causality).
3. Log Conflicts for Observability
Never resolve a conflict silently and move on without keeping a record. Log the occurrence of a conflict and the resolution chosen. This is vital for auditing and debugging. If your resolution logic is flawed, you will need the audit logs to revert the data to a clean state.
4. Idempotency is Mandatory
Your resolution logic must be idempotent. If the same merge operation is applied twice, it should not change the result after the first application. This is a requirement for any distributed system where network retries are common.
Warning: The "Tombstone" Problem Deletions are the most dangerous operations in a distributed system. If you delete a record in Region A, that deletion must be propagated as a "tombstone" (a marker saying the record is gone). If another region adds the record back, the systems might get confused about whether the record is active or deleted. Always ensure your tombstone logic includes a version or a timestamp that is higher than any previous update to that record.
Comparison of Resolution Strategies
| Strategy | Complexity | Best For | Risk |
|---|---|---|---|
| Last Write Wins (LWW) | Low | Non-critical data | High risk of data loss |
| Vector Clocks | Medium | Causal consistency | Increases object size |
| CRDTs | High | Counters, Sets, Maps | Limited to specific data types |
| Application Logic | High | Complex business rules | Requires careful testing |
Common Pitfalls and How to Avoid Them
Pitfall 1: Ignoring Network Partitions
Many developers assume the network will always be healthy. When a network partition occurs, the nodes in Region A cannot talk to Region B. If you design a system that requires a "majority vote" to resolve conflicts, the minority side will stop accepting writes. This is a valid design choice, but you must be prepared for the downtime. If your priority is uptime, you must accept that you will have to resolve conflicts after the network heals.
Pitfall 2: Over-Engineering
Do not apply complex CRDTs to data that rarely changes. If a record is updated once a month, simple versioning or even a manual resolution process might be cheaper and more reliable than a sophisticated automated system. Use the simplest tool that meets your consistency requirements.
Pitfall 3: Failing to Test Edge Cases
Engineers often test the "happy path" where two updates arrive at the same time. They rarely test the "unhappy path" where an update arrives, then a delete, then a late-arriving update from a third region. Use "chaos engineering" tools to simulate delayed packets and reordered messages to ensure your resolution logic holds up under stress.
Deep Dive: The Role of Causality
Causality is the relationship between events where one event influences another. In a distributed system, if User A updates their address and then sends a notification about the update, the address change is the "cause" and the notification is the "effect."
If these events are replicated across regions out of order, the system might try to process the notification before the address change exists. This is why custom resolution policies often need to be "causality-aware." By using Vector Clocks, you can ensure that the system refuses to process an effect until the cause has been applied.
Implementing a Causality-Aware Check
function canProcessEvent(event, state) {
// Check if the dependencies of this event are already in the state
for (let dependency of event.dependencies) {
if (!state.has(dependency)) {
return false; // Dependency missing, buffer this event
}
}
return true;
}
This simple check prevents the "effect before cause" problem. By buffering events that arrive out of order, you maintain the logical integrity of your data.
Integrating Resolution into Microservices
In a microservices architecture, conflict resolution often happens at the API Gateway or the Service layer rather than the database layer. When a service receives a write request, it can check the version of the record it holds. If the version is stale, the service can trigger a "reconciliation flow."
The Reconciliation Pattern
- Request Arrival: Service receives a write request with a version number.
- Version Comparison: Service queries the database for the current version.
- Conflict Detected: If the version in the request is lower than the database version, the service rejects the write or initiates a merge.
- Merge and Retry: The service fetches the latest data, applies the user's changes to the new data, and attempts the write again.
This approach is highly effective because the service layer has business context that the database lacks. For example, if a user tries to withdraw money from a bank account, the service layer can check if the balance is sufficient after merging the latest data, preventing an overdraft.
Scaling Conflict Resolution
As your system grows, the cost of conflict resolution can become a bottleneck. If you have thousands of conflicts per second, running complex resolution logic on every read will kill your performance.
Strategies for Scaling:
- Asynchronous Resolution: Instead of resolving on read, resolve in the background. Allow the user to see the "best guess" or a "pending" state while the system reconciles the data in the background.
- Batching: If you have many updates to the same record, batch them together before applying conflict resolution. This reduces the number of merge operations.
- Offloading: Use dedicated worker processes to handle conflict resolution. Keep the primary request/response path clean and fast.
Summary of Key Takeaways
- Understand the Trade-off: Multi-region writes force a choice between strict consistency and high availability. Custom conflict resolution is the bridge that makes high availability usable.
- Avoid LWW When Possible: "Last Write Wins" is easy but dangerous. Prioritize causal tracking or semantic merging (CRDTs) to preserve user intent and prevent data loss.
- Ensure Determinism: Any logic used to resolve conflicts must be deterministic. Every node in your system must arrive at the same result given the same input, or your data will permanently diverge.
- Design for Causality: Use logical clocks or version vectors to track the order of events. This prevents issues like processing a deletion before an update, or a notification before the underlying data exists.
- Test the Unhappy Path: Use simulation tools to test how your resolution logic handles out-of-order delivery, dropped packets, and network partitions.
- Keep it Simple: Use the simplest possible resolution strategy that meets your business needs. Do not implement complex CRDTs if simple field-level versioning will suffice.
- Monitor and Audit: Log every conflict and its resolution. You cannot fix what you cannot measure, and you cannot debug what you cannot see.
By implementing these strategies, you move from a fragile system that loses data to a resilient architecture that handles the complexities of the global internet. The key is to treat conflict resolution not as an afterthought, but as a core component of your data model design. When you design your data structures with the assumption that conflicts will happen, you build systems that are significantly more robust and easier to maintain over the long term.
FAQ: Common Questions
Q: Can I use a central locking service to prevent all conflicts? A: You can, but it defeats the purpose of a multi-region architecture. A central lock requires a round-trip to a single region, which introduces the very latency you are trying to avoid. Use locks only for extremely rare operations where consistency is more important than speed.
Q: Are CRDTs better than application-level resolution? A: CRDTs are generally safer because they are mathematically proven to converge. However, they are limited in the types of data they can represent. Use CRDTs for simple structures like counters and sets, and use application-level resolution for complex business logic.
Q: What happens if the conflict resolution logic itself has a bug? A: This is why logging is critical. If you discover a bug in your resolution logic, you will need the logs to identify which records were resolved incorrectly. You can then write a "repair script" to re-process those specific records using the corrected logic. Always ensure your resolution function is versioned so you can track which logic was applied to which record.
Q: How do I handle conflicts in a database that doesn't support custom hooks?
A: You can implement the "versioning" approach at the application layer. Store a version field in your database. Every update must include the version number you read. If the update fails because the version has changed, fetch the new version, merge, and retry. This is known as "optimistic concurrency control."
Q: Is "Always Ask the User" a valid conflict resolution policy? A: Yes, in some cases. For collaborative editing (like Google Docs), the system merges what it can and highlights the rest for the user to resolve. This is often the best approach for high-value data where the system cannot determine the "correct" outcome.
Reach the last section to complete this lesson and earn points — you're on section 1 of 11.
- Introduction to Cosmos DB Data Modeling
- Introduction to Cosmos DB Data Modeling Quiz5q
- Multiple Entity Types in Same Container
- Multiple Entity Types in Same Container Quiz5q
- Storing Related Entities in Same Document
- Storing Related Entities in Same Document Quiz5q
- Denormalizing Data Across Documents
- Denormalizing Data Across Documents Quiz5q
- Referencing Between Documents
- Referencing Between Documents Quiz5q
- Partition Keys and Document IDs
- Partition Keys and Document IDs Quiz5q
- Time to Live (TTL) Configuration
- Time to Live (TTL) Configuration Quiz5q
- Document Versioning Strategies
- Document Versioning Strategies Quiz5q
- Schema Versioning Patterns
- Schema Versioning Patterns Quiz5q
- Choosing Partition Strategies
- Choosing Partition Strategies Quiz5q
- Partition Key Selection Best Practices
- Partition Key Selection Best Practices Quiz5q
- Transactions and Partition Keys
- Transactions and Partition Keys Quiz5q
- Cross-Partition Query Costs
- Cross-Partition Query Costs Quiz5q
- Data Distribution Analysis
- Data Distribution Analysis Quiz5q
- Throughput Distribution Planning
- Throughput Distribution Planning Quiz5q
- Synthetic Partition Keys
- Synthetic Partition Keys Quiz5q
- Hierarchical Partition Keys
- Hierarchical Partition Keys Quiz5q
- Throughput and Storage Requirements
- Throughput and Storage Requirements Quiz5q
- Serverless vs Provisioned Throughput
- Serverless vs Provisioned Throughput Quiz5q
- Database-Level Provisioned Throughput
- Database-Level Provisioned Throughput Quiz5q
- Granular Scale Units
- Granular Scale Units Quiz5q
- Global Distribution Costs
- Global Distribution Costs Quiz5q
- Configuring Throughput in Portal
- Configuring Throughput in Portal Quiz5q
- Gateway vs Direct Connectivity Mode
- Gateway vs Direct Connectivity Mode Quiz5q
- Creating Database Connections
- Creating Database Connections Quiz5q
- Azure Cosmos DB Emulator
- Azure Cosmos DB Emulator Quiz5q
- Connection Error Handling
- Connection Error Handling Quiz5q
- Singleton Pattern for Clients
- Singleton Pattern for Clients Quiz5q
- Global Distribution Regions
- Global Distribution Regions Quiz5q
- Threading and Parallelism
- Threading and Parallelism Quiz5q
- Arrays and Nested Objects Queries
- Arrays and Nested Objects Queries Quiz5q
- Correlated Subqueries
- Correlated Subqueries Quiz5q
- Array and Type-Checking Functions
- Array and Type-Checking Functions Quiz5q
- Mathematical and String Functions
- Mathematical and String Functions Quiz5q
- Date Functions in Queries
- Date Functions in Queries Quiz5q
- Point Operations vs Query Operations
- Point Operations vs Query Operations Quiz5q
- CRUD Point Operations
- CRUD Point Operations Quiz5q
- Patch Operations for Updates
- Patch Operations for Updates Quiz5q
- Transactional Batch Operations
- Transactional Batch Operations Quiz5q
- Bulk Operations with SDK
- Bulk Operations with SDK Quiz5q
- Optimistic Concurrency with ETags
- Optimistic Concurrency with ETags Quiz5q
- Query Pagination and Continuation
- Query Pagination and Continuation Quiz5q
- Cosmos DB Mirroring for Fabric
- Cosmos DB Mirroring for Fabric Quiz5q
- Mirroring vs Spark Connector
- Mirroring vs Spark Connector Quiz5q
- Enabling Analytical Store
- Enabling Analytical Store Quiz5q
- Synapse Spark and SQL Queries
- Synapse Spark and SQL Queries Quiz5q
- Change Data Capture in Analytical Store
- Change Data Capture in Analytical Store Quiz5q
- Azure Functions and Event Hubs Integration
- Azure Functions and Event Hubs Integration Quiz5q
- Denormalization with Change Feed
- Denormalization with Change Feed Quiz5q
- Referential Integrity with Change Feed
- Referential Integrity with Change Feed Quiz5q
- Azure AI Search Integration
- Azure AI Search Integration Quiz5q
- Azure Functions Change Feed Trigger
- Azure Functions Change Feed Trigger Quiz5q
- Consuming Change Feed with SDK
- Consuming Change Feed with SDK Quiz5q
- Change Feed Estimator
- Change Feed Estimator Quiz5q
- Denormalization via Change Feed
- Denormalization via Change Feed Quiz5q
- Aggregation Persistence with Change Feed
- Aggregation Persistence with Change Feed Quiz5q
- Read-Heavy vs Write-Heavy Indexing
- Read-Heavy vs Write-Heavy Indexing Quiz5q
- Index Type Selection
- Index Type Selection Quiz5q
- Custom Indexing Policies
- Custom Indexing Policies Quiz5q
- Composite Index Implementation
- Composite Index Implementation Quiz5q
- Index Performance Optimization
- Index Performance Optimization Quiz5q
- Response Status Codes and Metrics
- Response Status Codes and Metrics Quiz5q
- Normalized RU Consumption Monitoring
- Normalized RU Consumption Monitoring Quiz5q
- Server-Side Latency Metrics
- Server-Side Latency Metrics Quiz5q
- Data Replication Monitoring
- Data Replication Monitoring Quiz5q
- Azure Monitor Alerts Configuration
- Azure Monitor Alerts Configuration Quiz5q
- Resource Logs Implementation
- Resource Logs Implementation Quiz5q
- Partition Throughput Monitoring
- Partition Throughput Monitoring Quiz5q
- Encryption Key Management
- Encryption Key Management Quiz5q
- Network-Level Access Control
- Network-Level Access Control Quiz5q
- Data Encryption Configuration
- Data Encryption Configuration Quiz5q
- Azure RBAC for Control Plane
- Azure RBAC for Control Plane Quiz5q
- Microsoft Entra ID for Data Plane
- Microsoft Entra ID for Data Plane Quiz5q
- CORS Settings Configuration
- CORS Settings Configuration Quiz5q
- Customer-Managed Keys
- Customer-Managed Keys Quiz5q
- Always Encrypted Implementation
- Always Encrypted Implementation Quiz5q
- Data Movement Strategy Selection
- Data Movement Strategy Selection Quiz5q
- SDK Bulk Operations for Data Movement
- SDK Bulk Operations for Data Movement Quiz5q
- Azure Data Factory Pipelines
- Azure Data Factory Pipelines Quiz5q
- Kafka Connector Integration
- Kafka Connector Integration Quiz5q
- Azure Stream Analytics Integration
- Azure Stream Analytics Integration Quiz5q
- Cosmos DB Spark Connector
- Cosmos DB Spark Connector Quiz5q
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles the points you earn on every lesson and quiz so you progress twice as fast, unlocks half of every practice exam — plus full case studies — with the Learn & Exam study modes, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× points per lesson & quiz
- ✓ 50% of every exam unlocked
- ✓ Learn & Exam modes
- ✓ Distraction-free lessons