Which Logic App Loop Saves Your Integration the Most Time?⏱️

Table of Contents

Background

Optimizing your Logic App for better performance is a hot topic, and there are many best practices and resources available. This blog post from Microsoft explains that Inline Code could be used as an option instead of the For Each action for better performance. Therefor we will conduct a benchmark between the Logic App Standard actions For Each, Filter Array, and Inline Code in order to gain more insight into how the different actions perform!

Exploring options for Loops 🔁

Looping over an array of elements is fundamental in programming. In Logic Apps, this is typically performed by the For Each action. However, there are many ways to interact with a collection, such as Inline Codde, Liquid Transformation, Data Operations, and even Expressions. Some of these methods are more limited, while others offer greater flexibility depending on the logic you wish to implement. For example, if you want to filter a collection based on a property, the Filter action is a good option. On the other hand, if you want to reverse an array, you can simply use the reverse Expression.

For Each action

This action allows you to loop over a collection. By default, iterations will run simultaneously in parallel. There are limitations related to the For Each action depending on whether you run Logic App Consumption or Standard. The devil is in the details, but generally, the For Each action can only loop over 100.000 items, with the concurrency set to 20 by default and a maximum of 50. Read more about the limitations here.

Filter Array action

The Filter array action is a part of the Data Operations family. It allows you to create a subset of items based on a criteria. The action takes a collection as input and can filter the array on a property equal to a specific value. The action cannot transform the output into a different format or alter any items; instead, these operations have to be done in later actions.

Inline Code action

Allows you to run “vanilla” JavaScript code within Logic Apps and can be used for a vast variety of tasks. The action can utilize outputs from other actions and can also return the output of the code, which, in turn, can be used in other actions in your workflows.

The Benchmark 📏

This benchmark focuses on a use case where there is a need to filter a collection and add a property to the remaining items. We will increase the number of elements by 500, starting at 500 and going up to 10.000 elements. The benchmark will be performed on three different implementations, each focusing on a different Logic App action:

  • For Each with Condition action
  • Filter Array with For Each action
  • and Inline Code action

The files for this benchmark was generated by dummy-data-cli. Its a tool built for generating dummy data based on JavaScript templates. The following template was used:

export function main(index) {
    return `
    [
      {{#repeat ${index}}}
      {
        "id": {{@index}},
        "name": "{{firstName}} {{lastName}}",
        "work": "{{company}}",
        "email": "{{email}}",
        "dob": "{{date '1900' '2000' 'YYYY'}}",
        "address": "{{int 1 100}} {{street}}",
        "city": "{{city}}",
        "optedin": {{boolean}},
        "coordinates": {
          "x": {{float -50 50 '0.00'}},
          "y": {{float -25 25 '0.00'}}
        },
        "price": "$ {{int 0 99999 '0,0'}}",
        "status": "{{random 'active' 'inactive' 'paused' }}"
      }
      {{/repeat}}
    ]`
}

Scenario Description:

  1. Read/Input Array
  2. Loop Over Array:
    • Once the array is retrieved, iterate over individual elements within the array.
  3. Conditional Handling:
    • Filter out objects based on status equal to active
  4. Add/enrich elements with additional data
  • Add batchId with a uuid string
  1. Return filtered array with enriched data

The order of the operations is not necessarily needed to be followed, the important is that the input array is filtered and individual items are enriched with more data.

Workflow implementation

For simplicity, all the workflows will return a 202 Accepted to the client once a request is received, allowing the rest of the workflow to continue its execution and process the payload.

For Each uses a Condition inside the For Each action:

For each with Filter Array workflow

Filter Array uses the Filter Array before looping over the elements with a For Each action:

For each with Filter Array workflow

Inline Code uses JavaScript to filter and loop over the collection:

JavaScript workflow

For more details, make sure to view my GitHub project!

Environment settings

All the benchmarks will use the same resource setup:

  • WS1 App Service Plan. The scale-out burst and minimum were set to 1 instance.
  • Logic Apps Standard, with the scale-out setting set to 1 Always Ready Instance.
  • Workflow mode was set to Stateful mode, and the concurrency settings remained at default, meaning that Logic App will process several elements simultaneously.

Result 📈

Time per element in seconds

Time_per_element_in_seconds

Difference compared to For Each action in seconds

time_per_element_difference

Average time per element in seconds

  • For each with Condition: 0.095
  • Filter Array with For Each: 0.067
  • Inline Code: 0.00049

Reflections

Before analyzing the results, it is important to mention that the results are not to be seen as good or bad; there are many factors to consider. For this benchmark, we only focused on the duration of the workflows.

A quick screening shows that the Inline Code implementation crushes its competition, being approximately 0.095 / 0.00049 ≈ 194.0 194 times faster than the For Each implementation, and 0.067 / 0.00049 ≈ 137.0 137 times faster than the Filter Array implementation. These metrics, and with an average time per element of 0.00049 seconds, indicate that it is quite fast! Why do we get these results? One reason could be that the Inline Code action runs within a single execution context, minimizing the overhead associated with action-by-action processing.

Looking at the Filter Array implementation combined with the For Each action, it definitely falls a bit under the shadow when compared to the Inline Code implementation. The time per element chart indicates some performance decrease as the number of elements grows. Compared to the For Each implementation, we can calculate that it is around 0.095 / 0.067 ≈ 1.4 1.4 times faster.

Lastly, the elephant (literally) in the room is the For Each implementation. It had impressive stable performance regardless of the number of elements. Unfortunately, it performed the worst out of the three methods.

Does this mean you should always inject JavaScript everywhere whenever you can? Probably not, but it has its place when you need to iterate over large collections and other tasks that require a more programmatic approach. There are also limitations to consider before jumping on the JavaScript hype train.

Key take away from this benchmark is:

  • If possible use the Filter Array action to only work with a subset of a collection.
  • Use JavaScript when you need to iterate a big collection, because its really fast!
  • For Each action, really stable performance.

During the writing of this blog post, Microsoft announced Custom C# Code for the Inline Code Action. I will do a benchmark on this as well once the documentation is a bit more comprehensive!

Interested in more benchmarks? Make sure to check my post about Choosing the Quickest Logic App Condition!🏎️!