Friday 14 February 2020

Before Save Flows and Apex Triggers Part 2

Before Save Flows and Apex Triggers Part 2

TL;DR Flows consume CPU time, but you might not find it easy to check how much.

Introduction


A couple of weeks ago I wrote a blog post about before save flows and triggers. It's fair to say that this caused some discussion in the Salesforce community. Salesforce themselves reached out to me for more information, as the results weren't what they were expecting to see, so I sent them details of the various tests that I'd run. The funny thing was, when I set out to write the post the CPU times were intended to be secondary, the message from the post was supposed to be about being aware of the impact of mixing tools and thinking about what is already present when adding new automation. So that went well.

Since I wrote the post I've been testing in pre-release, scratch and sandbox orgs, with varying log levels, and every time the results came out much the same. Some differences in the absolute numbers, but always flows using a very small amount of CPU.

I then got a comment on the original post from the legendary Dan Appleman, which included details of the test that he'd run.  Here's the key part of it:
This is inserting the the opportunities using anonymous Apex, with a Limits.getCPUTime statement before and after the insert, with the debug log captured at Error or None level.
The difference for me was that I wasn't outputting any CPU time during the transaction, I just had the debug levels turned all the way down and looked at the final CPU total from the cumulative limits messages:


mainly because whenever I've tried to debug CPU time usage in the past, I've found the results questionable.

That did mean my setup/teardown code was included in the calculation, but as that was the same in all cases I figured while it might change the exact number, the differences would still stand out.

Logging the CPU Time

Adding the following statement to my anonymous apex before the insert:

System.debug('Consumed CPU = ' + LoggingLevel.ERROR, Limits.getCPUTime());

had no effect - I was still seeing around 100 msec for the flow, and around 1500 msec for the equivalent trigger. The log message gave the same CPU time as the cumulative limits message.

Adding it after the insert, and suddenly everything changed - the CPU usage was logged as 2100
msec and the cumulative limits messages had the same value. So flows were consuming CPU time, it just wasn't being surfaced without some additional logging being present. This was a real surprise to me - while figuring out CPU time is always a little fraught, I've not had cause to doubt the cumulative limits before now. Note that this only had an impact in flow-only scenarios, with Apex it didn't change anything materially.

I then changed my anonymous apex so that the entire test was in a loop, and started logging at the CPU at various times. Regardless of when I did it, the cumulative CPU time message always reflected the value from the last time I logged it. So if I only logged the first time through, the cumulative would show as 2100, if it was the second time through, the cumulative would go up to 3500 and so on.

Based on this testing it appears that if there is no additional apex automation (triggers) then the act of accessing the CPU time sets that value into the cumulative limit tracker. I verified this by changing the log messages to just capture to a local variable, and the cumulative limit messages continued to show the actual CPU rather than a very small number.

In Conclusion


Flows do consume CPU time. They don't have a pass that excuses them from CPU duty and there isn't a conspiracy to drive everyone away from Apex. In my cursory testing with simple flows and triggers, triggers were around 33% faster - I intend to do some more testing to try to get some better figures, but right now I need to look at something else for a change!

Getting an accurate report of how much isn't as straightforward as it appears, and figuring out the impact in a transaction which doesn't involve any Apex may present a challenge.





Saturday 8 February 2020

Spring 20 Default Record Create Values

Spring 20 Default Record Create Values


Introduction


Spring 20 introduces something rather reminiscent of URL hacks - the capability to provide default values when creating a record via URL parameters.  Not quite the same though -  for one thing this is a supported mechanism, whereas we were constantly warned against using URL hacks in Classic by Salesforce. For another, this is currently only documented as working on record create, whereas URL hacks worked (although wasn't supported, might not work in the future etc) across most of the later Classic pages.

How It's Done


The first thing you need is the URL to the create page - this takes the form:

https://<salesforce_instance>/lightning/o/<object_api_name>/new

e.g. for Account this is :

https://<salesforce_instance>/lightning/o/Account/new

While for a custom object with an api name of Webinar__c it is:

https://<salesforce_instance>/lightning/o/Webinar__c/new

Next, you need the parameter the defines the defaults:

?defaultFieldValues=
and then the list of name=value pairs for the fields - again, these are the API names, so the record name would be:

Name=Test
while a custom field would have the API name, including the __c suffix:

Description__c=Test+record+for+blog
(note that I've used '+' to indicate a space in the query part of the URL - I could just as well have used '%32')

Multiple Values

To add multiple default parameter values, use the comma ',' character to separate them - don't use '&' as this will indicate the defaultFieldValues parameter has finished and a new parameter is starting!

Name=Test,Description__c=Test+record+for+blog,

All Together Now


Putting all the elements identified above, I have the following URL:


https://kabdev.lightning.force.com/lightning/o/Webinar__c
/new?defaultFieldValues=Name=Test,
Description__c=Test+record+for+blog,Planned_Duration__c=60

Entering this in my Spring 20 org takes me to the record create page for the Webinar custom object, with the defaults pre-populated:



Relationship Fields

 MyWebinar__c custom object has a lookup to a custom survey object, with the relationship field named Survey__c. When entering information on the create page, I put the text name of the field and choose the entry from the list, so it's tempting to specify the name of the record in the URL. That doesn't end well:



Leaving aside the frankly hilarious idea that I could take an internal error id from Salesforce to my administrator and that would help in any way, it clearly doesn't like the text. This is because the relationship field needs an ID, so if I specify one of those it populates correctly.



Out of curiosity I tried an non-existent ID to see if the error message is any more helpful - it is, and tells me that it can't find the record that the ID refers to, which is much better.


I must also say that in both error cases I love that the plucky Save button sticks around even though things have gone badly wrong. Clicking it doesn't do anything, in case you are wondering.


Related Posts