Terminal.skills
Use Cases/Build an Automated API Performance Benchmarking Suite with AI

Build an Automated API Performance Benchmarking Suite with AI

Create and run comprehensive API performance benchmarks with automated analysis and regression detection.

Development#api#testing#rest#graphql#http
Works with:claude-codeopenai-codexgemini-clicursor
$

The Problem

Your API handles 10,000 requests per minute, but you have no systematic way to catch performance regressions before they hit production. A developer merges a PR that adds an ORM eager-load, and suddenly the /orders endpoint is 3x slower. Nobody notices until the largest customer opens a support ticket three days later — by which time the regression has been live for 72 hours and affected thousands of requests.

Load testing happens once a quarter — if at all. There's no baseline, no trend data, and no automated gate that says "this PR made things worse." When someone does investigate a performance complaint, they're guessing: "I think it was fast before" isn't a useful data point when the CEO is asking why the biggest customer is threatening to churn.

The team tried adding a few console.time calls and watching New Relic dashboards, but neither approach catches regressions before they ship. APM tools show you that something got slow; they don't tell you which PR caused it or prevent the next one. Performance is treated as something you fix after it breaks instead of something you protect continuously. The gap between "we think the API is fast" and "we can prove it" is the gap where regressions live.

The Solution

Using the api-tester, coding-agent, and data-analysis skills, the workflow creates a benchmark suite with realistic payloads for every critical endpoint, runs it against staging on every deployment, and flags regressions with specific root-cause analysis — not just "it's slow" but "there's an N+1 query on the line_items relation causing 50 extra database queries per request." The benchmark becomes a CI gate so regressions get caught at the PR stage, not in production.

The entire setup — from defining scenarios to CI integration — takes one session. After that, every PR gets a performance report automatically.

Step-by-Step Walkthrough

Step 1: Define Benchmark Scenarios

Start by identifying the endpoints that matter most:

Create a performance benchmark suite for our REST API. Critical endpoints:
- GET /api/v1/products (list, paginated, ~2000 products)
- GET /api/v1/products/:id (single product with relations)
- POST /api/v1/orders (create order with 3-5 line items)
- GET /api/v1/users/:id/orders (user order history, ~50 orders average)

Target: p50 < 100ms, p95 < 300ms, p99 < 500ms for all endpoints.

Those targets aren't arbitrary — p50 is what most users experience on most requests, p95 catches the slow tail that affects your heaviest users, and p99 reveals the worst-case scenarios that drive support tickets and erode trust. Setting all three creates a performance budget that's harder to game than a single average.

Step 2: Generate the Benchmark Scripts

The agent produces a complete benchmark suite with six files:

Generate a k6 benchmark suite for those four endpoints. Use realistic payloads from our seed data, include authentication, and set concurrency levels that match our production traffic patterns.

Here's the resulting suite under benchmarks/api-performance/:

FilePurpose
scenarios/products-list.yml100 concurrent users, 60s duration
scenarios/product-detail.yml200 concurrent users, 60s duration
scenarios/create-order.yml50 concurrent users, 60s duration, realistic payloads
scenarios/user-orders.yml100 concurrent users, 60s duration
run-benchmarks.shOrchestrator script that runs all scenarios and collects results
thresholds.jsonPass/fail criteria based on the p50/p95/p99 targets

Each scenario includes realistic payloads, authentication headers, and data setup — not just GET / with an empty body. The order creation scenario generates random line items with valid product IDs and quantity ranges that match production patterns. The user orders scenario uses accounts with varying order histories (5 to 200 orders) to test how the endpoint behaves at different data volumes. The concurrency levels are calibrated to stress the endpoint without overwhelming it — the goal is to measure latency under realistic load, not to find the breaking point.

Step 3: Run Against Staging

Run the full benchmark suite against our staging API at https://staging-api.example.com and show me the results.

The orchestrator script runs each scenario sequentially (to avoid interference between tests), collects timing data at p50/p95/p99 percentiles, and compares against the thresholds defined in Step 1. Each scenario warms up for 10 seconds before measurement begins — cold-start latency (JIT compilation, connection pool initialization, cache warming) is a separate concern from sustained performance. The warm-up ensures the benchmarks measure the experience users actually have, not the first-request-after-deploy experience that's always slower.

Results are saved as JSON artifacts that the trend dashboard (Step 5) consumes. Each run is tagged with the git commit SHA so regressions can be traced to a specific change.

Step 4: Analyze Results and Diagnose Failures

The results come back as a clear pass/fail table with throughput data:

