Plantilla de Load Test Plan
Plantilla para planear y documentar load tests: test scenarios, user journey definitions, ramp-up strategies, success criteria, monitoring setup, tool selection (k6, JMeter, Locust), result analysis y reporting con ejemplos de codigo para cada tool.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Overview
Esta plantilla define un structured load test plan. Cubre test scenarios, user journey definitions, ramp-up strategies, success criteria, monitoring setup, tool selection (k6, JMeter, Locust), result analysis y reporting. Usa esta plantilla antes de correr load tests en un new service o antes de un major release.
1. Test Objectives
1.1 Goal Definition
Objective | Question Answered
─────────────────────────────┼──────────────────────────────────────
Baseline performance | Cual es current throughput y latency?
Capacity validation | Podemos handleer expected peak traffic?
Bottleneck identification | Donde breakea el system first?
Scalability validation | Funciona horizontal scaling como expected?
Regression detection | Degrado el latest deploy el performance?
Endurance / soak | Leakea memory over 24h de load?
Spike readiness | Podemos sobrevivir un 10x traffic spike?
1.2 Scope Definition
In scope | Out of scope
──────────────────────────────────┼──────────────────────────────────
API endpoints under test | Third-party API load testing
Database read/write performance | Browser rendering performance
Cache hit/miss behavior | Network latency simulation
Connection pool sizing | Security / penetration testing
Rate limiting behavior | UI / E2E testing
2. User Journey Definitions
2.1 Journey Mapping
Journey name | Steps | % of traffic | Requests/user
──────────────────┼──────────────────────────────────────────┼──────────────┼──────────────
Browse homepage | GET / → GET /api/products | 40% | 2
View product | GET / → GET /products/{id} → GET /api/reviews | 25% | 3
Search | GET /search?q={query} → GET /api/results | 20% | 2
Add to cart | View product → POST /api/cart → GET /api/cart | 10% | 4
Checkout | Add to cart → POST /api/checkout → POST /api/payment | 5% | 6
2.2 Test Data Requirements
Data type | Source | Volume needed | Notes
──────────────────┼─────────────────────┼──────────────────┼──────────────────
Product IDs | Production snapshot | 10000 | Usa real IDs
User accounts | Test data generator | 1000 | Pre-created accounts
Search queries | Query log sample | 500 | Realistic distribution
Payment tokens | Stripe test tokens | 100 | Usa test environment
Session tokens | Auth API | Per-test | Genera durante setup
3. Load Profiles
3.1 Ramp-Up Strategies
Profile type | Pattern | Use case
────────────────┼──────────────────────────────────┼──────────────────────────
Linear ramp | +10 users/sec until target | Baseline, capacity test
Step load | 50 → 100 → 200 → 500 (hold 5m) | Bottleneck identification
Spike | 0 → 1000 in 10s, hold 2m | Spike readiness, autoscaling
Soak | 200 users for 24h | Memory leaks, resource exhaustion
Stress | Increase until system fails | Find breaking point
3.2 Load Profile Configuration
Test phase | Duration | Users | Ramp rate | Purpose
────────────────┼──────────┼───────┼───────────┼──────────────────────
Warmup | 2 min | 10 | 5/sec | JIT compilation, caches
Baseline | 10 min | 50 | 5/sec | Normal load baseline
Ramp-up | 5 min | 200 | 30/sec | Peak traffic simulation
Peak hold | 15 min | 200 | - | Sustained peak performance
Ramp-down | 2 min | 0 | -/sec | Graceful shutdown
Cooldown | 2 min | 0 | - | Observe recovery
4. Success Criteria
4.1 Performance Thresholds
Metric | Target | Hard limit | Failure action
──────────────────────────┼─────────────┼──────────────┼──────────────────
P50 response time | < 100ms | < 200ms | Investiga
P95 response time | < 300ms | < 500ms | Investiga
P99 response time | < 500ms | < 1000ms | Investiga
Error rate | < 0.1% | < 1% | Para el test
Throughput (req/sec) | > 500 | > 300 | Investiga
CPU usage | < 70% | < 90% | Scalea up
Memory usage | < 75% | < 90% | Investiga leaks
DB connection pool usage | < 60% | < 80% | Increase pool
4.2 Pass/Fail Criteria
Result | Conditions
─────────┼──────────────────────────────────────────────────────────
PASS | All metrics within target thresholds
PASS | P95 within hard limit, P99 slightly over (documented)
FAIL | Any metric exceeds hard limit
FAIL | Error rate > 1% en any point durante el test
FAIL | System crashea o becomes unresponsive
INCONCL. | Test infrastructure issues preventieron valid results
5. Tool Selection
5.1 Tool Comparison
Tool | Language | Protocol support | Distributed | Scripting | Best for
────────┼────────────┼─────────────────────────┼─────────────┼───────────┼──────────────
k6 | JavaScript | HTTP, gRPC, WebSocket | Yes (k6 cloud) | JS/ES6 | API testing, CI/CD
JMeter | Java | HTTP, JDBC, JMS, SMTP | Yes (master/slave) | XML/Groovy | Enterprise, protocol variety
Locust | Python | HTTP, custom | Yes (master/worker) | Python | Custom protocols, flexibility
Artillery | Node.js | HTTP, WebSocket, Socket.io | Yes (AWS) | YAML/JS | Quick API tests
Gatling | Scala | HTTP, WebSocket | Yes | Scala DSL | High throughput, detailed reports
5.2 k6 Script Example
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const responseTime = new Trend('response_time');
export const options = {
stages: [
{ duration: '2m', target: 10 },
{ duration: '10m', target: 50 },
{ duration: '5m', target: 200 },
{ duration: '15m', target: 200 },
{ duration: '2m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<500'],
http_req_failed: ['rate<0.01'],
errors: ['rate<0.001'],
},
};
export default function () {
group('Browse homepage', function () {
const homeRes = http.get('https://staging.example.com/');
check(homeRes, {
'homepage status 200': (r) => r.status === 200,
'homepage LCP < 2.5s': (r) => r.timings.waiting < 2500,
});
errorRate.add(homeRes.status !== 200);
responseTime.add(homeRes.timings.duration);
sleep(1);
});
group('View product', function () {
const productId = Math.floor(Math.random() * 10000) + 1;
const productRes = http.get(`https://staging.example.com/api/products/${productId}`);
check(productRes, {
'product status 200': (r) => r.status === 200,
'product has name': (r) => r.json('name') !== undefined,
});
errorRate.add(productRes.status !== 200);
responseTime.add(productRes.timings.duration);
sleep(2);
});
group('Search', function () {
const queries = ['laptop', 'phone', 'headphones', 'keyboard', 'mouse'];
const query = queries[Math.floor(Math.random() * queries.length)];
const searchRes = http.get(`https://staging.example.com/api/search?q=${query}`);
check(searchRes, {
'search status 200': (r) => r.status === 200,
'search returns results': (r) => r.json('results').length > 0,
});
errorRate.add(searchRes.status !== 200);
responseTime.add(searchRes.timings.duration);
sleep(1.5);
});
}
5.3 JMeter Test Plan (XML)
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Load Test Plan">
<stringProp name="TestPlan.comments">Peak load test — 200 users</stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Users">
<intProp name="ThreadGroup.num_threads">200</intProp>
<intProp name="ThreadGroup.ramp_time">300</intProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.duration">1800</stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy testname="Homepage">
<stringProp name="HTTPSampler.domain">staging.example.com</stringProp>
<stringProp name="HTTPSampler.path">/</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<hashTree>
<ResponseAssertion testname="Status 200">
<collectionProp name="Asserion.test_strings">
<stringProp>200</stringProp>
</collectionProp>
</ResponseAssertion>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
5.4 Locust Script (Python)
from locust import HttpUser, task, between, events
class WebsiteUser(HttpUser):
wait_time = between(1, 3)
host = "https://staging.example.com"
@task(40)
def browse_homepage(self):
with self.client.get("/", catch_response=True) as response:
if response.status_code != 200:
response.failure(f"Homepage failed: {response.status_code}")
@task(25)
def view_product(self):
product_id = random.randint(1, 10000)
with self.client.get(f"/api/products/{product_id}", catch_response=True) as response:
if response.status_code != 200:
response.failure(f"Product failed: {response.status_code}")
elif response.elapsed.total_seconds() > 0.5:
response.failure("Product too slow")
@task(20)
def search(self):
queries = ['laptop', 'phone', 'headphones', 'keyboard', 'mouse']
query = random.choice(queries)
with self.client.get(f"/api/search?q={query}", catch_response=True) as response:
if response.status_code != 200:
response.failure(f"Search failed: {response.status_code}")
@task(10)
def add_to_cart(self):
product_id = random.randint(1, 10000)
with self.client.post("/api/cart",
json={"product_id": product_id, "quantity": 1},
catch_response=True
) as response:
if response.status_code != 201:
response.failure(f"Cart failed: {response.status_code}")
@task(5)
def checkout(self):
with self.client.post("/api/checkout",
json={"payment_token": "test_token", "address_id": 1},
catch_response=True
) as response:
if response.status_code not in [200, 201]:
response.failure(f"Checkout failed: {response.status_code}")
6. Monitoring Setup
6.1 Metrics para Collectar
Layer | Metric | Tool
─────────────┼───────────────────────────┼──────────────────────────
Application | Response time percentiles | k6/JMeter/Locust built-in
Application | Error rate | k6/JMeter/Locust built-in
Application | Throughput (req/sec) | k6/JMeter/Locust built-in
Server | CPU usage | Prometheus + node_exporter
Server | Memory usage | Prometheus + node_exporter
Server | Disk I/O | Prometheus + node_exporter
Database | Active connections | pg_stat_activity / SHOW PROCESSLIST
Database | Query latency | pg_stat_statements / slow query log
Database | Lock waits | pg_locks / information_schema
Cache | Hit rate | Redis INFO / Memcached stats
Cache | Evictions | Redis INFO / Memcached stats
Network | Bandwidth | iftop / CloudWatch
6.2 Grafana Dashboard Panels
Panel name | Query (Prometheus)
────────────────────────┼──────────────────────────────────────────
Request rate | rate(http_requests_total[1m])
P95 latency | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
Error rate | rate(http_requests_total{status=~"5.."}[1m]) / rate(http_requests_total[1m])
CPU usage | 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)
Memory usage | node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes
DB connections | pg_stat_activity_count
Cache hit rate | redis_keyspace_hits_total / (redis_keyspace_hits_total + redis_keyspace_misses_total)
7. Result Analysis and Reporting
7.1 Report Template
## Load Test Report — {Service Name}
**Date:** 2026-07-04
**Tester:** {Name}
**Environment:** Staging (replica of production)
**Tool:** k6 0.50.0
### Test Configuration
| Parameter | Value |
|-----------------|----------------|
| Max users | 200 |
| Duration | 34 min |
| Ramp-up | 5 min to 200 |
| Peak hold | 15 min at 200 |
### Results Summary
| Metric | Target | Actual | Status |
|---------------------|---------|---------|--------|
| P50 response time | < 100ms | 45ms | PASS |
| P95 response time | < 300ms | 180ms | PASS |
| P99 response time | < 500ms | 420ms | PASS |
| Error rate | < 0.1% | 0.02% | PASS |
| Throughput | > 500 | 680 rps | PASS |
| Peak CPU | < 70% | 65% | PASS |
| Peak memory | < 75% | 68% | PASS |
### Bottlenecks Identified
1. Database connection pool saturated at 180 users (pool size: 20)
- Fix: Increase pool size to 40 or add PgBouncer
2. Search API P95 spikes at 200 users (180ms → 350ms)
- Fix: Add Elasticsearch for search instead of PostgreSQL ILIKE
3. Redis eviction rate increases at peak (0 → 15/sec)
- Fix: Increase Redis maxmemory or add cache warming
### Recommendations
1. Increase DB connection pool to 40 before next traffic event
2. Migrate search to Elasticsearch (tracked in JIRA-1234)
3. Add autoscaling rule: scale at 70% CPU, not 80%
4. Re-run load test after fixes to verify improvements
Preguntas Frecuentes
¿Cuál es la diferencia entre load testing y stress testing?
Load testing valida que el system handlee expected production traffic within performance targets. Simulas realistic user loads (e.g., 200 concurrent users) y verifyeas que response times, error rates y resource usage se queden within thresholds. Stress testing pushea el system mas alla de expected capacity para find su breaking point — sigues increaseando load hasta que errors spiken o el system crashea. Load testing answer “podemos handleer nuestro expected traffic?” Stress testing answer “que pasa cuando lo exceedemos?” Corre load tests antes de every major release. Corre stress tests quarterly o antes de major traffic events (Black Friday, product launches) para entender tu system’s limits y autoscaling behavior.
¿Cuántos concurrent users deberia testear?
Empieza con tu current peak traffic — checkea analytics para el highest concurrent user count en los last 30 days. Testea a 1x (current peak), 2x (expected growth) y 5x (stress scenario). Si no tienes analytics data, estima desde request volume: si gets 100,000 requests per hour, eso es roughly 28 req/sec, que translates a about 50-100 concurrent users dependiendo del journey length. Para un new service sin traffic data, empieza con 100 users y scalea up. Siempre testea en un environment que mirror production — testear en un scaled-down staging environment da misleading results.
¿Qué load testing tool deberia elegir?
Para API testing con CI/CD integration, k6 es el best choice — scriptea en JavaScript, tiene built-in thresholds para pass/fail y integra con GitHub Actions. Para enterprise environments con diverse protocols (JMS, SMTP, JDBC), JMeter tiene el broadest protocol support y un GUI para non-developers. Para custom protocols o cuando needeas full programming flexibility, Locust (Python) es ideal. Para quick HTTP API tests, Artillery (Node.js) tiene el simplest YAML-based configuration. Considera tu team’s language expertise — un Python team estara mas comodo con Locust, un JavaScript team con k6. All four tools soportan distributed testing para high-load scenarios.
¿Cómo simulo realistic user behavior?
Mappea real user journeys desde tu analytics — que porcentaje de users browsean, searchean, view products, checkeout? Asigna weights a cada journey en tu test script (k6: @task(40), Locust: @task(40)). Addea think time entre requests (1-5 seconds) — real users no sendean requests back-to-back. Usa realistic test data — real product IDs, real search queries desde tu query log, no random strings. Varia el data per request para que caches no skween results. Incluye authentication — most production traffic es authenticated. Testea el full journey, no solo individual endpoints — el system behavior bajo un sequence de requests difiere de isolated calls. Si tienes session management, testea session creation y expiry.
¿Qué deberia hacer si el load test failea?
Primero, determina cual threshold se excedio. Si error rate es high, checkea server logs para exceptions y database logs para connection issues. Si response times son high, checkea CPU y memory usage durante el test — si CPU hitteo 90%, el server esta under-provisioned. Si database connections estan exhausted, increasea el pool size. Si el system crasheo, checkea para memory leaks (corre un soak test) o resource exhaustion. Documenta el failure en el test report con el specific metric, el actual value y el root cause. Fixea el issue y re-corre el test. No lowerees thresholds para hacer un faileando test pasar — eso defeat el purpose. Si el failure es due a test infrastructure (no el application), markea el result como inconclusive y re-corre con fixed infrastructure.
See Also
Recursos Relacionados
Performance Budget Template
Template for defining and enforcing web performance budgets: LCP, INP, CLS targets, resource budgets for JS, CSS, images, fonts, third-party scripts, CI/CD integration with Lighthouse CI, and alerting thresholds with examples for Next.js, Astro, and SPA architectures.
DocCore Web Vitals Audit Checklist
Checklist for auditing Core Web Vitals per page: LCP optimization steps, INP interaction tuning, CLS layout stability fixes, field data vs lab data analysis, CrUX integration, and remediation tracking with code examples for images, fonts, JavaScript, and CSS.
DocDatabase Query Tuning Checklist
Checklist for systematic SQL query optimization: EXPLAIN plan analysis, index strategy, N+1 query detection, join optimization, pagination patterns, connection pooling, query caching, and slow query log triage with examples for PostgreSQL and MySQL.