Saltar a contenido

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

  1. Cerrar aplicaciones innecesarias — reduce system noise
  2. Disable CPU frequency scaling:
    sudo cpupower frequency-set --governor performance
    
  3. Múltiples forks — account for JVM variance (default 2)
  4. Warmup adecuado — JIT compilation toma tiempo
  5. Sistema quieto — mínima actividad de fondo
  6. Heap fijo-Xms1G -Xmx1G para evitar resizing
  7. 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

  1. Usar JMH si se requieren microbenchmarks similares — config idéntica
  2. Pooling de objetos en hot paths — replicar para reducir GC pressure
  3. Construir un load-tester propio desde cero — el de Camunda no es accesible
  4. Profilers integrados desde día 1 — JFR + async profiler para producción