Multi-Region Replication Conflict: What’s Triggering It and How to Resolve
Multi-region replication conflicts happen when two regions write to the same key simultaneously. This fix walks you through identifying the conflict and resolving it with last-writer-wins or custom merge logic.
Quick Answer
Set conflict resolution to last-writer-wins (LWW) for most cases; if you need custom logic, write a stored procedure or merge function that picks the correct value based on your app’s rules.
Why This Happens
Multi-region replication conflicts happen when you’ve set up multi-master writes—like in DynamoDB Global Tables or Azure Cosmos DB multi-region accounts—and two regions write to the same key at roughly the same time. The database can’t decide which write is “right,” so it flags a conflict. I’ve seen this most often with session data or inventory counters where two users in different regions update the same record in the same millisecond. The conflict itself isn’t a bug; it’s a design feature of multi-master systems that prioritize availability over consistency. But if you don’t handle it, your app might serve stale data or throw 409 errors.
Fix It: Step by Step
- Identify the conflicting items — In DynamoDB, run
aws dynamodb describe-table --table-name your-tableand check theReplicaslist for conflicts. In Cosmos DB, query the conflicts feed:SELECT * FROM c WHERE c._ts > 0 AND c._conflictResolutionPolicy = 'Custom' - Check the conflict resolution policy — If you’re using LWW (last-writer-wins), the conflict auto-resolves based on a timestamp attribute (like
_tsor a custom timestamp). If the conflict persists, the timestamps are identical—this is rare but happens. You’ll need to switch to a custom merge function. - Fix identical timestamps — In DynamoDB, add a unique UUID to your timestamp field:
Make sure each write includes a microsecond or a random suffix to break ties.aws dynamodb update-item --table-name your-table --key '{"id":{"S":"abc123"}}' --update-expression "SET #ts = :newts" --expression-attribute-names '{"#ts":"timestamp"}' --expression-attribute-values '{":newts":{"S":"2025-03-15T12:00:00.123Z"}}' - Implement a custom merge function — If LWW doesn’t suit your app (e.g., you’re merging shopping carts), write a stored procedure. In Cosmos DB, deploy a JavaScript merge procedure:
function merge(existing, incoming) { // Merge arrays or sum counters if (Array.isArray(existing.items) && Array.isArray(incoming.items)) { existing.items = [...new Set([...existing.items, ...incoming.items])]; } return existing; } - Test conflict resolution — Simulate a conflict by writing the same key from two regions simultaneously (use a script with
setTimeoutto align writes). Then query the conflict feed again to confirm it’s empty.
If the Fix Doesn’t Work
Sometimes the policy is set but conflicts still pile up. This usually means the custom merge function is failing silently. Check your function logs. In Azure, look at the Microsoft.DocumentDB/conflictResolution logs under Insights. In AWS, enable DynamoDB Streams and watch the ConflictResolution event. If you see errors like Merge function threw an exception, fix the function—maybe it’s trying to access a property that doesn’t exist on one of the conflicting items. Simplify it: return the existing item if incoming is null, and vice versa.
If logs show nothing, the conflict resolution policy might not be applied retroactively. In Cosmos DB, you have to create a new collection with the policy enabled from the start. You can’t change it on an existing container. I’ve had to migrate data once because of this—painful but unavoidable.
Prevention Tips
- Use a deterministic conflict resolution strategy from day one. LWW is fine for most apps, but if you need custom logic, code it before you launch multi-region writes.
- Keep your conflict resolution function idempotent. It should produce the same result every time given the same inputs. That way, re-running it doesn’t corrupt data.
- Monitor conflict feed size. Set an alert when conflicts exceed 10 in your feed. A growing feed means something’s broken—don’t wait.
- Reduce concurrent writes to the same key. If possible, shard by region or user ID so two regions rarely write the same key. In practice, using a compound key (e.g.,
userId_region) eliminates most conflicts.
Multi-region replication conflicts aren’t the end of the world, but ignoring them will haunt your app’s reliability. Pick a strategy, test it under load, and move on. Got a specific conflict scenario? Drop it in the comments—I’ll help you sort it.
Was this solution helpful?