Start tracking your progress
Trailhead Home
Trailhead Home

Tune Transactions

Learning Objectives

After completing this unit, you’ll be able to:
  • Explain why we have transaction-based limits.
  • Identify processes that approach transaction-based limits.
  • Implement techniques to avoid transaction-based limits.

Why Transaction Limits?

When most programmers think of limits on the platform, they’re thinking of Apex transaction limits. We have these because each transaction is unique, and even the best programmers might not consider every possible corner case. Transaction-based limits ensure that one or two runaway transactions don’t steal all the resources from your other law-abiding transactions.

A meter showing how much of a resource you’re using
Application Resources
Transaction limits protect three primary application server resources: memory, CPU, and database. Memory and CPU are each protected by a single limit: respectively, the heap size limit and the CPU timeout. These are in place for different reasons, though.
  • Exceeding memory limits is a danger to the whole app server. A process taking too much memory can quickly box out all other processes.
  • CPU timeout mostly prevents infinite loops. You’d thank us for this if you authored one (totally by accident, of course). The limit is set at 10 seconds, in part because that’s about as long as a user is willing to stare at a blank screen, waiting for your logic to do its thing. At some point, the user gives up and refreshes the page. If the process takes longer than that, make it asynchronous.

We also have a stack-depth catch that spots infinite recursion, and loop restrictions to avoid infinite loops. Both are equally bad for your users.

Database Efficiency
For a database, Salesforce imposes no single overarching limit. Instead, there are several different limits. Taken together, they protect the database from inefficient or runaway querying. For example, there are limits to the number of queries per transaction and the number of records you can save.
Queries
The API limits queries to keep them within a manageable size. The length of your query and your WHERE clause can only be a certain length. The number of relationships between objects has to stay under a certain threshold. These are usually high enough that you’d have to really mess up to reach them.
Processes
Like the signals at a crosswalk, there are other resources that we don’t allow to flood any individual transaction.
  • Message queue (MQ). You can create only a certain number of async jobs in a single transaction. MQ manages the execution of these jobs to balance the load, but if the queue can’t process a billion jobs in a day, we shouldn’t put a billion jobs in there.
  • Job creation. Asynchronous jobs can spawn only one more asynchronous job. If a job could spawn more, it wouldn’t take long before it created a runaway process.
  • Email. Too many emails can brand you as a spammer. Then you won’t get any of your emails read. We make sure you don’t initiate an email campaign that gets out of control.
Visualforce Demands
We limit view state because we render the page on the server and send the whole thing back and forth with every request.

Solutions to Avoid Reaching Transaction Limits

You need ways to manage the number of your transactions.

  • Embrace asynchronous. We have higher limits for asynchronous processing. All asynchronous transactions go through a message queue, where they can be throttled if the system overheats. Synchronous transactions all happen at once and all have to complete, so there’s no similar throttle available to help manage it.
  • Test your code. High-volume and high-complexity corner cases need a lot of testing.
  • Aggregate queries. Iterate over results, rather than querying repeatedly for individual records.
  • Record collections. Save records as collections rather than one at a time.
  • Monitor your system. See if increased record counts and user traffic cause your code to approach your limits. Identify the problem before it happens!

Example: Efficient Queries

Let’s take a look at an Apex class TrailLoop that contains both good and bad examples of queries for comparison. In this class, BadLoop() uses a loop to keep adding tasks to taskless accounts, one at a time. GoodLoop() submits the tasks to all taskless accounts at once.

public class TrailLoop {
    //here is a bad loop
    public static void BadLoop() {
        List<Account> tasklessAccounts = [Select id from Account limit 5];
        for (Account a : tasklessAccounts) {
            Task t = new Task();
            t.Subject = 'make something up';
            t.whatId = a.id;
            insert t;
        }
    }
    //and here is how the developer could fix it
    public static void GoodLoop() {
        List<Account> tasklessAccounts = [Select id from Account limit 5];
        List<Task> insertTasks = new List<Task>();
        for (Account a : tasklessAccounts) {
            Task t = new Task();
            t.Subject = 'make something up';
            t.whatId = a.id;
            insertTasks.add(t);
        } 
        insert insertTasks;
    }
...

The class continues with a query that requests all tasks and searches them for status "Completed".

//here is a bad query with #nofilter    
    public static void LazyQuery(Account a) {
        List<Task> allMyTasks = [Select Id, status, whatId From Task];
        for (Task t : allMyTasks) {
            if (t.whatId == a.id) {
                t.status = 'Completed';
            }
        }
        update allMyTasks;
    }
…

With a filter and simpler loop logic, this query doesn’t make as many requests to the database.

//and here's how you should do this
    public static void SmarterQuery(Account a) {
        List<Task> allMyTasks = [Select Id, status, whatId From Task Where whatId = :a.id];
        for (Task t : allMyTasks) {
            t.status = 'Completed';
        }
        update allMyTasks;
    }
        
}

Your code is in shape! Time to put these techniques into practice.

retargeting