Apex Describe Performance

Salesforce Apex

Salesforce is a multi-tenant platform, which implies the necessity of certain rules, that are called Governor Limits. They are designed to ensure that all tenants receive a stable performance on a platform with a shared resource database, memory and processing time, so that no tenant can use too much CPU execution time or heap memory or other critical resources. If some tenant used too much CPU time, other tenants might experience performance degradation or even a service disruption on the Salesforce platform since shared resources are used for all the tenants.

CPU execution time is a special Governor Limit, since its usage may vary from execution to execution. The value of the limit is 10 seconds of CPU time for a synchronous transaction and 60 seconds of CPU time for an asynchronous transaction.

So, if a process uses more than 10 seconds of CPU time in a synchronous transaction, then Salesforce forcibly terminates that transaction with a fatal error which cannot be intercepted or recovered, unlike other standard and custom exceptions which can be caught or recovered.

If during deployment or validation, an error occurs due to CPU time overuse, such an error prevents the successful deployment or validation of new functionality to the production. I have experienced such errors multiple times in the project where I was leading a team of up to six Salesforce developers.

In my current project, I lead a team in the joint product of a Salesforce managed package and related iOS application that is developed and supported. This requires using a medatada service running on Heroku, which retrieves metadata from Salesforce instance and transforms it into a suitable form for the iOS application. Every time, when some settings or other metadata is changed on Salesforce instance, a process which is called metadata generation should be started to invoke the metadata service to regenerate the metadata cache used by the iOS application. Sometimes, invalid changes of the metadata led to crashes and failures in the iOS application, so some unit tests were implemented to validate the metadata changes. Generally the unit tests execution may either succeed or fail. When unit tests execution succeeds, this signalizes that everything is OK. When unit tests execution fails, this signalizes that something is wrong. In this particular case, unit test failure is expected to signalize about incorrect metadata configuration. However, when unit tests fail because of Governor Limits exceeding, this doesn’t say anything about correctness of metadata, this only signalizes that there is a problem with unit test performance which should be resolved before a judgement can be made about correctness of metadata configuration. So, the unit tests were expected to fail if the metadata configuration was obviously incorrect. Also, the unit tests were expected to run successfully when metadata configuration is valid and should not cause any failures in business logic execution. However, when the tests designed to validate metadata changes were failing because of a CPU time limit, they weren’t able to confirm or refute the validity of the metadata changes since, in this case, the test failures do not signify incorrectness of the metadata changes, but rather the poor efficiency of the code used for the metadata change validation or the increasing complexity of the metadata used for the package functionality.

Dynamic features available on Salesforce

According to Salesforce documentation, Dynamic Apex enables developers to create more flexible applications by providing them with the ability to access an sObject and its field describe information, in order to access Salesforce app information and to write dynamic Salesforce Object Query Language (SOQL) queries, dynamic Salesforce Object Search Language (SOSL) queries and dynamic Data Manipulation Language (DML) statements. Also, there are Dynamic Visualforce and Dynamic Aura Component Creation features, which allow someone to dynamically create a visualforce component or aura component. However, there is no Dynamic Lightning Web Component creation as I have mentioned in my previous article.

Dynamic DML may refer to the dynamic creation of an sObject record and a dynamic reference to fields, and also another feature which I call flexible DML that allows partial success operations, disablement of duplicate rules execution, field value truncation enablement, manual choice for assignment rule execution for cases and leads, auto response rules disablement and other system email delivery setting configurations.

So, the full list of dynamic Salesforce features available is the following:

  1. Dynamic access to entity and attribute describe information
  2. Dynamic access to tabs describe information
  3. Dynamic SOQL
  4. Dynamic SOSL
  5. Dynamic DML
  6. Dynamic Visualforce
  7. Dynamic Aura Component Creation
  8. No dynamic Lightning Web Component Creation

Dynamic SOQL and SOSL

Dynamic SOQL execution is implemented in Apex by a Database.query method. When using it, a developer can pass string with a query. There are differences in binding variables; it is possible to use only simple bind variables in SOQL query strings. However, this is not really necessary, since the value can be inserted literally into the query string.

