Skip to content

Service level objectives and capacity ​

This page states the service level objectives (SLOs) of a self-hosted CodeLotse installation, the load profiles they are measured under, and the latest measured results of the load, chaos and upgrade suites.

Objectives ​

SLOTargetMeasured as
Ingest acknowledgementp99 < 1 sTime from sending a signed webhook to the 202 response of POST /webhooks/github
Review startp95 < 60 s after the event, under the nominal profilereview_runs.started_at − webhook_deliveries.received_at, both taken from the database clock
Event lossZeroEvery acknowledged delivery leads to exactly one review run and one published review; no external write happens twice

The review start SLO is a capacity statement: it holds while the worker capacity is higher than the event rate. Use Sizing to plan worker processes.

Load profiles &ZeroWidthSpace;

ProfileShape
Ingress nominal (gated)50 signed pull_request webhooks per second for 60 s, 20 connections, 16 KB payloads, unique delivery ids
Ingress peak (reported)200 webhooks per second for 30 s, 50 connections
Pipeline nominal (gated)10 installations × 10 repositories, 60 pull requests opened at 0.1 per second (360 per hour), one worker process with the default review concurrency of 2
Worker scaling (reported)60 pull requests at 2 per second against 1, 2 and 4 worker processes
GitHub rate limits30 pull requests at 3 per second, 2 workers; the GitHub simulator allows 150 requests per 20 s (primary) and 10 writes per 10 s or 6 concurrent requests (secondary, retry-after: 5)

The model endpoint is an OpenAI-compatible simulator that answers every call after 2–5 s (0.2–0.5 s in the rate-limit run). A real model is usually slower, which lowers the reviews per minute per worker but not the ingest numbers.

Measured results &ZeroWidthSpace;

Hardware: Apple M2 Max (12 cores, 64 GB), macOS 27.0, Docker Desktop 29.8.0 with 12 CPUs and 31 GiB, Node.js 24.12.0. API and worker run as separate Node.js processes on the host; PostgreSQL 17.11 and Valkey 9.1.2 run in containers. Measured on 2026-10-13 with the suites at commit 4ab960b.

Ingress:

ProfileAchieved rateRequestsNon-2xxAcknowledged and storedp50p99Max
Nominal50/s3,00003,000 / 3,0008 ms36 ms65 ms
Peak200/s6,00006,000 / 6,00015 ms40 ms74 ms

Pipeline (webhook → ingest → review → publish):

ScenarioEventsPublishedDuplicate writesAck p99Start p50Start p95Finish p95Reviews/min
Nominal, 1 worker6060069 ms0.5 s0.7 s14.4 s6.0
Burst, 1 worker6060025 ms142 s268 s280 s11.0
Burst, 2 workers6060029 ms62 s122 s134 s20.8
Burst, 4 workers6060026 ms24 s50 s60 s38.0
Tight GitHub limits, 2 workers3030024 ms9.1 s12.1 s180 s7.8

Throughput scales almost linearly with worker processes (×3.5 from 1 to 4). The burst rows show queueing, not a breach of the nominal SLO: 2 events per second is 20 times the nominal rate. Under tight GitHub limits the simulator answered 32 requests with a secondary-limit 403; the shared budget gate kept the workers waiting, so only 1 request arrived while a block was active, and all reviews were published once.

Chaos &ZeroWidthSpace;

All scenarios use 2 workers, 2 installations × 5 repositories and 1 event per second; the model answers in 1–3 s.

FaultEventsPublishedDuplicate writesStart p95Finish p95Notes
Worker processes killed with SIGKILL 6 times mid-job4040017.0 s66.9 sStalled jobs are picked up again by BullMQ
Valkey stopped for 5 s4040028.8 s36.2 sWebhooks keep being acknowledged; the outbox holds jobs until Valkey returns
PostgreSQL stopped for 5 s4040029.8 s36.7 s5 webhooks were refused during the outage and succeeded on redelivery
Model endpoint unavailable for 20 s202000.8 s54.8 s63 calls refused; jobs retried with backoff
Model endpoint unavailable throughout (1 worker, deterministic checks on)3300.5 s35.4 s15 calls refused; after the last attempt each review published its deterministic finding only

When the model endpoint stays down past the retry window, a review with deterministic findings (secret and SAST checks) completes on the last attempt with those findings only, and its summary says that only deterministic checks ran. A review without deterministic findings ends as failed; the next push reviews again.

Not covered: Valkey failover to a replica (the product runs a single Valkey; the test stops and restarts it).

Upgrades &ZeroWidthSpace;

The upgrade suite upgrades seeded databases from every supported release to the current one; see upgrade.md. On the hardware above all four steps pass; the whole suite takes about 5 s.

Code context profiling &ZeroWidthSpace;

pnpm --filter @quavon/load-tests profile:context builds the review context for 30 changed TypeScript files of this repository (phase-4-done..6049ead) ten times:

Symbol contextWall p50Workspace I/O p50 (file reads, git grep)In-process CPU p50
Off479 ms475 ms31 ms
On929 ms837 ms148 ms

Tree-sitter parsing and symbol queries cost about 120 ms of CPU per review; the rest is waiting for git. Context building is not CPU-bound and is small next to model latency, so a native (napi-rs) parser is not proposed.

Sizing &ZeroWidthSpace;

Reviews per minute per worker ≈ 60 × review concurrency / review duration in seconds. With the default concurrency of 2 and the 2–5 s model simulator, one worker completed about 10 reviews per minute. With a real model that needs 30 s per review, one worker handles about 4 reviews per minute; size the worker count so that the expected peak rate of opened or updated pull requests stays below the combined capacity.

Running the suites &ZeroWidthSpace;

sh
pnpm test:load      # ingress and pipeline load, worker scaling, GitHub rate limits (about 26 min)
pnpm test:chaos     # worker kills, Valkey and PostgreSQL restarts, model outage (about 8 min)
pnpm test:upgrade   # upgrade matrix

They need Docker and run nightly and on demand in .github/workflows/hardening.yml. Results are written to dist/hardening/*.json. LOAD_* and CHAOS_EVENTS environment variables shorten the profiles (see tests/load/src/profiles.ts).

The open core is released under the Apache License 2.0.