Microbenchmark Methodology
Camunda usa JMH (Java Microbenchmark Harness) con configuración estándar: AverageTime mode, warmup 2×5s, measurement 10×10s, 2 forks con heap fijo 1GB, GC profiler habilitado. El monorepo solo tiene 2 microbenchmarks: MessagePack (serialización) y Deduplication (caching) — el resto de benchmarks viven en un load-tester separado no público.
Configuración estándar¶
Todos los microbenchmarks de Camunda usan anotaciones JMH idénticas:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS)
@Fork(value = 2, jvmArgsAppend = {"-Xms1G", "-Xmx1G"})
public class XxxBenchmark { ... }
Decisiones de diseño: - AverageTime: latencia por operación es más informativa que throughput total para componentes internos - Heap fijo (1GB): evita variabilidad por GC en benchmark - 2 forks: account for JVM variance entre runs - 10 iteraciones × 10s: muestra suficientemente grande para significancia estadística - GC profiler default: detectar allocation patterns problemáticos
Métricas reportadas¶
JMH reporta para cada benchmark:
| Métrica | Significado |
|---|---|
| Score | Tiempo medio por operación |
| Error | Intervalo de confianza 99.9% |
| Cnt | Total de iteraciones (forks × measurement) |
| gc.alloc.rate | MB/sec de allocations |
| gc.alloc.rate.norm | Bytes allocated per operation |
| gc.count | GC events durante el benchmark |
| gc.time | Tiempo total en GC (ms) |
Profilers disponibles¶
-prof gc # Garbage collection stats (siempre activo en Camunda)
-prof stack # Stack sampling (hotspot detection)
-prof jfr # Java Flight Recorder
-prof async:libPath=/path/to/libasyncProfiler.so # Native profiling
Output formats¶
-rf json -rff results.json # JSON para parsing automático
-rf csv -rff results.csv # CSV para spreadsheets
-rf text -rff results.txt # Human readable
Tips oficiales para mediciones precisas¶
- Cerrar aplicaciones innecesarias — reduce system noise
- Disable CPU frequency scaling:
- Múltiples forks — account for JVM variance (default 2)
- Warmup adecuado — JIT compilation toma tiempo
- Sistema quieto — mínima actividad de fondo
- Heap fijo —
-Xms1G -Xmx1Gpara evitar resizing - Disable turbo boost — más consistencia
Comparación entre GCs¶
Pattern oficial para comparar garbage collectors:
# G1GC (default)
java -jar benchmarks.jar XxxBenchmark -rf json -rff g1-results.json
# ZGC (low-latency)
java -XX:+UseZGC -jar benchmarks.jar XxxBenchmark -rf json -rff zgc-results.json
# ParallelGC (throughput)
java -XX:+UseParallelGC -jar benchmarks.jar XxxBenchmark -rf json -rff parallel-results.json
Lo que NO hay en el monorepo¶
El módulo microbenchmarks/ es muy pequeño: solo 2 áreas:
1. MsgpackBenchmark — serialización MessagePack
2. DeduplicationCacheBenchmark — caching
No hay benchmarks de: - End-to-end process instance throughput - Comparaciones de tamaño de partición - Tests de carga de gateway/broker - Benchmarks de exporters
Esos se ejecutan vía un load-tester separado (no público, mencionado en docs RDBMS). Esto significa que los benchmarks de Camunda no son reproducibles fácilmente desde el repo público.
Hallazgos cuantitativos del MsgpackBenchmark¶
Datos del README oficial (no inferencia):
Serialize¶
- 0.136 μs/op @ 1k records, 0.354 μs/op @ 10k, 0.270 μs/op @ 100k
- Allocations: ≈0 bytes/op
- GC events: ≈0
Deserialize WITH new constructor¶
- 0.420 μs/op (constante across batch sizes)
- Allocations: ~1,855 bytes/op
- GC alloc rate: ~4,000 MB/sec
Deserialize WITHOUT constructor (reuso)¶
- 0.254-0.338 μs/op
- Allocations: ~75 bytes/op
- GC alloc rate: ~213-282 MB/sec
Conclusión clave: el pattern de reusar instancias vs crear nuevas reduce: - Tiempo: ~40% más rápido - Allocations: ~25x menos bytes - GC pressure: ~17x menor alloc rate
Esta es la razón de la decisión arquitectónica de Zeebe de hacer pooling agresivo de POJOs en el record processing path. Ver concepts/stream-processing.
Implicaciones para el MVP¶
- Usar JMH si se requieren microbenchmarks similares — config idéntica
- Pooling de objetos en hot paths — replicar para reducir GC pressure
- Construir un load-tester propio desde cero — el de Camunda no es accesible
- Profilers integrados desde día 1 — JFR + async profiler para producción