Governor Limits

Salesforce being the multi-tenant environment it is, has to ensure resources are available for everyone to ensure everyone has a smooth experience. These limits are monitored per transaction and per customer and include resources such as CPU Time, Memory, Query Runtime, and Query Return Count, among others.

A transaction is a single unit of work. Let's say when you update a record that invokes a trigger, which invokes a flow and an email is sent, will be defined as one transaction.

Unlike Database methods where we could catch errors if and when they happened, Governor Limit Exceptions (errors) cannot be caught using code blocks, and if an Apex transaction hits the Governor Limits, the entire change is rolled back and nothing is committed to the database.

In coffee making terms, the amount of coffee you can brew at any given point is limited by the size of your pot, french press and the number of cups you have available. So if you can brew 4 cups of coffee at a time, the process of making 4 cups of coffee at once will be a single transaction and if you try to make 5 cups of coffee, you probably need to remove the extra ingredients before you proceed to avoid spilling.

Event Limit Method
Total DML Statements 150 limits.getLimitDmlStatements()
Total Records Processed 10,000 limits.getLimitDmlRows
Total SOSL Queries 20 limits.getLimitSOSLQueries()
Total SOQL Queries 100 limits.getLimitQueries()
Max CPU Time 10,000ms (10 seconds) limits.getLimitCpuTime()
HTTP / Web Service Callouts 100 limits.getLimitcallouts()
Max Future Callouts 50 limits.getLimitFutureCalls()
Total Heap Size 6MB limits.getLimitHeapSize()

We can check for current Governor Limits by using the methods for each set. Let's run this code:

System.debug('Total DML Statements: ' + Limits.getLimitDmlStatements());
System.debug('Total Records Processed: ' + Limits.getLimitDmlRows());
System.debug('Total SOSL Queries: ' + Limits.getLimitSOSLQueries());
System.debug('Total SOQL Queries: ' + Limits.getLimitQueries());
System.debug('Max CPU Time: ' + Limits.getLimitCpuTime());
System.debug('HTTP / Web Service Limits: ' + Limits.getLimitCallouts());
System.debug('Max Future Callouts: ' + Limits.getLimitFutureCalls());
System.debug('Total Heap Size: ' + Limits.getLimitHeapSize());

To check for our current usage, we remove the Limit from our methods, so Limits.getLimitDmlStatements() becomes Limits.getDmlStatements(). Let's write a basic program and see it in action

List<Contact> finalList = new List<Contact>();

for (Integer i = 1; i<=10; i++){
	Contact con = new Contact();
	con.lastName = 'Test ' + i;
	finalList.add(con);
}

insert finalList;
System.debug(Limits.getDmlStatements());

The result for this code would be 1, but we did process 10 contacts, right? It's because we are inserting multiple contacts using one DML statement. If we add the con object directly, we will see the number go much higher. Let's run this code:

for (Integer i = 1; i<=10; i++){
	Contact con = new Contact();
	con.lastName = 'Test ' + i;
	insert con;
}
System.debug(Limits.getDmlStatements());

The result for this code is 10 because we ran the insert statement 10 times. This is the exact reason why we don't use DML statements inside a loop. What if we exceed the DML limit? Let's try to insert 200 objects inside a loop.

for (Integer i = 1; i<=200; i++){
	Contact con = new Contact();
	con.lastName = 'Test ' + i;
	insert con;
}
System.debug(Limits.getDmlStatements());

You will get the error:

Line: 4, Column: 1
System.LimitException: Too many DML statements: 151

This is because the moment we exceed our Governor Limits, the program stops and there is no way to safeguard from this error except to write better code. But there are scenarios where you might exceed the limits? Let's say your firm acquired a new business and now needs to insert over 100,000 contacts in the Salesforce org and edit their country field to match org's naming system, and as a developer, you are limited to 10,000 records being processed at a time. For this, we use something called Batch Apex

Batch Apex

Batch Apex is used when we are working with enormous amounts of data that cross Governor Limits. This is achieved by Batch Apex breaking down the entire query into smaller subsets that works in smaller batches and does not affect regular functioning of the org.

