Стрес-тестування: визначення меж навантаження
Стрес-тест навмисно перевантажує систему за межами нормальної експлуатації для знаходження точки відмови. Відповідь на запитання: при якому RPS починають зростати помилки? Як система відновлюється після перевантаження? Де вузьке місце — БД, CPU, пам'ять, мережа?
Методологія
Крок 1: Визначити baseline. Запустити нормальне навантаження (50–70% від очікуваного піку) та зафіксувати метрики: p95 latency, error rate, CPU/memory.
Крок 2: Поступове збільшення. Підвищити навантаження кроками по 10–20% кожні 2–5 хвилин. Зафіксувати точку, де починають зростати помилки або latency.
Крок 3: Знайти breaking point. Продовжити до деградації (error rate > 5% або latency > 5x baseline).
Крок 4: Відновлення. Зняти навантаження та спостерігати, як швидко система повертається в норму.
k6 сценарій стрес-тесту
// tests/stress/breaking-point.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend, Counter } from 'k6/metrics'
const errorRate = new Rate('errors')
const requestsPerSecond = new Counter('requests_per_second')
export const options = {
stages: [
// Розігрів до нормального трафіку
{ duration: '2m', target: 50 },
{ duration: '3m', target: 50 }, // базовий рівень
// Поступове нарастання
{ duration: '2m', target: 100 },
{ duration: '3m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '3m', target: 200 },
{ duration: '2m', target: 400 },
{ duration: '3m', target: 400 },
{ duration: '2m', target: 800 },
{ duration: '3m', target: 800 },
{ duration: '2m', target: 1600 },
{ duration: '3m', target: 1600 },
// Охолодження та спостереження за відновленням
{ duration: '5m', target: 50 },
{ duration: '3m', target: 0 },
],
// Не переривати тест при перевищенні порогів — потрібно бачити повну картину
thresholds: {
http_req_duration: [
{ threshold: 'p(95)<2000', abortOnFail: false },
],
errors: [
{ threshold: 'rate<0.1', abortOnFail: false }
]
}
}
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
export default function() {
const responses = http.batch([
['GET', `${BASE_URL}/api/products?limit=20`],
['GET', `${BASE_URL}/api/categories`],
])
responses.forEach(r => {
check(r, { 'status 2xx': (r) => r.status >= 200 && r.status < 300 })
errorRate.add(r.status >= 400)
})
requestsPerSecond.add(2)
sleep(0.1)
}
export function handleSummary(data) {
// Знайти точку деградації з зібраних даних
const stages = analyzeStages(data)
return {
'stress-results.json': JSON.stringify(data, null, 2),
stdout: generateReport(stages)
}
}
function generateReport(stages) {
return `
=== STRESS TEST REPORT ===
Breaking Point Analysis:
${stages.map(s => ` VUs: ${s.vus} | p95: ${s.p95}ms | Errors: ${(s.errorRate*100).toFixed(1)}%`).join('\n')}
`
}
Моніторинг під час тесту
Запустити паралельно збір системних метрик:
#!/bin/bash
# scripts/monitor-stress-test.sh
TARGET_HOST="app-server-ip"
INTERVAL=10 # секунди
while true; do
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# CPU, Memory, Load Average
ssh $TARGET_HOST "
echo -n '$TIMESTAMP '
echo -n 'cpu:'; top -bn1 | grep 'Cpu(s)' | awk '{print \$2}'; echo -n ' '
echo -n 'mem:'; free | grep Mem | awk '{print \$3/\$2 * 100}'; echo -n ' '
echo -n 'load:'; cat /proc/loadavg | awk '{print \$1}'
echo -n 'conns:'; ss -s | grep -o 'estab [0-9]*' | awk '{print \$2}'
"
# PostgreSQL: активні запити та блокування
ssh $TARGET_HOST "
PGPASSWORD=pass psql -U app -d appdb -t -c \"
SELECT 'active_queries:', count(*) FROM pg_stat_activity
WHERE state = 'active' AND query NOT LIKE '%pg_stat%';
SELECT 'long_queries:', count(*) FROM pg_stat_activity
WHERE state = 'active' AND query_start < NOW() - interval '5 seconds';
SELECT 'locks:', count(*) FROM pg_locks WHERE NOT granted;
\"
"
sleep $INTERVAL
done | tee stress-monitor.log
Аналіз результатів з Prometheus + Grafana
# k6 з Prometheus Remote Write
k6 run \
-o experimental-prometheus-rw \
--env K6_PROMETHEUS_RW_SERVER_URL=http://prometheus:9090/api/v1/write \
--env K6_PROMETHEUS_RW_TREND_AS_NATIVE_HISTOGRAM=true \
tests/stress/breaking-point.js
# Запити в Grafana для аналізу стрес-тесту
# RPS у реальному часі
rate(k6_http_reqs_total[30s])
# Error rate по часу (знайти момент деградації)
rate(k6_http_req_failed_total[30s]) / rate(k6_http_reqs_total[30s])
# p95 latency у реальному часі
histogram_quantile(0.95, rate(k6_http_req_duration_seconds_bucket[30s]))
# Кореляція: навантаження vs latency vs помилки
Визначення вузького місця
# analyze_stress_results.py
import json
import pandas as pd
def analyze_breaking_point(results_file):
with open(results_file) as f:
data = json.load(f)
# Витягти часові ряди
metrics = data['metrics']
analysis = {
'max_rps_before_errors': find_max_sustainable_rps(metrics),
'error_threshold_rps': find_error_threshold(metrics),
'latency_degradation_point': find_latency_degradation(metrics),
'recovery_time_seconds': find_recovery_time(metrics),
}
print("=== Breaking Point Analysis ===")
print(f"Max sustainable RPS (< 1% errors): {analysis['max_rps_before_errors']}")
print(f"Error threshold RPS: {analysis['error_threshold_rps']}")
print(f"p95 > 1s at RPS: {analysis['latency_degradation_point']}")
print(f"Recovery time after load removal: {analysis['recovery_time_seconds']}s")
# Рекомендації
if analysis['max_rps_before_errors'] < 100:
print("\n[!] LOW capacity. Consider: DB connection pooling, caching, horizontal scaling")
elif analysis['recovery_time_seconds'] > 120:
print("\n[!] SLOW recovery. Consider: circuit breakers, graceful degradation")
return analysis
Типові вузькі місця та діагностика
| Симптом | Ймовірна причина | Діагностика |
|---|---|---|
| Latency зростає, CPU низька | Блокування БД або повільні запити | pg_stat_activity, slow query log |
| CPU 100%, мало помилок | Обчислювальний bottleneck | top, application profiler |
ENOMEM помилки |
Витік пам'яті або OOM | free -m, /proc/meminfo |
| Connection refused | Connection pool вичерпаний | pgBouncer stats, netstat |
| 502 Bad Gateway | Worker processes перевантажені | Nginx error log, worker_processes |
Часовий графік
Стрес-тест з поступовим профілем навантаження, моніторингом та аналізом breaking point — 2–3 робочих дні.







