Files
magnus919_agent-skills/qa-methodology/references/performance-testing.md
T
usernameandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 5bad13af56 feat(qa-methodology): refresh 5 traditional QA support references
- test-data-management: add time-travel testing (freezegun/timecop),
  data masking (static vs dynamic), GDPR/right-to-erasure, PII rules
- performance-testing: add tool landscape (k6/Locust/Gatling/JMeter),
  SLO-based threshold design, baseline-then-regress, CI cadence,
  gate-governance link to quality-gates-and-metrics
- security-testing: update to OWASP Top 10:2025 (A01-A10), add
  supply-chain/SBOM, STRIDE threat modeling, SAST/DAST/SCA landscape
- ci-failure-triage: add exit-code taxonomy (1/2/126/127/137/139/143),
  git bisect run, flake-vs-failure rerun-once protocol, numbered
  5-step diagnostic procedure
- test-debugging: add CI-vs-local divergence checklist (7 causes),
  test ordering/shared state (pytest-randomly), mock path binding

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-01 17:41:52 -04:00

7.1 KiB
Raw Blame History

Performance Testing

Types

Type Question Answered Duration Frequency
Load test Does it handle expected traffic? 515 min Nightly / pre-release
Stress test Where does it break? Ramp until failure On-demand / architectural changes
Soak test Does it degrade over time (leaks)? 424 hours Weekly
Spike test Does it survive sudden bursts? Seconds to minutes Pre-launch / event prep
Benchmark What's the raw throughput/latency? 30s2 min On PR (smoke)

Tool Landscape

Tool Language Strengths Weaknesses Best For
k6 JavaScript (Go runtime) CI-native, thresholds as code, low resource overhead, Grafana ecosystem No GUI scripting; HTTP/gRPC/WebSocket/browser (experimental) CI-integrated load testing, developer-owned perf
Locust Python Distributed by design, real-user behavior modeling, event-driven Higher memory per VU than k6; Python GIL limits single-node throughput Complex user flows, distributed load generation
Gatling Scala/Java/Kotlin Highest throughput per node, detailed HTML reports, protocol breadth Steep learning curve; commercial features paywalled High-throughput enterprise, protocol-heavy (JMS, JDBC)
JMeter Java (GUI + CLI) Widest protocol support, plugin ecosystem, 20+ years of community Heavy resource usage, brittle test plans, XML config Legacy protocol support, non-developer QA teams

Selection Decision

Signal Choose
CI/CD integration is the primary driver k6
Team is Python-centric, needs distributed load Locust
Need maximum VUs per machine, detailed reporting Gatling
Must test JMS, JDBC, FTP, or other non-HTTP protocols JMeter
Browser-level rendering performance matters k6 (browser module) or Lighthouse CI

SLO-Based Threshold Design

Performance thresholds must derive from product requirements, not arbitrary round numbers.

Deriving Thresholds from SLOs

  1. Start with the product SLO. Example: "95% of API requests complete in < 300ms" → p95 < 300ms.
  2. Set the test threshold at the SLO. The load test asserts p(95)<300.
  3. Add a warning threshold below the SLO for early signal: p(95)<250 (advisory, does not fail CI).
  4. Set error-rate threshold from availability SLO. If availability SLO is 99.9%, error threshold is rate<0.001.

Baseline-Then-Regress Pattern

Never evaluate a single run in isolation. Establish a baseline first:

1. Run the load test on a known-good commit (e.g., main HEAD).
2. Store p50, p95, p99, throughput, error rate as the baseline.
3. On subsequent runs, compare against the 7-day rolling baseline.
4. Alert if p95 regresses > 20% vs baseline.
5. Fail CI (blocking gate) if p95 exceeds the absolute SLO.
Comparison Action
p95 within 10% of baseline Pass — no action
p95 regressed 1020% Advisory warning — investigate if trend continues
p95 regressed > 20% vs baseline Alert — likely regression; investigate before merge
p95 exceeds absolute SLO Block merge — SLO breach

Worked Example (k6 thresholds)

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 },   // ramp up
    { duration: '1m', target: 20 },    // steady state
    { duration: '10s', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<300', 'p(99)<800'],  // from product SLO
    http_req_failed: ['rate<0.001'],                  // from availability SLO
  },
};

export default function () {
  const res = http.get('https://staging.example.com/api/items');
  check(res, {
    'status 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  sleep(1);
}

Key Metrics

Metric Definition Target Guidance
p50 latency Median response time User-perceived "normal"
p95 latency 95th percentile SLO boundary for most APIs
p99 latency 99th percentile Tail latency — catches GC pauses, cold starts
Throughput Requests/sec sustained Compare against capacity plan
Error rate 5xx / total < 0.1% under load
Saturation CPU/memory/connections at peak < 80% = headroom

CI Cadence

Trigger Test Type Duration Gate
PR (hot-path changes only) Smoke benchmark (10 VUs, 30s) < 2 min Advisory — catches 10× regressions
Nightly (staging) Full load test (expected traffic profile) 1015 min Blocking for release branch
Weekly Soak test (constant load, 48h) 48 hours Alert on degradation trend
Pre-release Stress test + spike 30 min Blocking — must not regress vs last release

Not on every PR. Full load tests on every PR waste CI resources and produce noisy results. Smoke benchmarks on PRs catch gross regressions; nightly runs catch subtle ones.

Interpreting Results

Symptom Likely Cause Next Step
Latency climbs linearly with VUs Single-threaded bottleneck or lock contention Profile CPU, check for global locks
Latency flat then sudden cliff Resource exhaustion (connections, memory, FDs) Check pool sizes, ulimit, OOM killer
Throughput plateaus early Downstream dependency is the bottleneck Test the dependency in isolation
Errors only at high concurrency Race condition or timeout misconfiguration Check connection pool, retry storms
Memory grows during soak Leak — unclosed connections, unbounded cache Heap dump at intervals, diff allocations

Gate Governance

Performance thresholds feed into quality gate decisions — they are not standalone pass/fail numbers. A perf regression that exceeds the SLO is a blocking gate (prevents merge/deploy); a regression within the warning band is an advisory gate (flags for review). For the full gate design framework (blocking vs advisory, pipeline stages, anti-patterns), see quality-gates-and-metrics.md.

Gotchas

  • Thresholds without baselines are meaningless. A p95 of 280ms tells you nothing without knowing whether last week's was 120ms. Always baseline first.
  • Testing against shared staging environments produces noisy results from other teams' load. Use dedicated perf environments or time-boxed windows.
  • Ignoring tail latency. p50 can look healthy while p99 is 10× the SLO. Always assert on percentiles, not averages.
  • Running the full suite on every PR wastes CI minutes and teaches teams to ignore perf results. Tier by trigger.

Sources: k6 documentation (grafana.com/k6), Locust docs (locust.io), Gatling docs (gatling.io), JMeter docs (jmeter.apache.org), Google SRE Workbook (O'Reilly, 2018), DORA State of DevOps Reports.