We have Apex code that needs to interrogate the schema to dynamically handle configuration-based behaviour (e.g. for defining fields that should be loaded or saved) for specific types of object (such as "Account" or "MyCustomObject__c") and have seen that a lot of CPU time is spent just loading the schema. What's the best way to use the Salesforce APIs to minimize the overheads?
-
I have tried to provide a question/answer that pulls together a few different points, notably a key part of sfdcfox's excellent reply in the cited question - which I have myself linked to. So, it is partially a duplicate, but looking at it more broadly and pointing out that there are TWO Salesforce behind-the-API caches for the "dynamic" and "static" schemas. If you query something both statically AND dynamically the data is loaded twice and cached twice.– Phil WCommented May 13, 2019 at 11:26
1 Answer
The first thing to realize is that calls to Schema.getGlobalDescribe are cripplingly slow and should be avoided at (pretty much) all costs. The usual approach here is to replace this call with an appropriately targeted call to Schema.describeSObjects so you only load the schema detail for explicitly named object types (e.g. 'Account', 'MyCustomObject__c' etc.), though that has been shown as ineffective.
The second thing to realize is that Salesforce's Schema mechanism actually has two different flavours and two different session caches for this data. I am unsure what Salesforce officially calls these, but I have badged them:
- Dynamic - this is what is used when calling the Schema.getGlobalDescribe and Schema.describeSObjects methods or when using custom metadata Field Definition fields in queries
- Static - this is what is used when explicitly accessing schema elements via things like Schema.SObjectType.Account or Schema.SObjectType.MyCustomObject__c (and in a few other cases - see below).
Static schema access is much, much faster than dynamic access (literally orders of magnitude faster) and is the cornerstone for improving the performance you get.
While we can't always make use of Static schema access (see this question for an example) there are ways to avoid use of both Schema.getGlobalDescribe and Schema.describeSObjects when you already know the name(s) of the type(s) of SObject that you want to get the schema details for.
Given an SObject API name, such as "Account" or "MyCustomObject__c", as a string and realizing that the different types of SObject are actually Apex classes too, you can statically access the schema detail using the following (based on this question's answer):
SObject obj = (SObject) Type.forName(theApiNameString).newInstance();
DescribeSObjectResult describe = obj.getSObjectType().getDescribe();
The describe thus obtained does not even need to be added to a custom session cache (static map or similar) since Salesforce optimizes accessing that same describe again in the same session as part of its behind-the-API caching of results, and the reflective instantiation used is quite a cheap operation.
NB: Code that accesses the schema for a given object both "statically" and "dynamically" will have Salesforce load (and session cache behind the API) the schema details twice, as two different instances of the DescribeSObjectResult. For this reason it is best to ensure you always access the schema in the one way (ideally "statically").
Kudos to sfdcfox for identifying this alternative, Type-based approach.
UPDATE (2023)
If you use the updated Schema.describeSObjects
, ensuring that the SObjectDescribeOptions.DEFERRED
option is applied, you can get even better performance. For example:
String typeName = ...;
List<Schema.DescribeSObjectResult> describes = Schema.describeSObjects(new String[] { typeName }, SObjectDescribeOptions.DEFERRED);
DescribeSObjectResult describe = describes[0];
This, surprisingly, has better performance compared with the use of Type.forName
.
-
Keep in mind that
getGlobalDescribe()
also caches its results, so if you need to get describe for many objectsgetGlobalDescribe()
may still be better. I tested the exact breakpoint awhile ago, I think it was about 10 objects (as in,Type.ForName().newInstance().getDescribe()
was about 10x faster), but that was on a live org so YMMV depending on the amount of custom metadata you have. Commented May 13, 2019 at 12:07 -
@IllusiveBrian, yes, indeed, which is why I mention two different caches (static and dynamic). This double caching means you actually get different instances of the describe from the two different approaches. We have a lot of custom fields in our schema, typically extended further by solutions using our package, across a core set of perhaps 8 commonly used object types. For us the "static" schema access is massively better. We were initially using getGlobalDescribe but that was taking over 300ms per request. With the "static" access we are down to 10s of milliseconds instead.– Phil WCommented May 13, 2019 at 12:23
-
1This is great! Thank you, and yes, it probably explains the difference with querying FieldDefinition in CustomMetadata. I guess the "correct" way to fix that problem, then, is to use strings, and run a nightly process to ensure your metadata and CustomMetadata coincide. Commented Aug 18, 2019 at 2:11