In coffee making terms, if you can make 4 cups of coffee at once, but there are 9 people who want to drink your coffee, you'll have to run the process thrice (4+4+1) to get 9 cups of coffee.

To implement Batch Apex, we need to write a class that inherits from Database.batchable interface provided by Salesforce using the implements keyword available to Classes. The class must be global and must implement the start(), execute() and finish() methods.

The start() method is all about Querying and collecting the datasets. The execute() method is all about working on the dataset. The finish() method is about what needs to be done after the records have been processes (Like send an email or create a task).

Let's look at the syntax and then break it down:

global class ClassName implements Database.Batchable<sObject> {
    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
    	//Query
    }
    global void execute(Database.BatchableContext bc, List<P> records){
    	//Process
    }
    global void finish(Database.BatchableContext bc){
	    //Post Processing
    }
}

Class

  • The classes and methods are defined global so they're available throughout Apex. This is different from public that is only available to the specific application.

start

  • The start method takes either a Database.QueryLocator or an sObject that can be iterated over
  • Database.BatchableContext is used to get values like JobID()
  • Returns a Query

execute

  • Database.BatchableContext defines what job are we executing
  • List

    records is the List returned from start()

finish

  • Database.BatchableContext defines the batch job we are waiting to finish

Let's make some coffee before we jump on our org.

global class ClassName implements Database.Batchable<sObject> {
  global List<items> start(CoffeeBatchNumber batchNumber) {
    //Query
    List<items> coffeeItems = new List<items>();
    List<items>.add(arabicaCoffee);
    List<items>.add(frenchPress);
    List<items>.add(water);
    List<items>.add(boilingPot);
    List<items>.add(cup);
  }
  global void execute(CoffeeBatchNumber batchNumber, List<items> itemList){
    //Process
    tableTop = itemList;
    pot = boil(water);
    if (water == boiling){
      frenchPress = brew(coffee);
    }
  }
  global void finish(CoffeeBatchNumber batchNumber){
    //Post Processing
    cup = pour(frenchPress);
    drink;
  }
} 

Now let's write some actual code to better understand what's happening. First let's write our class that implements Database.Batchable. We will leave it on sObject so we are not restricted by the Object we want to use.

global class ClassBatch implements Database.Batchable<sObject>{
}

Now, lets write our start() method that queries for all contacts that have the last name similar to Test:

global class ClassBatch implements Database.Batchable<sObject>{

	global Database.QueryLocator start(Database.BatchableContext bc){
		return Database.getQueryLocator([SELECT ID, LastName from Contact WHERE LastName LIKE 'Test%']);
	}

}

Database.QueryLocator returns a recordset and is used with Batch Apex and takes an SOQL query as it's parameter, and can return 50 Million records at a time. There is a similar method called Database.Query that we look into later in this chapter. Now let's manipulate these records in our execute() method:

global class ClassBatch implements Database.Batchable<sObject>{

	global Database.QueryLocator start(Database.BatchableContext bc){
		return Database.getQueryLocator([SELECT ID, LastName FROM Contact WHERE LastName LIKE 'Test%']);
	}

	global void execute(Database.BatchableContext bc, List<Contact> conList){
		List<Contact> finalList = new List<Contact>();
		for (Contact iterator: conList){
        iterator.firstName = iterator.LastName;
        finalList.add(iterator);
		}
		update finalList;
	}

}

In our execute method, we again call the same Database.BatchableContext job, and declare a new List of contacts that was returned by our start() method. While the first parameter stays the same, the second parameter is always what's returned by the start() method. If we ran a query on Accounts, our execute method would have List<Account> instead of List<Contact>. In the method, we iterate over what's returned by the start method and make the first name the same as last name, and add it to a list before updating our values. Let's write our finish() method now:

