Administration & hosting

Performance & load testing

Measure throughput and latency of an install, results, and what to scale when.

scripts/load-test.mjs measures a running Diffbridge: concurrent API reads (signed in, the pages people open most), the deep health check, and a burst of comparisons for the background worker. It creates its own throwaway workspace with the simulated demo orgs, so run it against a test install in demo mode, never production.

RF_URL=http://127.0.0.1:8080 node scripts/load-test.mjs --seconds 30 --concurrency 20 --comparisons 10

Results on a developer laptop (2026-11-05)

npm start (API and worker in one process, embedded PGlite database, file storage), Windows 11, Node 24:

Load Throughput Latency p50 / p95 / p99 Errors
20 concurrent clients, 30 s 113 requests/s 175 / 294 / 487 ms 0
50 concurrent clients, 15 s 114 requests/s 416 / 902 / 1125 ms 0
Worker burst Finished First / median / last
10 comparisons started at once 10 of 10 1.2 / 1.5 / 2.1 s
20 comparisons started at once 20 of 20 1.0 / 2.2 / 3.4 s

Reading these: throughput stays at about 115 requests a second whatever the concurrency; only latency grows. The limit is PGlite, the embedded database used for local development. It runs Postgres in WebAssembly on one connection, so queries wait their turn. Production uses real Postgres with a connection pool, so these numbers are a floor, not the production capacity.

The first run found a real bug: under concurrent load the deep health check (/healthz?deep=1) sometimes reported storage as down, because simultaneous checks wrote the same probe file. Checks now share one run and use their own probe file. A regression test covers it.

Measuring production capacity

Run the same script on the production VM's twin (same shape, Postgres, Object Storage), against a copy of the stack in demo mode:

RF_URL=https://staging.example.com node scripts/load-test.mjs --seconds 60 --concurrency 50 --comparisons 20

While it runs, watch:

  • docker stats: CPU of api, worker, postgres;
  • /metrics: rapidforge_http_request_duration_seconds by route, rapidforge_background_tasks;
  • Postgres: select count(*) from pg_stat_activity (connections) and slow queries.

What to expect, and what to do about it:

  • API latency rises first: run more API containers behind Caddy. They share nothing but Postgres.
  • Comparisons and deployments wait in the queue: docker compose up -d --scale worker=N. Each org still runs one deployment at a time; different orgs run in parallel.
  • Postgres CPU saturated: move to a bigger VM shape, or managed Postgres (Migrating to AWS).
  • Real Salesforce orgs are usually the slowest part: metadata retrieves take seconds to minutes and are limited by Salesforce, not Diffbridge. Large orgs are retrieved in chunks of up to 10,000 files, three at a time.
Something unclear or missing on this page?