Azure Logic Apps Parallel Processing

For Each Concurrency and Optimization


Introduction

The For Each action in Azure Logic Apps allows you to iterate over arrays and process each item. By default, For Each processes items sequentially, but you can enable parallel execution to dramatically improve throughput. Understanding how to properly configure concurrency is essential for building high-performance workflows that can handle large volumes of data efficiently.

This comprehensive guide covers:

  • For Each basics — Understanding iteration mechanics
  • Concurrency control — Enabling and configuring parallel processing
  • Sequential vs parallel — Choosing the right execution mode
  • Throttling strategies — Managing API rate limits
  • Error handling — Handling failures in parallel loops
  • Best practices — Performance optimization techniques

Understanding Execution Modes

Sequential vs Parallel Execution

┌─────────────────────────────────────────────────────────────────────┐
│                    EXECUTION MODE COMPARISON                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   SEQUENTIAL EXECUTION                                              │
│   ─────────────────────────                                         │
│                                                                     │
│   ┌────────┐                                                        │
│   │ Item 1 │ ────────┐                                              │
│   └────────┘         │                                              │
│   ┌────────┐         │         ┌────────┐                           │
│   │ Item 2 │ ────────┼────────▶│ Process│                           │
│   └────────┘         │         └────────┘                           │
│   ┌────────┐         │         ┌────────┐                           │
│   │ Item 3 │ ────────┘         └────────┘                           │
│   └────────┘                      │                                 │
│                                   ▼                                 │
│   Time: 3x processing time        Result: All processed             │
│                                                                     │
│   PARALLEL EXECUTION (20 concurrent)                                │
│   ─────────────────────────────────────                             │
│                                                                     │
│   ┌────────┐ ┌────────┐ ┌────────┐                                  │
│   │ Item 1 │ │ Item 2 │ │ Item 3 │ ───────────────────▶┐            │
│   └────────┘ └────────┘ └────────┘                     │            │
│   ┌────────┐ ┌────────┐ ┌────────┐                     │            │
│   │ Item 4 │ │ Item 5 │ │ Item 6 │ ───────────────────▶│ Process    │  
│   └────────┘ └────────┘ └────────┘                     │            │
│   ... remaining items ...                              │            │
│                                                        ▼            │
│   Time: ~processing time / 20      Result: All processed            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Concurrency Control Settings

┌─────────────────────────────────────────────────────────────────────┐
│                    CONCURRENCY SETTINGS                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   For Each → Settings                                               │
│   ─────────────────────                                             │
│                                                                     │
│   Concurrency Control: [Toggle: On/Off]                             │
│                                                                     │
│   Degree of Parallelism: [Number: 1-50]                             │
│                                                                     │
│   - Default (Off): Sequential (1 item at a time)                    │
│   - On (1): Sequential, equivalent to default                       │
│   - On (50): Process up to 50 items simultaneously                  │
│                                                                     │
│   When to use specific values:                                      │
│   ─────────────────────────────────                                 │
│   1-5:   For external APIs with strict rate limits                  │
│   10-20: For most scenarios                                         │
│   50:    For high-volume, fast internal operations                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Configure For Each

Basic Sequential For Each

{
  "Get_Orders": {
    "type": "Http",
    "inputs": {
      "method": "GET",
      "uri": "https://api.example.com/orders"
    }
  },
  "Process_Orders": {
    "type": "Foreach",
    "foreach": "@body('Get_Orders')",
    "actions": {
      "Send_notification": {
        "type": "SendEmail",
        "inputs": {
          "to": "@item()['email']",
          "subject": "Order Update",
          "body": "Your order @item()['orderId'] is ready"
        }
      }
    },
    "runAfter": {
      "Get_Orders": ["Succeeded"]
    }
  }
}

Parallel For Each (20 concurrent)

{
  "Process_Orders_Parallel": {
    "type": "Foreach",
    "foreach": "@body('Get_Orders')",
    "operationOptions": "Sequential",
    "runtimeConfiguration": {
      "concurrency": 20
    },
    "actions": {
      "Process_Order": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "https://api.example.com/process",
          "body": "@item()"
        }
      }
    }
  }
}

Real-World Patterns

Batch Processing with Rate Limiting

{
  "Process_Orders_With_Throttle": {
    "type": "Foreach",
    "foreach": "@body('Get_Orders')",
    "operationOptions": "Sequential",
    "runtimeConfiguration": {
      "concurrency": 10
    },
    "actions": {
      "Process_Single_Order": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "https://api.example.com/orders/process",
          "body": "@item()"
        },
        "runAfter": {}
      },
      "Wait_If_Needed": {
        "type": "If",
        "expression": "@greater(outputs('Process_Single_Order')['statusCode'], 429)",
        "actions": {
          "Wait_Before_Retry": {
            "type": "Wait",
            "inputs": {
              "duration": "PT5S"
            }
          }
        },
        "else": {
          "actions": {
            "Continue": {
              "type": "Pass"
            }
          }
        },
        "runAfter": {
          "Process_Single_Order": ["Succeeded"]
        }
      }
    },
    "runAfter": {
      "Get_Orders": ["Succeeded"]
    }
  }
}

Chained Dependencies

