Soak-тестування: виявлення витоків пам'яті та деградації

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Soak-тестування: виявлення витоків пам'яті та деградації
Складний
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    956
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Ви запускаєте сервіс у production. Через 12 годин він падає з OOM — пам'ять витекла. На коротких навантажувальних тестах (5–10 хвилин) усе було чисто. Знайома ситуація? Це типовий сценарій, де потрібен soak-тест. Ми, інженери True Tech, проектуємо тривалі тести, щоб виявити приховані деградації до того, як вони відбудуться у вас на production. Один наш клієнт втратив 8 годин роботи через невиявлений витік пам'яті в Node.js застосунку — після soak-тесту ми знайшли його за перші 3 години аналізу.

Soak test (він же endurance test) — це запуск системи під нормальним або помірним навантаженням протягом 4–24 годин. Він виявляє проблеми, які не проявляються за хвилини: витоки пам'яті, накопичення файлових дескрипторів, деградацію пула з'єднань БД, зростання повільних запитів через накопичення даних у таблицях. Без soak-тестування ви ризикуєте отримати нез'ясовані збої після кількох годин роботи.

Чому soak-тест виявляє витоки пам'яті?

Витоки пам'яті — класичний ефект «повільного кипіння». Застосунок зростає по пам'яті на 100–200 MB/год і через 12 годин падає з OOM. Короткі тести просто не встигають помітити ріст. Soak з моніторингом RSS та heap дозволяє зафіксувати лінійний тренд і спрогнозувати момент відмови. Згідно з документацією k6, soak-тести тривалістю 8 годин дозволяють виявити 90% витоків пам'яті, не виявлених при коротких тестах.

Soak-тест у 10 разів ефективніший за короткі тести для пошуку витоків пам'яті — це підтверджується нашою практикою на 50+ проектах.

Які проблеми виявляє soak-тест?

  • Витоки пам'яті: застосунок зростає по пам'яті на 100–200 MB/год і через 12 годин падає з OOM.
  • Connection pool exhaustion: з'єднання з БД не повертаються в пул, через 6 годин pool вичерпано — нові запити чекають до таймауту.
  • Накопичення в heap: JVM/Node.js GC справляється перші 2 години, потім паузи Full GC починають впливати на latency.
  • Зростання таблиць без autovacuum: PostgreSQL bloat — після мільйона операцій UPDATE/DELETE продуктивність деградує без vacuum.
  • File descriptor leak: кожен запит відкриває лог-файл або сокет і не закриває — через 8 годин ulimit вичерпано.
Тип тесту Тривалість Мета Виявляє
Load test 10–30 хв Перевірка під очікуваним навантаженням Пропускна здатність, час відповіді
Stress test 5–15 хв Перевірка під піковим навантаженням Точка відмови, помилки при перевантаженні
Soak test 4–24 год Перевірка під помірним навантаженням Витоки пам'яті, деградація, накопичувальні помилки

Як ми проводимо soak-тест: процес та інструментарій

Етапи проведення

  1. Аналітика: збираємо профіль навантаження вашого production (traffic, ендпоінти, сценарії).
  2. Проєктування сценарію: пишемо k6-скрипти з реалістичним міксом запитів (читання, запис, пошук).
  3. Налаштування моніторингу: підключаємо збір метрик пам'яті, файлових дескрипторів, БД (PostgreSQL, MySQL).
  4. Запуск тесту: на staging-стенді з 8-годинним вікном.
  5. Аналіз трендів: будуємо регресію RSS, P95 latency, dead tuple ratio; шукаємо статистично значущий ріст.
  6. Підготовка звіту: візуалізація деградації, рекомендації по фіксу коду та конфігурації.

Приклад k6-сценарію

// tests/soak/endurance.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend, Gauge } from 'k6/metrics'

const errorRate = new Rate('errors')
const p95Latency = new Trend('p95_latency_trend', true)
const activeUsers = new Gauge('active_users')

