Fixing a common cause of System.LimitException: Apex CPU time limit exceeded

When developing code, automated unit tests and interactive testing naturally tend to use small numbers of objects. As the number of objects increases, the execution time has to increase in proportion – linearly. But it is all too easy to introduce code where the execution time grows as the square of the number of objects or the cube of the number of objects. (See e.g. Time complexity for some background.) For example, even with 100 objects, code that takes 100ms for the linear case, takes 10s for the squared case and 1,000s for the cubed case. So for the squared or cubed cases

System.LimitException: Apex CPU time limit exceeded

exceptions can result with even modest numbers of objects.

Is this governor limit a good thing? On the positive side, it forces a bad algorithm to be replaced by a better one. But on the negative side, your customer is stuck unable to work until you can deliver a fix to them. Some sort of alarm and forgiveness from the platform for a while would be more helpful…

Here is a blatant example of the problem (assuming all the collections include large numbers of objects):

// Linear - OK
for (Parent__c p : parents) {
    // Squared - problem
    for (Child__c c : children) {
        // Cubed - big problem
        for (GrandChild__c bc : grandChildren) {
            // ...
        }
    }
}

So review all nested loops carefully. Sometimes the problem is hidden, with one loop in one method or class and another loop in another method or class, and so harder to find.

Often the purpose of the loops is just to find the objects/object in one collection that match an object in another collection. There are two contexts that require two different approaches (though in more complicated cases the approaches can be combined) to fix the problem or better to avoid it in the first place:

  • In e.g. a controller where the query is explicit, a parent and child can be queried together with direct relationship references available using a relationship query.
  • In a trigger, no __r collections are populated, so maps have to be used. A map allows a value to be looked up without the cost of going through every entry in a list.

Here is how to fix the problem for the two contexts in parent-child relationships:

// Explicit SOQL context
for (Parent__c p : [
        select Name, (select Name from Childs__r)
        from Parent__c
        ]) {
    // Loop is over the small number of related Child__c not all of the Child__c
    for (Child__c c : p.Childs__r) {
        // ...
    }
}


// Trigger context
Map<Id, List<Child__c>> children = new Map<Id, List<Child__c>>();
for (Child__c c : [
        select Parent__c, Name
        from Child__c
        where Parent__c in Trigger.newMap.keySet()
        ]) {
    List<Child__c> l = children.get(c.Parent__c);
    if (l == null) {
        l = new List<Child__c>();
        children.put(c.Parent__c, l);
    }
    l.add(c);
}
for (Parent__c p : Trigger.new) {
    // Loop is over the small number of related Child__c not all of the Child__c
    if (children.containsKey(p.Id)) {
        for (Child__c c : children.get(p.Id) {
            // ...
        }
    }
}

And for the two contexts in child-parent relationships:

// Explicit SOQL context
for (Child__c c : [
        select Name, Parent__r.Name
        from Child__c
        ]) {
    // The one parent
    Parent__c p = c.Parent__r;
    if (p != null) {
        // ...
    }
}


// Trigger context
Set<Id> parentIds = new Set<Id>();
for (Child__c c : Trigger.new) {
    if (c.Parent__c != null) {
        parentIds.add(c.Parent__c);
    }
}
if (parentIds.size() > 0) {
    Map<Id, Parent__c> parents = new Map<Id, Parent__c>([
            select Id, Name
            from Parent__c
            where Id in :parentIds
            ]);
    for (Child__c c : Trigger.new) {
        // The one parent
        if (c.Parent__c != null) {
            Parent__c p = parents.get(c.Parent__c);
            if (p != null) {
                // ...
            }
        }
    }
}

This fixed code will operate in close to linear time.

In praise of apex:inlineEditSupport

I recently had a requirement where one date value in a calculated table needed to be manually editable. While it would be possible to use an apex:inputField for all the values, using apex:inlineEditSupport seemed like a better approach because the need to edit is relatively rare. That allows the table to look uncluttered for the common case of no editing, yet still allows the values to be changed.

This is the result:

inlineedit

The point of this post is to highlight how easy this is to accomplish and to give a +1 to apex:inlineEditSupport in case anyone has been wary of using it in the several years it has been available…

All that was needed was to change this:

<apex:pageBlockTable value="{!payments}" var="p">
    ...
    <apex:column value="{!p.IssueDate__c}"/>
    ...
</apex:pageBloackTable>

to this:

<apex:pageBlockTable value="{!payments}" var="p">
    ...            
    <apex:column>
        <apex:facet name="header">
            <span class="inlineEditPencil">
                {!$ObjectType.Payment__c.fields.IssueDate__c.label}
            </span>
        </apex:facet>
        <apex:outputField value="{!p.IssueDate__c}">
            <apex:inlineEditSupport event="ondblclick"
                    showOnEdit="save, cancel"
                    hideOnEdit="submit, approve, send"
                    />
        </apex:outputField>
    </apex:column>
    ...
</apex:pageBloackTable>

and to add a couple of new command buttons (“save” and “cancel”) for the inline editing mode.

This CSS displays the pencil icon in the heading (to hint that the column is different):

<style type="text/css">
/* Fragile */
.inlineEditPencil {
    padding-right: 16px;
    background: url(/img/func_icons/util/pencil12.gif) no-repeat right 2px;
}
</style>