Expressions that use a large amount of memory can cause performance problems, both for that particular expression and possibly even the system as a whole. To prevent this, the Memory Circuit Breaker will automatically detect expressions that use too much memory and abort those evaluations to prevent the application server from running out of resources. Only the offending expression fails when the circuit breaker is tripped, allowing all other evaluations to complete successfully.
The threshold for the Memory Circuit Breaker is set at 100,000 AMUs, though that may change in the future. Based on our analysis of actual expression evaluations, this would abort less than 0.01% of all expression evaluations. This threshold is constant regardless of the max heap space configurations for your site, so the only way to avoid the circuit breaker is to modify your design.
It is important to design expressions to limit their memory footprint to ensure they will always evaluate successfully. All functions will use some amount of memory during evaluation, but some design patterns will use more memory than others or retain that memory for a longer period of time.
This page provides guidance on how to design memory-efficient expressions and diagnosing memory issues.
When an expression uses too much memory, it is typically doing one of the following:
This section provides guidance on how to avoid these design patterns.
Large queries are one of the easiest ways to store too much data in local variables. When you query a lot of data and store it in a variable, that data is stored in memory for as long as that variable can be used. For local variables configured to
refreshAlways, these values are stored for the entire evaluation of the
a!localVariables() function. For local variables with any of the other refresh configurations, the values are stored across all evaluations on that interface. See the Interface Evaluation Lifecycle page for more information on how local variables are evaluated.
To avoid memory issues with storing too much data in local variables, always do at least one of the following:
queryrecord, and query rules all support paging using a!pagingInfo
Looping over a large array of data can use a large amount of memory. While a looping function is being evaluated, the results of each iteration are accumulated so they can be returned at the end of the function evaluation. Even if each iteration is only returning a very small value, the overall result can use a lot of memory if the number of iterations is very large.
This problem can be compounded if you have nested looping functions. Memory will be allocated for each looping function iteration across all nested loops and won't be reclaimed until the parent loop is completed.
To avoid memory issues with looping over too many items:
In reality, the result of your expression is just a piece of data, and interfaces are no exception. Interfaces that contain a large number of components or components that contain a large amount of data can cause memory issues when being processed by the system. This is usually more of a problem when the interface contains a dynamic number of components based on a large data set, such as a grid with a dynamic number of rows.
To avoid memory issues with too many components on an interface:
When Appian aborts an expression that is using too much memory, the error message will typically include information about the last function that was evaluating when the error occurred and its line number. This gives you a good starting point for diagnosing the issue.
However, because memory is accumulated throughout the course of the evaluation, it's possible that the function mentioned was just the tipping point, not the actual root cause. Here are some tips for diagnosing the issue:
Remember that local variables with a refresh configuration other than
refreshAlways are persisted across evaluations of interfaces. Even if the initial value as configured in the expression is small, it could cause issues if a large value is saved into it on a subsequent reevaluation.