← Back to blog

Capacity Planning & Performance Testing: A Complete Walkthrough from Estimation to Launch

Capacity planning is not about "buying bigger servers." This article walks through the full decision chain: translating business targets into technical metrics, resource estimation for web/AI/database tiers, load testing methodology, and capacity decisions. With estimation formulas and test scripts. [See the full walkthrough →]

Bottom Line: The Goal of Capacity Planning Is Not “Never Go Down” — It’s Knowing When You Will

Capacity planning and performance testing are the most overlooked step in “prevention” — not because people do not know they are important, but because “a quick estimate” sounds too easy, and “testing takes too long, let us ship first” is too tempting.

But a significant portion of production incidents trace back to one root cause: the system was never validated under real-world load.

This article skips theory and starts from a concrete scenario: you are launching a new project projected to have 100,000 registered users in the first month, roughly 5,000 DAU, and a peak of 2,000 concurrent users. How many servers do you need? What database configuration? How do you verify it can handle the load?


Step 1: Translate Business Metrics Into Technical Metrics

Before touching any server, translate business language into technical language.

Business MetricConversionTechnical Metric
100K registered users≈ 10% DAU conversion10,000 DAU
5,000 DAU30 requests/user/day150,000 requests/day
2,000 peak concurrentFactor of 5Peak QPS ≈ 350
30% AI features50% users × 3 uses/dayAI inference peak ≈ 50 QPS

Baseline formula:

Average QPS = Daily requests / 86400 ≈ 1.7 QPS
Peak QPS = Average QPS × peak factor (5-8) ≈ 350 QPS

This is your technical target: the system must handle 350 QPS stably without degradation.


Step 2: Resource Estimation by Layer

Web Service Layer

A single 4C8G cloud server can typically handle 200-400 QPS for lightweight requests. But here is a common estimation trap: ignoring slow queries and third-party dependencies.

Better estimation:

Per-instance capacity = 1000ms / (avg processing time × serial dependencies)

If your API averages 20ms (including DB query + cache) with 2 downstream calls:

Capacity ≈ 1000 / (20 × 2) ≈ 25 QPS per connection

For 350 QPS, two 4C8G servers in active-standby are sufficient. Key insight: 90% of bottlenecks are not in the web server itself — they are in the downstream database and dependencies.

Database Layer

This is where capacity planning goes wrong most often. A common mistake: “My table only has 100K rows — how could a SELECT be slow?”

Consider this query:

SELECT orders.*, users.name, products.title
FROM orders
JOIN users ON orders.user_id = users.id
JOIN products ON orders.product_id = products.id
WHERE orders.status = 'pending'
ORDER BY orders.created_at DESC
LIMIT 20;

With 100K rows, a three-table JOIN, and no suitable indexes — a single query can take 500ms+. At 350 QPS with a 50-connection pool, slow queries will drain the pool quickly.

Three principles for database estimation:

  1. Measure each query independently — do not assume “this query should be fast.” Run EXPLAIN.
  2. Account for connection pools — 350 QPS requires evaluating pool size and per-connection processing time.
  3. Plan for write amplification — one INSERT may trigger index updates, triggers, and CDC replication.

For 350 QPS, recommended starting point: 8C16G database instance (PG/MySQL), connection pool 50-80, with Redis caching.

AI Inference Layer (if applicable)

AI inference capacity planning differs fundamentally from traditional web serving — the bottleneck is GPU memory and compute, not CPU and RAM.

Estimation flow:

Memory per request = model size + KV Cache overhead
Max concurrency per GPU = (available memory - model size) / KV Cache per request × 0.65 safety

Throughput (Token/s) = GPU compute / model parameters × batch size

For a typical 14B model (FP16 ~28GB):

  • Single A100 (80GB): theoretical max ≈ (80 - 28) / 2×128K ≈ 200 concurrent (theoretical, far lower in practice)
  • Realistic stable concurrency: 40-60
  • 500 tokens per request → ~3-5 seconds at 40 concurrency

AI capacity planning cannot be linearly scaled by adding more GPUs — it requires accounting for memory bandwidth, inter-card communication efficiency, and batch scheduling strategy. The only reliable validation is load testing with real data and real models.


Step 3: Load Testing — Validate Your Estimates

Estimates are done. How do you know they are correct? Load testing.

The Testing Pyramid

         /   Full-chain (end-to-end)
        /   Module-level (single service)
       /   Component-level (single endpoint/query)

Bottom-up: test each endpoint and each query independently before full-chain testing. Otherwise, one slow query can tank the entire test without telling you where to optimize.

Tool Selection

ScenarioRecommended ToolWhy
HTTP APIwrk or k6wrk is lightweight for single-node testing; k6 supports scenarios and metrics output
Databasepgbench / sysbenchDirectly simulates query load without network overhead
AI inferencevLLM benchmark_serving.pyOfficial tool supporting multiple request rates and concurrency modes
Full-chaink6 + influxdb or locustk6 is more scriptable; locust suits teams in the Python ecosystem

A Complete Load Test Workflow

# 1. Baseline: 100 QPS, 3 minutes
k6 run --vus 50 --duration 3m --rps 100 script.js