Endpointp50p95p99ThroughputStatus
GET /products82ms245ms410ms1,230 req/sPASS
GET /products/:id24ms68ms142ms3,450 req/sPASS
POST /orders156ms380ms720ms312 req/sFAIL
GET /users/:id/orders210ms890ms1,420ms95 req/sFAIL

Two of four endpoints exceed thresholds. But the results don't stop at "FAIL" — the analysis digs into why:

POST /orders (p99: 720ms):

The bottleneck is a synchronous inventory check inside the request cycle. Every order creation hits the inventory service and waits for a response before committing the transaction. The p50 is fine at 156ms because the inventory service is usually fast, but the p99 spikes to 720ms when the inventory service is under load or experiencing network latency.

Recommendation: move inventory validation to an async step — validate optimistically at order creation, reconcile with a background job that catches edge cases (sold-out items, price changes). This pattern is standard in high-throughput e-commerce and would bring the p99 under 300ms.

GET /users/:id/orders (p99: 1,420ms):

Classic N+1 query. Each order in the response triggers a separate query for its line items. For a user with 50 orders, that's 51 database queries (1 for the orders list + 50 individual queries for line items). Adding eager loading for the line_items relation collapses those 51 queries into 2 and should cut response time by 10x.

The 95 req/s throughput confirms the database is the bottleneck — the server isn't CPU-bound, it's waiting on sequential queries. A single JOIN or IN query replaces 50 round-trips to the database.

Step 5: Track Performance Trends Over Time

One benchmark run tells you where you are. Trend data tells you where you're heading:

Create a chart showing p95 latency trends for all endpoints over the last 10 benchmark runs.

The time-series visualization makes regressions visually obvious — a flat line that suddenly spikes after a specific deployment tells you exactly which release introduced the problem, and the git log for that release tells you which PR to investigate. A gradual upward trend is equally valuable: it means the endpoint is degrading slowly as data grows, and you need to optimize before it crosses the threshold.

This is where benchmarking becomes genuinely useful: not as a one-time audit, but as a continuous signal that makes performance visible across the team. The chart also helps with capacity planning — if p95 for the products endpoint is climbing 5ms per month, you can project when it'll cross the 300ms threshold and optimize proactively instead of reactively.

Step 6: Add the CI Performance Gate

The final step is prevention:

Add the benchmark suite as a GitHub Actions step that runs on every PR targeting main. If any endpoint exceeds its threshold, fail the check and post a comment showing the regression.

The CI integration runs the benchmark suite against a staging deployment of the PR branch. If any endpoint's p95 or p99 exceeds the threshold, the check fails and a PR comment shows exactly which endpoint regressed and by how much:

Performance Regression Detected GET /users/:id/orders p95: 245ms -> 890ms (+263%, threshold: 300ms) Possible cause: query plan changed — check for missing indexes or removed eager loads

The developer sees this before the PR is even reviewed — and they fix it before it reaches production. The comment includes enough context to start debugging immediately, not just a red X that requires digging through logs.

This changes the team's relationship with performance. Instead of "we'll optimize later" (which means never), performance is part of the merge criteria alongside passing tests and code review approval. Regressions are caught by machines, not by customers.

Real-World Example

Kai is a backend engineer at a 25-person SaaS startup. The largest customer — $80K ARR — just complained about slow API responses and is evaluating competitors. Kai has no data to diagnose whether this is new or has been degrading for weeks. "It feels slow" is all anyone on the team can say, and "it feels slow" doesn't help when the customer wants a concrete answer.

He asks the agent to create benchmark scenarios for the 8 most-used endpoints based on access logs. The agent generates a complete suite with realistic payloads modeled after production traffic patterns — not synthetic data, but payload shapes and sizes that match what real users send.

Running against staging reveals the /search endpoint regressed from 120ms p95 to 890ms p95 after last week's deployment — a 7x slowdown that nobody noticed because there was no baseline to compare against. The agent traces the regression to a missing database index on a new column added in the search query. A developer added a WHERE category = ? filter but didn't create the corresponding index. One CREATE INDEX statement later, the endpoint is back to 95ms.

Kai adds the benchmark suite to the CI pipeline. Now every PR gets a performance gate, and the first regression it catches — a developer accidentally removing an eager-load while refactoring a model file — gets flagged in the PR comment before the reviewer even opens the diff. The developer adds the eager-load back, the benchmark passes, and the PR merges cleanly. The largest customer doesn't file another performance ticket, and the team has something they never had before: proof that the API is fast, updated with every deployment.