High-performance MuleSoft applications require careful tuning across multiple dimensions. This guide covers comprehensive optimization techniques for production-grade integrations.
Threading Model Optimization
Understanding and tuning Mule's threading model:
<!-- Custom threading configuration -->
<configuration>
<!-- GRIZZLY HTTP threading -->
<http:listener-config name="HTTP_Listener_config">
<http:listener-connection
host="0.0.0.0"
port="8081"
protocol="HTTP">
<!-- Worker thread pool configuration -->
<http:worker-thread-pool
poolExhaustedAction="WAIT"
maxThreads="128"
minThreads="8"
threadTtl="60000"
threadWaitTimeout="30000"/>
<!-- Selector threads for NIO -->
<http:selector-threads>4</http:selector-threads>
</http:listener-connection>
</http:listener-config>
<!-- Flow processing strategy -->
<flow name="high-throughput-flow" processingStrategy="asynchronous">
<!-- Configure async processing -->
<async-processing-strategy
name="custom-async"
maxThreads="32"
minThreads="4"
poolExhaustedAction="WAIT"
threadTTL="60000"
maxBufferSize="1000"/>
</flow>
</configuration>Threading Best Practices
# threading-config.yaml
threading:
http_listener:
# Rule: Start with CPU cores * 2, tune based on monitoring
selector_threads: ${sys:cpu.cores} # NIO selectors
worker_threads:
min: 8
max: 128 # For I/O bound workloads
# For CPU bound: max = CPU cores + 1
flow_processing:
# Synchronous: Single thread per request (default)
# Asynchronous: Thread pool for parallel processing
strategy: "asynchronous"
batch_processing:
# Batch jobs use separate thread pools
max_concurrent_instances: 4
thread_pool_size: 16
guidelines:
io_bound_apps:
description: "High external calls, DB queries"
recommended_threads: "CPU * 4 to CPU * 8"
rationale: "Threads wait on I/O, more threads increase concurrency"
cpu_bound_apps:
description: "Heavy transformations, calculations"
recommended_threads: "CPU + 1"
rationale: "More threads cause context switching overhead"
mixed_workloads:
description: "Combination of I/O and CPU work"
recommended_threads: "CPU * 2 to CPU * 4"
strategy: "Profile and adjust"Memory Management
JVM Heap Configuration
#!/bin/bash
# JVM tuning for MuleSoft applications
# Heap sizing guidelines:
# - Production: Start with 50% of available memory
# - Leave room for native memory, OS, and other processes
# - G1GC recommended for heaps > 4GB
# CloudHub (set via Runtime Manager)
# JAVA_OPTS automatically configured based on worker size
# RTF / On-Premise configuration
export JAVA_OPTS="
-Xms2g
-Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1HeapRegionSize=16m
-XX:+ParallelRefProcEnabled
-XX:+UseStringDeduplication
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/mule/heapdump.hprof
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/mule/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10M
"
# For large heaps (>8GB), consider ZGC (Java 17+)
export JAVA_OPTS_ZGC="
-Xms8g
-Xmx16g
-XX:+UseZGC
-XX:ZCollectionInterval=30
-XX:+ZGenerational
"Memory Leak Prevention
<!-- Prevent common memory leaks -->
<flow name="memory-safe-flow">
<!-- Always limit collection sizes -->
<foreach collection="#[payload[0 to 999]]" doc:name="Bounded Foreach">
<!-- Process items -->
</foreach>
<!-- Clear variables after use -->
<remove-variable variableName="largePayload" doc:name="Clear Large Variable"/>
<!-- Use streaming for large payloads -->
<ee:transform doc:name="Streaming Transform">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json deferred=true streaming=true
---
payload map {
// Transform without loading all in memory
id: $.id,
name: $.name
}]]></ee:set-payload>
</ee:message>
</ee:transform>
<!-- Close resources properly -->
<try doc:name="Resource Management">
<db:select config-ref="Database_Config" doc:name="Query">
<db:sql>SELECT * FROM large_table LIMIT 10000</db:sql>
</db:select>
<error-handler>
<on-error-continue type="ANY">
<!-- Resources automatically cleaned up -->
</on-error-continue>
</error-handler>
</try>
</flow>Connection Pooling
Database Connection Pool
<!-- Optimized database connection pool -->
<db:config name="Database_Config">
<db:generic-connection
url="${db.url}"
user="${db.user}"
password="${db.password}"
driverClassName="com.mysql.cj.jdbc.Driver">
<!-- Connection pool configuration -->
<db:pooling-profile
maxPoolSize="20"
minPoolSize="5"
acquireIncrement="2"
preparedStatementCacheSize="50"
maxWait="30"
maxWaitUnit="SECONDS"
maxIdleTime="600"/>
<!-- Connection validation -->
<db:connection-properties>
<db:connection-property key="validationQuery" value="SELECT 1"/>
<db:connection-property key="testOnBorrow" value="true"/>
<db:connection-property key="testWhileIdle" value="true"/>
<db:connection-property key="timeBetweenEvictionRunsMillis" value="30000"/>
</db:connection-properties>
</db:generic-connection>
</db:config>HTTP Connection Pool
<!-- HTTP requester with connection pooling -->
<http:request-config name="HTTP_Request_Config">
<http:request-connection
host="${api.host}"
port="${api.port}"
protocol="HTTPS">
<!-- Connection pool settings -->
<http:client-socket-properties>
<http:tcp-client-socket-properties
connectionTimeout="10000"
sendTcpNoDelay="true"
keepAlive="true"
receiveBufferSize="65536"
sendBufferSize="65536"/>
</http:tcp-client-socket-properties>
<!-- Pool configuration -->
<http:pooling-profile
maxConnections="50"
maxConnectionsPerRoute="10"
connectionIdleTimeout="30000"
exhaustedAction="WHEN_EXHAUSTED_WAIT"
maxWait="10000"/>
<!-- Timeout configuration -->
<http:default-headers>
<http:default-header key="Connection" value="keep-alive"/>
</http:default-headers>
</http:request-connection>
<!-- Request timeouts -->
<http:request-config
responseTimeout="30000"
followRedirects="true"/>
</http:request-config>Caching Strategies
Object Store Caching
<!-- Cache configuration with Object Store -->
<os:object-store
name="API_Response_Cache"
config-ref="ObjectStore_Config"
persistent="false"
maxEntries="1000"
entryTtl="300"
entryTtlUnit="SECONDS"
expirationInterval="60"
expirationIntervalUnit="SECONDS"/>
<flow name="cached-api-call">
<http:listener config-ref="HTTP_Listener_config" path="/products/{productId}"/>
<!-- Generate cache key -->
<set-variable variableName="cacheKey" value="product_#[attributes.uriParams.productId]"/>
<!-- Check cache first -->
<try doc:name="Cache Lookup">
<os:retrieve
key="#[vars.cacheKey]"
objectStore="API_Response_Cache"
target="cachedResponse"
doc:name="Get from Cache"/>
<choice doc:name="Cache Hit?">
<when expression="#[vars.cachedResponse != null]">
<set-payload value="#[vars.cachedResponse]" doc:name="Use Cached"/>
<set-variable variableName="cacheHit" value="true"/>
</when>
<otherwise>
<set-variable variableName="cacheHit" value="false"/>
</otherwise>
</choice>
<error-handler>
<on-error-continue type="OS:KEY_NOT_FOUND">
<set-variable variableName="cacheHit" value="false"/>
</on-error-continue>
</error-handler>
</try>
<!-- If cache miss, call API and cache result -->
<choice doc:name="Need API Call?">
<when expression="#[vars.cacheHit == 'false']">
<http:request
method="GET"
config-ref="Product_API_Config"
path="/products/{productId}"
doc:name="Get Product"/>
<!-- Cache the response -->
<os:store
key="#[vars.cacheKey]"
objectStore="API_Response_Cache"
doc:name="Store in Cache">
<os:value><![CDATA[#[payload]]]></os:value>
</os:store>
</when>
</choice>
<!-- Add cache headers -->
<ee:transform doc:name="Add Cache Headers">
<ee:variables>
<ee:set-variable variableName="responseHeaders"><![CDATA[%dw 2.0
output application/java
---
{
"X-Cache": if (vars.cacheHit == "true") "HIT" else "MISS",
"Cache-Control": "max-age=300"
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
</flow>DataWeave Caching
%dw 2.0
output application/json
// Cache expensive computations using lazy evaluation
var expensiveCalculation = () -> do {
// This only executes once, result is cached
payload map {
id: $.id,
complexScore: calculateComplexScore($)
}
}
// Memoization pattern
var memoizedLookup = (id) -> do {
var cache = {}
---
if (cache[id]?)
cache[id]
else do {
var result = lookupValue(id)
---
cache ++ {(id): result}
result
}
}
fun calculateComplexScore(item) =
// Complex calculation here
item.value * 1.5 + item.weight * 0.3
---
{
results: expensiveCalculation(),
cached: true
}Batch Processing Optimization
<!-- Optimized batch processing configuration -->
<batch:job name="optimized-batch-job"
maxFailedRecords="100"
jobInstanceId="#[correlationId]">
<!-- Input phase -->
<batch:process-records>
<!-- Step 1: Enrich with database lookup -->
<batch:step name="enrich-step"
acceptPolicy="ALL"
acceptExpression="#[true]">
<!-- Use batch aggregator for bulk operations -->
<batch:aggregator
size="100"
timeout="60"
streaming="true"
doc:name="Bulk Enrich">
<!-- Single batch query instead of N+1 queries -->
<db:select config-ref="Database_Config" doc:name="Bulk Lookup">
<db:sql><![CDATA[
SELECT * FROM reference_data
WHERE id IN (:ids)
]]></db:sql>
<db:input-parameters><![CDATA[#[{
ids: payload.id
}]]]></db:input-parameters>
</db:select>
<!-- Merge results back to records -->
<ee:transform doc:name="Merge Enrichment">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/java
var lookupMap = payload groupBy $.id
---
vars.records map (record) -> record ++ {
enrichment: lookupMap[record.id][0] default {}
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</batch:aggregator>
</batch:step>
<!-- Step 2: Transform -->
<batch:step name="transform-step">
<ee:transform doc:name="Transform Record">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/java
---
{
id: payload.id,
transformedValue: payload.value * 1.1,
enrichedData: payload.enrichment
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</batch:step>
<!-- Step 3: Bulk write -->
<batch:step name="write-step">
<batch:aggregator
size="500"
timeout="120"
streaming="true"
doc:name="Bulk Write">
<db:bulk-insert config-ref="Database_Config" doc:name="Bulk Insert">
<db:sql><![CDATA[
INSERT INTO processed_records (id, value, data)
VALUES (:id, :transformedValue, :enrichedData)
]]></db:sql>
</db:bulk-insert>
</batch:aggregator>
</batch:step>
</batch:process-records>
<!-- Completion phase -->
<batch:on-complete>
<logger level="INFO" doc:name="Batch Complete"
message="Batch completed: #[payload.totalRecords] records, #[payload.failedRecords] failures"/>
</batch:on-complete>
</batch:job>Monitoring and Profiling
Custom Metrics
<!-- Custom performance metrics -->
<flow name="monitored-flow">
<http:listener config-ref="HTTP_Listener_config" path="/api/process"/>
<!-- Start timing -->
<set-variable variableName="startTime" value="#[now()]" doc:name="Start Timer"/>
<!-- Processing steps -->
<flow-ref name="business-logic" doc:name="Process"/>
<!-- Calculate duration and log metrics -->
<ee:transform doc:name="Calculate Metrics">
<ee:variables>
<ee:set-variable variableName="processingTime"><![CDATA[%dw 2.0
output application/java
---
(now() - vars.startTime) as Number {unit: "milliseconds"}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<!-- Log structured metrics -->
<logger level="INFO" doc:name="Log Metrics">
<ee:message><![CDATA[%dw 2.0
output application/json
---
{
metricType: "API_LATENCY",
endpoint: attributes.requestPath,
method: attributes.method,
processingTimeMs: vars.processingTime,
statusCode: 200,
correlationId: correlationId,
timestamp: now()
}]]></ee:message>
</logger>
<!-- Send to monitoring system -->
<async doc:name="Async Metrics">
<http:request
method="POST"
config-ref="Metrics_HTTP_Config"
path="/metrics"
doc:name="Send to Prometheus/DataDog">
<http:body><![CDATA[#[{
name: "mule_api_latency_ms",
value: vars.processingTime,
tags: {
app: p('app.name'),
endpoint: attributes.requestPath,
environment: p('mule.env')
}
}]]]></http:body>
</http:request>
</async>
</flow>Performance Dashboard Metrics
# Key metrics to monitor
performance_metrics:
throughput:
- name: "requests_per_second"
query: "rate(http_requests_total[1m])"
alert_threshold: "> expected * 1.5 or < expected * 0.5"
- name: "messages_processed_per_second"
query: "rate(mule_messages_total[1m])"
latency:
- name: "response_time_p50"
query: "histogram_quantile(0.5, http_request_duration_seconds)"
target: "<100ms"
- name: "response_time_p95"
query: "histogram_quantile(0.95, http_request_duration_seconds)"
target: "<500ms"
- name: "response_time_p99"
query: "histogram_quantile(0.99, http_request_duration_seconds)"
target: "<1000ms"
resources:
- name: "heap_usage_percent"
query: "jvm_memory_used_bytes{area='heap'} / jvm_memory_max_bytes{area='heap'}"
alert_threshold: ">85%"
- name: "gc_pause_time"
query: "rate(jvm_gc_pause_seconds_sum[1m])"
alert_threshold: ">100ms/min"
- name: "thread_pool_active"
query: "jvm_threads_current"
alert_threshold: ">thread_pool_max * 0.9"
connections:
- name: "db_connection_pool_active"
query: "hikaricp_connections_active"
alert_threshold: ">pool_size * 0.8"
- name: "http_connections_active"
query: "http_client_connections_active"Performance Checklist
performance_checklist:
design:
- "Use async processing for independent operations"
- "Implement caching for repeated lookups"
- "Use streaming for large payloads"
- "Avoid N+1 query patterns"
- "Design for horizontal scaling"
configuration:
- "Right-size worker/pod resources"
- "Configure appropriate thread pools"
- "Tune connection pool sizes"
- "Set reasonable timeouts"
- "Enable keep-alive for HTTP"
code:
- "Use DataWeave streaming mode"
- "Avoid unnecessary transformations"
- "Clear large variables after use"
- "Use batch aggregators for bulk operations"
- "Implement circuit breakers"
monitoring:
- "Track response time percentiles"
- "Monitor memory and GC"
- "Alert on error rates"
- "Track connection pool usage"
- "Log structured performance data"
testing:
- "Load test before production"
- "Identify bottlenecks with profiling"
- "Test failure scenarios"
- "Validate auto-scaling behavior"
- "Benchmark against SLAs"Conclusion
MuleSoft performance optimization requires attention to threading, memory, connections, and caching. Profile your specific workloads to identify bottlenecks, then apply targeted tuning. Continuous monitoring ensures performance remains optimal as traffic patterns change. Start with defaults, measure, and tune iteratively based on real production data.