What are Apex Performance Best Practices?

Great question. In opening this can of worms, I would like to noodle the premise if you don't mind :-) just to learn if we're solving performance in the right quadrant. What motivates your question exactly?

easy problem         ^         hard problem
easy solution        |        easy solution
                     |                     
[start here]         |   [graduate to here]
                     |                     
<--------------------+-------------------->
                     |                     
[dragons be here]    |    [data scientists]
                     |                     
easy problem         |         hard problem
hard solution        |        hard solution

Definitely performance is important, but the Force.com platform is pretty good at keeping you within reasonable boundaries. You don't have to worry about nginx vs iis vs apache serving XYZ requests per second. Float above that stuff. Salesforce throws smarts and hardware at those problems so we don't have to.

As a service layer developer, err on the side of inspecting:

  • performance of callouts (web services, third party hooks hanging off your controllers, or crazy stuff living in JavaScript code facing your users via custom buttons, etc)
  • if you're an ISV developing real product, keep swathes of DML out of tests for your patience/sanity's sake, check large inserts and large deletes aren't anywhere near governor limits,
  • check your use of asynchronous tools / set-and-forget methods (like @future, batches, schedules) to detour any heavy lifting away from execution contexts invoked by user interfaces,

Rather than doing legwork for the sake of the Apex runtime, optimize for you the architect, us the developers, them the future maintainers. The Apex runtime will get faster and smarter, you don't need to do it any favours. Principle of least astonishment and semantics wins over tricks every time.

Governor limits are the thoughtful and useful straitjacket that gives us a gentle slap in the face as course correction if code falls outside those reasonable boundaries.

As a client-side developer, invest your valuable time:

  • taking advantage of speedy (JavaScript Remoting) and reactive (Streaming API) features to offer the snappiness (or perceived snappiness) your users expect, decoupled from Apex performance,

  • check the expires attributes of pages holding JavaScript clients, the cache control attributes of static resources (zips of course, concatenated CSS/JS courtesy a non-overkill build script)

  • profile first, shoot later!


Most general optimizations don't matter, and you really shouldn't worry about performance terribly, as the other answer said, but I'm going to provide some very specific rules that you should follow regardless:

Use Configuration instead of Code, if at all possible.

This is simply because code uses execution time, and configuration does not (the 10,000 ms limit). There is a higher limit of 10 minutes per transaction that does include validations, etc, but those are much harder to reach. Validation rules, workflow rules, sharing rules, and rollup summaries are standard elements that can be replicated in Apex Code, but shouldn't be without reason.

Use Maps instead of Loops, if at all possible.

A for loop inside a for loop uses multiplicative processing time (specifically, x*y processing time), which is a major performance drain if not considered carefully. For example, if you're in a contact trigger, and you query all accounts, and loop through each account to see if it matches the contact, that's bad. 200 accounts and 200 contacts becomes 40,000 loop cycles instead of 200 cycles by using a map. You can, and probably will, time out on larger transactions without the use of maps.

Query before Branches, if at all possible.

You should never write a loop like this:

for(Account record: [SELECT Id, Name, Date__c FROM Account WHERE Id IN :accountIds]) {
    if(record.Date__c != null) {
        ...

When you could reduce the number of rows queried, loop cycles, and so on with this:

for(Account record: [SELECT Id, Name, Date__c FROM Account WHERE Id IN :accountIds AND Date__c != NULL]) {
    ...

The performance benefits are amazing when you do it this way. Also, query time isn't counted towards the execution time of 10,000 ms.

Query only fields you need.

If you know in advance which fields you need, query just those. Don't use dynamic field lists if you can help it, because it will slow down your query time and execution time, increase memory usage, and generally slow things down-- significantly. It can also cause errors in situations that wouldn't occur without dynamic lists.

Never query inside a loop.

You'll probably read about this elsewhere, so I won't say much on it, but remember that you'll hit your limits with a lot less data if you do this. Using maps correctly usually means you won't be doing this anyways, because you'll know better.

Other than these simple guidelines, most other optimizations are more cosmetic than any serious performance gains, and you'll pick those up as you go along.


Ways to speed up your queries:

1) Do not use formula fields in filters - they are recalculated on access.

2) If need to filter on a formula field often, could have SF Support index it. This would only be possible for "deterministic" formulas, whose values don't change all the time depending on today's date for example, and which do not have cross-object lookups.

3) Do not use exclusion conditions (!=, NOT), or comparison to NULL ( != NULL). Those cause SF Query Optimizer to not use indexes on indexed fields. Instead, try to use = or IN.

4) Try to use standard indexed fields in filters, like ID, name, createdDate, lastModifiedDate, etc. Also try to use date ranges in the filters, to reduce the number of available records.

5) Do not use the leading % wildcard in the LIKE criteria. e.g. LIKE '%somestring'. This will also cause query optimizer to not use indexes on indexed fields.

6) Use "with sharing" in classes where it makes sense - this will limit the number of rows searched to the ones available to the user through sharing rules, reducing the total number of rows searched, and reducing the length of the query.