The Dynamic SOSL execution is provided by a Search class of System namespace. Method Search.query is used for generic searches by the string query parameter provided. Method Search.find supports SOSL queries which include the “WITH SNIPPET” keyword. It is used to provide search context for articles, cases, feeds and ideas, so that it is easier to find the context of search words. This method returns a Search.SuggestionResults model. Method Search.suggest can be used to find knowledge articles whose names or titles match the search query string. This method can be useful in providing shortcuts to navigate to relevant records before performing a search.

If a developer uses a dynamic query to build a query from user input, the method escapeSingleQuotes should be applied. This helps to avoid both SOQL and SOSL injections, which is a technique that makes an application execute database methods not intended by passing SOQL statements.

Apex DML operations

There are 6 DML operations in Apex:

  • insert,
  • update,
  • delete,
  • upsert,
  • undelete and
  • merge.

Insertupdate and delete correspond to the atomic Create, Update and Delete of CRUD basic operations. An operation upsert performs either an insert or update on the record from the list, based on if a record has an Id already or if it doesn’t. So, upsert is a combined DML operation which performs an insert on the records from the list without an Id and updates the records from the list having an Id populated. Undelete is the Salesforce specific operation of restoring records which have been deleted and that have not been purged from the Recycle Bin. Deleted records are kept for 15 days in the Recycle Bin. After 15 days, they are automatically purged and permanently deleted. However, they can be purged and permanently deleted earlier.  An undelete operation restores deleted records from the Recycle Bin. A merge operation is available only for accounts, contacts and leads. This operation performs an update on the winning record and a delete on the losing records. Also, all the child records are updated and reparented to the winning record.

Dynamic Construction of Record

There are several ways of creating an sObject record dynamically.

The SObjectType.newSObject() method for a SObjectType class in the Schema namespace can be invoked.  It is possible to pass record an Id if a developer needs to construct a dynamically SObject record for an update, delete, merge or undelete operation.

Another option to construct records dynamically is to deserialize the JSON string with related external Id keys populated, which can help to decrease the amount of DML operations as I have described in one of my previous articles,

