Welcome back for another Flow benchmarking post. This time the relative performance of querying every field in a 100 field, 300 field, and 500 field custom object using a “Get Records” in a flow is done.
# of Columns | No Automation Average | 10 of N Fields Selected Average | All Fields Selected Average |
100 | 247.1 ms | 661.8 ms | 2592.7 ms |
300 | 657.5 ms | 1265.0 ms | 6867.8 ms |
500 | 1074.3 ms | 1830.5 ms | 11,364.8 ms |
Let me start off with saying that these numbers shouldn’t be taken to mean that N columns being queried take X time. It’s more like X query time plus Y overhead. Also, the “All Fields Selected” used an invocable apex action to force the Flow to query all fields and that’s an extra step compared with the “10 of N fields” which doesn’t use the apex invocable action.
Observations
- Inserting 200 records with all 500 custom text(255) fields took longer than expected. I thought maybe 500 milliseconds but took just over a second on average.
- As the number of columns increase, the performance in each case is increasing linearly. I thought it would’ve increased faster but glad it doesn’t.
- Selecting all the fields takes a lot more time than just the manually selected ones, especially when invoking Apex that does nothing.
Get Records Guidelines
- Use “Automatically store all fields” as the default option but only if it doesn’t get passed into a sub-flow or apex action which causes it to have all fields queried.
- Don’t pass records from “Get Records” to sub-flows or apex actions. Pass record ids instead so that the sub-flows and apex actions can query the information needed. This leads to better reuse because it has a very loose dependency on the calling flow. If the calling flow has to know which fields the sub-flow or apex action needs, it makes maintenance harder since if the sub-flow or apex action needs more fields, everything that uses it know has to query the additional fields.
Methodology
A custom object was created for each scenario. 9 in total. Each object was created with N custom text(255) fields. Execute anonymous code was used to insert 200 records with every custom text field filled with text. Limits.getCPUTime was executed before and after the insert statement and then used to calculate the total insertion time. 10 run records were then inserted in the “Benchmark Results” object.
The “No Automation” objects contains no automation whatsoever. It was used to establish a baseline for inserting 200 records with more and more fields as the objects got larger.
10 of N Fields Selected. 10 fields from Objects with 100, 300, and 500 fields were queried using a “Get Records” where 10 text fields were manually selected to be queried. This was done in an after-insert flow where the inserted record was queried by its Id in the “Get Records”.
All Fields Selected. All fields from Objects with 100, 300, and 500 fields were queried using a “Get Records”. That was done by using the “Automatically store all fields” and passing that variable to an Apex Invocable action whose input were those records. The Apex Invocable Action doesn’t do anything and is used solely for causing all the object’s fields to be queried. This was done in an after-insert flow where the inserted record was queried by its Id in the “Get Records”.
100 Fields
Baseline No Automation Performance
Run # | Time (Milliseconds) |
1 | 218 |
2 | 252 |
3 | 261 |
4 | 217 |
5 | 211 |
6 | 283 |
7 | 261 |
8 | 265 |
9 | 268 |
10 | 235 |
Average: 247.1 milliseconds
Select 10 of 100 Fields Performance
Run # | Time (Milliseconds) |
1 | 716 |
2 | 659 |
3 | 693 |
4 | 597 |
5 | 649 |
6 | 769 |
7 | 591 |
8 | 579 |
9 | 577 |
10 | 788 |
Average: 661.8 milliseconds
All 100 Fields Performance
Run # | Time (Milliseconds) |
1 | 2201 |
2 | 2400 |
3 | 2523 |
4 | 2671 |
5 | 2575 |
6 | 2634 |
7 | 2218 |
8 | 3118 |
9 | 3148 |
10 | 2439 |
Average: 2592.7 milliseconds
300 Fields
Baseline No Automation Performance
Run # | Time (Milliseconds) |
1 | 620 |
2 | 706 |
3 | 597 |
4 | 581 |
5 | 716 |
6 | 613 |
7 | 678 |
8 | 582 |
9 | 786 |
10 | 696 |
Average: 657.5 milliseconds
Select 10 of 300 Fields Performance
Run # | Time (Milliseconds) |
1 | 1384 |
2 | 1225 |
3 | 1272 |
4 | 1254 |
5 | 1223 |
6 | 1156 |
7 | 1246 |
8 | 1301 |
9 | 1147 |
10 | 1442 |
Average: 1265 milliseconds
All 300 Fields Performance
Run # | Time (Milliseconds) |
1 | 7606 |
2 | 6619 |
3 | 6596 |
4 | 6600 |
5 | 7266 |
6 | 7071 |
7 | 6713 |
8 | 6807 |
9 | 6843 |
10 | 6557 |
Average: 6867.8 milliseconds
500 Fields
Baseline No Automation Performance
Run # | Time (Milliseconds) |
1 | 1057 |
2 | 980 |
3 | 945 |
4 | 1247 |
5 | 1079 |
6 | 1137 |
7 | 1065 |
8 | 989 |
9 | 1029 |
10 | 1215 |
Average: 1074.3 milliseconds
Select 10 of 500 Fields Performance
Run # | Time (Milliseconds) |
1 | 1740 |
2 | 1986 |
3 | 1752 |
4 | 1641 |
5 | 1631 |
6 | 2028 |
7 | 2044 |
8 | 1700 |
9 | 1824 |
10 | 1959 |
Average: 1830.5 milliseconds
All 500 Fields Performance
Run # | Time (Milliseconds) |
1 | 10,930 |
2 | 12,133 |
3 | 10,535 |
4 | 12,114 |
5 | 10,790 |
6 | 11,087 |
7 | 12,167 |
8 | 11,326 |
9 | 10,776 |
10 | 11,790 |
Average: 11,364.8 milliseconds
Next Steps / Further Research
- Update the Benchmarking Package to include this work.
- Benchmark overhead simply invoking Apex from a flow. I wonder how much of the Select All performance is attributable to the apex invocation.
- Do the Select All by manually selecting all the fields. I’m not doing that by point-n-click. I suspect I’ll have to download the flow metadata using a tool, update the raw metadata using some code to specify all the fields to query, and then save it back to the org.
What are thoughts??
Luke, this is great research, and I’m glad that Adam White posted a link to this from UnofficialSF.com.
I’d like to encourage you to take this one step farther. What conclusions do you draw from this data? Do you have any recommendations as a result of it?
Alex,
Thanks for the feedback.
My recommendations were put in the guidelines section. In short, use the “Automatically store all fields” option by default. Also, don’t use “Automatically store all fields” to pass to a sub-flow or apex action because it’ll query all fields so pass record ids instead.
It would be awesome if the Salesforce documentation was updated to explain how the option worked in more detail.
I don’t understand the recommendation to “Automatically store all fields” by default and why it’s not to select the fields you need instead. The #’s in the tests here seem to support only selecting the fields needed throughout the whole process first time vs all. From what I understand the flow view state is also affected when we select more fields which can lead to the “Interview too large to save” message.
I think you also got tripped up by the “Automatically store all fields” setting’s name. “Automatically store all fields” only queries the fields used in the flow and not every field on the Object. From what I’ve seen, the only exception is that every field will be queried if the Get Record’s record(s) are passed to a sub-flow or an Apex Action as input. That’s how I was able to test querying these very large custom objects in this post.
Based on that, “Automatically store all fields” should be the default since the engine will query the fields it needs and reduces build time / maintenance for the flow builder AND one should only pass record ids to a sub-flow or apex action. The sub-flows or apex actions should query the fields they need and not require the caller to know which fields to pass to it.
Hopefully that clears it up.