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
| Practice | Description |
|---|---|
| Start with concurrency 5-10 | Test before increasing |
| Monitor rate limits | Watch for 429 errors |
| Use sequential for DB writes | Avoid conflicts |
| Add delays between batches | Prevent overwhelming targets |
| Handle errors per item | Use scopes within For Each |
| Log processed items | Track for debugging |
Related Topics
- Expressions — Dynamic content in loops
- Error Handling — Try/Catch patterns
- Service Bus — Queue integration
Azure Integration Hub - Intermediate Level