CX Works

A single portal for curated, field-tested and SAP-verified expertise for your SAP C/4HANA suite. Whether it's a new implementation, adding new features, or getting additional value from an existing deployment, get it here, at CX Works.

How to Boost and Maximize Your SAP CPQ Scripting Performance

SAP CPQ (Configure, Price, and Quote) is known for its flexibility with customizations. Project implementers usually use SAP CPQ to introduce lots of scripting and to facilitate highly custom business requirements. This means that the key contributor to whether an implementation is acceptable or not relies on the many Python scripts running inside of an SAP CPQ model, pending a single user action, and having the procedures coded optimally. Scripting is also growing in its presence as a part of the standard SAP CPQ implementation paradigm, so performance degradations that come from scripting are getting more and more costly.

This article will cover the three most severe and frequent causes of scripting implementation failures that SAP registers regularly in project reviews. Another article ("How to further enhance your SAP CPQ Scripting Performance work"), will cover three more problems from the field of scripting performances, that even though being noticed a bit less frequently in reviews, are of equal severity.

The issues described here are, unfortunately, registered for almost all projects executed by experienced SAP CPQ developers and are usually focal areas of project reviews by the SAP team.

The article aims to be a single place that you can use as a list of tips on how to prevent or resolve issues in your scripting before any review processes or requests for external help. Cost savings are quite obvious. As the guidebook given here is tailored to the length of an article, we expect the content is comprehensive enough that you become aware of issues not specifically described, through an example.

Table of Contents

Code Organization Inside of Loops

What usually makes a difference between a script that blocks a screen for several minutes and a script that runs in the background instantaneously (from a user perspective), is the arrangement of the code around loops. The goal is not to avoid loops, each of them you need, but the essence lies in which code portions are placed inside of the loops.

Let us examine an example with SQL queries that scripts typically use to fetch additional required master data. A script like this will, most often, be coded in the following way: 


for item in Quote.Items:
    '''
    some item-data processing code
    '''
    sqlResult = SqlHelper.GetFirst("select Price_EUR, Description_ENG, Supplier_Code from ProductMasterData where CatalogueCode = '" + str(item.PartNumber) + "'") 
    '''
    some item-data processing code using the result from above
    '''


This is the natural and typical way of how code gets built or altered with maintenance. However, this is not good enough and can cause massive issues with performance. Instead, you are expected to aggregate all search keys of a query in a set and use a single database access to obtain all data you need. Then, you can store the data in a dictionary in memory, access it whenever it is needed, or just loop though the database set as in the following example:


stringBuilder = ""
for item in Quote.Items:
    stringBuilder = stringBuilder + "'" + item.PartNumber + "',"
if stringBuilder[-1] == ',':
    stringBuilder = stringBuilder[:-1]
sqlResults = SqlHelper.GetList("select Price_EUR, Description_ENG, Supplier_Code from ProductMasterData where CatalogueCode in (" + stringBuilder + ")")
for sqlResult in sqlResults:
    '''
    some data processing code
    '''


The upper code is 60 (warning) times slower than the lower one and that is just for the empty loops given. The numbers are for the scripts execution for a quote with 100 items against the ProductMasterData table that has around 50 000 rows (quite below production size) and when there is no index created on the CatalogueCode column (which definitely should exist). Now, put this into perspective of production data and code, as well as nested method calls where subroutines can access master data in this way. You will quickly understand the magnitude of the problem.


Simply altering a few loops in your code function (only few lines of code modified) can turn your project performance from appallingly bad to production-ready. This applies also for other coding operations that take time, like web services. So make sure that web services are not called inside of loops, but that all data is fetched at once, stored in memory, and only used later.

Additionally, keep in mind that the above does not only apply to direct code loops. SAP CPQ scripts are usually very layered where methods and code from other modules are invoked in deep levels of nesting. You should be aware that performance hits can also occur when the whole code is not taken into perspective. For example, a subroutine might be accessing the database in the correct way, but the caller might be unaware of this and invoke the method in a loop. In cases like this, you should restructure the code and use other methods, or re-code database-accessing segments in the caller method.

Details of SAP CPQ Events Mechanism

