MuleSoft Performance and Tuning
Performance tuning makes MuleSoft applications handle more traffic with fewer resources, respond faster, and scale reliably under load. This final topic covers the most impactful performance techniques that senior MuleSoft developers apply in production environments.
Understanding the Mule Threading Model
MuleSoft 4 uses a non-blocking threading model based on Project Reactor. Instead of one thread per request (which wastes threads waiting for I/O), Mule reuses a small pool of threads efficiently. Understanding this model helps you configure the right thread counts and identify threading bottlenecks.
Non-Blocking Thread Model
Traditional (blocking — 1 thread per request):
Thread 1 → handles Request A → waits 200ms for DB → returns
Thread 2 → handles Request B → waits 200ms for DB → returns
Thread 3 → handles Request C → waits 200ms for DB → returns
(100 concurrent requests = 100 threads, most idle, waiting for I/O)
Mule 4 (non-blocking):
Thread 1 → starts Request A → hands off to DB →
immediately starts Request B → hands off to DB →
immediately starts Request C → hands off to DB
When DB responds for A: Thread 1 (or any available thread) resumes A
(100 concurrent requests = far fewer threads, none sitting idle)
Connection Pool Tuning
Connection pools are the most common performance bottleneck in MuleSoft integrations. Too few connections cause requests to queue up. Too many connections overwhelm the backend system.
Database Connection Pool Settings
Database Connector Pool Configuration: Min Pool Size: 2 ← always keep 2 connections ready Max Pool Size: 10 ← never exceed 10 simultaneous connections Acquire Timeout: 5000ms ← fail fast if no connection available Max Wait Time: 30000ms ← abandon connection after 30 seconds Max Idle Time: 60000ms ← close idle connections after 1 minute How to choose Max Pool Size: Start with 10 for most production APIs. Monitor "connection wait time" in Anypoint Monitoring. If waits appear frequently, increase Max Pool Size. If the database reports "too many connections", decrease it.
HTTP Connection Pool Settings
HTTP Request Connector Pool: Max Connections: 10 ← max simultaneous HTTP connections per host Connection Idle Timeout: 30000ms Response Timeout: 10000ms ← fail if response takes > 10 seconds For high-throughput APIs calling the same external service: Max Connections: 25–50 (test against the external service's limits)
Caching Strategies
Cache frequently accessed data that changes rarely. A product catalog, currency exchange rates, or user permission lookups are ideal caching candidates. Instead of hitting the database 1,000 times per minute for the same data, cache it once and serve 999 requests from memory.
Object Store Cache Pattern
Flow: getExchangeRateFlow
[HTTP Listener: GET /rates/{currency}]
│
▼
[Object Store: Retrieve]
Key: "rate_" ++ attributes.uriParams.currency
Target: cachedRate
│
▼
[Choice Router: cache hit?]
│
├── YES (cachedRate not null):
│ [Set Payload: cachedRate] ← served from cache in <1ms
│
└── NO (cache miss):
[HTTP Request: GET rates-api/current?currency=...]
[Object Store: Store]
Key: "rate_" ++ attributes.uriParams.currency
Value: #[payload]
TTL: 300 seconds ← cache for 5 minutes
(next 300 seconds: all requests served from cache)
HTTP Caching Policy (API Manager)
For APIs that serve the same GET response to many clients, apply the HTTP Caching policy in API Manager. The API Gateway caches responses and serves them without calling your Mule application at all.
HTTP Caching Policy:
Cacheable Methods: GET, HEAD
TTL: 60 seconds
Invalidation Header: X-Invalidate-Cache (set to true to force refresh)
Effect on a popular endpoint:
Without cache: 1,000 requests/minute → 1,000 DB queries/minute
With cache: 1,000 requests/minute → 1 DB query/minute (first request)
(999 requests served from gateway cache)
DataWeave Performance
DataWeave scripts run in memory. Large payloads processed in-memory can exhaust heap space. Apply these practices for large-data scenarios.
Streaming for Large Payloads
Processing a 500MB CSV file: Without streaming (bad): [File Read] → loads entire 500MB into heap → [Transform] → transforms 500MB in memory → (very likely to cause OutOfMemoryError) With streaming (good): [File Read: streaming=true] → reads chunks as stream → [Transform: streamingStrategy=AUTO] → transforms chunk by chunk → [Database: Bulk Insert] → inserts each chunk as processed → (heap usage stays low regardless of file size)
Efficient DataWeave Patterns
INEFFICIENT (filter AFTER map — transforms everything first):
payload
map (i) -> { "id": i.id, "name": upper(i.name), "tax": i.price * 0.08 }
filter (i) -> i.tax > 5
EFFICIENT (filter BEFORE map — transforms only what you keep):
payload
filter (i) -> (i.price * 0.08) > 5
map (i) -> { "id": i.id, "name": upper(i.name), "tax": i.price * 0.08 }
Batch Job Performance Tuning
Optimal Batch Settings
Batch Job Tuning:
Chunk Size: Larger = fewer DB round trips, more memory per chunk
Start: 200. Increase if CPU is low and DB round trips dominate.
Decrease if OutOfMemoryError occurs.
Max Concurrency: Matches number of available threads and backend capacity
If Salesforce rate limit = 10,000 requests/min:
Max Concurrency = 4, Chunk Size = 200
→ 4 × 200 = 800 records/cycle → safe under SF limits
Example tuning for 100,000 records:
Chunk Size: 200
Max Concurrency: 5
Total chunks: 500
Parallel chunks at once: 5
DB round trips: 500 (one per chunk)
Estimated time: ~4 minutes (vs 40 minutes sequential)
JVM Memory Tuning for CloudHub
In wrapper.conf (for on-premises) or CloudHub properties:
Memory allocation:
wrapper.java.additional.1=-Xms512m ← initial heap size
wrapper.java.additional.2=-Xmx1024m ← maximum heap size
For 1 vCore CloudHub worker (1.5GB RAM):
Leave 500MB for the OS and Mule overhead
Set max heap to 1024MB:
mule.maxHeap=1024m
Signs you need more memory:
→ OutOfMemoryError in logs
→ GC pause duration increasing
→ Memory usage near 90% in Anypoint Monitoring
Async Processing for Throughput
Move non-critical processing out of the synchronous request path. Return a response to the client immediately and handle background work asynchronously. This makes APIs feel faster even when total processing time is the same.
BEFORE (synchronous, client waits 1.2 seconds): [HTTP Listener] [Validate: 50ms] [Save to DB: 150ms] [Send to Salesforce: 500ms] [Send confirmation email: 500ms] [HTTP Response] ← client waited 1.2 seconds AFTER (async for non-critical steps, client waits 200ms): [HTTP Listener] [Validate: 50ms] [Save to DB: 150ms] [Publish to queue: 5ms] ← Salesforce + email go to queue [HTTP Response] ← client waited 200ms ✓ Background: [Queue Consumer] [Send to Salesforce: 500ms] [Send confirmation email: 500ms] (client does not wait for these)
Load Testing Before Production
Always load test before releasing a new API version. Tools like Apache JMeter, Gatling, or k6 simulate hundreds or thousands of concurrent users. Set up a test that runs at 2x your expected peak traffic. Identify the breaking point before real users hit it.
Load Test Checklist
Before production release: ✓ Baseline test: single user, measure average response time ✓ Load test: expected peak (e.g. 500 req/min for 30 minutes) ✓ Stress test: 2× peak (e.g. 1,000 req/min until failures appear) ✓ Soak test: expected load for 8 hours (detects memory leaks) ✓ Spike test: sudden jump from 10 to 500 req/min (tests scaling) Review after testing: ✓ P95 response time under SLA threshold ✓ Error rate stays below 0.1% ✓ Memory usage stable (not growing) over time ✓ CPU usage under 80% at peak load ✓ No GC pauses over 500ms
