2
  1. We have many composite apis for external party to enter data or modify data in our org in real time when the external system receives data.

  2. We also have an integration with CTI <-> SF, where when a user makes them selves as available we modify User record via composite api calls.

In the second scenario we cannot control the concurrency as its from CTI.

Question :

When a standard rest api is fired, and when triggers runs for this transaction, which governor limit is it subjected to? In the above 2 point, the UserTrigger does some complex operation. Before passing the transaction to async. And on heavy load we have observed via EventLog that the RUNTIME shows it takes around 15-20 seconds.

a) Concurrent API Request Limits (20 seconds limit for each call)
b) Concurrent Long Running Transaction (5 seconds limit for each call)

Probable Solution

Instead of composite api, let external system fire a platform event, and Apex will process this PE and update the user. Here I assume the limits would be 2,50,000 per hour for firing a PE from external system.

2 Answers 2

2

Sorry for the long post. But an interesting finding with data points. I did some testing around this. Here are my findings.

FIRST

services/data/v52.0/composite

{
"allOrNone":true,
"compositeRequest":[
    {
        "method":"PATCH",
        "url":"/services/data/v48.0/sobjects/User/Username/***@***.com",
        "referenceId":"updateMentorAvailabilityStatus",
        "body":{
            "CustomField__c":"true"
        }
    }
]
}

If we use Composite API and triggers are doing some heavy custom logic which takes more than 10 seconds.

trigger UserTrigger on User (before insert, before update, after insert, after update) {

    Long startTime = System.currentTimeMillis();
    for (Integer i = 0; i < 30000000; i++) {

    }
    Long endTime = System.currentTimeMillis();
    System.debug('Total Time Taken -> ' + String.valueOf(endTime-startTime));
}

Then before hitting Concurrent API Request Limits (20 seconds limit for each call) we will start getting Apex CPU Time Limit

{
    "compositeResponse": [
        {
            "body": [
                {
                    "message": "UserTrigger: System.LimitException: Apex CPU time limit exceeded",
                    "errorCode": "CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY",
                    "fields": []
                }
            ],
            "httpHeaders": {},
            "httpStatusCode": 400,
            "referenceId": "updateMentorAvailabilityStatus"
        }
    ]
}

Which means that even though we have this limit of standard api for 20 seconds each, the triggers and apex classes running under this context still have 10 seconds apex CPU time limit.

SECOND

Tried reproducing REQUEST_LIMIT_EXCEEDED: ConcurrentRequests (Concurrent API Requests) Limit exceeded. This comes when apex can run below 10 seconds. But there are more than required number threads accessing the apis at the same time. Other concurrent requests starts showing this exception.

Here I modified apex loop to run under the cpu time limit.

trigger UserTrigger on User (before insert, before update, after insert, after update) {

    Long startTime = System.currentTimeMillis();
    for (Integer i = 0; i < 20000000; i++) {

    }
    Long endTime = System.currentTimeMillis();
    System.debug('Total Time Taken -> ' + String.valueOf(endTime-startTime));
}

And triggered 10 instances in parallel to user composite api.

And got this response . Error: REQUEST_LIMIT_EXCEEDED: ConcurrentRequests (Concurrent API Requests) Limit exceeded.

THIRD

"Error performing apex: ConcurrentPerOrgLongTxn Limit exceeded.

This comes when I modified LongTxn class to increase the time in which the system returns the response. So the scenario becomes. When a webservice call gets hit concurrently and the apex takes more than approx 8-9 seconds to return the response constantly. Then we receive this exception.

@RestResource(urlMapping='/LongTxn')
global with sharing class LongTxn {
    @HttpGet
    global static void handleGet() {
        System.debug(LoggingLevel.ERROR, 'HandleGet called');
        RestResponse res = RestContext.response;
        res.addHeader('Content-Type', 'application/json');
            for (Integer i = 0; i < 22000000; i++) {

        }
        res.responseBody = Blob.valueOf('{}');
}

SOLUTION for Concurrent API

Instead of composite api, I used Platform event, and used the same replication manner to replicate 10 concurrent calls.

And wrote a trigger on PE to update user.

/**
 * Created by nagendrasingh on 29/05/22.
 */

trigger UserPETrigger on UserEvent__e (after insert) {

    List<UserEvent__e> userEvents = Trigger.new;

    List<User> lstUsers = new List<User>();
    for (Integer i = 0; i < userEvents.size(); i++) {
        lstUsers.add(new User(Username = userEvents.get(i).Username__c, MKApp_Availability_Status__c = userEvents.get(i).MKApp_Availability_Status__c));
    }

}

And this was not giving any exceptions such as Concurrent API Requests as the response is always under 200 ms.

0

All the standard apex governor limits apply here for every individual REST API inbound call or sub requests within composite inbound call. As mentioned here, the entire composite inbound counts towards the API limit (the limits will not be reset for each subrequest).

The entire series of requests counts as a single call toward your API limits.

Both the concurrent limits apply as well. However, both shouldn't be confused as applicable only for REST inbound requests.

  1. Concurrent API Request Limit is API request limit and it supports up to 25 requests with a duration of 20 seconds or more. This applies to all inbound API requests.
  2. Concurrent Long Running Transaction is Apex governor limit and supports up to 10 long running request with over 5 seconds for each request. This means it's not just for inbound API requests, but for any long running apex transactions.

I couldn't find documentation about how exactly SF platform juggles between maintaining these limits, esp. for orgs that handle large data volume/ processing.

If you are dealing with a LDV org, then offloading all the processing to async apex might not be a good solution as there are daily limits for the number to async executions. Deciding on a good design would be dependent on the actual requirements, but building your solution around High Volume Platform Events could be a appropriate. Note the following though:

  1. Limit of 2,50,000 HVPE published per hour is not just for HVPE published by external systems, but applies for all publishing methods, including apex, APIs, flows & process.
  2. The maximum event message size that you can publish is 1 MB

You may refer here for more details on HVPE allocations.

7
  • As our org has 9,00,000 async limits per day. And we dont use any PE as of now. So I was thinking of using PE + Async mode as a viable integration solution. Commented May 28, 2022 at 7:16
  • Incorporating async processing totally depends on the various factors like daily max & average data volume to processed, functional & non-functional requirements, existing # of daily async process etc. I've seen orgs where this async limit was exceeded few times and it brought down the business to temporary halt (and we had to reach out to Salesforce support team). If you don't expect such large data volumes any time of the year and are well below the daily async limit, I see no reason for why shouldn't take this approach.
    – arut
    Commented May 28, 2022 at 7:46
  • 1
    Just be aware that while an apex trigger subscriber for PE seems to run asynchronously, it has sync apex limits.
    – Phil W
    Commented May 28, 2022 at 9:31
  • Yes @PhilW, I am aware of this. The documentation around PE is misleading as it mentions the word async around it. Commented May 28, 2022 at 13:15
  • One more caveat: for external client to generate a PE, you have to call a REST/SOAP/Bulk API. Still better than a "fat" composite request. Another option for processing the event is a Function.
    – identigral
    Commented May 28, 2022 at 15:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .