Высокие нагрузки и большие объёмы данных: как их проверять и что показывают цифры
«Выдержит ли система нагрузку?» — вопрос, на который чаще всего отвечают «должна». Цифры при этом никто не видел. Я отвечаю измерениями: сначала перевожу требование в байты и сообщения в секунду, затем создаю такую нагрузку на стенде и смотрю, сколько дошло, целым ли и за какое время. Ниже — как это устроено на примере испытаний, где через один шлюз прошло около 960 ГБ данных, и что из них следует для любой системы с большими потоками. Общий подход к проверке перед релизом описан в статье Тестирование и приёмка ПО: как убедиться, что система работает до релиза.
Начать нужно с перевода требования в цифры
Требование «система должна выдерживать нагрузку предприятия» нельзя проверить. Его нужно разложить на числа. Вот как это выглядело в реальной задаче:
| Параметр профиля | Значение |
|---|---|
| Станков на предприятии | 100–200 |
| Показателей на станок | расчётное значение 50 |
| Периодичность сбора | не чаще 1 раза в секунду |
| Пакет телеметрии | около 300 КБ на сообщение |
| Файловый обмен | файл до 1 МБ раз в несколько минут |
Из этого следует требование: 200 станков × 50 показателей × 1 Гц = 10 000 индикаторов в секунду. Запись одного индикатора занимает около 130 байт, то есть поток составляет 1,24 МБ/с (10,4 Мбит/с). Для консервативной оценки в 200 байт на запись — 1,91 МБ/с. В сутки это около 0,10 ТБ.
Теперь у требования есть единицы измерения, и его можно сравнивать с тем, что показал стенд. Без этого шага любой «успешный тест» ничего не доказывает.
Что показали измерения
Нагрузку создавали по пяти протоколам одновременно (MQTT, TCP, SFTP, Modbus TCP, OPC UA) на двух независимых площадках с разной сетевой инфраструктурой.
| Показатель | Площадка 1 | Площадка 2 |
|---|---|---|
| Непрерывная работа под нагрузкой | 4 ч 31 мин | 40 мин |
| Доставлено данных | 834 ГБ | 127 ГБ |
| Доставлено сообщений | 2,57 млн | 0,39 млн |
| Полоса доставки | 52,5 МБ/с (440 Мбит/с) | 52,9 МБ/с (444 Мбит/с) |
| Расхождений контрольной суммы SHA-256 | 0 | 0 |
| Ошибок CRC16 в кадрах телеметрии | 0 | 0 |
Суммарно: около 961 ГБ, 2,96 млн сообщений и 789 тысяч кадров промышленной телеметрии без единого искажённого сообщения. Основные показатели двух площадок расходятся меньше чем на 1 %, значит, результат воспроизводим и не зависит от конкретной сети.
Запас относительно требования получился так:
| Требование | Нужно | Измерено | Запас |
|---|---|---|---|
| 10 000 индикаторов/с | 1,24 МБ/с | 52,5–52,9 МБ/с | 42× (при 200 Б на запись — 27×) |
| Те же данные сообщениями по 300 КБ | 4,2 сообщ./с | 158–159 сообщ./с | не менее 24× |
| Файл 1 МБ раз в 3 минуты | 5,7 КБ/с | 1,06 МБ/с | 191× |
Запас в два порядка и есть правильный ответ на вопрос «выдержит ли»: не «да», а «в 24 раза больше, чем нужно, и вот как мы это измерили».
Как устроен стенд
Для таких проверок я сделал «Исток». Это две независимые службы по разные стороны проверяемой системы: генератор нагрузки на входе и приёмник-верификатор на выходе. Между ними может не быть обратного канала, поэтому приёмник сам считает, что дошло, а что нет.
Профили нагрузки
Один «максимум запросов» ничего не говорит о системе. Нужны разные профили:
| Профиль | Что проверяет |
|---|---|
| Потоковый | равномерная передача по одному сообщению, как в реальной работе |
| Пакетный | предельную пропускную способность при параллельной отправке |
| Крупноблочный | полосу и поведение на пакетах 5–100 МБ |
| Промышленная телеметрия | цикл «N станков × M показателей × F Гц» |
| Комбинированный | все протоколы одновременно и то, как они делят общую полосу |
Все профили запускаются командой через API, без ручных действий. Это нужно для воспроизводимости: прогон можно повторить и сравнить с предыдущим.
Честная нагрузка
Два решения, которые определяют, чему можно верить:
- Равномерный темп. Ограничение скорости сделано алгоритмом «маркерная корзина» без накопления всплеска. После паузы генератор не высыпает накопленное залпом, поэтому измеренная задержка отражает поведение системы, а не буфера генератора.
- Детерминированные данные. Содержимое задаётся зерном генератора: при том же зерне последовательность воспроизводится побитово.
Сам генератор не должен быть узким местом
Если генератор не вытягивает нужный темп, вы измеряете его, а не систему. Поэтому инструмент нагрузки нужно проверять так же строго, как проверяемую систему. «Исток» у меня существовал в нескольких версиях, и разница оказалась существенной:
| Версия | Что показали замеры |
|---|---|
| Rust | около 120 тыс. сообщений в секунду |
| Go | около 20 тыс. сообщений в секунду, то есть в 6 раз меньше |
| Deno | при длительной нагрузке проявилась утечка памяти |
Для 10 000 индикаторов в секунду хватило бы любой из них. Но стенд должен давать нагрузку с большим запасом выше проверяемой системы, иначе вы не узнаете её предел: упрётесь в потолок генератора и примете его за потолок системы. Кроме того, прогон идёт часами, и инструмент обязан держать нагрузку так же стабильно в конце, как в начале. Утечка памяти в генераторе портит длинный тест: стенд деградирует, и непонятно, что именно вы измерили.
Поэтому рабочей версией стала Rust: у неё запас по темпу и предсказуемое потребление памяти на длинной дистанции.
Как считать потери и искажения
Если обратного канала нет, «подтверждения доставки» взять неоткуда. Потери считаются двумя независимыми способами:
- Сравнение счётчиков сторон. Сквозной номер сообщения отличает потерю от переупорядочивания.
- Разрывы последовательности. В каждом кадре телеметрии есть циклический счётчик. Дыра в нём означает потерянные кадры, и для этого не нужны данные передающей стороны.
Искажения считаются отдельно от потерь: расхождение SHA-256 и ошибка CRC16 — это другой вид отказа. Если их смешать, по итоговой цифре будет непонятно, что сломалось: сеть, шлюз или формат.
Для системы, в которой данные нельзя переслать повторно, целостность — главный результат. Здесь она подтверждена на объёме 961 ГБ: ноль искажений.
Задержка и часы
Задержка считается как разность времени приёма и метки источника в сообщении. Если часы серверов расходятся, цифры бессмысленны, а в однонаправленной схеме синхронизировать часы по каналу нельзя.
На испытаниях часы стенда разошлись примерно на 7 минут. Стенд заметил это сам: отрицательная задержка физически невозможна, и по числу таких отсчётов он оценил смещение и пометил показатели как UNRELIABLE. Поэтому в итоговом отчёте задержка не приводится вообще. Честное «не измерено» лучше красивой цифры, которую нельзя защитить.
Как не сломать измерение самим измерением
Несколько правил, которые кажутся мелочью, пока не испортят прогон:
- Не писать в журнал каждое сообщение. При тысячах сообщений в секунду это само искажает результат. Журнал ведётся агрегатами раз в 10 секунд.
- Писать снимки статистики на диск по расписанию. Если служба упала посреди суточного прогона, результат остаётся, и оператор рядом не нужен.
- Мерить полный кадр и полезную нагрузку отдельно. Полосу сравнивают с заявленной по полному размеру кадра, а полезную отдачу канала — по нагрузке.
- Проверять каналы по одному до длинного прогона. Ошибка конфигурации одного протокола обесценивает сутки комбинированного теста по остальным.
- Обнулять счётчики перед стартом. Исходное состояние фиксируется явно.
Что эти испытания не доказали
Это не менее важно, чем результаты, и я писал это в отчёт прямо:
- Сутки не измерены. Максимальная непрерывная работа — 4 ч 31 мин. Значения около 3,4 ТБ за сутки получены пересчётом: это оценка, а не измерение.
- Потолок не найден. Канал стенда был близок к насыщению: гигабитная линия на 440 Мбит/с при половине занятой лишней нагрузкой. Полученные 52 МБ/с — это доставленный поток при данной конфигурации, а не предел системы.
- Данные хорошо сжимались. Объём на входе и на выходе шлюза различался так, что виден эффект сжатия: тестовая нагрузка содержала пробельное заполнение. На плохо сжимаемых данных предел может быть ниже.
- Точка насыщения одного из протоколов не определена. Известен только интервал: потери 2,7 % при 17 кадрах/с и 79 % при 95 кадрах/с. Требование профиля лежит внутри этого интервала, поэтому честный ответ — «не доказано, нужен ещё прогон с шагом по частоте».
Такой список нужен не для самокритики. Он определяет, что именно заказчик может принимать как факт, а что — как оценку, и какие прогоны провести дальше.
Что из этого следует для вашей системы
- Переведите требование в числа до тестов: сообщения в секунду, байты в секунду, объём в сутки.
- Измеряйте запас, а не «работает/не работает». Кратность запаса видна сразу, и по ней понятно, где риск.
- Проверьте сам генератор нагрузки: его потолок по темпу и поведение памяти на длинной дистанции. Выбор языка здесь влияет на результат: в моих замерах разница между Rust и Go составила 6 раз.
- Считайте потери и искажения отдельно и независимыми счётчиками.
- Разделяйте измерение и оценку. Пересчёт на сутки — не то же самое, что сутки работы.
- Заранее проверьте сам стенд: пропускную способность канала, часы, сжатие данных.
- Тестируйте по нескольким профилям и на двух площадках, если можете: воспроизводимость сама по себе результат.
Те же принципы работают и для хранения потока: как я выбираю базу под большой поток телеметрии, рассказано в статье «Русло»: как строится IIoT-платформа для закрытой сети предприятия, а как нефункциональные требования о нагрузке влияют на выбор архитектуры — в статье Архитектура ПО: проектирование и планирование до первой строки кода.
Нужно проверить нагрузку вашей системы?
Если вам важно знать не «должно работать», а «выдерживает в N раз больше нужного, и вот доказательство», напишите: Telegram @w1shmaster. Начнём с перевода требований в числа и выбора профилей.