n8n/packages/testing/performance
Iván Ovejero f8c21276cb
feat(core): Make VM expression bridge timeout and memory limit configurable (#27962)
Co-authored-by: Danny Martini <danny@n8n.io>
2026-04-08 16:07:16 +00:00
..
benchmarks/expression-engine feat(core): Make VM expression bridge timeout and memory limit configurable (#27962) 2026-04-08 16:07:16 +00:00
profiles feat(benchmark): Add expression engine benchmark suite with production patterns (#26451) 2026-03-11 13:24:40 +00:00
scripts feat(benchmark): Add expression engine benchmark suite with production patterns (#26451) 2026-03-11 13:24:40 +00:00
package.json chore: Vite 8 upgrade (#27680) 2026-04-02 09:27:10 +03:00
README.md chore: Vite 8 upgrade (#27680) 2026-04-02 09:27:10 +03:00
vitest.config.ts ci: Add performance benchmark regression detection to CI (#26156) 2026-03-03 15:52:04 +00:00

Performance Benchmarks

Microbenchmarks for measuring and tracking performance of critical code paths.

When to Use Benchmarks

Good fit:

  • Hot paths executed thousands of times (expression evaluation, data transforms)
  • Comparing implementation approaches (current vs proposed)
  • Detecting regressions in critical code

Not a good fit:

  • API endpoint latency (use load testing - k6, artillery)
  • Database query performance (use query analysis tools)
  • Frontend rendering (use browser profiling)
  • One-off operations (startup time, migrations)

Rule of thumb: If it runs millions of times per day across all users, benchmark it.

Commands

pnpm --filter=@n8n/performance bench          # Run benchmarks
pnpm --filter=@n8n/performance bench:baseline  # Save baseline for local comparison
pnpm --filter=@n8n/performance bench:compare   # Compare against baseline (>10% = fail)

CI Regression Detection

Benchmarks run automatically on PRs that touch packages/testing/performance/** or packages/workflow/src/**. CodSpeed counts CPU instructions instead of wall-clock time, producing deterministic results regardless of runner load. It comments on PRs with results and regression warnings.

You can also trigger benchmarks manually for any branch via Actions > Test: Benchmarks > Run workflow.

Local vs CI

Local (bench) CI
Measurement Wall-clock time (Hz, ms) CPU instruction count
Noise 15-30% variance Near-zero variance
Best for Quick sanity checks, comparing approaches Automated regression detection

Local benchmarks are useful for eyeballing performance during development. Use bench:baseline + bench:compare for before/after comparisons on the same machine in the same session.

Adding a Benchmark

// benchmarks/my-feature/thing.bench.ts
import { bench, describe } from 'vitest';

// Setup runs once, not measured
const data = createTestData();

describe('My Feature', () => {
  bench('operation name', () => {
    doTheThing(data);
  });
});

Reading Results

name                hz      min    max   mean    p99    rme   samples
my operation    20,000   0.04   0.20   0.05   0.10  ±0.5%   10000
Column Meaning
hz Operations per second (higher = faster)
mean Average time per operation in ms
p99 99th percentile - worst case latency
rme Margin of error - lower = more reliable
samples Number of iterations run

Current Benchmarks

Area What it measures Why it matters
Expression Engine ={{ }} evaluation speed Runs for every node parameter

Tips

  1. Keep benchmarks focused - one thing per bench, not workflows
  2. Use realistic data sizes - 100 items is typical, 10k is stress test
  3. Compare approaches - benchmark both before deciding
  4. Don't over-benchmark - only critical hot paths need this