Scope: all three services running in AWS staging (
api-staging, face-engine-staging, registry-staging), tested with real HTTP traffic via k6, not synthetic benchmarks or local containers. Staging runs fixed capacity with no autoscaling (see terraform/variables.tf — no aws_appautoscaling_target exists anywhere in the stack), so these numbers are the actual ceiling of the deployed infrastructure as it exists today, not a theoretical estimate.Methodology
Each service got its own staged ramp — VU (virtual user) count stepped up every 20-45s rather than jumping straight to peak load, so the point where each service starts degrading is visible in the data, not just the final collapse. Stage sizes were chosen per-service based on its actual allocated capacity (see each service’s own load-testing page for the exact stages):- face-engine’s results — 2 → 30 VUs, real
/v1/embedinference calls with a real JPEG - registry-simulator’s results — 2 → 40 VUs, mix of health/NIN-lookup/search
- api-staging (this service) — 5 → 100 VUs, mix of
/health,/models,/auth/otp/request
GET /health + aws ecs describe-services) before moving to the next — no service was left in a degraded state.
Summary
The most notable pattern across all three: none of them were CPU- or memory-bound at the point they degraded. CPUUtilization and MemoryUtilization (pulled from CloudWatch for the exact test windows) stayed well under 40% everywhere, even as latency and error rates climbed sharply. This means the current fix for “add more capacity” is not simply “give the ECS tasks more CPU” — the real bottlenecks are elsewhere (see each service’s own page for specifics).
api-staging: full results
Stages: 5 → 20 → 50 → 100 VUs over ~3m15s, mixedGET /health (50%), GET /models (25%), POST /auth/otp/request with a randomized phone per iteration (25%, to avoid the per-phone rate limiter from dominating the results).
api_desired_count = 2) stayed up the whole time; no restarts, no OOM kills.
Read as: the API absorbs load gracefully rather than shedding it — a real production incident here would look like “everything is slow” rather than “things are failing,” which is a reasonable failure mode but still worth fixing before real traffic, since a 42-second worst case is a timeout in most client SDKs regardless of the eventual 200. The low CPU/memory at peak points at something other than raw compute — likely the size of the Postgres/Redis connection pools, or /models’s synchronous per-request proxy call to face-engine’s /v1/version (2s timeout) becoming a queuing point under concurrent load.
What this doesn’t cover
- No auth-token-bearing endpoints were load tested (
/identity/*,/verify/*,/users/*) — they need a real enrolled user and a valid session per request, which the current script doesn’t set up. The/health//models//auth/otp/requestmix tested here exercises the HTTP/routing/DB-connection layer but not the face-comparison or vector-search code paths under load. - No sustained (soak) test — every run here is a single ramp-up-and-down over a few minutes, not hours. Whether these services stay stable under sustained moderate load (as opposed to a short spike) is untested.
- No multi-service concurrent load — each service was tested in isolation, sequentially. Real traffic would hit all three simultaneously (api calling face-engine and registry-simulator on every
/identify), which could compound each service’s individual bottleneck.