Understanding Apex Execution Context

Multi-Tenant Constraints

Apex operates in a multi-tenant environment where shared resources impose strict governor limits. Understanding execution context—trigger, batch, queueable, or scheduled—is key to predicting system behavior under load.

Common Governor Limits

  • SOQL Queries: 100 synchronous / 200 async
  • DML Statements: 150 per execution
  • Heap Size: 6MB synchronous / 12MB async
  • CPU Time: 10,000ms per transaction

Common Troubleshooting Scenarios

1. Hitting CPU Time Limit

In complex triggers, nested loops or poorly optimized queries can breach the 10-second CPU time limit.

System.LimitException: Apex CPU time limit exceeded

Solution:

  • Break logic into smaller chunks using Queueable or Batch Apex
  • Use Limits.getCpuTime() to track consumption mid-execution
  • Optimize loops and avoid redundant SOQL inside them

2. Querying Too Many Records

Running into the 50,000 record query limit often happens in list views, reports, or custom batch operations.

System.LimitException: Too many query rows: 50001

Fix: Use pagination via SOQL OFFSET or batch records via Batch Apex. Apply WHERE clauses to reduce dataset size. When appropriate, use custom indexes or selective filters.

3. DML Limit Violations in Triggers

Triggers that perform DML inside loops risk hitting the 150 DML limit.

System.LimitException: Too many DML statements: 151

Best Practice: Always bulkify triggers. Use collection-level operations outside of loops:

List updates = new List();
for (Contact c : Trigger.new) {
  Account a = new Account(Id=c.AccountId, Name='Updated');
  updates.add(a);
}
update updates;

4. Async Conflicts (Future vs Queueable)

Mixing asynchronous operations without respecting order or platform limits causes unpredictable behavior or delays.

Avoid: Calling @future from batch or queueable Apex. Instead, chain Queueables or use Platform Events where ordering is needed.

Diagnosing Apex Issues

Use Developer Console

Inspect logs for CPU usage, heap allocation, SOQL/DML counts. Filter by execution unit (trigger, class, queueable).

Limit Monitoring

Use System.debug(Limits.getDmlStatements()) to debug incrementally. Aggregate via Platform Events or Logging objects for persistent tracking in enterprise environments.

Heap Size Debugging

Large datasets or object graphs can breach heap limits. Serialize large maps/lists only when required, and nullify unused objects explicitly.

Fixing and Refactoring Patterns

1. Use Batch Apex for Large Volume Processing

When processing over 50,000 records, define a class implementing Database.Batchable. Set small batch sizes (e.g., 200) for better control.

global class MyBatch implements Database.Batchable {
  global Database.QueryLocator start(Database.BatchableContext bc) {
    return Database.getQueryLocator('SELECT Id FROM Account');
  }
  global void execute(Database.BatchableContext bc, List scope) {
    // logic here
  }
  global void finish(Database.BatchableContext bc) {}
}

2. Switch to Queueable Apex for Async Chaining

Use Queueable Apex for finer control over chained async logic compared to @future methods.

3. Decouple with Platform Events

For integration-heavy logic, publish Platform Events to decouple trigger logic from external systems and reduce direct DML/SOQL usage.

Best Practices

  • Always bulkify logic—never write SOQL/DML inside loops
  • Use Custom Metadata/Settings to avoid hardcoding logic
  • Use feature toggles for async operations to reduce runtime overhead
  • Split logic into handler classes for maintainability and testability
  • Run Apex tests with seeAllData=false to ensure isolation

Conclusion

Apex troubleshooting at scale requires a deep understanding of Salesforce's execution model and limits. Whether it's CPU time, heap size, or DML count, systematic diagnostics, asynchronous design patterns, and best practices like bulkification help maintain reliable enterprise applications. For architects, the key lies in proactive instrumentation, refactoring for async safety, and using decoupled approaches like Platform Events to future-proof complex Apex logic.

FAQs

1. How can I debug governor limit errors more efficiently?

Use Developer Console logs to trace limit consumption. Add debug logs programmatically using the Limits class for visibility inside methods and loops.

2. What's the difference between Future and Queueable Apex?

Queueable Apex offers more control and supports chaining, complex objects, and error handling. Future methods are simpler but more limited in flexibility and context.

3. Can I increase Apex governor limits?

No, Salesforce enforces strict limits to ensure multi-tenant stability. The recommended approach is to architect around limits using async processing, Platform Events, and bulk design patterns.

4. What is the best way to handle large datasets?

Use Batch Apex for transactional control and scalability. Consider External Objects or callouts to external systems if data doesn't need to reside in Salesforce.

5. How do I monitor Apex performance across orgs?

Leverage Event Monitoring, custom logging via Platform Events, and integrate with external observability tools via Salesforce Connect or APIs for broader insights.