export const options = {
  stages: [
    { duration: '5m',  target: 50 },   // розігрів
    { duration: '8h',  target: 50 },   // 8 годин нормального навантаження
    { duration: '5m',  target: 0 },    // охолодження
  ],

  thresholds: {
    // Latency не повинна деградувати протягом тесту
    http_req_duration: ['p(95)<600'],

    // Помилок не повинно бути взагалі (витоки проявляються через помилки)
    errors: ['rate<0.001'],

    // Час підключення до БД не повинен зростати
    http_req_connecting: ['p(95)<50'],
  }
}

const BASE_URL = __ENV.BASE_URL || 'https://staging.example.com'

export function setup() {
  const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
    email: '[email protected]',
    password: __ENV.TEST_PASSWORD
  }), { headers: { 'Content-Type': 'application/json' } })

  return { token: res.json('token') }
}

export default function(data) {
  const headers = {
    'Authorization': `Bearer ${data.token}`,
    'Content-Type': 'application/json'
  }

  activeUsers.add(1)

  // Мікс операцій, типових для реального трафіку
  const scenario = Math.random()

  if (scenario < 0.6) {
    // 60%: читання даних
    const r = http.get(`${BASE_URL}/api/products?page=${Math.ceil(Math.random() * 50)}`,
      { headers })
    check(r, { 'read: 200': (r) => r.status === 200 })
    errorRate.add(r.status !== 200)

  } else if (scenario < 0.8) {
    // 20%: запис даних (створюємо реальні записи)
    const r = http.post(`${BASE_URL}/api/cart/items`, JSON.stringify({
      productId: Math.ceil(Math.random() * 1000),
      quantity: 1
    }), { headers })
    check(r, { 'write: 2xx': (r) => r.status < 300 })
    errorRate.add(r.status >= 400)

  } else if (scenario < 0.9) {
    // 10%: пошук
    const r = http.get(`${BASE_URL}/api/search?q=test&limit=20`, { headers })
    check(r, { 'search: 200': (r) => r.status === 200 })
    errorRate.add(r.status !== 200)

  } else {
    // 10%: профіль користувача
    const r = http.get(`${BASE_URL}/api/me`, { headers })
    check(r, { 'profile: 200': (r) => r.status === 200 })
    errorRate.add(r.status !== 200)
  }

  // Додати p95 для часового ряду
  p95Latency.add(http.get(`${BASE_URL}/api/health`).timings.duration)

  sleep(Math.random() * 2 + 0.5)  // 0.5–2.5 секунди між запитами
}

Моніторинг витоків пам'яті

В паралель з k6 запускаємо скрипт моніторингу RSS та файлових дескрипторів:

#!/bin/bash
# scripts/memory-soak-monitor.sh
APP_PID=$(pgrep -f "node server.js")
LOG_FILE="soak-memory-$(date +%Y%m%d-%H%M).csv"

echo "timestamp,rss_mb,heap_used_mb,heap_total_mb,external_mb,fd_count" > $LOG_FILE

