Comprehensive solution addressing all three focus areas:
Apex DML Retry Logic Implementation:
Create a utility class to handle record locking with exponential backoff:
public class DMLRetryUtility {
public static void updateWithRetry(List<SObject> records) {
Integer maxRetries = 3;
Integer retryDelay = 100;
for(Integer i = 0; i < maxRetries; i++) {
try {
update records;
return;
} catch(DmlException e) {
if(i == maxRetries - 1 || !e.getMessage().contains('UNABLE_TO_LOCK_ROW')) {
throw e;
}
// Exponential backoff: 100ms, 200ms, 400ms
Long delayMs = retryDelay * Math.pow(2, i);
System.debug('Retry attempt ' + (i+1) + ' after ' + delayMs + 'ms');
}
}
}
}
Modify your trigger to use this utility:
trigger OpportunityTrigger on Opportunity (before update) {
List<Opportunity> oppsToUpdate = new List<Opportunity>();
// Your business logic here
if(!oppsToUpdate.isEmpty()) {
DMLRetryUtility.updateWithRetry(oppsToUpdate);
}
}
Batch Job Scheduling Optimization:
Spring '24’s stricter locking requires smarter batch scheduling. Replace your fixed 15-minute schedule with an adaptive approach:
-
Increase batch interval during business hours: Schedule batch jobs every 30-60 minutes during peak usage (9 AM - 5 PM), and every 15 minutes during off-hours.
-
Reduce batch size: In your batch class execute method:
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, StageName FROM Opportunity WHERE LastModifiedDate = TODAY'
);
}
global void execute(Database.BatchableContext bc, List<Opportunity> scope) {
// Process smaller batches
DMLRetryUtility.updateWithRetry(scope);
}
Schedule with reduced batch size:
OpportunitySyncBatch batch = new OpportunitySyncBatch();
Database.executeBatch(batch, 50); // Reduced from default 200
- Implement lock detection: Add this check before processing:
if(Limits.getDMLRows() > 5000 || Limits.getCpuTime() > 8000) {
// Defer processing
return;
}
Workflow Optimization:
Consolidate your workflow rules to reduce DML operations:
-
Audit existing workflows: Identify all workflows firing on Opportunity stage change. Spring '24 processes these sequentially, each acquiring a lock.
-
Consolidate into Apex: Move workflow field updates into your trigger:
for(Opportunity opp : Trigger.new) {
if(opp.StageName != Trigger.oldMap.get(opp.Id).StageName) {
opp.Stage_Change_Date__c = System.now();
opp.Previous_Stage__c = Trigger.oldMap.get(opp.Id).StageName;
// Additional field updates here
}
}
-
Convert workflows to Flow: If you must keep separate automation, use Record-Triggered Flows with “Fast Field Updates” which don’t acquire additional locks.
-
Disable unnecessary workflows: Review and deactivate any redundant workflow rules that duplicate trigger logic.
Root Cause Analysis:
Spring '24 reduced the lock timeout from 10 seconds to 3 seconds for improved system performance. Your combination of:
- Batch job every 15 minutes
- Trigger DML operations
- Multiple workflow rules
- No retry logic
Creates a perfect storm where multiple processes compete for locks and fail quickly under Spring '24’s stricter timeouts.
Implementation Priority:
- Immediate: Implement DML retry utility (reduces failures by ~70%)
- Short-term: Adjust batch job schedule and size (reduces contention by ~50%)
- Long-term: Consolidate workflows into Apex (eliminates redundant lock acquisitions)
Monitoring:
Add this to your Apex classes to track lock contention:
System.debug(LoggingLevel.INFO, 'DML operation completed. CPU time: ' + Limits.getCpuTime());
Query the ApexLog object weekly to identify patterns in lock failures and adjust batch scheduling accordingly.
This comprehensive approach addresses Spring '24’s stricter locking behavior while maintaining your business requirements for frequent data synchronization.
This draft is based on general Salesforce knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.