Introduction
We were lucky enough to be invited by our internal Performance, Stability, and Reliability (PSR) team to review a new document that provides recommendations on tuning your Groovy scripts to ensure optimal run-time performance. The document was written by performance expert Richard Ver Steeg and is based on a combination of his own testing and from real problem analysis. We will publish the entire original document as a whitepaper shortly.
In this series of posts we'll go through the content of the document, highlighting the key messages and offering additional explanation and illustrative examples.We are breaking the series into the following articles, so if you don't see your area of focus in this one, then it should be covered later in the series. All articles will be prefixed with "Groovy Performance Series:" and be tagged with the keyword groovyperf.
- Application Composer and Groovy Performance Tuning - An Overview (this article)
- Optimizing Database Queries In Your Groovy Scripts
- Using Aggregation Functions In Your Groovy Scripts
- Instantiating View Objects In Your Groovy Scripts
- Avoiding Threshold Errors In Your Groovy Scripts
- Using Triggers and Validation Effectively In Your Groovy Scripts
Developing With Performance In Mind
Let's first consider why a set of guidelines is important for developing on the Oracle Sales Cloud platform. Review these factors carefully as some may unearth other steps you can take to ensure successful customization and integration projects.
- The flexibility of the development platform allows for creation of artifacts to meet business requirements with as few limitations as possible, and therefore no inherent performance consideration.
- The users of Application Composer may not have a background in application development and be unfamiliar with underlying aspects that may affect run-time performance.
- Informal QA and minimal testing of these typically small customization projects may mean production usage is not fully replicated. Similarly small customization tweaks and scripting projects are often subject to less rigorous review.
- The Cloud-based deployment passes most application maintenance and monitoring responsibilities over to Oracle, and as such there is an assumption that this includes all aspects of performance.
So whilst we are not suggesting you need full-blown IT projects for your small customizations, this article series should make you more aware of the potential impact of even small adjustments and justify the addition of appropriate user training, project reviews, and solution testing.
Recommendations
The following short section covers many of the most prevalent and high-impact areas where Applications Composer customizations can affect performance. We've grouped similar considerations and kept the content relatively brief for easy-of-use, where dedicated articles will go into more depth.
Custom Fields
We recently worked on a proof-of-concept app for an Oracle partner, implemented entirely in Application Composer. The objective was to capture information from users and call internal web services to create various records and make appropriate updates. Clearly as a POC this never made it out of a sandbox, however we did note that during development we created many custom fields. In addition to those required to capture data, some we had to create more than once because we found limitations or better implementations for our original field types. Again it did not matter too much in this case, however without support for the deletion of fields care should be taken when replicating the design into production, as the larger the number of fields on an object the more performance may be impacted.
In addition to field volume, the types of fields and their structure is a consideration too. If many of your custom fields have groovy expressions that set the default values, or are complex types like dynamic choice lists or long texts (CLOB) then their use will carry some performance overhead.
If you have many obsolete custom fields or significant numbers that are not displayed on the User Interface, then you might wish to contact Oracle Support regarding the availability of a patch for ER 15874819 for your release. This decouples the fields from the UI rendering process and has been seen to improve performance. Also to help reduce the overhead of large numbers of complex fields please ensure only those displayed are associated with the UI Pages, and that any groovy scripts that run do not reference fields that are not used.
Logging
When the Run-Time Messages feature of Application Composer is enabled then all println() calls in your Groovy scripts write to the user/session log. When you do not need this for debugging it is easy to assume that just unchecking the box on the page to disable it is enough. For some cases seen there was enough manipulation of the log output (often associate with using String functions) in the code before the final println() call that this was affecting performance. As such it became clear that turning off the logging alone is not sufficient.
The recommendation is to move your log-related code to Global Functions, against which calls can be either commented out once debugging is complete, or can contain a condition to prevent execution when not required.
Related to effective logging output, you can write to the log timestamps (using the now() function) on either side of your more complex code sections, recording the execution time. This is useful for reviewing the time taken for potentially slow moving parts like API calls, database queries, and code integration/loops.
Events
When tuning your solution or identifying causes of performance problems look at what scripts get triggered at what point. A dedicated post will look at this in details also.
For example, if a page is slow to load then look for scripts inside formula fields, conditionally updatable fields, conditionally required fields, or object/global functions that are getting executed.
Loops
It was noted that whilst most discrete operations are generally very fast, in some cases of excessive repetition in loosely constructed FOR and WHILE loops a degradation of performance was seen.
The recommendation is to use conditions to ensure your code runs only when absolutely necessary, reducing the performance. Here are two examples of checking for a new record and if a field was changed.
IF (getPrimaryRowState().isNew()) { ...IF (isAttributeChanged('Status_c')) { ...
Another tips is to reducing the number of iterations required by narrowing down the data upon which is operating, such as a applying a view criteria on a VO result set. Of course exit the loop once you get what you need, again reducing unnecessary iterations.
As mentioned above, implementing logging can you understand what your loop is doing during debugging.
Data
As the document says there is "One Tip to Rule Them All – Always Query and Fetch the Fewest Possible Rows from the Database.". We'll cover this aspect in additional posts, however suffice to say when you are writing scripts that create new instances of View Objects using the newView() method then always make sure you are including the equivalent of a where clause (findbyKey() or using a newViewCriteria) and where possible you are specifying precisely what column/field names you wish to return.
Consider this, if this were SQL then you'd expect a performance hit from issuing a "SELECT * FROM MY_TABLE" with no criteria resulting in a full table scan.
In addition, testing properly will allow your to better understand your data set, including the use of functions such as row counts as described in this post.
API's
Generally speaking calling an API's is expensive and care should be taken to test for performance issues. Clearly this applies mainly to using web service calls, however also consider that API's are not only external - they can be any exposed function, such as the getAttribute() call used on a VO Row of data.
As such be careful when using API function calls inside loops, and try to structure your code around this. Again logging is useful when understanding the time taken on most API's, especially web service calls.
String Variables
So second-only to optimizing your database queries, significant performance problems were observed when incorrectly manipulating String variables. As such the following is a summary of some key recommendations.
Optimizing your loops where Strings were involved, and always create new variables outside loops.
Extracting excessive string manipulation into separate logging code.
Use Groovys own String substitution feature rather than excessive concatenation (which creates new string objects every time). Such as
def confirmation = "${numMessages} message(s) sent to ${name}"
Consider using the StringBuilder class which is an alternative way to combine your Strings efficiently.
Groovy usually expects your to create variables to store attribute values, however it was observed that when this is not enforced it can create unexpected results.
It was found that as long as you are creating a literal String with no substitution expressions, it is slightly more efficient to use single-quotes rather than double quotes (although both are supported).
Summary
This post covered some of the key recommendations from our look at performance tuning in Application Composer and Groovy. More posts will look at specific area in details, and as always if you have your own experiences please use the comments below as we'd love to make these posts as complete as possible.