How to Further Enhance Your SAP CPQ Scripting Performance Work
SAP CPQ (Configure, Price, and Quote) offers a wide range of scripting possibilities to be included in the standard engine execution flows. This broad offer can result with scripts that, even though it facilitates business logic that matches client's detailed requirements, fail to provide acceptable execution times and often produces application pages that load endlessly. Scripting performances are usually the main topic of our project reviews and this is, by rule, the main culprit for project failures due to lousy performances.
The "How to Boost and Maximize your SAP CPQ Scripting Performance" article gives you a detailed overview of three problems that are the most frequently identified in our partner's project reviews. This article will broaden these with three more that are also of significance. The goal of this article is not to list all methods and cases that are frequently marked as responsible for performance degradation, but to give a single, representative example of the group. The length of this article is just one of the limiters for this approach, keeping the information in a short and digestible form, and not to transform it into a documentation portal. Keep the information in this article in mind and apply it when implementing all SAP CPQ modules, guiding yourself with analogies of business objects, and coding structures.
Table of Contents
Repeated Execution of General-Purpose Functions
Let us illustrate this on the example of Quote.Save() method for quote 1.0 and the way it is frequently used in code.
By default, custom actions and global scripts executed in quote modify the object in memory without dealing with permanent store of updates. The implementer is expected to execute store lines himself and be responsible for the optimizations of the execution flow.
Usually, developers code scripts in the way that the ending lines contain method calls to push changes to database. The Quote.Save() is a single example where updates of the quote object by custom scripts are permanently saved. If the Quote.Save() does not get invoked, the updates would be forever lost with any click outside of the quote object.
Typically, the SAP CPQ project implementation for bigger clients last for a few years and amass large amount of scripts chained in execution sequences for all major events in the system. The scripts are usually built by different teams, in different project life phases, and each of the coders ensures that his script stores the updates.
In the case of the Quote.Save() and the "OnQuoteEdited" event (the event of a quote loading after selecting a quote in the quotes list), the situation could look like this (if three scripts were previously attached):
Each of these scripts would usually contain a Quote.Save() at the ending portion of the script in order to assure updates are saving.
If we observe execution of these scripts in the case of a large production quote which might contain up to 1000 items, keeping in mind that a quote is the bearer object instantiating all quote-module business objects, a push of a quote like this to the database would consume more time than the rest of a procedural script. If each of the scripts repeats the procedure, it is easy to understand the negative impact of this redundancy on performance is the main contributor in overall execution time for the quote loading.
The priority is to remove the repeated calls. One of the ways to perform this, if you have the rights to modify all the scripts or have influence on the code design behind all of the scripts, is to remove the Quote.Save() from all but "Script Z" and to make sure that "Script Z" is always at the end of the execution chain.
Another way to resolve the performance issue would be to remove the Quote.Save() from all of the scripts above and introduce a single-line "Save Quote" script that does only quote saving. The script should always remain at the end of the execution chain. This approach releases you from the responsibility to memorize which scripts pushes the content to the database and helps you to avoid unintentional errors from other coders who are unaware of the agreement. The execution chain would look like this:
Do remember that the Quote.Save is just an example. You have other potentially expensive operations in a quote like Reprice and Reconfigure. As stated, this example, as all the others, should be applied to all objects in SAP CPQ. For example, if it is up to save operations, using the IQuoteTable.Save() in multiple scripts to handle a quote table event would be another good example.
All examples should also consider all calls of the same methods and be automatically invoked by the engine itself (for certain events). For example, quite often project implementers would code multiple scripts that calculate the quote data and attach those in a chain to the "OnItemAdded" event. They would also use the Quote.Save() at the end of each event (in this case it is even worse as the event gets triggered for each quote item). This would be unnecessary as the engine will execute a quote save at the end of the event and hence would repeat the operation once more. You are expected to know if the system automatically executes an operation you intend to call from a script and if yes, omit the line to save system performances. These steps are critical and usually make the difference between successful and unusable implementations of individual end-user actions.
Learn How Scripting-Related Objects Work
Let us observe this on an example that is often listed in the reports for project reviews: Implementers would usually code a "CartItem"-type custom quote calculation in the following way:
The developer would loop, in code, through all quote items and execute calculations for all item custom fields.
The big performance issue here is the lack of awareness on the developer's side that if the calculation type is "CartItem" the engine would invoke the calculation for each item separately. This means that for a quote of 500 items, your item-level calculation would unnecessarily run for 500 x 500 = 250 000 times .
If the developer had in mind that the "CartItem" calculations are invoked in a loop by the engine itself (unlike "ProductType" of "CartTotal" calculations), he would do this the right way and remove any item loops and code the calculation lines by using the"Item" environment variable. The variable is instantiated by the engine to always point to the currently referenced item.
Again, the concept above should be applied in the general case. Therefore, you must become familiar with the objects functioning before you start using them.
Intricacies of Scripting Interface
If you want to do SAP CPQ implementations successfully, you must familiarized yourself with point-level specifics of the Python interface.
One example would be a product model implementation that comprises a product-referencing container, where each row is an instance of the same template, a simple product, used only to be a leaf item in quote and to facilitate a generic implementation, and be used for different purposes. In this case, the container child product would be named "Line Item" and its pricing field values would be set by a configuration-level script that could dynamically populate the container with the following code:
Because of a large number of child products added in real time (each being a BOM element) performances could worsen. If child-related updates, at the end of the script, are minimal, the situation could be qualitatively improved if row copying was used instead:
Justification for this improvement lays in the inner functioning of our interface and the fact that the CopyRow does a quick in-memory copy, while the AddNewRow has to instantiate a child product model in the background, and return it as the result parameter. This is why deeper knowledge of SAP CPQ scripting interface is needed and might be the crucial factor in the ability to resolve performance issues.
This example should be taken in broader context. For example, the very same IContainerRow.AddNewRow() method has the default value True for the single parameter which sets whether rules should run for the child product. The optimization you need might be just to use the IContainerRow.AddNewRow(False), if that fits to your model or if you can let the Add To Quote action to run rules in the child later. This could also be the key performance saver for your model.
Before you start delivering enterprise-level implementations, you must familiarized yourself with different aspects of the SAP CPQ Python interface and coding. From our two short articles, you should be able to start developing SAP CPQ quickly.
Remember to read "How to Boost and Maximize your SAP CPQ Scripting Performance". That article precedes this article and covers similar topics.
For more help, you can read CPQ scripting help which broadly covers all points of interest for CPQ developers.