docs/guides/load-testing/page.md
{% answer %} Load test GoFr services with k6 or vegeta from outside the cluster, scrape Prometheus during the run for server-side truth, and look at p50/p95/p99 latency, error rate, and throughput together — never just an average. The framework gives you the metrics surface; the test is your responsibility. {% /answer %}
A single number ("we did 5k RPS") is not enough. Always report the tuple:
Run the test long enough to see whether numbers are stable. The first 30 seconds usually contain JIT/warmup artifacts.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 }, // ramp up
{ duration: '2m', target: 50 }, // steady
{ duration: '30s', target: 200 }, // step up
{ duration: '2m', target: 200 }, // steady
{ duration: '30s', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<800'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://api.example.com/orders/42');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
Run with k6 run --out json=results.json script.js. Thresholds turn the run into a pass/fail.
For simpler GET-heavy tests, vegeta is one shell command:
echo "GET https://api.example.com/orders/42" | \
vegeta attack -rate=100 -duration=2m | \
vegeta report -type=hist[0,10ms,50ms,100ms,500ms,1s]
vegeta also writes raw results that you can replay through vegeta plot for visual inspection.
GoFr exposes Prometheus metrics on METRICS_PORT (default 2121, see pkg/gofr/factory.go). Scrape them during the run for server-side truth. Useful series:
app_http_circuit_breaker_state (see Circuit Breaker).go_goroutines, go_gc_duration_seconds, process_resident_memory_bytes.A 3-minute test should be reflected in a Grafana dashboard with at least these panels open. If client-side and server-side latency diverge, suspect the network or the load generator.
When latency rises, look in this order:
pprof: GoFr already mounts the standard net/http/pprof handlers (/debug/pprof/, /debug/pprof/profile, /debug/pprof/heap, etc.) on the metrics server (port METRICS_PORT, default 2121) — fetch a profile with go tool pprof http://<host>:2121/debug/pprof/profile. There's no need to register your own handler. In production, restrict access to that port to your internal network, since it exposes goroutine and heap profiles.MaxOpenConns is often the culprit.app_http_circuit_breaker_state.go_gc_duration_seconds and runtime/metrics show this.Run the same scenario monthly and on every major release. Save the k6/vegeta output and the Prometheus snapshots. Regressions become obvious only when there is a baseline to compare against. This guide deliberately does not publish baseline numbers — they depend entirely on your hardware, payload, and dependencies.
Running k6 from a developer laptop hits the public Internet path. That is fine for end-to-end SLO checks, but if you want to know "how fast is GoFr itself", run the generator inside the same cluster on a pod with no resource limits, hitting the Service ClusterIP. That isolates the application from edge variability.
vus (virtual users) below the connection ceiling of any rate-limited downstream you cannot mock.Capture, for each run:
This metadata is what makes a regression diagnosable a month later.
{% faq %}
{% faq-item question="Where do I scrape GoFr's metrics during a load test?" %}
On the metrics port, default 2121, configurable via METRICS_PORT. Path is /metrics. Set METRICS_PORT=0 to disable.
{% /faq-item %}
{% faq-item question="Should I report average latency?" %}
No. Average hides tail latency, which is where users feel pain. Always report p50, p95, and p99 together, plus error rate.
{% /faq-item %}
{% faq-item question="k6 or vegeta?" %}
k6 is better for scripted scenarios with thresholds and assertions. vegeta is faster to set up for steady-rate GET load. Both work fine against GoFr.
{% /faq-item %}
{% /faq %}