**Source URL:** https://general.veevavault.dev/vault-sdk/entry-points/triggers/performance-considerations.md

# Performance Considerations

Triggers should be designed to process records in bulk, especially when making service calls, such as `QueryService`, `RecordService` and `RecordRoleService`. These services are designed to take a list of records or record role changes as input for CRUD operations. It is much more efficient to build a list of record for input and make a single call to these services rather than make service calls one record at a time inside a loop.

Triggers that do not process records in bulk will perform poorly, especially when there are multiple triggers (including nested triggers), execution will likely exceed the maximum elapsed time (100s) or CPU time (10s) allowed. In addition, queries that return large number of records with large number of fields (including fields not used in your code) will likely exceed the maximum memory allowed (40 MB).

Generally, you should never run a query or perform CRUD operations on records in a loop. Each iteration will make unnecessary service calls which can be easily batched to get the same result with a single service call.

## Performance Example {#performance-example}

The following poorly performing code executes a query inside a "for" loop, for each *Product* record in a request. That means when a request has multiple records, like from an API call or bulk update wizard, the `QueryService#query` call is made for each of the records. The only difference between each query is the `WHERE` clause contains a different *Country* reference field value. Performing multiple queries in this case is inefficient and time consuming. A better approach is to make a single query with a `CONTAINS` clause for each *Country* referenced by the *Product* records in the request.

To make performance even worse, as each query is executed to retrieve related records, a `forEach` loop is used to call `RecordService.batchSaveRecords` to save each new *Country Brand* record one at a time. Creating, updating, and deleting records are the most expensive and time-consuming operations. You should always batch records up in a list as input when calling `batchSaveRecords`.

While the better performing code requires more lines of code as illustrated below, it performs much better because it reduces data operations significantly by leveraging the Vault Java SDK's interfaces to process records in bulk.

### Poorly Performing Code: {#poorly-performing-code}

```
@RecordTriggerInfo(object = "product__v", events = RecordEvent.AFTER_INSERT)
public class ProductCreateRelatedCountryBrand implements RecordTrigger {
	public void execute(RecordTriggerContext recordTriggerContext) {

		for (RecordChange inputRecord : recordTriggerContext.getRecordChanges()) {

			QueryService queryService = ServiceLocator.locate(QueryService.class);
			String queryCountry = "select id, name__v from country__v where region__c=" + "'" + region + "'";
			QueryResponse queryResponse = queryService.query(queryCountry);

				queryResponse.streamResults().forEach(queryResult -> {
				Record r = recordService.newRecord("country_brand__c");
				r.setValue("name__v", internalName + " (" + queryResult.getValue("name__v", ValueType.STRING) + ")");
				r.setValue("country__c",queryResult.getValue("id",ValueType.STRING));
				r.setValue("product__c",productId);

				RecordService recordService = ServiceLocator.locate(RecordService.class);
				recordService.batchSaveRecords(VaultCollections.asList(r)).rollbackOnErrors().execute();

			});
		}

}

```

### Better Performing Code: {#better-performing-code}

```
@RecordTriggerInfo(object = "product__v", name= "product_create_related_country_brand__c", events = RecordEvent.AFTER_INSERT)
public class ProductCreateRelatedCountryBrand implements RecordTrigger  {

    public void execute(RecordTriggerContext recordTriggerContext) {

        // Get an instance of the Record service
        RecordService recordService = ServiceLocator.locate(RecordService.class);
        List<Record> recordList = VaultCollections.newList();

        // Retrieve Regions from all Product input records
        Set<String> regions = VaultCollections.newSet();
        recordTriggerContext.getRecordChanges().stream().forEach(recordChange -> {
            String regionId = recordChange.getNew().getValue("region__c", ValueType.STRING);
            regions.add("'" + regionId + "'");
        });
        String regionsToQuery = String.join (",",regions);

        // Query Country object to select countries for regions referenced by all Product input records
        QueryService queryService = ServiceLocator.locate(QueryService.class);
        String queryCountry = "select id, name__v, region__c " +
                "from country__v where region__c contains (" + regionsToQuery + ")";
        QueryResponse queryResponse = queryService.query(queryCountry);

        // Build a Map of Regions (key) and Countries (value) from the query result
        Map<String, List<QueryResult>> countriesInRegionMap = VaultCollections.newMap();
        queryResponse.streamResults().forEach(queryResult -> {
            String region = queryResult.getValue("region__c",ValueType.STRING);
            if (countriesInRegionMap.containsKey(region)) {
                List<QueryResult> countries = countriesInRegionMap.get(region);
                countries.add(queryResult);
                countriesInRegionMap.put(region,countries);
            } else
                countriesInRegionMap.putIfAbsent(region,VaultCollections.asList(queryResult));
        });

        // Go through each Product record, look up countries for the region assigned to the Product,
        // and create new Country Brand records for each country.
        for (RecordChange inputRecord : recordTriggerContext.getRecordChanges()) {

            String regionId = inputRecord.getNew().getValue("region__c", ValueType.STRING);
            String internalName = inputRecord.getNew().getValue("internal_name__c", ValueType.STRING);
            String productId = inputRecord.getNew().getValue("id", ValueType.STRING);

            Iterator<QueryResult> countries = countriesInRegionMap.get(regionId).iterator();

            while (countries.hasNext()){
                QueryResult country =countries.next();
                Record r = recordService.newRecord("country_brand__c");
                r.setValue("name__v", internalName + " (" + country.getValue("name__v", ValueType.STRING) + ")");
                r.setValue("country__c", country.getValue("id", ValueType.STRING));
                r.setValue("product__c", productId);
                recordList.add(r);
            }

        }

        // Save the new Country Brand records in bulk. Rollback the entire transaction when encountering errors.
        recordService.batchSaveRecords(recordList).rollbackOnErrors().execute();
    }
}

```


---

**Previous:** [Data Availability](/vault-sdk/entry-points/triggers/data-availability)  
**Next:** [Record Triggers](/vault-sdk/entry-points/triggers/record-triggers)