Add canonical operations/security/access/feature docs and fix path integrity to improve onboarding and incident readiness.
6.8 KiB
Executable File
Conflict Resolution
CBDDC v0.6.0 introduces pluggable conflict resolution strategies to handle concurrent updates to the same document across different nodes.
Overview
When two nodes modify the same document offline and later sync, a conflict occurs. CBDDC provides two built-in strategies:
- Last Write Wins (LWW) - Simple, timestamp-based resolution
- Recursive Merge - Intelligent JSON merging with array handling
Conflict Resolution Strategies
Last Write Wins (LWW)
How it works:
- Each document has a Hybrid Logical Clock (HLC) timestamp
- During sync, the document with the highest timestamp wins
- Conflicts are resolved automatically with no merge attempt
Pros:
- ✅ Simple and predictable
- ✅ Fast (no merge computation)
- ✅ No data corruption or invalid states
Cons:
- ❌ Data loss - one change is discarded entirely
- ❌ Not suitable for collaborative editing
Use Cases:
- Configuration data with infrequent updates
- Reference data (product catalogs, price lists)
- Single-user scenarios with backup sync
Example
// Both nodes start with same document
{ "id": "doc-1", "name": "Alice", "age": 25 }
// Node A updates (timestamp: 100)
{ "id": "doc-1", "name": "Alice", "age": 26 }
// Node B updates (timestamp: 105)
{ "id": "doc-1", "name": "Alicia", "age": 25 }
// After sync: Node B wins (higher timestamp)
{ "id": "doc-1", "name": "Alicia", "age": 25 }
// Node A's age change is LOST
Recursive Merge
How it works:
- Performs deep JSON merge of conflicting documents
- Uses the highest timestamp for each individual field
- Arrays with
idor_idfields are merged by identity - Arrays without IDs are concatenated and deduplicated
Pros:
- ✅ Preserves changes from both nodes
- ✅ Suitable for collaborative scenarios
- ✅ Intelligent array handling
Cons:
- ❌ More complex logic
- ❌ Slightly slower (~5-10ms overhead)
- ❌ May produce unexpected results with complex nested structures
Use Cases:
- TodoLists, shopping carts (demonstrated in samples)
- Collaborative documents
- Complex objects with independent fields
- Data where every change matters
Example: Field-Level Merge
// Both nodes start with same document
{ "id": "doc-1", "name": "Alice", "age": 25 }
// Node A updates (timestamp: 100)
{ "id": "doc-1", "name": "Alice", "age": 26 }
// Node B updates (timestamp: 105)
{ "id": "doc-1", "name": "Alicia", "age": 25 }
// After Recursive Merge:
{ "id": "doc-1", "name": "Alicia", "age": 26 }
// Uses latest timestamp for each field independently
Example: Array Merging with IDs
// Initial TodoList
{
"id": "list-1",
"name": "Shopping",
"items": [
{ "id": "1", "task": "Buy milk", "completed": false },
{ "id": "2", "task": "Buy bread", "completed": false }
]
}
// Node A: Completes milk, adds eggs
{
"items": [
{ "id": "1", "task": "Buy milk", "completed": true },
{ "id": "2", "task": "Buy bread", "completed": false },
{ "id": "3", "task": "Buy eggs", "completed": false }
]
}
// Node B: Completes bread, adds cheese
{
"items": [
{ "id": "1", "task": "Buy milk", "completed": false },
{ "id": "2", "task": "Buy bread", "completed": true },
{ "id": "4", "task": "Buy cheese", "completed": false }
]
}
// After Recursive Merge: ALL changes preserved!
{
"items": [
{ "id": "1", "task": "Buy milk", "completed": true }, // A's completion
{ "id": "2", "task": "Buy bread", "completed": true }, // B's completion
{ "id": "3", "task": "Buy eggs", "completed": false }, // A's addition
{ "id": "4", "task": "Buy cheese", "completed": false } // B's addition
]
}
Configuration
Select Strategy at Startup
using ZB.MOM.WW.CBDDC.Core.Sync;
// Last Write Wins (default)
services.AddSingleton<IConflictResolver, LastWriteWinsConflictResolver>();
// OR Recursive Merge
services.AddSingleton<IConflictResolver, RecursiveNodeMergeConflictResolver>();
Console Sample
# Use Recursive Merge
dotnet run --merge
# Use Last Write Wins (default)
dotnet run
UI Clients
Resolver selection can be exposed in your UI:
- Choose "Recursive Merge" or "Last Write Wins"
- Save configuration
- Restart the application if required by your host
Interactive Demo
A UI demo can expose a "Run Conflict Demo" action that:
- Creates a TodoList with 2 items
- Simulates concurrent edits from two "nodes"
- Shows the merged result
- Compares LWW vs Recursive Merge behavior
Try it yourself to see the difference!
Best Practices
Use LWW When:
- Only one user/node typically writes
- Data is reference/configuration
- Simplicity is more important than preserving every change
- Performance is critical
Use Recursive Merge When:
- Multiple users collaborate
- Every change is valuable (e.g., TodoItems, cart items)
- Data has independent fields that can conflict
- Arrays have
idfields for identity
Avoid Conflicts Entirely:
- Use different collections for different data types
- Implement optimistic locking with version fields
- Design data models to minimize overlapping writes
Custom Resolvers
You can implement IConflictResolver for custom logic:
public class CustomResolver : IConflictResolver
{
public ValueTask<string> ResolveConflict(
string localJson,
string remoteJson,
long localTimestamp,
long remoteTimestamp)
{
// Your custom merge logic here
return new ValueTask<string>(result);
}
}
Register your resolver:
services.AddSingleton<IConflictResolver, CustomResolver>();
Performance
Benchmark results (1000 conflict resolutions):
| Strategy | Avg Time | Throughput |
|---|---|---|
| Last Write Wins | 0.05ms | 20,000 ops/sec |
| Recursive Merge | 0.15ms | 6,600 ops/sec |
Recommendation: Performance difference is negligible for most applications. Choose based on data preservation needs.
FAQ
Q: Can I switch resolvers at runtime?
A: No. The resolver is injected at startup. Changing requires application restart.
Q: What happens if I change resolvers after data exists?
A: Existing data is unaffected. Only future conflicts use the new strategy.
Q: Can different nodes use different resolvers?
A: Technically yes, but not recommended. All nodes should use the same strategy for consistency.
Q: Does this handle schema changes?
A: No. Conflict resolution assumes both documents have compatible schemas.
See Also: