SaaS / Developer tools · client work, details anonymised
StressForge
A multi-tenant load-testing platform that runs k6 tests on a worker pool and streams live latency and throughput to the browser while the test runs.
- Role
- Sole engineer: product spec, architecture and first build
- When
- 2026
- Source
- Client code, not public
- Stack
- TypeScript
- Fastify
- Next.js
- k6
- BullMQ
- Redis Streams
- PostgreSQL + TimescaleDB
- WebSockets
- Took the product from brainstorm and PRD to a working monorepo (API, worker, dashboard, SDK) in 26 commits over two days
- Tenant isolation enforced in Postgres itself with row-level security, on top of the checks in application code
- Live run metrics flow from k6 through Redis Streams into TimescaleDB and out to connected browsers
Problem
Teams that want to load-test an API usually pick one of two options. They run k6 or a similar tool by hand from a laptop, which caps the load and leaves no history, or they buy an enterprise tool that is heavy to set up. The brief was a hosted, self-serve platform. You define a target and a test, press run, watch percentiles and error rates move live, and compare runs over time. Each customer organisation’s data had to be strictly isolated.
My role
Sole engineer on the engagement as recorded in the repository. That covered the product brainstorm and PRD, a technical spec for the first UI phase, the data model and migrations, and the first build of the API, worker, web dashboard and TypeScript SDK.
Architecture
A pnpm and Turborepo monorepo with three apps and two shared packages:
- API (Fastify): REST routes for targets, tests, runs, schedules, quick tests, billing and webhooks, plus a WebSocket endpoint where clients join a room per run.
- Worker (BullMQ consumer): takes run jobs off a Redis-backed queue, generates a k6 script or uses a custom one, and spawns k6 as a child process. Metric events go to a Redis Stream while the test runs.
- Metrics consumer: reads the stream, batch-inserts into TimescaleDB (50 rows or every second) and republishes per-run updates for fan-out to browsers.
- Web (Next.js App Router): a dashboard with run history and live run monitoring, with charts drawn in uPlot.
- Shared and SDK packages: Zod schemas and types used across the apps, and a typed client that CI can use to trigger a run and wait for its result.
Key decisions
- Row-level security for tenancy. Every tenant table has a Postgres RLS policy keyed on an
app.current_org_idsession variable, set per transaction from the organisation ID in the authenticated JWT. The application role cannot see other organisations’ rows. Only the separate migrator and analytics roles bypass RLS. - TimescaleDB for run metrics.
test_run_metricsis a hypertable with one-day chunks, compression and retention policies. Historical charts read from continuous aggregates (hourly per run, daily per target), so the raw series can expire. - Redis Streams between k6 and everything else. The worker never talks to the database or browsers directly. It appends to a capped stream (
MAXLEN ~ 10000), and persistence and live fan-out each read from that stream on their own, so a slow database write can’t stall the live view. The design notes record that Redis Streams won over Kafka for simplicity at the expected scale. - k6 as the load engine. It’s open source and scriptable. The worker passes VUs, duration and an output target on the command line, then reads the summary export for p50/p95/p99, RPS and error rate.
- Scale-to-zero workers (designed). The production design scales worker pods on queue depth so idle capacity costs nothing. The autoscaling itself is not in the repository.
Outcomes
- A running Phase 0 with sign-in, target and test management, run history and live run monitoring. Five SQL migrations cover the schema, hypertable, indexes and RLS.
- Before any code was written, the build was specified through a multi-agent planning workflow (brainstorm, PRD, tech spec) and then executed from it.