進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

Avoid Concurrency

Learning Objectives

After completing this unit, you’ll be able to:
  • Explain why we have concurrency limits.
  • Identify processes that create concurrency problems.
  • Implement techniques to avoid concurrency limits.

How Concurrency Works

Concurrency is when multiple processes are running at the same time. When too many processes are trying to do something simultaneously, that can slow the system down.

If your app has a lot of users doing something that calls out to another system, such as a Visualforce page that requests information from another source, you end up with many pages simultaneously waiting for a response. When the response from each callout takes longer than 5 seconds, you reach the concurrent long-running transaction limit.

It’s like riding in an elevator in the morning with dozens of other people. It can take time to get them all to their floors. You can optimize by grouping floors in certain elevator banks, programming elevator banks to be more efficient about which floors a given car goes to.

People waiting for two elevators

Concurrency Limits

The Salesforce system is the same. We have a certain number of processes (threads) that can be used at any given time. Normally the processes run fast, and we do not experience slowdowns. However, the longer a process runs, the less available processing capacity we have. To accommodate this, we allow only a certain number of long-running processes to function per org at a time. When you reach your limit, you cannot start a new long-running transaction until a previous one has completed.

Identifying long-running processes can take a bit of time. But the more you practice, the better you become. Generally, these processes fall into a few buckets:

  • API Requests. A long-running API request is one that takes over 20 seconds. Salesforce allows only 25 to run at a time in an org.
  • Callouts. When we make calls to another system, Salesforce waits for a response. If the responding system is slow, that can cause issues. You can have only a certain number of callouts at a time.
  • Concurrency between Salesforce systems. Though Salesforce is a large ecosystem, it does have multiple subsystems in place. The core clouds (Sales Cloud, Service Cloud, Platform) are all built on our core application. Other applications, though, are built in their own stacks, including Marketing Cloud, BigObjects, and Retail Cloud. When calling to these systems, we also need to limit long-running callouts.

Tune Your Long-Running Transactions

If your calls are taking a long time, you need to tune your code. This can mean:

  • Reducing the number of Apex trigger, workflow, and validation rules on your objects. All of these take time, and the more you have, the longer Salesforce takes to save a record.
  • Reducing the complexity of your query. Salesforce allows queries to run for up to 2 minutes, but generally we want them to run as fast as possible. Adding additional filters, custom indexes, and ID filters to your query statements are good starts.
  • Switching to asynchronous processes. When making a call, you can do it asynchronously (send a request to be made when there are resources free, and periodically check to see if the request is done). Moving from synchronous to asynchronous where possible affords you higher limits, because Salesforce can do the processing as resources become available.
  • Reducing the amount of data you are passing. Creating 200 records per call takes a long time. Try creating 100 per call, or 50. Tuning your request size can be a bit of an art form and requires a deep understanding of your data model.

Let’s look at some tuning examples.

Reduce the Number of Apex Triggers

A common development pitfall is the assumption that trigger invocations never include more than one record. Apex triggers are optimized to operate in bulk, which, by definition, requires developers to write logic that supports bulk operations.

This is an example of a flawed programming pattern. It assumes that only one record is pulled in during a trigger invocation. While this might support most user interface events, it does not support bulk operations invoked through SOAP API or Visualforce.
trigger MileageTrigger on Mileage__c (before insert, before update) {
   User c = [SELECT Id FROM User WHERE mileageid__c = Trigger.new[0].id];
}
This is another example of a flawed programming pattern. It assumes that fewer than 100 records are in scope during a trigger invocation. If more than 100 queries are issued, the trigger would exceed the SOQL query limit.
trigger MileageTrigger on Mileage__c (before insert, before update) {
   for(mileage__c m : Trigger.new){ 
      User c = [SELECT Id FROM user WHERE mileageid__c = m.Id];
   }
}
This example demonstrates the correct pattern to support the bulk nature of triggers while respecting the governor limits:
Trigger MileageTrigger on Mileage__c (before insert, before update) {
   Set<ID> ids = Trigger.newMap.keySet();
   List<User> c = [SELECT Id FROM user WHERE mileageid__c in :ids];
}

This pattern respects the bulk nature of the trigger by passing the Trigger.new collection to a set, then using the set in a single SOQL query. This pattern captures all incoming records within the request while limiting the number of SOQL queries.

Best Practices for Designing Bulk Programs

