By Performate
Smoke, Load, and Stress in CI: A Minimal Pipeline That Still Catches Regressions
Smoke, load, stress in CI: minimal k6 pipeline with thresholds—k6.io test types plus fast merge gates vs scheduled suites.
Running a three-hour stress suite on every PR trains teams to disable the pipeline. Minimal CI perf means: fast smoke on merge, scheduled load, optional manual stress—same k6 repo, different options via env (k6 test types). Gates must be fast enough to stay enabled and meaningful enough to catch regressions that unit tests miss.
This guide layers smoke, load, and stress by frequency, gives one script with a TEST_PROFILE switch, and documents what belongs in merge gates vs nightly jobs vs pre-launch rituals. Desktop tuning (Postman → k6 workflow) should happen before CI carries the script.
Layering gates: frequency vs risk
| Layer | Duration | Blocks merge? | Typical trigger |
|---|---|---|---|
| Smoke | 1–3 min | Yes | Every PR / merge to main |
| Load | 10–20 min | Nightly / release branch | Scheduled + pre-release |
| Stress | 30+ min | Weekly / manual | Infra change, launch rehearsal |
Smoke proves wiring, auth, and catastrophic latency regression at low RPS. Load validates SLO-shaped traffic. Stress maps breaking points—never daily on shared staging without approval.
Why one repo beats three
Duplicated scripts drift within two sprints. One export with profile env var keeps thresholds and request definitions aligned—Performate desktop validates the same file CI runs.
k6: one script, TEST_PROFILE switch
Illustrative—not production-ready. CI sets TEST_PROFILE=smoke on PR; nightly cron sets load.
What this demonstrates:
- Single
profilesmap—smoke/load/stress differ by rate, duration, thresholds. - Tags
profileon metrics for filtering nightly vs PR runs in Grafana. - Think time scales with profile—smoke stays fast; load more realistic.
- Fail build on thresholds, not only checks—k6 exits non-zero on threshold breach.
import http from 'k6/http';
import { check, sleep } from 'k6';
const profile = __ENV.TEST_PROFILE || 'smoke';
const profiles = {
smoke: {
rate: 2,
duration: '1m',
thresholds: {
http_req_failed: ['rate<0.01'],
'http_req_duration{profile:smoke}': ['p(95)<800'],
},
},
load: {
rate: 25,
duration: '10m',
thresholds: {
'http_req_duration{profile:load}': ['p(95)<600', 'p(99)<900'],
http_req_failed: ['rate<0.01'],
},
},
stress: {
rate: 60,
duration: '15m',
thresholds: {
http_req_failed: ['rate<0.05'],
},
},
};
const p = profiles[profile] || profiles.smoke;
export const options = {
scenarios: {
main: {
executor: 'constant-arrival-rate',
rate: p.rate,
timeUnit: '1s',
duration: p.duration,
preAllocatedVUs: 20,
maxVUs: 100,
tags: { profile },
},
},
thresholds: p.thresholds,
};
export default function () {
const res = http.get(`${__ENV.API_BASE}/api/core`, { tags: { route: 'core', profile } });
check(res, { ok: (r) => r.status < 500 });
sleep(profile === 'smoke' ? 0.1 : 0.4);
}
Patterns that work
- Tune load profile on desktop until thresholds stable three runs—then promote to nightly CI.
- Archive JSON summary per release with
profiletag—quarterly review compares evidence. - Smoke uses staging secrets via CI secret store (k6 secrets)—never inline in repo.
- Stress manual workflow with runbook link—on-call approves window.
Anti-patterns to avoid
- Stress on every push—pipeline disabled within a month.
- Smoke without thresholds—passes while
p95doubled. - Different scripts for CI vs local—"works on my laptop" returns.
- Running load against production from CI without change control.
Pro tip (example command): local profile switch mirrors CI.
k6 run ci-profiles.js --env TEST_PROFILE=smoke --env API_BASE=https://staging.example.com
What this command demonstrates: developers reproduce CI failure in one command before pushing threshold tweaks—reduces merge-queue noise.
Decision table: which profile when
| Event | Profile | Blocks deploy? |
|---|---|---|
| PR opened | smoke | Yes, if on critical path |
| Nightly main | load | Alert, optional block |
| Pre-launch week | stress (manual) | Advisory unless SLO committee says block |
| Hotfix branch | smoke minimum | Yes |
| Infra resize | load + optional stress | Yes on staging gate |
Keep smoke under three minutes—includes checkout, install k6, run, upload artifact if possible.
Schedule load when staging is quiet—not concurrent with QA full regression without coordination.
Observability and CI checklist
- Smoke uses staging secrets (k6 secrets)—rotated independently of repo.
- Fail build on thresholds, not only checks—configure CI to respect k6 exit code.
- Archive JSON summary for quarterly review.
- Document
TEST_PROFILEvalues in README—on-call reproduces nightly failure. - Link CI smoke to same commit desktop export validated.
- Alert on load profile threshold miss separately from smoke—different response playbooks.
How Performate supports minimal CI perf
Below is a concrete workflow example for promoting desktop-tuned script to layered CI—adapt rates to your SLO doc.
Example: desktop tune → smoke gate → nightly load
- Tune load profile on desktop until
p95stable—10m at business RPS. Problem solved: CI does not become your debugger. - Export script with
TEST_PROFILEswitch intact. Problem solved: one file, three gates—no drift. - Wire smoke in CI on PR—
TEST_PROFILE=smoke, 1–3 min. Problem solved: merge blocked on real regression, not unit test gap alone. - Schedule load nightly on main—same script, different env. Problem solved: SLO-shaped signal without PR latency tax.
- Document stress as manual workflow with Performate template saved. Problem solved: launch rehearsal exists without daily cost.
- Compare reports release over release in comparison view. Problem solved: trends visible to release managers, not only engineers.
That workflow maps directly to the cta in this post: turn playbook into runnable scenarios without glue-code sprints.
Closing takeaway
CI perf wins with smoke every merge, load on a schedule—not stress on every push. Export one profile-aware script from desktop, enable smoke gate this week, and schedule load before the next release train.
Merge the smoke job before adding load—teams that enable both at once often disable both when staging queues back up.
Document the TEST_PROFILE values in your service README and link the nightly load job in the same doc—on-call should not grep CI YAML at 2 AM to reproduce a red threshold.
When smoke fails on a hotfix branch, rerun locally with the same env profile before disabling the gate—most failures are stale secrets or wrong API_BASE, not perf regressions.
Ready to optimize your API performance?
Use Performate to turn this playbook into runnable k6 scenarios, thresholds, and shareable reports without losing days to glue code.