Error Medic

Troubleshooting Salesforce Data Migration Timeouts: Fixing UNABLE_TO_LOCK_ROW and CPU Limit Crashes

Fix Salesforce data migration timeouts and slow performance by optimizing batch sizes, disabling complex APEX triggers, and resolving row lock contention.

Last updated:
Last verified:
1,316 words
Key Takeaways
  • Root Cause 1: Parallel Bulk API batches attempting to update child records linked to the same parent, causing UNABLE_TO_LOCK_ROW exceptions.
  • Root Cause 2: Recursive APEX triggers and Flows consuming the 60,000ms asynchronous CPU time limit (System.LimitException).
  • Quick Fix: Sort CSV data by Parent ID to avoid lock contention, utilize Bulk API v2, and implement a Custom Setting to bypass triggers during large data loads.
Salesforce Data Migration Methods Compared
MethodWhen to UseTime / SpeedRisk / Impact
REST API (Composite)Real-time syncs, small transactional batches (< 2,000 records).Fastest for single recordsHigh risk of Apex CPU limits on large volumes; consumes API limits quickly.
Bulk API v1 (Parallel)Large historical loads (1M+ records) with flat data structures.Extremely FastHigh risk of UNABLE_TO_LOCK_ROW if data is highly skewed to single parents.
Bulk API v2Modern large-scale integrations. Automated chunking.Fast (Optimized automatically)Low risk. Handles automatic retries, but can still hit CPU limits if triggers are unoptimized.
Data Loader (Serial)Manual fixes, highly skewed data requiring strict ordered processing.SlowLowest risk for lock contention, but causes 'Salesforce slow performance' for longer durations.

Understanding the Error

When performing a Salesforce data migration, teams often encounter catastrophic failures, including the platform becoming unresponsive ('Salesforce crash' or 'Salesforce not working'), severe degradation in API response times ('Salesforce slow performance'), or complete batch execution halts ('Salesforce timeout'). These are typically accompanied by specific platform exceptions: System.LimitException: Apex CPU time limit exceeded, System.LimitException: Too many SOQL queries: 101, or the dreaded UNABLE_TO_LOCK_ROW.

Unlike traditional relational databases where raw compute power can overcome poorly optimized queries, Salesforce's multi-tenant architecture strictly enforces governor limits. A data migration configuration that works perfectly in a sandbox with 10,000 records will frequently crash in a production environment with 10 million records due to complex sharing recalculations, unoptimized APEX triggers, and cascading automation (Flows and Process Builders).

Step 1: Diagnose the Bottleneck

Before arbitrarily changing batch sizes or modifying your salesforce configuration tutorial steps, you must identify what is consuming the execution context.

1. Analyze the Debug Logs

Set your Trace Flags for the integration user performing the migration. Look for the MAX_TIME and LIMIT_USAGE events in the Developer Console or via the CLI. If you see Apex CPU time limit exceeded (limit is 10,000 ms for synchronous, 60,000 ms for asynchronous), identify which trigger or flow is consuming the time. Often, this is caused by nested FOR loops, inefficient SOQL queries inside triggers, or un-indexed lookup fields causing full table scans.

2. Identify Row Lock Contention (UNABLE_TO_LOCK_ROW)

This error occurs when two parallel bulk execution batches attempt to update the same parent record simultaneously. For example, if you are loading 100,000 Contacts, and 50,000 of them belong to the same Account, parallel chunks will fight for a lock on that single Account record to roll up summary fields or update system modstamps. After 10 seconds of waiting, Salesforce kills the transaction.

3. Review Execution Context

Check if multiple automation tools are firing sequentially. A single record insert might trigger: Before Insert Trigger -> After Insert Trigger -> Record-Triggered Flow -> Workflow Rule (which triggers a field update) -> Trigger fires again. This recursion multiplies the CPU time and SOQL query limits, acting as a force multiplier for timeouts.

Step 2: Implement the Fix

To stabilize the Salesforce configuration and ensure a smooth, high-throughput data migration, implement the following architectural and procedural changes:

Fix 1: Optimize Data Load Order and Sorting

To eliminate UNABLE_TO_LOCK_ROW errors, sort your source CSV or JSON data by the Parent ID (e.g., AccountId) before submitting the job. This ensures that all child records for a specific parent end up in the same processing batch. Furthermore, switch your Bulk API job from Parallel mode to Serial mode if sorting alone does not resolve the lock contention. While Serial mode is slower, it guarantees that batches are processed one at a time, eliminating deadlocks.

Fix 2: Bypass Complex Automation During Migrations

Create a hierarchical Custom Setting or Custom Metadata Type called Automation_Bypass__c (e.g., with a checkbox field Bypass_All_Triggers__c). Wrap your APEX trigger logic and Flow entry conditions to check this setting. During the migration window, toggle this setting to true for the integration user. This prevents non-essential business logic from executing during historical data loads, drastically reducing CPU time and preventing timeouts.

Fix 3: Defer Sharing Calculations

If you are loading millions of records into objects with complex Private sharing models, the system will attempt to recalculate sharing rules for every inserted record, leading to severe 'Salesforce slow performance'. Navigate to Setup > Defer Sharing Calculations and suspend them. Once the migration is complete, you can recalculate the sharing rules asynchronously during off-peak hours.

Fix 4: Leverage Bulk API v2 over REST/SOAP

For payloads exceeding 50,000 records, abandon the standard REST API or older SOAP endpoints. Bulk API v2 automatically chunks data, handles retries, and respects server loads, minimizing platform degradation complaints from end-users by offloading the processing to background queues heavily optimized for multi-tenant resource allocation.

Step 3: Validate and Monitor

After applying these fixes, use the Salesforce CLI (sf commands) to monitor the ingestion rate. Ensure that the batch failure rate remains at 0% and that the average processing time per batch is well under the 10-minute timeout threshold. Monitor the Setup Audit Trail to confirm that configurations (like trigger bypasses and deferred sharing) were properly reverted post-migration to restore standard operating procedures.

Frequently Asked Questions

bash
# Authenticate to the Salesforce org using the CLI
sf org login web --alias production-org

# Create a bulk v2 ingest job for Account data (Upsert based on External ID)
# Bulk V2 automatically optimizes chunking to prevent standard timeouts
sf data bulk upsert --target-org production-org \
  --sobject Account \
  --file ./transformed_accounts.csv \
  --external-id External_System_Id__c \
  --wait 30

# Check the status of a specific bulk job if it timed out or is running slow
sf data bulk resume --target-org production-org --job-id 750xx0000000001AAA

# Tail logs to catch System.LimitExceptions in real-time to identify the crashing trigger
sf apex tail log --target-org production-org --debug-level FINEST | grep -E "System.LimitException|UNABLE_TO_LOCK_ROW"
E

Error Medic Editorial

Error Medic Editorial is composed of Senior DevOps, SRE, and Enterprise Architects dedicated to solving complex infrastructure and platform-specific engineering bottlenecks.

Sources

Related Guides