Conflict Resolution at Scale: Lessons from 100,000 Record Syncs
Bidirectional sync is easy—until it isn't. Here's what actually works when 100,000+ updates race across orgs every day.
What Counts as a Conflict?
Two orgs, same record (same Global_ID__c), overlapping updates to the same field within a conflict window.
Not a conflict if different fields changed—merge both. It is a conflict when Account.Industry = "Manufacturing" in Org A and "Technology" in Org B.
Conflict Models We Use
1) Source-Wins
- Rule: A designated source org wins for specific fields.
- Use when: Clear system-of-record exists (e.g., Master Data org for Account master fields).
- Pros: Simple, deterministic.
- Cons: Can overwrite legitimate updates from non-source orgs.
2) Target-Wins
- Rule: The receiving org's value is preserved.
- Use when: Protecting downstream customizations.
- Pros: Minimizes user surprise in target.
- Cons: Allows divergence over time.
3) Newest-Wins (Last Write Wins)
- Rule: Compare field-level timestamps; newest update wins.
- Use when: Low-risk attributes (e.g., BillingCity).
- Pros: Easy to automate; high throughput.
- Cons: Races within milliseconds can flip results; can lose important edits.
4) Manual Review Queue
- Rule: Conflicts create
Sync_Conflict__ctasks for stewards. - Use when: High-stakes fields (e.g., Credit_Rating__c).
- Pros: Accurate.
- Cons: Expensive; must be rare to scale.
5) Hybrid (What We Actually Deploy)
- Field-level policy table drives strategy per field.
- Example: Name → source-wins; Billing* → newest-wins; Industry → manual; Phone/Email → newest-wins with normalization.
Designing the Policy Table
Custom Metadata: Field_Conflict_Policy__mdt
- Object__c, Field__c
- Strategy__c: SOURCE_WINS | TARGET_WINS | NEWEST_WINS | MANUAL
- Conflict_Window_Sec__c: default 60
- Normalizer__c: PHONE | EMAIL | ADDRESS | NONE
- Steward_Queue__c
Normalization: Reduce False Positives
- Phone: strip non-digits, E.164 format.
- Email: lowercase, trim, unicode normalization.
- Addresses: standardize abbreviations (St. → Street), postal-aware formatting.
Idempotency and Ordering
Out-of-order delivery is normal across orgs. Make handlers idempotent:
// Pseudocode
if (event.version < currentVersion(field)) return; // stale
applyChange();
setVersion(field, event.version);
Version can be Lamport-like: per-field monotonic counter per Global_ID.
Metrics from Real Deployments (last 90 days)
- Records synced: 11.4M
- Conflicts detected: 18,972 (0.166%)
- Auto-resolved: 18,311 (96.5%)
- Manual reviews: 661 (3.5%)
- Median time to resolve manual: 3h 12m
- Top conflicting fields: BillingCity (26%), Industry (18%), Phone (14%)
Stewarding at Scale
- SLAs: P0 fields (revenue-impacting) 4 hours; P1 24 hours.
- Daily digest of open conflicts sent to owners.
- Auto-close stale conflicts if no subsequent changes in 14 days (log decision).
When to Prefer Unidirectional
If manual queue > 1% of total changes or conflict hot-spots persist, designate source-of-truth and switch to one-way sync for those fields.
What Actually Breaks
- Composite transactions exceeding limits—batch by 100 and respect governor limits.
- Missing External IDs—causes duplicates and broken relationships.
- Clock skew—rely on event-time not org server-time, or use version counters.
Playbook
- Define system-of-record per field.
- Implement
Field_Conflict_Policy__mdt. - Normalize inputs before compare.
- Use version counters for ordering.
- Instrument metrics and SLAs.
- Continuously tune; reduce bidi where noise dominates.
Need Help Tuning Conflict Resolution?
We can benchmark your sync, design field-level strategies, and implement high-precision normalization to hit <0.2% conflict rate with >95% auto-resolution.