Introduction
Salesforce is a multitenant environment, which means there are many users all share the servers. Due to this, Salesforce has many limits regarding Apex to ensure your code or processes doesn’t monopolize the Salesforce servers. For example, there are limits to the number of SOQL queries you can run, the number of records you are allowed to retrieve via SOQL, the total number of HTTP callouts you can make etc. and this is all per transaction. You can see all the execution governers and limits here
One limit that can be difficult for novice developers to resolve is the Apex CPU time limit. This is when the maximum time your code, and an processes called from you code, is allowed to run for. This limit is 10,000 milliseconds for synchronous code and 60,000 milliseconds for asynchronous code. Here, I will look at a synchronous piece of code that I’ve created specifically to break the Apex CPU time limit and to show you how we can resolve this using tools available in the developer console.
Take the following piece of Apex code:
@IsTest public class TestApexCpuLimitTests {
static List newAccounts = new List();
static List newContacts = new List();
@IsTest public static void testHittingApexCpuLimit() {
createAccounts();
createContacts();
matchContactWithAccount();
System.assertEquals(newAccounts[0].Id, newContacts[0].AccountId);
System.assertEquals(newAccounts[999].Id, newContacts[999].AccountId);
}
static void matchContactWithAccount() {
integer i = 0;
for (Contact c: newContacts) {
Account acc = getAccountByName('test' + i);
i++;
c.AccountId = acc.Id;
}
update newContacts;
}
static Account getAccountByName(string accountName) {
for (Account acc: newAccounts) {
if (acc.Name == accountName) {
return acc;
}
}
return null;
}
static void createContacts() {
for (integer i = 0; i < 1000; i++) {
Contact newContact = new Contact(FirstName = 'first' + i, LastName = 'last' + i);
newContacts.add(newContact);
}
insert newContacts;
}
static void createAccounts() {
for (integer i = 0; i < 1000; i++) {
Account newAccount = new Account(Name = 'test' + i);
newAccounts.add(newAccount);
}
insert newAccounts;
}
}
Here, we have 3 methods: createAccounts which creates 1,000 accounts, createContacts which creates 1,000 contacts. Finally, we have matchContactWithAccount which iterates through all the contacts and sets the relevant account to the for the contact object (of course, we could have done this when creating the contacts, but this is just an example to illustrate the problem).
If you run this test, you will receive the error “Apex CPU time limit exceeded”, but what is actually causing the problem? Is it the creation and inserting of the contacts and accounts, the updating of the contacts, or the matching the contacts with an account? Where exactly is the CPU limit being exceeded?
In the developer console, if you click the log, you’ll be displayed the log. If you then go into the Debug menu and select Swich Perspective to Analysis (this is a predefined perspective in Salesforce which contains the relevant windows we need, alternatively you could create your own and display the required windows):
Then you’ll be displayed the Analysis dashboard for the transaction for the test we just ran:
The window we’re specifically interested in here is the top left, the Performance Tree tab in the Stack Tree window:
The duration for the entire TestApexCpuLimitTests test shows 44,822 milliseconds, but this the wall clock, not the actual CPU time on the Salesforce servers.
Time spent in the database and callouts doesn't count for CPU usage, but does count in real time ("wall clock time").
If we look at the logs, we can see the Apex CPU time limit has been exceeded:
LIMIT_USAGE_FOR_NS Maximum CPU time: 15046 out of 10000 ******* CLOSE TO LIMIT.
So the matchContactWithAccount function takes the most time and this is the method we need to improve. Instead of iterating through each Account to retrieve the Account by name, instead we can create a map of the Accounts with the key being the Account name. This way, we can quickly retrieve the Account by name without iterating through the entire list.
To do this, when creating the Accounts, instead of adding each Account to a list, we can add it to a map, with the key being the Account name:
newAccountsMapByName.put('test' + i, newAccount);
Then when retrieving the Account, we can get it from the map like so:
Account acc = newAccountsMapByName.get('test' + i);
After making these change and running the test again, the limit is no longer hit and the test passes. Looking at the performance tree again, we can see the duration for the matchContactWithAccount function has reduced slightly:
But in the logs, the CPU time taken is still more than 10,000:
LIMIT_USAGE_FOR_NS Maximum CPU time: 13853 out of 10000 ******* CLOSE TO LIMIT
The reason an error wasn’t thrown is because although more than 10,000 milliseconds of CPU time was used, CPU limits are flexible. You're only guaranteed 10,000 ms, but it is not unusual to see values as high as 15,000 in production not fail.
And that’s how you can analyse Apex CPU time limit errors and find how to optimise your code.
Happy coding!
Comments