Data Management in Salesforce: Avoiding SOQL Limits and CPU Timeouts

Data Management in Salesforce: Avoiding SOQL Limits and CPU Timeouts

Managing data efficiently in Salesforce isn’t just about keeping records clean — it’s also about keeping your org performant and staying within governor limits.

If you’ve ever faced the dreaded “Too many SOQL queries: 101” or “Apex CPU time limit exceeded” error, you know how painful it can be. These issues usually appear when your code or automation touches large datasets without optimization.

In this blog, we’ll explore why these limits exist, what causes them, and how to prevent them with smart design practices.


Why Salesforce Has Limits

Salesforce is a multi-tenant platform, meaning multiple customers share the same underlying infrastructure.
To make sure no single customer consumes all the resources, Salesforce enforces Governor Limits — boundaries around how much data or computation any transaction can consume.

These limits include:

  • SOQL Queries per transaction (usually 100)

  • DML statements per transaction (usually 150)

  • CPU time (max 10,000 ms)

  • Heap size

  • Callouts per transaction

  • And many others

Understanding and respecting these limits ensures that your code runs efficiently for everyone on the platform.


Common Causes of SOQL Limit & CPU Timeout Errors

Let’s look at what typically causes these headaches:

1. SOQL Queries Inside Loops

for (Account acc : accList) {
List<Contact> conList = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

Problem: You’re running one query per Account.
If accList has 200 records → you’ll hit the 101 SOQL limit fast.

2. Inefficient Triggers or Flows

  • Multiple automation tools (Flows, Triggers, Process Builders) firing on the same record.

  • Recursive updates causing repeated DML operations.

3. Unoptimized Loops and Collections

  • Nested loops processing large lists.

  • Repeated use of get() or containsKey() without caching results.

4. Heavy Business Logic in Single Transaction

  • Performing large calculations or sorting thousands of records.

  • Using synchronous calls for operations that can be batched.


Best Practices to Avoid SOQL Limits

1. Use Bulkified Queries

Always query outside loops and use collection filters:

Set<Id> accIds = new Set<Id>();
for (Account acc : accList) {
accIds.add(acc.Id);
}

List<Contact> contacts =
[SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds];

✅ One query for all records.


2. Use Maps for Fast Lookups

Instead of querying repeatedly, use a map:

Map<Id, Account> accMap =
new Map<Id, Account>([SELECT Id, Name FROM Account WHERE Id IN :accIds]);

Then, retrieve data in O(1) time:

Account acc = accMap.get(contact.AccountId);

3. Use Relationship Queries

If you only need related data, use parent-child queries:

List<Account> accList =
[SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account];

This avoids multiple queries and keeps logic simple.


4. Limit Query Scope

Don’t fetch unnecessary fields or records.
Bad:

SELECT * FROM Account

Good:

SELECT Id, Name FROM Account WHERE CreatedDate = LAST_N_DAYS:30

Use selective filters to avoid query selectivity issues and improve performance.


Best Practices to Avoid CPU Timeouts

CPU timeouts often result from too much computation or too many automations executing together.

1. Avoid Nested Loops

Nested loops are CPU killers.
Bad:

for(Account acc : accList){
for(Contact con : contactList){
if(con.AccountId == acc.Id){ ... }
}
}

Good:

Map<Id, List<Contact>> accToContacts = new Map<Id, List<Contact>>();
for(Contact con : contactList){
if(!accToContacts.containsKey(con.AccountId))
accToContacts.put(con.AccountId, new List<Contact>());
accToContacts.get(con.AccountId).add(con);
}

2. Minimize Data Volume in One Transaction

If you need to process thousands of records:

  • Use Batch Apex

  • Use Queueable Apex

  • Use Future methods
    These split the logic into smaller, more manageable chunks.


3. Turn Off Unnecessary Automation Temporarily

Flows, validation rules, and triggers may fire on the same update.
Temporarily disabling or merging them can reduce CPU time dramatically.


4. Cache Data in Memory

If you’re calling the same method or query multiple times, cache the result in a variable or static map.


5. Check Recursive Logic

Ensure triggers or flows don’t re-fire themselves unnecessarily. Use a static variable flag like:

public class AccountTriggerHelper {
private static Boolean isExecuted = false;
public static void updateContacts(List<Account> accList) {

if(isExecuted) return;
isExecuted = true;
// Your logic
}
}


Example: Before & After Optimization

Before:

for (Opportunity opp : [SELECT Id, AccountId FROM Opportunity WHERE StageName='Closed Won']) {
List<Invoice__c> invList =
[SELECT Id FROM Invoice__c WHERE Opportunity__c = :opp.Id];
insert invList;
}

After:

List<Opportunity> oppList = [SELECT Id FROM Opportunity WHERE StageName='Closed Won'];
Map<Id, List<Invoice__c>> oppToInv = new Map<Id, List<Invoice__c>>();
for (Invoice__c inv :
[SELECT Id, Opportunity__c FROM Invoice__c WHERE Opportunity__c IN :oppList]) {
if(!oppToInv.containsKey(inv.Opportunity__c))
oppToInv.put(inv.Opportunity__c, new List<Invoice__c>());
oppToInv.get(inv.Opportunity__c).add(inv);
}

insert oppToInv.values().flatten();

✅ Bulkified, efficient, and within limits.


Tools & Techniques to Monitor Limits

  • Debug Logs: Always check “Limits” section for SOQL and CPU usage.

  • Apex Limits Class: Use Limits.getQueries() or Limits.getCpuTime() to monitor runtime.

  • Salesforce Optimizer Report: Identifies performance and configuration issues.

  • Event Monitoring: For large orgs, track API and transaction performance.


Conclusion

In Salesforce, data management isn’t just about storing data — it’s about processing it efficiently.
By bulkifying your logic, reducing redundant automations, and using asynchronous processing where needed, you can keep your org’s performance healthy and scalable.

Leave a Comment

Your email address will not be published. Required fields are marked *