Usually, the way you expect an event will behave in all different cases is not how the execution model functions in real-time. It is crucial that when you hook up some routine to a system event, you know precisely what you have done.

Let us demonstrate this on one of the commonest examples in SAP CPQ projects that, as stated in the caption, experienced SAP CPQ implementers also tend to execute incorrectly. We will observe a global script attached to a global "After adding products to quote" event for quote 1.0 ("OnItemAdded" is the system event name). The event is one of the most frequently used execution points in the system.

Often there is a business requirement that when a product is added to a quote, a particular operation is executed (as a configurator to quote a context switch is one of the focal points in the system). That could be, for example, a call to a web service to fetch current prices from an external system, for all quote items. Here, the implementer would usually code a script attached to a global "OnItemAdded" event with the intention that the script runs for every product model, that it iterates through all items added, call the web service, and fetch and store the data.

Now, let us observe how this runs on the example of a deep parent/child product structure where several levels of hierarchy are present, and all leaf products have multiple line items that will be added to a quote. All in all, the configuration could result with 200 quote items. The expectation of the implementer would be that the script runs once for the whole parent/child product, as this is a single configuration. But the OnItemAdded is triggered for every item being added to a quote separately, so the web-service call will be executed 200 times (warning) and the script would likely be coded with looping through all of the items. This means that a lot of redundant processing would occur.

The performance impact on the whole system is obvious. A solution here would be that the implementer, next to building deeper knowledge of the events system, should opt for a product-level event giving similar functionality ("OnProductAddedToQuote"). This is set on the product-level and runs once for the whole product (the root of the hierarchy only). The "OnItemAdded" event is just an example used for the article, many others are present ("OnProductRulesExecutionEnd" and its triggers regarding the parent/child levels is another good example). You should make sure that before starting to code inside of SAP CPQ as a platform, you know details of its events model.

Minimal Execution Scope

The previous tip dealt with the intricacies of the events execution model. However, this one will deal with decisions made on the implementer's side that, even though the scripts are functionally valid, they could have significantly less impact on performance. 

Since our system abounds with different hook-up events and has a very complex execution flow, inexperienced implementers usually choose the maximum and safest execution scope, just to be sure that the setup in the script will be applied. This is unfortunately the main cause of major system performance degradations. One of the most typical examples is "Every time a quote is changed" ("OnEveryTimeQuoteChanged" being its system name) and the scripts by default attached to it just to make sure that the functionality will execute whenever it is needed.


Actual requirements could be, for example, that a script should run for any updates of customer objects or value changes for a list of custom fields. In this situation, the easiest way for an implementer would be that he attaches the script to "OnEveryTimeQuoteChanged" event and make sure it always runs. The script might be a computationally intensive operation and if it runs for every click in a quote, it sensibly slows down every user interaction with the system. This is what we constantly face in project reviews.

This example can be easily fixed with the script being attached to several events simultaneously, each having a narrower scope. In this way, the script would run only for rare events and performance would be much less impacted. Assuming that the script name is "Script X", the following events would be a better choice:




The issue described above was the reason for not including "OnEveryTimeQuoteChanged" event in the quote engine 2.0 implementation. This issue is still being debated. The "OnEveryTimeQuoteChanged" is, of course, just one example. There are other script triggers in the system whose execution scope is inadequately wide for the code attached to it (configuration events, for example, attached to every rules execution).

Bear in mind that next to the global and configuration events, you have multiple other options to attach your script to, attribute triggers, container row and cell events, table cell events and actions, custom quote actions, and custom calculations that run on value updates in editable groups in quote.

Conclusion

SAP CPQ is a very wide platform with a multitude of ways and means to hook up a script to your system. The fact that the system enables you to do some scripting setup, does not mean it is recommended and, even further, that an error made in that way will not be the main cause for your implementation being unusable to end users. Make sure you know details of SAP CPQ scripting and that you go deep enough to understand the inner functioning of the SAP CPQ scripting engine.

The following article ("How to further enhance your SAP CPQ Scripting Performance work") will cover three problems that you are also highly advised to review.

Finally, here is a great start for you to extend your knowledge on SAP CPQ scripting details.