StaticResource x = [ SELECT Body FROM StaticResource WHERE Name = 
      List<SObject> records = (List<SObject>) 
JSON.deserialize(x.Body.toString(), List<SObject>.class);
      insert records;
      System.debug(LoggingLevel.ERROR, '@@@ Limits.getDmlStatements(): ' + 
Limits.getDmlStatements() );

which used one DML statement instead of five statements used in the original code.

Dynamic Reference to Fields while working with sObjects

Every standard and custom object instance in Apex will inherit from an SObject class, which has methods to retrieve and set values of fields dynamically. I would like to mention also the method getPopulatedFieldsAsMap, which allows someone to get a map of populated field names and values. A question may arise, about why this method is needed if developers can use get and put methods of the SObject class itself to get and set values of fields, and a getSObject and putSObject to get and set parent relationships, and getSObjects to get child relationships?

Without the method getPopulatedFieldsAsMap it is not possible to iterate over all fields set in a record.

Flexible DML methods

By default, all six DML statements work in all-or-none mode. When a chunk of records is processed and a validation rule, trigger or process builder blocks the DML operation on a specific record, the whole chunk fails, so no change is committed to the database because of a DML exception.

However, it is possible to process partial success operations, using the following methods having the boolean parameter allOrNone set to false:

  • insert
  • update
  • upsert
  • delete
  • undelete
  • merge

It is also possible to disable the duplicate rules execution; to enable the field value truncation which prevent errors when the field value exceeds the field size; to choose an assignment rule for cases and leads; to disable or enable auto response rules; and to configure other system email delivery settings configurations by using the methods below that have the dmlOptions attribute.

  • insert
  • update

Dynamic Visualforce

It is possible to dynamically create and display components on the Visualforce page by using a special tag in the Visualforce markup and the related code in the Apex controller. This might be useful to achieve some complicated user interfaces that are difficult or impossible with a standard static markup.

To display the component dynamically, the componentValue attribute of <apex:dynamicComponent> tag should be set to a property which can dynamically receive a standard or custom visualforce component definition by the Apex code in the controller.

Dynamic Aura Components Creation

Methods $A.createComponent() and $A.createComponents() can be used for the dynamic creation of a single component or multiple components. It is recommended to use the <aura:dependency>  tag to increase performance for dynamic Aura components creation. To avoid memory leaks, it is important to manually destroy components created in Javascript by calling the Component.destroy() method.

Unlike the Aura component, a Lightning Web Component does not support the dynamic creation inside the code of a Lightning Web Component, however, it is possible to dynamically create a Lightning Web Component inside a code of the Lightning Aura component.

Dynamic Apex Describe

Many Salesforce projects need Dynamic Apex Describe methods to get the metadata of entities and attributes to pass the entity name or column name to the method as a string.

Method Schema.getGlobalDescribe() is used to get a map with describe information about every single object in the organization.

Method Schema.describeSObjects is used to retrieve describe information for a specified list of objects. Sometimes this is not used efficiently.

Method Schema.describeTabs is used to get the list of tabs grouped by app. Salesforce app is just a collection of tabs.

Apex Describe Performance Issue

Almost a year ago I was investigating a performance issue related to describe methods.

There was a method which was implemented to get a SObjectType describe information by object name.

public Schema.SObjectType getSObjectType(String objectName) {
   return Schema.getGlobalDescribe().get( objectName );

This was inefficient, since the method Schema.getGlobalDescribe() has a very heavy performance on an organization with many custom and standard objects, custom settings, custom metadata definitions, external objects and big objects. If this method is called several times, either inside of a loop or by other means, the full map is built under the hood which takes a lot of CPU time. If describe information for several objects is needed, it is more efficient to use another option to get the describe.

public Schema.SObjectType method2(String objectName) {
   return ((SObject)Type.forName(objectName).newInstance()).getSObjectType();

In this implementation, a full map is not built for every SObject reference that’s why performance is much more efficient.

Now I am working on another project in which another approach is used to derive an object type.

public Schema.SObjectType method1(String objectName) {
   return Schema.describeSObjects(
      new List&amp;lt;String&amp;gt;{objectName}

The method Schema.describeSObjects is working faster than Schema.getGlobalDescribe(), however, it still takes a lot of CPU time if this method is called inside a loop or called multiple times.

I performed an analysis and found out that if method1 is called for every object in a sandbox on a new project, it is 12 to 18 times less efficient than method2; if it is called for every object.

Method  Time Method1ExecutedFirst Performance bust
Method1 7,800 No 17.93103448
Method2 435 No
Method1 6,073 No 13.00428266
Method2 467 No
Method1 6,134 No 13.48131868
Method2 455 No
Method1 6,340 No 11.98487713
Method2 529 No
Method1 6,311 No 13.57204301
Method2 465 No
Method2 440 YES
Method1 6,598 YES 14.99545455
Method2 413 YES
Method1 6,377 YES 15.44067797
Method2 458 YES
Method1 7,112 YES 15.52838428
Method2 418 YES
Method1 6,358 YES 15.21052632
Method2 454 YES
Method1 6,528 YES 14.37885463

Also, if getSObjectType is refactored to cache the results of method Schema.getGlobalDescribe()

public static Map&amp;lt;String, SObjectType&amp;gt; gd = Schema.getGlobalDescribe();
public Schema.SObjectType method0(String objectName) {
   return gd.get( objectName );

then it would behave even more efficiently than method2.

So, generally, the following approaches should be followed.

  1. Methods Schema.getGlobalDescribe() or Schema.describeSObjects should be called, at most, once per an Apex Transaction. Also, it doesn’t make sense to use both of these methods in the same transaction.
    These methods should never be called inside a loop or repeatedly.
  2. If a developer needs the describe information for every object or for almost every object in the organization, the method Schema.getGlobalDescribe() should be used and its results should be cached and reused instead of calling the method Schema.getGlobalDescribe() again.
  3. If a developer needs the describe information for many objects and the list of these objects is known in advance, the method Schema.describeSObjects() should be used with a parameter of the list of object names for which describe information is needed and its results should be cached and reused instead of calling method the Schema.describeSObjects() again.
  4. If a developer needs describe information for a few objects or for a list of objects which are unknown at the start of the transaction, a custom approach defined in method2, listed above, should be applied as it is the most efficient way to obtain the describe information needed for unknown objects. Also it makes sense to cache the results in a map.

I hope this article is beneficial for both beginners and experienced developers. First of all, if a reader has never used dynamic Apex features, it is always good to try and explore more options in order to achieve the desired results. All of the dynamic Apex features are designed to help developers meet complicated and challenging customer needs. Also, it is always worth to know how to get the best performance using dynamic features.

Have a project in mind and not sure how to get started?
Take the first step.
Back to overview