I recently got an opportunity to work on an effort related to Triggers/Flow refactoring. The trigger on an object has grown so much that it was almost impossible to put any new logic involving SOQL or FOR loops. The issue was governor limits around the number of SOQLs & CPU time limit. There were n numbers of problem with the trigger ranging from recursion , redundant DMLs , redundant SOQLs, unnecessary for loops etc. There was one more problem that the trigger has grown like ginger over the course of continuous development in almost a decade. One thing to keep in mind that when there is lot of logic then the SOQL and CPU time consumption becomes a SEE-SAW game. if you try to reduce SOQL then CPU time consumption will increase and if you try to decrease the CPU time consumption then the number of SOQLs would increase. So there has to be a balance between these too and we should refrain ourselves from going to extreme by focusing on only one of these problems.
The major culprit was redundant DMLs , as it run the trigger again and again.
There is a way to quickly check what part of code is taking lot of time and then we can focus on refactoring that part before some of the others. For this we can use log inspector in salesforce dev console. https://help.salesforce.com/s/articleView?id=sf.code_dev_console_view_system_log.htm&type=5
Other Issues found with the trigger ::
- Various functionalities were implemented by using there own individual methods and there was a ‘FOR’ loop on individual methods to iterate on a list of trigger.new.
- No or very less use of aggregate queries : aggregate queries limit is separate from the SOQL limits
- Multiple SOQL statements on same object in a particular context (eg: multiple queries in before or after trigger context).
- Queries on Custom settings.
- Workflow and record triggered flows firing the record updates multiple times and causing trigger to fire multiple time.
The below helped in refactoring:
- Single FOR loop at the handler level and create the methods to process individual records instead of bulk records.
- Use aggregate queries to query parent and children together
- Consolidate the SOQL queries and instead of filtering records via query , put additional filtering in the Apex code (based on the scenario)
- Execute different logic very condition specific . Don’t run logic everytime the trigger is fired.
- If you have very complex logic built then it may not be a good idea to spread the logic across various automation tools. I recommend using only Apex trigger instead of combination of record trigger flow/Workflow/Apex trigger. However, you can use flow along with apex trigger only for requirements like sending email, time based action, outbound message etc. Don’t use the flow for synchronous record update.
- If you have async apex logic called from the trigger then consolidate that logic to fire only from the after trigger and at the end.
- Unnecessary Loop inside loop should be avoided. Use your Map collection efficiently.
- Code reviews should be done with a mindset of the efficient utilization of the platform.
- If you have less data to fetch from custom setting then avoid SOQL . SOQL might be required if the custom setting has large number of data to filter records without doing additional FOR loop to filter in the apex code.
- Use async context via future/queueable implementation to do the rollups and update on other objects.