Salesforce’s Summer ‘24 release introduced Apex cursors (Beta), a powerful and revolutionary feature that enables developers to efficiently process large SOQL query results within a single transaction. Unlike batch Apex, which can be limiting in certain scenarios, Apex cursors offer greater flexibility and power. By breaking down query results into smaller, manageable chunks, cursors allow developers to work with extensive datasets without exceeding platform limits.
Cursors have the ability to navigate query result sets incrementally, both forward and backward, without returning the entire result set at once. This feature is particularly useful for handling resource-intensive jobs, offering flexibility in processing data while staying within platform limits. Apex cursors provide an alternative to batch Apex, addressing some of its limitations and offering enhanced capabilities such as chaining with queueable Apex jobs, making them a game-changer for high-volume data processing in Salesforce.
Why Apex Cursors?
Apex cursors are essential for efficiently handling large data sets in Salesforce, allowing developers to fetch records incrementally from SOQL queries. These cursors are stateless and enable the processing of results from a specified offset position, making it crucial to track these offsets during operations. The Database class provides methods to create and manage cursors, ensuring developers can work with vast amounts of data while adhering to platform limits and handling exceptions effectively. Below are key methods associated with Apex cursors, each serving a distinct purpose in the data retrieval process.
Key Methods
Database.getCursor(String queryString)
Accepts a query string as a parameter and returns a Database.Cursor. This method initializes a cursor based on the specified query, facilitating data retrieval.
Cursor.fetch(integer position, integer count)
Retrieves data starting from a specified offset (position) and fetches the defined number of records (count). Since the cursor is stateless, it’s important to track the offset to manage data retrieval effectively.
Cursor.getNumRecords()
Returns the total number of records that the query will yield. This information is crucial for planning the data processing strategy and determining if further processing is necessary.
Database.getCursorWithBinds(String queryString, Map<String, Object> bindVariableMap)
Enables the execution of dynamic queries by accepting a query string and a map of bind variables as key-value pairs. This method allows developers to replace dynamic variables within the query string, enhancing flexibility in data retrieval.
Apex Cursors Limit Methods and Exceptions
To obtain Apex cursor limits, utilize the new methods available in the Limits class, specifically Limits.getApexCursorRows() and its upper bound Limits.getLimitApexCursorRows(), as well as Limits.getFetchCallsOnApexCursor() and its upper bound Limits.getLimitFetchCallsOnApexCursor().
Apex cursors can also throw two new System exceptions: System.FatalCursorException and System.TransientCursorException, with the latter allowing for transaction retries. Additionally, it’s important to note that Apex cursors share the same expiration limits as API Query cursors.
These Apex governor limits have been adjusted to accommodate the new feature:
- Maximum number of rows per cursor: Increased to 50 million for both synchronous and asynchronous cursors.
- Maximum number of fetch calls per transaction: Limited to 10 for both synchronous and asynchronous fetch calls.
- Maximum number of cursors per day: Capped at 10,000 for both synchronous and asynchronous cursors.
- Maximum number of rows per day (aggregate limit): Set to 100 million.
Advantages of Using Apex Cursors
Efficient Asynchronous Processing for Large-Scale Data Sets
One of the primary benefits of Apex cursors is their capability to be utilized in a series of queueable Apex jobs, allowing for efficient handling of large-scale processing tasks in an asynchronous manner. Apex cursors enable quicker iteration through large datasets by processing data in manageable chunks instead of loading everything simultaneously. This results in improved execution times for your Apex code that employs cursors.
Optimized Memory Usage
Large SOQL queries can yield substantial datasets that can overload memory resources. Cursors address this issue by fetching data in smaller, manageable batches, thereby reducing the amount of data loaded into memory at once. This not only conserves resources but also boosts overall system performance.
Below are sample code snippets that help in understanding how Apex cursors work.
Database.Cursor locator = Database.getCursor('SELECT Id FROM Contact WHERE CreatedDate =
LAST_N_DAYS:30');
List<Contact> lstContacts = locator.fetch(0,10);
System.debug('No of Records===>'+locator.getNumRecords());
System.debug('lstContacts==>'+lstContacts);
This code snippet retrieves a list of Contact records created in the last 30 days using a Database.Cursor which efficiently processes large query results by fetching them in smaller batches.
- Database.getCursor(): This method initializes a cursor for the SOQL query, selecting the Id of contacts created within the past 30 days.
- locator.fetch(0, 10): This method retrieves up to 10 Contact records from the cursor, starting from the first record. It allows for fetching small, manageable portions of the larger result set.
- locator.getNumRecords(): This method provides the total number of records the cursor will retrieve, even though it only fetches a subset at a time.
You can execute the code snippet in the Execute Anonymous window.
In this example, the ‘LeadsQueryChunkingQueuable’ class retrieves and updates leads in batches of 200. After each batch is updated, it checks if additional records remain to be processed. If so, it queues another job to handle the next batch, repeating this process until all records are updated.
public class LeadsQueryChunkingQueuable implements Queueable {
private Database.Cursor locator;
private integer position;
public LeadsQueryChunkingQueuable()
{
locator = Database.getCursor('SELECT Id, Name FROM Lead WHERE CreatedDate = LAST_N_DAYS:30');
position = 0;
}
public void execute(QueueableContext qc)
{
List<Lead> lstLeads = locator.fetch(position, 200);
position += lstLeads.size();
for (Lead objLead : lstLeads) {
objLead.Description = 'Test Lead update';
}
Database.update(lstLeads);
if(position < locator.getNumRecords() )
{
System.enqueueJob(this);
}
}
}
Logic Implemented in LeadsQueryChunkingQueuable
The Database.getCursor() method is used to create an instance of LeadsQueryChunkingQueuable for processing large datasets in a batch-oriented manner. This method requires a SOQL query as input. In this example, the query is designed to retrieve all leads whose CreatedDate falls within the past 30 days.
Within the execute() method of the LeadsQueryChunkingQueuable class:
Fetch a Batch
The locator.fetch(position, count) method is used to retrieve a batch of 200 leads at a time. The position parameter acts as a pointer, keeping track of the current position in the dataset to ensure incremental processing.
Check for Remaining Data
After processing the current batch, the code checks if there are more records to be processed. If there are more than 200 remaining records, a new instance of LeadsQueryChunkingQueuable is enqueued to continue processing the remaining data in subsequent batches.
Summary
This article has hopefully helped you understand how to utilize Apex cursors for segmenting and processing large cursor results across multiple queueable jobs. By doing so, it helps enhance performance and manage large datasets more effectively.
Make sure to leave your feedback in the comments below!