Перейти к основному содержимому
Нормативная версия документации - английская

Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.

Методология

Как ruststream-nats измеряет себя: три цикла, прогон на настоящем сервере, арифметика «упирается в брокер» и подсчёт кода. Процедура общая для всего фреймворка (методология RustStream), а эта страница говорит, как она выглядит на NATS.

Три цикла​

Один процесс прогоняет каждый сценарий трижды, тремя циклами, которые различаются одним: тем, что доставляет сообщения.

  • Сырой клиент. async-nats напрямую: подписка Core или pull-консьюмер JetStream, сообщения которых читаются, декодируются и, в JetStream, подтверждаются вручную.
  • Крейт брокера. Этот крейт и ничего над ним: брокер подключается, дескриптор открывает подписку, а цикл забирает доставки из её потока, декодирует их и отправляет подтверждения.
  • RustStream. Сервис, который пишет пользователь: обработчик #[subscriber] и приложение вокруг него.

Всё остальное в трёх циклах одинаково: параметры подключения, подписка, конфигурация консьюмера, место ack, декодирование в ту же структуру, байты полезной нагрузки, рантайм tokio с четырьмя рабочими потоками (воркерами) и бинарник.

Тело сообщения - 512 байт JSON. Оно декодируется в структуру из двух целочисленных полей, поэтому декодирование ничего не выделяет, и число говорит об этом крейте, а не о парсере JSON.

Прогон​

  • Новый субъект на каждый прогон, а в JetStream ещё новые стрим и консьюмер. Прогон никогда не видит того, что оставил предыдущий.
  • Стрим JetStream хранится в памяти, с политикой хранения work queue. Диск под сервером не участвует в замере, который посвящён диспетчеризации.
  • Консьюмер подписывается первым. Затем ему отправляет сообщения издатель на втором соединении и опережает его не больше чем на 32768 сообщений, потому что Core NATS отбрасывает то, что не успевает принять медленный подписчик.
  • Окно открывается на первой доставке и закрывается, когда декодирована последняя, во всех трёх циклах. Подключение, подписка и создание стрима в него не входят.
  • Число сообщений даёт пробный замер скорости сырого цикла, поэтому измеряемый прогон на любой машине длится не меньше пяти секунд.
  • Циклы чередуются: сырой, крейт, RustStream, три круга. Каждый цикл сообщает свой лучший, медианный и худший круг.

Строка получает пометку «упирается в брокер», когда консьюмер большую часть прогона ждал сервер. Это арифметика. Пробный замер засекает 30000 запросов, на которые сервер отвечает сам ($JS.API.INFO), и берёт медиану. Транспорт сообщает, сколько круговых обменов стоит одна доставка, и строка получает пометку, когда их произведение достигает половины измеренного времени на сообщение. Доставка Core не стоит ни одного кругового обмена, потому что сервер отправляет её сам. Доставка JetStream стоит одну двухсотую обмена: pull-запрос на пакет из 200 сообщений. Круговая задержка публикуется вместе с машиной, так что арифметику можно проверить.

Подсчёт кода​

Каждый сценарий графика стоимости - сервис, который пишет пользователь, на NatsBroker против того же сервера, собранный без фичи testing, на однопоточном рантайме. Pull-консьюмер JetStream читает стрим, который издатель в другом потоке заполнил до того, как открывается измеряемая область.

Инструкции даёт callgrind, выделения памяти - DHAT. Сценарий прогоняется на одной доставке, затем на 1000, затем на 2000. Разность двух последних - установившаяся стоимость сообщения, а одна доставка - холодный старт.

Сервер настоящий, поэтому счёт немного меняется между прогонами: то, как сокет отдаёт клиенту байты, зависит от времени. За семь прогонов число инструкций на сообщение сдвинулось меньше чем на полпроцента, а выделения памяти - на один блок в прогоне из 2000. Прогон не проходит, если сценарий выделяет больше памяти, чем наибольший замеченный у него итог плюс десятая доля процента. Против сохранённого базового замера прогон также не проходит, если сценарий тратит на два процента больше инструкций.

Как запустить самому​

just bench

Рецепт поднимает стенд из docker-compose.test.yml, прогоняет оба сценария сравнения, останавливает стенд и перезаписывает docs/benchmarks/results.json. Это занимает несколько минут, и машина на это время нужна целиком. Рецепт очищает RUSTFLAGS, поэтому бинарник не привязан к процессору, на котором его собрали. Оба рецепта собирают бенчмарки так же, как собирается выпуск: с полной оптимизацией при компоновке через границы крейтов. Поэтому числа описывают тот код, который получает сервис.

just bench-code

Подсчёт кода занимает меньше минуты и требует valgrind. Он устанавливает исполнитель бенчмарков той версии, которую закрепляет Cargo.lock выпуска. just bench-code --save-baseline=main на main записывает базовый замер, а just bench-code --baseline=main на ветке сравнивает с ним. Число в начале команды задаёт другое количество доставок (just bench-code 5000). Опубликованный график посчитан на 1000.