while true; do
  TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  METRICS=$(curl -s http://localhost:3000/metrics/memory)
  RSS=$(echo $METRICS | jq -r '.rss')
  HEAP_USED=$(echo $METRICS | jq -r '.heapUsed')
  HEAP_TOTAL=$(echo $METRICS | jq -r '.heapTotal')
  EXTERNAL=$(echo $METRICS | jq -r '.external')
  FD_COUNT=$(ls /proc/$APP_PID/fd 2>/dev/null | wc -l)
  echo "$TS,$RSS,$HEAP_USED,$HEAP_TOTAL,$EXTERNAL,$FD_COUNT" >> $LOG_FILE
  sleep 60
done

А на стороні застосунку експонуємо метрики через endpoint:

// Express/Fastify endpoint для експонування пам'яті
app.get('/metrics/memory', (req, res) => {
  const mem = process.memoryUsage()
  res.json({
    rss: Math.round(mem.rss / 1024 / 1024),
    heapUsed: Math.round(mem.heapUsed / 1024 / 1024),
    heapTotal: Math.round(mem.heapTotal / 1024 / 1024),
    external: Math.round(mem.external / 1024 / 1024),
  })
})

PostgreSQL моніторинг під час soak

-- Зростання таблиць (bloat)
SELECT relname, n_live_tup, n_dead_tup,
       round(n_dead_tup::numeric / nullif(n_live_tup + n_dead_tup, 0) * 100, 1) AS dead_pct,
       last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC LIMIT 10;

-- Накопичення idle транзакцій (connection leak)
SELECT count(*), state, wait_event_type
FROM pg_stat_activity
WHERE pid != pg_backend_pid()
GROUP BY state, wait_event_type
ORDER BY count DESC;

-- Зростання розмірів тимчасових файлів
SELECT temp_files, temp_bytes
FROM pg_stat_database
WHERE datname = current_database();

Аналіз тренду деградації

Після тесту запускаємо Python-скрипт, що обчислює регресію RSS:

# analyze_soak.py
import pandas as pd
import numpy as np
from scipy import stats

def analyze_memory_trend(csv_file: str):
    df = pd.read_csv(csv_file, parse_dates=['timestamp'])
    df['minutes'] = (df['timestamp'] - df['timestamp'].iloc[0]).dt.total_seconds() / 60
    slope, intercept, r_value, p_value, std_err = stats.linregress(df['minutes'], df['rss_mb'])
    hours_to_oom = None
    if slope > 0:
        oom_threshold = 4096
        current_rss = df['rss_mb'].iloc[-1]
        hours_to_oom = (oom_threshold - current_rss) / (slope * 60)
    print(f"Memory growth rate: {slope:.2f} MB/min ({slope*60:.1f} MB/hour)")
    if hours_to_oom:
        print(f"Estimated OOM in: {hours_to_oom:.1f} hours")
    if p_value < 0.01 and slope > 0.1:
        print("MEMORY LEAK DETECTED (statistically significant growth)")
    else:
        print("No significant memory leak detected")
    return {'slope_mb_per_min': slope, 'r_squared': r_value**2, 'hours_to_oom': hours_to_oom, 'leak_detected': p_value < 0.01 and slope > 0.1}

Як інтерпретувати результати soak-тесту?

Побудований часовий ряд RSS та P95 latency — ключ до виявлення деградації. Якщо нахил тренду RSS додатний і статистично значущий, це витік. P95 latency, що зростає після 2–4 годин, вказує на проблеми з GC або пулом з'єднань. Додатково перевіряємо dead tuple ratio в PostgreSQL: якщо він перевищує 10%, це bloat. Для кожної проблеми ми даємо конкретні рекомендації: від оптимізації коду до зміни конфігурації БД.

Що входить у роботу

  • Аналіз архітектури та профілю навантаження вашого застосунку.
  • Розробка k6-сценарію з реалістичними користувацькими сценаріями.
  • Налаштування моніторингу пам'яті, з'єднань БД, файлових дескрипторів.
  • Запуск soak-тесту тривалістю 8–24 години на staging-стенді.
  • Побудова графіків часових рядів та регресійний аналіз трендів.
  • Детальний звіт з виявленими деградаціями та рекомендаціями щодо їх усунення.
  • Консультація по фіксам коду та конфігурації.
  • Безкоштовний повторний тест, якщо проблему не було виявлено.

Типові знахідки та рішення

Проблема Симптом Рішення
Витік EventEmitter (Node.js) MaxListenersExceededWarning Використовувати emitter.removeListener() або once()
Незакриті DB connections Зростання з'єднань у pg_stat_activity pool.release() у finally блоці або ORM-level connection pooling
Накопичення cron jobs Дублювання фонових завдань Додати mutex lock (Redis lock)
Redis pub/sub leak Зростання підписок на канали Відписуватися при завершенні з'єднання

Наш досвід та гарантії

Ми займаємося навантажувальним тестуванням понад 7 років. На рахунку — 50+ проектів, де soak-тести запобігли критичним збоям на production. Гарантуємо якість: після виконання робіт ви отримуєте детальний звіт з графіками та рекомендаціями. Якщо проблема залишиться невиявленою — проведемо повторний тест безкоштовно.

Економія від запобігання одному інциденту OOM може сягати значної суми, а збитки від невиявленого витоку пам'яті — суттєві.

Терміни виконання: налаштування та запуск soak-тесту на 8–24 години з аналізом трендів — від 2 до 4 робочих днів. Оцінимо ваш проект за 1 день.

Замовте soak-тестування у наших інженерів — ми виявимо приховані деградації до того, як вони відбудуться на вашому продакшні. Отримайте консультацію та оцінку проекту, зв'язавшись з нами.

Чому юніт-тести важливі, але не панацея?

Баг, знайдений юніт-тестом, коштує хвилини виправлення. Той самий баг у продакшені — години інциденту, компенсації та втрата довіри. На проекті інтернет-магазину помилка в розрахунку знижки пройшла ручне тестування, потрапила в прод і за 4 години обробила 37 замовлень за нульовою ціною. Автотест на граничні випадки розрахунку зловив би її при першому ж push. Оцініть свій проект — ми проведемо аудит поточного покриття і дамо рекомендації.

Jest — стандарт для JavaScript/TypeScript, але юніт-тести виправдані тільки там, де є ізольована логіка: функції трансформації, валідатори, бізнес-правила, утиліти. Тестувати React-компоненти через Jest + Testing Library правильно для поведінкових тестів: «кнопка з'являється після завантаження», «форма показує помилку при порожньому email». Снепшот-тести (toMatchSnapshot) — пастка: вони ламаються при будь-якій зміні верстки і стають шумом, який розробники оновлюють не дивлячись. Покриття коду (code coverage) — погана метрика якості: 80% coverage можна отримати тестами, які нічого не перевіряють. Coverage показує, що код виконався, а не те, що він працює правильно.

Критерій Jest Vitest
Швидкість для великих проектів Середня (Babel-трансформація) В 10–20 разів швидше (ES modules)
Інтеграція з Vite Через плагін Нативна
Монорепозиторії Вимагає конфігурації З коробки

Vitest як альтернатива Jest для Vite-проектів: в 10–20 разів швидше завдяки нативним ES modules без трансформації через Babel. Для монорепозиторіїв з тисячами тестів різниця у швидкості відчутна. Детальніше про юніт-тестування.

Як налаштувати E2E тести, які не будуть flaky?

Playwright обійшов Cypress за ключовими параметрами: нативна підтримка multi-tab, multi-origin, iframe; паралельне виконання на рівні тестів; WebKit, Firefox, Chromium з коробки; немає iframe для додатку — тести працюють в реальному браузері.

Playwright codegen записує дії та генерує тест — хороша точка старту, але згенерований код потрібно рефакторити. Локатори за text content крихкі: getByRole('button', { name: 'Оформить заказ' }) — стійкіше, ніж locator('.btn-primary').

Page Object Model — стандарт організації E2E тестів. Кожна сторінка — окремий клас з методами замість прямих локаторів. Коли кнопка переїхала з хедера в сайдбар — міняємо в одному місці, не шукаємо по всіх тестах.

Як уникнути flaky тестів? Типова проблема — flaky tests. Причини: race condition між запитом і рендером, анімації без очікування, залежність від зовнішніх API. Рішення: `page.waitForResponse()` замість `page.waitForTimeout()`, мокування зовнішніх API через `page.route()`.
// Погано
await page.click('#submit');
await page.waitForTimeout(2000);
await expect(page.locator('.success')).toBeVisible();

// Добре
await page.click('#submit');
await page.waitForResponse(resp =>
  resp.url().includes('/api/orders') && resp.status() === 201
);
await expect(page.getByRole('alert', { name: /заказ создан/i })).toBeVisible();

Наші інженери гарантують стабільність тестів у CI. Документація Playwright — основний інструмент на проектах з мільйонами користувачів.

Навантажувальне тестування з k6

k6 — інструмент для навантажувального тестування з JavaScript API. Сценарії пишуться як код, версіонуються в git, запускаються в CI. Три основних сценарії:

  • Spike test — різке зростання навантаження: 0 → 1000 користувачів за 30 секунд. Імітує запуск рекламної кампанії. Показує здатність системи реагувати на піки.
  • Soak test — стабільне навантаження на 2–4 години. Виявляє memory leaks, connection pool exhaustion, деградацію продуктивності.
  • Stress test — навантаження вище розрахункової (150–200% від очікуваного піку). Показує точку відмови та graceful degradation.

Порогові значення:

thresholds: {
  http_req_duration: ['p95<500', 'p99<1000'],
  http_req_failed: ['rate<0.01'],
}

p95 < 500ms означає: 95% запитів відповідають швидше півсекунди. Якщо поріг не виконується — k6 завершується з кодом помилки, CI-пайплайн падає.

На одному проекті інтернет-магазину ми виявили деградацію API на 4-й годині тесту: p95 зріс з 200ms до 2s через витік з'єднань. Після оптимізації клієнт заощадив значну суму на інцидентах та зайвих ресурсах. Отримайте аналогічний аудит вашого проекту — замовте навантажувальне тестування.

Як Core Web Vitals впливають на ранжування?

Google використовує Core Web Vitals у ранжуванні. Lighthouse CLI в CI-пайплайні: при кожному деплої перевіряємо, що LCP < 2.5s, CLS < 0.1, INP < 200ms. Детальніше про веб-продуктивність. Реальні проблеми, які Lighthouse знаходить:

  • Hero image без атрибутів width/height: CLS 0.35 при завантаженні.
  • JavaScript-бандл 2.1MB синхронно блокує парсинг: INP 450ms.
  • Шрифти без font-display: swap: невидимий текст до завантаження шрифту (FOIT).
  • Неоптимізований hero image 4MB: LCP 8.2s.

Lighthouse CI (lhci) зберігає історію метрик і надсилає коментар до PR з деградацією. За даними Google, 53% користувачів залишають сайт при завантаженні довше 3 секунд — наші тести запобігають таким втратам.

Піраміда тестування в проекті

Рівень Інструмент Кількість Швидкість
Юніт Vitest/Jest Багато (тисячі) <5 хв
Інтеграція Vitest + supertest Середня 5–15 хв
E2E Playwright Мало (happy path) 10–30 хв
Навантаження k6 За розкладом 30–60 хв
Продуктивність Lighthouse CI При кожному деплої 5 хв

Що входить в роботу?

  • Аудит поточного покриття та визначення критичних user flows.
  • Написання unit-тестів для ключової бізнес-логіки, інтеграційних тестів для API, E2E для сценаріїв користувача.
  • Налаштування паралельного виконання в CI (sharded workers для Playwright).
  • Навантажувальне тестування зі звітом та рекомендаціями.
  • Документація за тест-кейсами, навчання вашої команди роботі з тестами.
  • Гарантійна підтримка 1 місяць після впровадження.

Процес роботи

  1. Аналітика — аудит поточного тестування, виявлення слабких місць, визначення пріоритетів.
  2. Проектування — вибір інструментів, написання тест-плану, узгодження.
  3. Реалізація — написання тестів, інтеграція в CI.
  4. Тестування — прогін всіх рівнів, аналіз результатів, виправлення помилок.
  5. Деплой — запуск в прод, моніторинг метрик, навчання команди.

Терміни

Налаштування повного тест-пайплайна (Jest + Playwright + k6 + Lighthouse CI) з нуля: 2–4 тижні. Покриття E2E-тестами існуючого проекту (20–30 сценаріїв): 3–6 тижнів. Навантажувальне тестування зі звітом та рекомендаціями: 1–2 тижні. Вартість розраховується індивідуально після аудиту.

Готові обговорити ваш проект? Залиште заявку — ми проведемо аудит поточного тестування безкоштовно і запропонуємо план з економією до 60% часу на інциденти. Отримайте консультацію з тестування веб-додатків — напишіть нам.