The following are the best practices for this design pattern:
  • Minimize the number of data manipulation language (DML) operations by adding records to collections and performing DML operations against these collections.
  • Minimize the number of SOQL statements by preprocessing records and generating sets, which can be placed in single SOQL statement used with the IN clause.

Reduce the Complexity of Queries

You can take several approaches to reduce the complexity of your queries. Sometimes you can only go so far squeezing efficiency out of your queries, but the more effective they are, the better. Salesforce provides the Query resource so you can get information about how Salesforce executes your query. Using this resource, you can see which approaches are the most effective ones. Use the ones you can. The more efficient your queries, the more likely they can avoid limits and run fast. Here are some examples.

  • Avoid Negative Operators like '!=' or NOT CONTAINS.
  • Avoid Inefficient Operators like leading wildcards.
  • Sort optimization, which is used with date and number fields. Use an ORDER BY with a LIMIT clause. In cases where the selectivity threshold can’t be met, sort optimization might be better than a table scan.

Go Asynchronous

Use asynchronous callouts to make long-running requests from a Visualforce page to an external Web service and process responses in callback methods. Asynchronous callouts that are made from a Visualforce page don’t count toward the Apex limit of 10 synchronous requests that last longer than five seconds. As a result, you can make more long-running callouts and you can integrate your Visualforce pages with complex back-end assets.

This diagram shows the execution path of an asynchronous callout, starting from a Visualforce page. A user invokes an action on a Visualforce page that requests information from a Web service (step 1). The app server hands the callout request to the Continuation server before returning to the Visualforce page (steps 2–3). The Continuation server sends the request to the Web service and receives the response (steps 4–7), then hands the response back to the app server (step 8). Finally, the response is returned to the Visualforce page (step 9).

Execution Flow of an Asynchronous Callout
Diagram for the execution flow of a continuation

A typical Salesforce application that benefits from asynchronous callouts contains a Visualforce page with a button. Users click that button to get data from an external Web service. For example, a Visualforce page that gets warranty information for a certain product from a Web service. Thousands of agents in the organization can use this page. Therefore, a hundred of those agents can click the same button to process warranty information for products at the same time. These hundred simultaneous actions exceed the limit of concurrent long-running requests of 10. But by using asynchronous callouts, the requests aren’t subjected to this limit and can be executed.

Example: Asynchronous Callout

You have a Visualforce page with a button that performs a callout to a web service. When users click the button, it calls a startRequest() method in the CalloutController Apex class. This method waits for a response from the web service. As you can imagine, as you wait for the response, other web service requests could pile up and you’d approach the concurrent API request limit.

You need to add a continuation and callback method, which waits for the response. The following code shows a synchronous callout, which could hit the concurrent request limit. The code also shows how to write a continuation and callback method to avoid concurrency, so you can compare the two approaches.

public class CalloutController {
  // Unique label corresponding to the continuation
  public String requestLabel;
  // Result of callout
  public String result {get;set;}
  
  
  public Object startRequest() {
    //this part up here is common to both the callout 
    //and continuations approaches
    String sessionId = UserInfo.getSessionId();
  
    HttpRequest req = new HttpRequest();
    req.setHeader('Authorization', 'Bearer ' + sessionID);
    req.setHeader('Content-Type', 'application/json');
    req.setHeader('Accept', 'application/json');
    
    req.setEndpoint('https://na14.salesforce.com/services/data/v40.0/tooling/' +
    'completions?type=apex');
    
    req.setMethod('GET');
    
    //Here's how it works with a callout
    Http h = new Http();
    HttpResponse res = h.send(req);
    
    processResponse(res);
    
    //and that is all. This works fine until the service is too slow and lots of people use it.
    
    
    //Here is what it needs to change to
    Continuation con = new Continuation(40);
    con.continuationMethod='handleCallback';
    this.requestLabel = con.addHttpRequest(req);
    return con;
    //that's the difference for continuations.
  }
  
  //Here is the callback that waits for a response before continuing
  public Object handleCallback() {
    HttpResponse response = Continuation.getResponse(this.requestLabel);
    return processResponse(response);
  }   
  
  
  // Callback method is only needed for continuations.
  public Object processResponse(HttpResponse response) {   
    result = response.getBody();
  
    // Return null to re-render the original Visualforce page
    return null;
  }
}

Then the Visualforce page wakes up when the callout returns.

Look at you! You’re already identifying concurrent requests and learning how to avoid them. This does take some knowledge and practice, but you’re on your way. In the next unit, we take a deep dive into another way to shape up your code.