A top-level Errors page that aggregates errors from failed runs with occurrences metrics. https://github.com/user-attachments/assets/8f0ef55e-90dd-4faa-9051-59f4665181e4 Errors are “fingerprinted” so similar errors are grouped together (e.g. has an ID in the error message). You can view an individual error to view a timeline of when it fired, the runs, and bulk replay them.
8.2 KiB
RunsReplicationService Error Fingerprinting Benchmark
This benchmark measures the performance impact of error fingerprinting in the RunsReplicationService.
Overview
The benchmark:
- Creates a realistic dataset of TaskRuns (7% with errors by default)
- Runs the producer in a separate process to simulate real-world load
- Measures replication throughput and Event Loop Utilization (ELU)
- Compares performance with fingerprinting enabled vs disabled
Architecture
┌─────────────────┐ ┌──────────────────────┐
│ Producer │ │ Benchmark Test │
│ (Child Process)│─────────│ (Main Process) │
│ │ IPC │ │
│ - Inserts │ │ - RunsReplication │
│ TaskRuns │ │ Service │
│ to Postgres │ │ - ELU Monitor │
│ │ │ - Metrics │
└─────────────────┘ └──────────────────────┘
│ │
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Postgres │ │ ClickHouse │
└──────────┘ └──────────────┘
Files
runsReplicationBenchmark.test.ts- Main benchmark testrunsReplicationBenchmark.producer.ts- Producer script (runs in child process)runsReplicationBenchmark.README.md- This file
Configuration
The benchmark can be configured via environment variables or by editing BENCHMARK_CONFIG in the test file:
const BENCHMARK_CONFIG = {
// Number of runs to create
NUM_RUNS: parseInt(process.env.BENCHMARK_NUM_RUNS || "5000", 10),
// Error rate (0.07 = 7%)
ERROR_RATE: 0.07,
// Producer batch size
PRODUCER_BATCH_SIZE: 100,
// Replication service settings
FLUSH_BATCH_SIZE: 50,
FLUSH_INTERVAL_MS: 100,
MAX_FLUSH_CONCURRENCY: 4,
// Timeout
REPLICATION_TIMEOUT_MS: 120_000, // 2 minutes
};
Running the Benchmark
Quick Test (Small Dataset)
cd apps/webapp
BENCHMARK_NUM_RUNS=1000 pnpm run test ./test/runsReplicationBenchmark.test.ts --run
Realistic Benchmark (Larger Dataset)
cd apps/webapp
BENCHMARK_NUM_RUNS=10000 pnpm run test ./test/runsReplicationBenchmark.test.ts --run
High Volume Benchmark
cd apps/webapp
BENCHMARK_NUM_RUNS=50000 pnpm run test ./test/runsReplicationBenchmark.test.ts --run
Note: The benchmark is gated by the BENCHMARKS_ENABLED environment variable
(via containerTest.skipIf), so you don't need to edit the test file. Set
BENCHMARKS_ENABLED=1 (and optionally BENCHMARK_NUM_RUNS) then run:
cd apps/webapp
BENCHMARKS_ENABLED=1 pnpm run test ./test/runsReplicationBenchmark.test.ts --run
What Gets Measured
1. Producer Metrics
- Total runs created
- Runs with errors (should be ~7%)
- Duration
- Throughput (runs/sec)
2. Replication Metrics
- Total runs replicated to ClickHouse
- Replication duration
- Replication throughput (runs/sec)
3. Event Loop Utilization (ELU)
- Mean utilization (%)
- P50 (median) utilization (%)
- P95 utilization (%)
- P99 utilization (%)
- All samples for detailed analysis
4. OpenTelemetry Metrics
- Batches flushed
- Task runs inserted
- Payloads inserted
- Events processed
Output
The benchmark produces detailed output including:
================================================================================
BENCHMARK: baseline-no-fingerprinting
Error Fingerprinting: DISABLED
Runs: 5000, Error Rate: 7.0%
================================================================================
[Producer] Starting - will create 5000 runs (7.0% with errors)
[Producer] Progress: 1000/5000 runs (2500 runs/sec)
...
[Producer] Completed:
- Total runs: 5000
- With errors: 352 (7.0%)
- Duration: 2145ms
- Throughput: 2331 runs/sec
[Benchmark] Waiting for replication to complete...
================================================================================
RESULTS: baseline-no-fingerprinting
================================================================================
Producer:
Created: 5000 runs
With errors: 352 (7.0%)
Duration: 2145ms
Throughput: 2331 runs/sec
Replication:
Replicated: 5000 runs
Duration: 3456ms
Throughput: 1447 runs/sec
Event Loop Utilization:
Mean: 23.45%
P50: 22.10%
P95: 34.20%
P99: 41.30%
Samples: 346
Metrics:
Batches flushed: 102
Task runs inserted: 5000
Payloads inserted: 5000
Events processed: 5000
================================================================================
[... Similar output for "with-fingerprinting" benchmark ...]
================================================================================
COMPARISON
Baseline: baseline-no-fingerprinting (fingerprinting OFF)
Comparison: with-fingerprinting (fingerprinting ON)
================================================================================
Replication Duration:
3456ms → 3512ms (+1.62%)
Throughput:
1447 → 1424 runs/sec (-1.59%)
Event Loop Utilization (Mean):
23.45% → 24.12% (+2.86%)
Event Loop Utilization (P99):
41.30% → 43.20% (+4.60%)
================================================================================
BENCHMARK COMPLETE
Fingerprinting impact on replication duration: +1.62%
Fingerprinting impact on throughput: -1.59%
Fingerprinting impact on ELU (mean): +2.86%
Fingerprinting impact on ELU (P99): +4.60%
Interpreting Results
What to Look For
- Replication Duration Delta - How much longer replication takes with fingerprinting
- Throughput Delta - Change in runs processed per second
- ELU Delta - Change in event loop utilization (higher = more CPU bound)
Expected Results
With a 7% error rate and SHA-256 hashing:
- Small impact (<5% overhead): Fingerprinting is well optimized
- Moderate impact (5-15% overhead): May want to consider optimizations
- Large impact (>15% overhead): Fingerprinting needs optimization
Performance Optimization Ideas
If the benchmark shows significant overhead, consider:
- Faster hashing algorithm - Replace SHA-256 with xxHash or MurmurHash3
- Worker threads - Move fingerprinting to worker threads
- Caching - Cache fingerprints for identical errors
- Lazy computation - Only compute fingerprints when needed
- Batch processing - Group similar errors before hashing
Dataset Characteristics
The producer generates realistic error variety:
- TypeError (undefined property access)
- Error (API fetch failures)
- ValidationError (input validation)
- TimeoutError (operation timeouts)
- DatabaseError (connection failures)
- ReferenceError (undefined variables)
Each error template includes:
- Realistic stack traces
- Variable IDs and timestamps
- Line/column numbers
- File paths
This ensures the fingerprinting algorithm is tested with realistic data.
Troubleshooting
Benchmark Times Out
Increase the timeout:
REPLICATION_TIMEOUT_MS: 300_000, // 5 minutes
Producer Fails
Check Postgres connection and ensure:
- Docker services are running (
pnpm run docker) - Database is accessible
- Sufficient disk space
Different Results Each Run
This is normal! Factors affecting variance:
- System load
- Docker container overhead
- Database I/O
- Network latency (even localhost)
Run multiple times and look at trends.
Future Enhancements
Potential improvements to the benchmark:
- Multiple error rates - Test 0%, 5%, 10%, 25%, 50% error rates
- Different hash algorithms - Compare SHA-256 vs xxHash vs MurmurHash3
- Worker thread comparison - Test main thread vs worker threads
- Concurrent producers - Multiple producer processes
- Memory profiling - Track memory usage over time
- Flame graphs - Generate CPU flame graphs for analysis
- Historical tracking - Store results over time to track regressions