{
  "Sequential_Processing": {
    "type": "Foreach",
    "foreach": "@body('Get_Orders')",
    "operationOptions": "Sequential",
    "actions": {
      "Step1": {
        "type": "Http",
        "inputs": { "method": "POST", "uri": "..." }
      },
      "Step2": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "...",
          "body": {
            "result": "@outputs('Step1')['body']",
            "order": "@item()"
          }
        }
      },
      "Step3": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "...",
          "body": {
            "step2Result": "@outputs('Step2')['body']"
          }
        }
      },
      "runAfter": {
        "Step1": ["Succeeded"],
        "Step2": ["Succeeded"]
      }
    }
  }
}

Error Handling in Parallel

Handle Individual Failures

{
  "Process_Items": {
    "type": "Foreach",
    "foreach": "@body('Get_Items')",
    "runtimeConfiguration": {
      "concurrency": 20
    },
    "actions": {
      "Try_Process": {
        "type": "Scope",
        "actions": {
          "Process_Item": {
            "type": "Http",
            "inputs": {
              "method": "POST",
              "uri": "https://api.example.com/process",
              "body": "@item()"
            }
          }
        },
        "runAfter": {}
      },
      "Catch_Error": {
        "type": "Scope",
        "runAfter": {
          "Try_Process": ["Failed"]
        },
        "actions": {
          "Log_Failure": {
            "type": "Compose",
            "inputs": {
              "item": "@item()",
              "error": "@workflow()['errors']",
              "timestamp": "@utcNow()"
            }
          },
          "Add_To_Retry_Queue": {
            "type": "ServiceBus",
            "inputs": {
              "method": "post",
              "path": "/queues/retry-queue/messages",
              "body": "@outputs('Log_Failure')"
            }
          }
        }
      }
    }
  }
}

Continue on Failure

{
  "Process_All_Continue": {
    "type": "Foreach",
    "foreach": "@body('Get_Items')",
    "operationOptions": "Sequential",
    "runtimeConfiguration": {
      "concurrency": 10,
      "jobConfiguration": {
        "consecutiveRetryCount": 0
      }
    },
    "actions": {
      "Process_Item": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "https://api.example.com/process",
          "body": "@item()"
        },
        "runAfter": {}
      }
    }
  }
}

Performance Optimization

Determine Optimal Concurrency

┌─────────────────────────────────────────────────────────────────────┐
│                  CONCURRENCY OPTIMIZATION GUIDE                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   Start with lower concurrency:                                     │
│   ─────────────────────────────                                     │
│   1. Test with concurrency = 5                                      │
│   2. Monitor:                                                       │
│      - Execution time                                               │
│      - API rate limit errors (429)                                  │
│      - Throttling errors                                            │
│      - Memory usage                                                 │
│   3. Gradually increase if no errors                                │
│                                                                     │
│   Adjust based on target:                                           │
│   ─────────────────────────                                         │
│   API with 100 req/sec limit:  concurrency = 10                     │
│   API with 1000 req/sec limit: concurrency = 50                     │
│   Internal processing:        concurrency = 50                      │
│   Database operations:        concurrency = 1-5                     │
│                                                                     │
│   Watch for:                                                        │
│   ────────────                                                      │
│   ✗ 429 Too Many Requests → Lower concurrency                       │
│   ✗ Timeouts → Check API response time                              │
│   ✗ Memory pressure → Reduce batch size                             │
│   ✗ Logic App throttling → Monitor usage                            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Batch Processing Pattern

{
  "Batch_Process": {
    "type": "Foreach",
    "foreach": "@split(join(body('Get_All_Records'), ','), ',')",
    "operationOptions": "Sequential",
    "runtimeConfiguration": {
      "concurrency": 20
    },
    "actions": {
      "Process_Batch": {
        "type": "Http",
        "inputs": {
          "method": "POST",
          "uri": "https://api.example.com/batch",
          "body": {
            "records": "@createArray(item())"
          }
        }
      }
    }
  }
}

Common Mistakes and Solutions

Mistake 1: Default Sequential Processing

{
  "Problem": {
    "description": "Using default For Each without concurrency",
    "impact": "Takes 50x longer than needed for 50 items"
  },
  "Solution": {
    "type": "Enable concurrency",
    "code": "runtimeConfiguration: { concurrency: 20 }"
  }
}

Mistake 2: Too High Concurrency

{
  "Problem": {
    "description": "Setting concurrency too high for API limits",
    "impact": "Rate limiting, failed requests, retries"
  },
  "Solution": {
    "type": "Start low and increase",
    "steps": ["Start at 5", "Monitor for 429s", "Increase gradually"]
  }
}

Mistake 3: Not Handling Errors

{
  "Problem": {
    "description": "Single failure stops entire For Each",
    "impact": "Lost processing for all items"
  },
  "Solution": {
    "type": "Use scope for error handling",
    "pattern": "Try/Catch within For Each"
  }
}

Best Practices Summary

PracticeDescription
Start with concurrency 5-10Test before increasing
Monitor rate limitsWatch for 429 errors
Use sequential for DB writesAvoid conflicts
Add delays between batchesPrevent overwhelming targets
Handle errors per itemUse scopes within For Each
Log processed itemsTrack for debugging

Related Topics


Azure Integration Hub - Intermediate Level