In my last post some testing showed how Apex class deploy times are proportional to the size of code in the class being deployed and all its dependant classes. If you read this alone you might think that best practice would be to always write very small classes. What we also have to consider though is what impact that will have on our customers experience. In this post I am going to focus on one aspect of this, what happens to response times when there are invalid classes in an org.
Lets start with the headline result:
This graph is showing how long it takes to recompile classes of various sizes once they have become invalid. By re-compile here I really mean the process that converts an invalid class to a valid one on-demand as some execution context attempts to use it. I think it’s fair to assume this a recompile of the class source code into the byte code format used by Salesforce given we have a ‘Compile All Classes’ link that appears to do exactly this operation.
As you can see there is fairly high initial cost of ~20ms/class + a variable component depending on class size. The time here is measured as the additional time needed to make an anonymous Apex request when that request uses classes which are invalid. So if the request required ten 8 kB sized classes to be recompiled we would expect the request to take 0.5seconds longer than if those classes were not invalid. You could also likely time this on the platform as well as calling Type.forName on a class with causes it to become valid but I am using the Tooling API for tests so timing some Anonymous Apex that will invoke a method on the class is more convenient.
To calculate this I used the same binary tree of classes approach from this post but made a few adjustments. When the tree is initially constructed all the classes are in a valid state, to invalidate part of the tree the test updates all the leaf node classes which causes all non-leaf node classes to become invalid. The test then times an anonymous Apex call to the root node class which will in turn call methods on all classes in the tree requiring any invalid classes to be recompiled. To get the additional cost of the re-compile I ran the test with and without the code that invalidates part of the tree and subtracted the results before dividing by the known number of invalid classes in the tree. The number reported is geometric mean of 10 runs.
This was the second test I ran, the earlier test showed that the cost to recompile invalid classes appears to be linear to the number of invalid classes. I tested that up to ~512 invalid classes so there is a possibility that the linear relationship will break down a bit after this. I wasn’t really expecting this to show anything interesting but you never know.
Ideal size & compile on deploy
This result does leave us with a bit of a problem. We would really like to have class size driven by ‘clean code’ considerations, not the implementation details of Salesforce orgs, but that may well hurt either our productivity via excessive class deploy times or our user experience via poor responsiveness depending on what turns out to be a typical class size for the type of product we are developing.
Salesforce have recently introduced ‘Compile on deploy’ support to try and address the responsiveness side of this problem. My experience with this so far has been pretty disastrous, if enabled developer deploys for single classes can go from 30 seconds worst case to many minutes. Worst still, on some org types you don’t have the ability to disable via Apex Settings, it’s locked on.
The positive side of compile-on-deploy is it should mean that on customer orgs the cost of invalidation recompiles is not important anymore and so using the smallest possible classes looks desirable to improve developer experience. There is however another fly here, the class caching that is used to improve the runtime access to recently used classes. Studying caching behaviour is always difficult but maybe I can find a way to look at what is happening to get a more complete picture.
Inner classes to the rescue?
Thinking about how these result might change my coding style it feels obvious that my own best interest is served by making classes as small as possible in an attempt to reduce class update times to the minimum possible to improve my own productivity. Doing that though may well cause issues with system response times depending on the compile-on-deploy setting and class caching behaviours.
Perhaps a way around this is write outer classes as though they were inner classes (i.e. no inner classes or statics). That would allow for a very easy route to automatically convert a set of outer classes into inner classes which can be combined together to reduce the class count needed. In a DX project where we can at last use directories to organise code you could place all the related classes together and auto-merge them for packaging. Just a thought…