Владимир Семакин · Статьи ← все статьи

Нагрузочное тестирование без профиля трафика: что оно скрывает

Началось с рилса @oleg_system_design про нагрузочное тестирование: сервис показал миллион RPS, а выдержит ли он даже половину — неизвестно (https://www.instagram.com/reel/Ddy_Da9I8sJ/). Мысль там простая и неудобная: цифра RPS без профиля трафика ничего не доказывает. Когда продукт тебе пишет ИИ, а ты отвечаешь за результат перед клиентом, этот вопрос становится твоим: красивый отчёт о тесте легко принять за гарантию, которой в нём нет.

Разбираю, что именно скрывает круглая цифра и как поставить тест так, чтобы он отвечал на вопрос «переживём ли мы распродажу».

Почему «миллион RPS» — не ответ

Нагрузочный тест проверяет не «скорость вообще», а конкретное сочетание запросов. Если гонять только карточки товаров, тест покажет потолок чтения. Магазин при этом упадёт не на карточках, а на оформлении заказа — потому что заказы это записи, а записи устроены иначе.

Чтение и запись масштабируются по-разному. Чтение разгоняется горизонтально: горячие данные уходят в кэш, остаток распределяется по репликам — PostgreSQL позволяет читать со standby-сервера, все такие подключения строго read-only (документация PostgreSQL, hot standby). Запись до недавнего времени почти всегда упиралась в один мастер: реплика в классической потоковой репликации читать умеет, писать — нет.

Отсюда ловушка: тест без заказов покажет «выдержали миллион RPS», добавите оформление заказа — и тот же сервис упрётся в потолок на порядки раньше. Разработчики в рилсе так и наткнулись: чтение уже умело масштабироваться с нескольких узлов, а записи шли в один мастер, и именно write-нагрузка стала тем местом, где пришлось шардировать данные.

Как собрать профиль трафика для теста

Профиль трафика — это соотношение типов запросов, приближенное к продакшену. Собирается он из метрик живого сервиса, а не из фантазии. Порядок такой:

  1. Возьмите из аналитики реальное соотношение действий: сколько просмотров карточек на одну добавку в корзину, сколько корзин на один заказ. На распродаже это соотношение смещается — смотреть товар смогут все, оформлять не все, и тест должен это смещение воспроизводить.
  2. Задайте нагрузку как поток запросов в секунду (arrival rate), а не как число виртуальных пользователей. У Grafana k6 для этого есть arrival-rate executors: constant-arrival-rate держит фиксированное число итераций в секунду, ramping-arrival-rate наращивает его ступенями. Разница принципиальна: в закрытой модели (фиксированные VU) при деградации ответов поток запросов сам собой падает — и вы тестируете не то. В открытой модели поток идёт независимо от ответов системы, как в реальности.
  3. Воспроизведите данные неравномерно. Если в проде один товар берут в сто раз чаще остальных, а тест стучится в базу равномерно по всему каталогу, узкое место вы не увидите — оно проявится только на живой распродаже.

Пункт 3 — самый частый пропуск. Равномерный синтетический трафик льстит сервису: он распределяет нагрузку так, как реальный трафик никогда не распределяется.

Куда упирается сервис первым: кэш, реплики, мастер

Когда горячие чтения унесены в кэш и раскиданы по репликам, следующим потолком становится запись в мастер. Дальше начинается шардирование — деление данных между несколькими пишущими узлами, — и здесь тест на реальном профиле важен вдвойне.

Дело в том, что «поделить пользователей поровну» не значит «поделить нагрузку поровну». При шардировании по идентификатору пользователя или товара распределение записей по шардам неравномерно: документация Citus прямо посвящает выбору колонки распределения отдельный раздел, потому что от неё зависит, как строки лягут по шардам. Один шард забивается, остальные стоят почти пустые — при этом средняя задержка по кластеру может выглядеть прилично, потому что усреднение прячет горячий шард. Именно это и есть главная причина, почему цифры RPS без профиля трафика почти ничего не говорят: метрика усреднена, а отказ локален.

Практический вывод: перед шардированием требуйте от команды анализ перекоса — распределение записей по будущим шардам на реальных данных, а не в равных долях. И отдельно — тест с профилем «распродажи», когда один якорный товар собирает непропорциональную долю и чтений, и записей.

Чек-лист: что спросить с нагрузочного теста

Прежде чем принять отчёт о тесте как пропускной билет, я проверяю четыре вещи.

  • Соотношение запросов в тесте совпадает с продом: просмотры, корзины, заказы, записи — в тех же долях.
  • Данные в тесте неравномерные: есть «горячие» товары и пользователи, как в жизни.
  • Нагрузка задана как поток в секунду, открытой моделью, — и в отчёте видны не только средние, но и перцентили по каждому типу запроса отдельно.
  • Тест включает сценарий пикового дня: не средний вторник, а распродажу с перекосом к якорным товарам и записям.

Если хоть один пункт не закрыт, «миллион RPS» в отчёте — это результат другого эксперимента, не того, который вам нужен. Нагрузочное тестирование доказывает что-то только тогда, когда воспроизводит трафик, который придёт в прод. В этом смысле тест — не про мощность железа, а про честность модели: соберите профиль трафика один раз, и каждый следующий тест будет отвечать на вопрос бизнеса, а не создавать красивые цифры для презентации.

Источники