Skip to content

Load testing

Retsu includes k6 scenarios for correctness checks, performance work, and the local product showcase.

Run tests only against an environment you own and are allowed to test. Start small and watch the API, database, and queue state while increasing load.

Start the complete stack:

just local-up

Run a scenario in a temporary k6 container:

just local-load smoke
just local-load enqueue
just local-load consume
just local-load mixed
just local-load saturation

The runner checks that the API, workers, Prometheus, and Grafana are ready. It removes the k6 container after the run and keeps the normal console summary.

Scenarios

Scenario Purpose
smoke Create a queue, enqueue, dequeue, acknowledge, and confirm it is empty
enqueue Measure enqueue rate while intentionally building a backlog
consume Prefill queues, then measure dequeue and acknowledgement drain rate
mixed Run steady producers and consumers together
saturation Ramp up, spike, recover, and ramp down to find a capacity boundary
showcase Demonstrate priorities, retries, expiry, and dead-letter cleanup

Every run creates queue names with a unique RUN_ID. Set one when a recognizable suffix is useful:

RUN_ID=regression-001 just local-load mixed

Showcase

Open the Retsu Showcase dashboard, then run:

just local-showcase

The default five-minute run applies active load for three minutes, then drains consumers, waits through a cleaner cycle, and checks final server state. Use a whole number from 5 through 20 to choose the total duration:

just local-showcase 20

The workload sends a repeating 50–150 enqueue requests per second across five queues. Two queues receive most of the traffic. Priorities follow a 70/20/10 HIGH/MEDIUM/LOW mix. Controlled missing acknowledgements and short lifetimes create retries, expiry, and dead-letter records.

This is a product demonstration, not a capacity claim.

Changing a run

Pass scenario settings on the same command:

MIXED_PRODUCER_RATE=25 \
MIXED_CONSUMER_RATE=25 \
MIXED_DURATION=2m \
QUEUE_COUNT=4 \
  just local-load mixed

Common settings:

Variable Default Purpose
BASE_URL http://127.0.0.1:2424 API root without /v1
RUN_ID Generated Unique queue suffix
QUEUE_PREFIX retsu-k6 Queue name prefix
QUEUE_COUNT 1 Number of queues
PAYLOAD_BYTES 1024 Generated payload size
PRIORITY_MIX 20,60,20 HIGH, MEDIUM, LOW weights
MESSAGE_TTL_SECONDS 180 Generated message lifetime
VISIBILITY_TIMEOUT_SECONDS 10 Generated queue visibility timeout
MAX_DELIVERY_ATTEMPTS 3 Generated queue delivery limit
REQUEST_TIMEOUT 5s Per-request timeout

Each scenario has additional rate, duration, consumer, and prefill settings in load/k6/support/config.js.

Thresholds

The normal scenarios fail when:

  • HTTP or unexpected-status errors reach 0.01 percent;
  • check success or lifecycle correctness reaches 99 percent or lower;
  • p95 enqueue, dequeue, or acknowledgement time reaches 750 milliseconds.

A saturation run may cross latency thresholds by design. The first stage where latency, errors, dropped work, or backlog grows without recovering marks a capacity boundary.

Reading results

Use the k6 summary together with the Grafana dashboards:

  • Compare enqueue and acknowledgement counts to see whether backlog grows.
  • Treat empty dequeue responses as valid, not as request failures.
  • Watch dropped iterations to see whether k6 could schedule the offered load.
  • Check PostgreSQL, PgBouncer, cache, and container measurements before attributing a limit to the API.

Local k6 shares Docker capacity with Retsu and its supporting services. Use it for correctness, tuning, and repeatable comparisons on the same machine. Generate final environment capacity measurements from separate infrastructure.

Direct k6 execution

The scripts can target a reachable API with k6 2.0.0 or later:

BASE_URL=https://retsu.example.com \
RUN_ID=smoke-001 \
  k6 run load/k6/smoke.js

Use the files in load/k6/ as executable test assets. Reader documentation stays in this guide.

Cleanup

Retsu does not currently provide queue deletion or purge endpoints. Load runs leave their queues and any remaining messages in PostgreSQL. Use a disposable database and reset it between benchmark series.