global class ClassBatch implements Database.Batchable<sObject>{

	global Database.QueryLocator start(Database.BatchableContext bc){
		return Database.getQueryLocator([SELECT ID, LastName FROM Contact WHERE LastName LIKE 'Test%']);
	}

	global void execute(Database.BatchableContext bc, List<Contact> conList){
		List<Contact> finalList = new List<Contact>();
		for (Contact iterator: conList){
			iterator.firstName = iterator.LastName;
      finalList.add(iterator);
		}
		update finalList;
	}

	global void finish(Database.BatchableContext bc){
		System.debug('Batch successfully executed');
	}

}

Now that we have written our Batch Class, we need to run it. To do that, we make a new object of the Batch class and use the Database.execute() method to run it, and assign it to an ID data type. The syntax for it is:

BatchClassName variableName = new BatchClassName();
ID variableName2 = Database.executeBatch(variableName);

Now let's run our Batch Class:

ClassBatch newBatch = new ClassBatch();
ID batchId = Database.executeBatch(newBatch);

Optionally, we can pass how many records to process in a single batch as a parameter to our Database.execute() method.

ClassBatch newBatch = new ClassBatch();
ID batchId = Database.executeBatch(newBatch, 100);

This will run on 100 records at a time, and the variable batchId contains the job ID of the process. Another great thing about using Batch Apex is every batch is a transaction, so every time a batch runs, it resets the Governor Limits, and since every run is a transaction, if a batch fails, it won't affect other batches.

Database.GetQueryLocator() and Database.Query()

Database.Query allows you to make dynamic SOQL queries in runtime and can return up to 50,000 records.

String runningObj = 'Contact';
String queryString = 'SELECT ID, LastName FROM ' + runningObj + ' WHERE LastName LIKE 'Test' LIMIT 100';

Database.Query(queryString);

Database.GetQueryLocator allows you to run SOQL queries and return up to 50 Million records at a time and is used with batch apex.

This does not mean that Database.Query cannot be used in Batch Apex. This is exactly what iterable<sObject> is used for:

global class ClassBatch implements Database.Batchable<sObject>{


    global iterable<sObject> start(Database.BatchableContext bc){
        String qString = 'SELECT ID, LastName FROM Contact WHERE LastName = \'Test\'';
            return Database.query(qString);
    }

    global void execute(Database.BatchableContext bc, List<Contact> conList){
        List<Contact> finalList = new List<Contact>();
        for (Contact iterator: conList){
            iterator.firstName = iterator.LastName;
            finalList.add(iterator);
        }
        update finalList;
    }

    global void finish(Database.BatchableContext bc){
        System.debug('Batch successfully executed');
    }

}

In the start() method, we now expect a iterable<sObject> return type and are using the Database.query() method to run our SOQL query and the rest of the code remains the same. Now, let's actually build a dynamic SOQL query.

global class ClassBatch implements Database.Batchable<sObject>{

    global String queryString;

    public ClassBatch(String inputString){
        queryString = inputString;
    }

    global iterable<sObject> start(Database.BatchableContext bc){
        return Database.query(queryString);
    }

    global void execute(Database.BatchableContext bc, List<Contact> conList){
        List<Contact> finalList = new List<Contact>();
        for (Contact iterator: conList){
            iterator.firstName = iterator.LastName;
            finalList.add(iterator);
        }
        update finalList;
    }

    global void finish(Database.BatchableContext bc){
        System.debug('Batch successfully executed');
    }

}

A thing to notice here is, we have a method with the exact same name as our Class. This is called an init method, where we initialize a class with parameters. It takes a String as an input parameter and assigns it to an internal global variable which is them used to query. Let's write out execution line:

String queryString = 'SELECT id, lastname FROM contact WHERE lastname = \'test\'';

ClassBatch batchVariable = new ClassBatch(queryString);
Database.executeBatch(batchVariable);

Here we first define our query as a string and then create a new object of the class and pass a parameter. This means that batchVariable is an object of the class with queryString as an init method. This code is 'dangerous' on a longer term because it only iterates over Contact objects, and it's extremely easy to forget that and then run into errors.

Summary

  • Governor Limits are put in place to ensure there's enough computing resources available for everyone.
  • Batch Apex has three methods, start(), execute() and finish() that must be defined.
  • Batch Apex may not run always run immediately, but will run when enough computing resources are available.
Day 15: Batch Apex Test