Soak Testing: выявление утечек памяти и деградации

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Soak Testing: выявление утечек памяти и деградации
Сложный
~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 test. Мы, инженеры 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
Accumulating 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 из-за утечки соединений. После оптимизации клиент сэкономил около $15,000 в год на инцидентах и лишних ресурсах. Получите аналогичный аудит вашего проекта — закажите нагрузочное тестирование.

Как 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 мин
Performance 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% времени на инциденты. Получите консультацию по тестированию веб-приложений — напишите нам.