Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

whenever Whenever you need to improve performance of repetitive reading of the same data, the caching principles helps help a lot. Note: We speak here about application server caching, not DB caching.

In Pricefx this applies to all tasks, which processes process a long list of lines/rows, for example:

  • Pricelist Price List calculation

  • Live Price Grid calculation

  • Data Enrichment Taskstasks:

    • Calculated Field Set

    • Calculation Dataload

  • Quotes with many lines

Generally you can cache any kind of data, for example:

  • query Query result of a DS/DM query

  • list List of rows from Price Company Parameter

  • list List of rows from Product Extension

Principles of

speed optimization

Speed Optimization

  • plan Plan the code and measure the performance on the EXPECTED volume of data.

    • always Always ask customer about the current and expected (in the future) expected amount of lines to process.

    • generate Generate simple mock data lines to get real performance measurements.

    • Testing with 5 lines will NOT give you a good idea.

    • each Each process has certain time "overhead", which could look quite big , when you compare it to the time spent on 5 lines , but could be minor, when compared to time spent on 10,000 rows.

  • start Start with the biggest issues based on measurement.

    • measure Measure the current performance.

      • use Use the Performance Log/Trace.

      • consider Consider also measuring of the time spent in blocks of code, not only the whole elements.

    • start Start with optimization of the code, which takes the longest time.

      • The usual suspect is repetitive data reading , but it could be also in inefficient code.

Principles of

caching

Caching

Scenario:

Your line/row logic is executed many times , and each time it needs to read data from the same table and

  • the query either always returns completely the same result,

  • or you’re reading from the same table using different filters , but the table is so small that it’s ok to read it completely into memory.

In such a case you can cache the data:

  1. you You want to ensure to read the data only once (the first time you need them)

    1. and store the data in a List or in a Map in the cache.

  2. the The next time you need the data, you will read them from the cache.

Feature \ Cacheapi.localapi.globalShared Cache

Accessible via

binding/variable api.local

binding/variable api.global

function api.getSharedCache()

Stored Data Types

any object

String only

Content Scope

only during execution of Logic for 1 item

shared across executions of Logic for more items on one Node. Additionally also across certain Header and Line logics.

shared across Nodes and processes

Storage

memory

noSQL in-memory database

TTL (TimeToLive)

no limit, but cache survives only until the end of single logic execution

no limit, but cache survives only until the end of the process (e.g. end of pricelist items calculation)

15 mins

Speed

fast

slower

In-memory

cache

Cache api.global

the The content of the api.global binding variable "survives" between the subsequent calls of your row/line logic during the execution of the process, so you can use it for caching in, for example:

  • pricelist Price List line item logic

  • live price grid Live Price Grid line item logic

  • quote Quote line item logic

  • CFS row logic

  • DL "row" logic

Warning
During the distributed calculations (PricelistPrice List, LPG, CFS), the process is executed across several server Nodes, each sub-process calculating small part of the lines/rows. Each sub-process has its own api.global content, i.e., it’s not shared across the sub-processes. So, if you’re running a PL calculation distributed across e.g., 3 Nodes, the system will hold the 3 instances of api.global in total in the memory.

api.global

  • This binding/variable is available in every type of Logic.

  • it It behaves as a Map, so you can use usual ways to access the values via keys.

  • it It keeps all the values during the whole process, i.e., in between all the calls to the same line/row Logic.

Info
  • in In former Pricefx versions, the "global" nature of the variable had to be switched on by a statement api.retainGlobal = true, otherwise it actually behaved the same as binding/variable api.local. You can find this statement in many former projects.

  • in In the recent versions of Pricefx, there’s a configuration setting, which causes the api.global to work in the "global" way, even without using the special statement.

    • alsoAlso, all newly created partitions have this setting ON by default.

ApiRetainGlobalSettingImage Modified

Code Samples

using

Using api.global

Code Block
languagegroovy
themeMidnight
titleSample of reading a full content of
Price
Company Parameter into in-memory cache:
linenumbersfalse
def getAllFreightSurcharges() {
    return api.namedEntities(
                   api.findLookupTableValues("FreightSurcharge")  //
(3)

    )
}

def getCachedAllFreightSurcharges() {
    final key = "FreightSurchargeDataCacheKey"

    if (api.global.containsKey(key)) {  //
(1)

        return api.global[key]          //
(2)

    }

    def data = getAllFreightSurcharges()

    api.global[key] = data              // 
(4)


    return data                         //
(5)

}
check

❶ Check, if the data were already stored in the cache under the given key.

if

❷ If it’s already in the cache, return immediately a reference to it.

read

❸ Read the data from the table.

store

❹ Store the data into cache.

return

❺ Return the data.

Shared Cache

The shared cache is a distributed key-value store (distributed cache). Main The main benefit of Shared Cache is that it’s shared across logics and across Nodes.

  • So, for example, if a Pricelist Price List is running in Distributed mode, the logics running on different nodes can share data.

  • Another use case could be , that some process will write data into it , and later some scheduled task will pick up the value and process.

Features:

  • It can store only String - if If you need to store something else, you need to convert to JSON.

  • shared Shared across Nodes and processes

    • data Data stored in DB (usually powered by Redis).

  • has Has TTL (Time To Live) - The data are not stored there forever, the usual limit is 15 mins.

Code Block
languagegroovy
themeMidnight
titleSample of reading a full content of
Price
Company Parameter into Shared
cache
Cache:
linenumbersfalse
def getAllMarginAdjustments() {
    return api.namedEntities(
                   api.findLookupTableValues("MarginAdjustment")  //
(3)

               )
}

def getCachedAllMarginAdjustments() {
    final key = "MarginAdjustmentsDataCacheKey"

    if ((stringData = api.getSharedCache(key)) != null) {  //
(1)

        return api.jsonDecodeList(stringData)              //
(2)

    }

    def data = getAllMarginAdjustments()

    api.setSharedCache(key, api.jsonEncode(data))                // 
(4)


    return data                         //
(5)

}
check

❶ Check, if the data are available in the cache.

if

❷ If it’s already in the cache, decode the data rows from JSON and return it. As you’re constantly decoding the JSON, if this mechanism would be used frequently inside of a process on one Node, it would be good to consider caching of the decoded value in api.global.

read

❸ Read the data from the table.

store

❹ Store data into cache.

return

❺ Return the data.

Monitoring Performance

Each process (PL, CFS, DL, …​) has a Performance Trace, where you can see:

  • the The total amount of time spent in each Element of your logic.

  • how How many times was each Element executed.

Tip
If you will run your logic only for e.g., 5 lines, then the summary of time of those 5 executions could be highly influenced by the caching which happened during 1st execution of the Logic. So we recommend test the time on more lines - e.g., 100 and more.

To see the Performance Tracking:

  1. navigate Navigate to Administration   Logs   Jobs&Tasks.

  2. find Find the process, which you’re interested aboutin, e.g., a PricelistPrice List. You can find it by filtering by Type, Target Object, Name, etc.

  3. then Then click on the "eye" symbol of the process.

JobTasksImage Modified PricelistPerformanceTraceImage Modified

You notice that:

  • the The "ReadData" element

    • was executed 58 times,

    • all together it took 76.44ms,

    • which was 35% of time of the Pricelist calculation process.

  • the The function api.findLookupTableValues() was called 58x.

CachedPricelistPerformanceTraceImage Modified

In this Cached version of the same pricelist Price List (we used api.global for caching), you can see 2 improvements:

  • the The element total execution is only 8.12ms.

  • the The function api.findLookupTableValues was called only 1x !.