# 2. Limit test: gradually increase, find the breaking point
k6 run --vus 200 --duration 5m --rps 500 script.js

# 3. Endurance: sustained target QPS for 30 minutes
k6 run --vus 100 --duration 30m --rps 350 script.js

# 4. Burst: simulate instantaneous traffic spike
k6 run --vus 200 --duration 1m --rps 700 script.js

Four metrics you must track:

  • P50 / P95 / P99 latency — flag if P99 exceeds 1000ms more than 5% of the time
  • Error rate — any 5xx is a problem; check if 4xx are business logic rate limits
  • CPU and memory trends — a healthy system plateaus under sustained load; continuous growth indicates a leak
  • DB connections and slow queries — database state under load reveals real issues most effectively

Step 4: From Results to Capacity Decisions

Tests are done. How do you interpret the results?

Results Interpretation Matrix

Test OutcomeLikely CauseSolution
QPS far below target, CPU idleI/O bottleneck (DB, network, disk)Check slow queries, add indexes, use connection pool, upgrade IOPS
QPS near target but P99 degrades sharplyResource contention (pool exhaustion, lock contention)Increase connections, optimize lock granularity, introduce read replicas
Errors start after 10 minutes of sustained loadMemory leak or GC stormCheck heap trends, find unclosed resources, tune GC
Instant 5xx under burst trafficNo rate limitingAdd server-side rate limiting (token bucket / leaky bucket), degrade non-critical features
DB CPU at 100%, queries 10× slowerQuery plan degraded due to data changesANALYZE to update statistics, rewrite queries, add indexes or change partition key

Pre-Launch Capacity Checklist

  • Target peak QPS defined and verified with 30% headroom?
  • Single point of failure mitigated? At least 2-node standby?
  • Database connection pool sized appropriately? 50-80 connections for 350 QPS?
  • Caching strategy in place? Expected cache hit rate for hot data?
  • Rate limiting and degradation implemented? Defined behavior when traffic exceeds targets?
  • Scale-up plan documented? How long to add a new instance?
  • AI inference: TTFT and TPOT within expected range? GPU memory utilization stable?

Step 5: Gradual Rollout — Validate with Real Traffic

Load testing, no matter how realistic, is still simulation. Go-live traffic is the ultimate validation.

Recommended rollout strategy:

5% → 20% → 50% → 100%

Observe 30 minutes to 2 hours at each stage before proceeding. If P99 exceeds 1000ms at any stage, pause and investigate.

Focus on three metrics on launch day:

  1. Error rate — any upward trend requires immediate attention
  2. P99 latency — should stabilize under 500ms
  3. Database connection count — should stay within the expected range

Summary

Capacity planning is not about calculating “exact numbers” — it is about building a closed loop from estimation to validation:

Business metrics → Technical metrics → Resource estimation → Load testing → Capacity decisions → Gradual rollout

Run this cycle for every new system or major feature. Not every change needs full-chain testing — small changes can rely on estimation and endpoint tests. But for changes involving AI inference, database schema changes, or new external dependencies, full-chain load testing is essential.

You will not miss a launch because you spent time on load testing. You will only miss a launch because you had a production incident you did not prepare for.


Designing system capacity? Need a complete load testing plan? Contact us for a free consultation.

Related reading:

FAQ

When should capacity planning start?

At least three weeks before launch. Week 1: estimation and selection (determine target QPS and resource baseline). Week 2: set up the environment and write load test scripts. Week 3: execute tests and optimize. If you start thinking about capacity the week before go-live, it is already too late — you may need code changes, new indexes, or parameter tuning, all of which take time.

What QPS target should I set for load testing?

Target 3-5× your expected peak traffic. Without historical data, derive it from your business model: DAU × daily requests per user ÷ 86400 × peak factor. The peak factor should be 5-8 depending on business type (e-commerce: 8, SaaS: 5, internal enterprise: 3). Test not only sustained QPS but also burst QPS (instant 2× spike) and endurance (30+ minutes at target load).

Why is real-world performance so different from estimates?

Three common causes: ① Queries are not O(1) — you estimated 2ms per SELECT, but the actual query JOINs 5 tables and takes 200ms. ③ Cold vs hot state — a freshly restarted service needs tens of seconds to warm up before reaching peak performance. ② Third-party dependencies bottleneck — your API is fast but the downstream Elasticsearch cluster rate-limits. Mitigation: be pessimistic in estimates (×3 for each dependency), and cover both cold and hot states in testing.

What is special about capacity planning for AI inference?

AI inference amplifies capacity planning complexity: GPU memory determines "whether it can run" while compute determines "how fast" — and the two are coupled. Key metrics are TTFT (Time to First Token) and TPOT (Time Per Output Token). Estimation: single-GPU concurrency = (available memory − model size) ÷ KV Cache per request. Realistic stable concurrency is typically only 60-70% of the theoretical value. Always validate with real benchmarking tools like vLLM's benchmark_serving.py rather than relying on theoretical calculations.

This article comes from AI Enable Harness front-line delivery practice. Need a similar system or optimization service?

Subscribe to Updates

Get notified when new articles are published. No spam, occasional updates only.

Subscribe →