Бенчмарки¶
Дорога от очереди RabbitMQ до обработчика стоит времени на каждом сообщении: поток подписки,
декодирование, диспетчеризация, ack. Эта страница говорит, сколько именно, и делит цену надвое: что
добавляет этот крейт к клиенту lapin, который он оборачивает, и что добавляет рантайм сверху.
Один процесс выполняет сценарий тремя циклами, и различаются они одним: чем везутся сообщения. Всё остальное держится одинаковым: соединение, канал, который получает подписка, аргументы очереди, окно предвыборки, место подтверждения, декодирование в тот же тип, байты тела, рантайм tokio и сборка. Процедура принадлежит самому фреймворку и описана на странице бенчмарков RustStream; здесь опубликовано то, что она дала на этой машине.
Числа¶
Лучший из трёх чередующихся раундов, в скобках медианный и худший. Больше значит лучше.
Таблица читается в браузере из документа, который записал последний прогон, так что на этой странице нет копии, успевшей устареть.
«Сырой клиент» - это lapin, которым управляет написанный руками цикл. «Этот крейт» - тот же цикл
поверх самого ruststream-lapin: брокер, дескриптор очереди, поток подписки, собственный ack
доставки и его издатель, без всякого сервиса вокруг. «Сервис целиком» - то, что пишет пользователь:
обработчик #[subscriber] под приложением и рантаймом, публикующий через того же издателя.
Поэтому «Крейт против сырого» - цена адаптера из этого репозитория, и за неё отвечает он. Шаг от неё к «Сервис против сырого» - цена рантайма сверху, именно на RabbitMQ: собственную цену фреймворка меряет и публикует ядро, а эта строка говорит, во что она обходится на этом транспорте.
Две строки - это две реализации очередей, которые есть в RabbitMQ, и каждая ставит эти цены против другого транспорта. Классическая очередь держит сообщения в памяти и закрывает доставку одним кадром. Кворумная очередь проводит каждое сообщение через Raft и записывает его на диск, прежде чем отдать, и настолько дорогой транспорт почти не оставляет места тому, что работает над ним.
Накладные расходы с отметкой «неразличимо» - те, где колонка и сырой клиент расходятся меньше, чем разброс между прогонами каждого из них. Число ниже шума прогонов читалось бы как точность, которой никто не измерял, поэтому оно не публикуется.
Строка с отметкой «упирается в брокер» - та, где сырой клиент провёл прогон в ожидании сервера, а не в работе. Отметку ставит измерение, а не догадка: проба вне раундов замеряет один запрос, на который сервер отвечает, и отметка появляется, когда круговые задержки, которые стоит одна доставка, набирают не меньше половины времени сообщения. Измеренная задержка опубликована ниже, в блоке про машину, так что арифметику можно проверить. Доставка в AMQP не ждёт собственного оборота: сервер сам присылает тело и не отвечает на подтверждение. Ждать приходится кредита, который возвращает подтверждение, а окно предвыборки растягивает это ожидание на все сообщения окна.
Машинная форма того же прогона, из которой сайт фреймворка строит сводную таблицу по брокерам,
лежит по адресу
benchmarks/results.json.
Собственный код крейта¶
Вторая таблица - цена сообщения в коде, подсчитанная, а не засечённая по часам: инструкции под
callgrind и выделения памяти под DHAT. Каждый сценарий - сервис в том виде, в каком его пишет
пользователь, на LapinBroker, подключённом к узлу из стенда docker compose. Сервис объявляет
классическую очередь при запуске, читает её с тем же окном предвыборки, что и сравнение выше, и
работает на однопоточном рантайме tokio. Очередь наполняется из другого потока до того, как сервис
возьмётся за первое сообщение, и наполнение ждёт, пока узел подтвердит каждое сообщение.
Считается всё, что выполняется в потоке сервиса: диспетчеризация фреймворка, кодек, код этого
крейта и работа, которую клиент lapin делает в этом потоке. Читает и пишет сокет lapin в
собственном потоке, и этот поток не считается. Большая часть выделений памяти в каждой строке -
выделения клиента: каждое подтверждение и каждая публикация заводят в потоке сервиса обещания и
внутреннюю задачу.
Инструкции и выделения даны на сообщение в установившемся режиме: это наклон между прогоном на 1000 доставок и прогоном на 2000. Последняя колонка - то, во что один раз обошлись запуск сервиса и первая доставка: соединение, открытие каналов, объявление очереди и регистрация потребителя. Числа абсолютные, собственная цена фреймворка входит в них; отдельно её публикует ядро на своей странице бенчмарков.
Настоящий узел отвечает в своём темпе, поэтому то, как часто поток сервиса ждёт сокет, от прогона к
прогону меняется. За семь прогонов одного и того же кода инструкции расходились до 2,4 процента, а
выделения памяти - не больше чем на один блок. Порог выделений в сценарии стоит на десятую долю
процента выше самого большого числа, которое он давал, и just bench-code падает, если выделений
больше. С --baseline=main рецепт падает ещё и на приросте инструкций больше пяти процентов; простой
прогон инструкции не ограничивает. Запрос на слияние, который меняет цену, приводит свои числа.
Машина¶
Флаги сборки публикуются вместе с числами, потому что они эти числа меняют. Бинарник, собранный с
-C target-cpu=native, даёт результат, который не воспроизвести на другой машине, поэтому рецепт
очищает переменную перед сборкой.
Чего эти числа не значат¶
Это один потребитель на одной очереди, маленькое тело и узел на локальной петле. Измеряется то, во что доставка обходится внутри этого крейта, а не то, сколько способен пронести RabbitMQ. Строку отсюда нельзя сравнивать со строкой, опубликованной для другого брокера: транспорты выполняют на сообщение разную работу.
Окно предвыборки задаёт абсолютные величины. Потребитель, который подтверждает каждое сообщение и которому разрешена одна неподтверждённая доставка за раз, тратит на сообщение полный оборот до сервера, и все три цикла выходят на скорость этого оборота. Широкое окно, с которым сняты эти прогоны, и позволяет ценам над транспортом вообще проявиться; сдвиг окна двигает все колонки разом.
Каждый прогон объявляет собственную очередь и удаляет её после себя, так что ни один цикл не разбирает то, что оставил предыдущий прогон. Тела публикуются без требования сохранности: классическая очередь держит их в памяти, а кворумная строка несёт собственную надёжность RabbitMQ, потому что другого режима у кворумной очереди нет.
Окно прогона открывается на первой доставке и закрывается, когда закончена работа над последней, одинаково во всех трёх циклах. Каждый цикл подтверждает доставку после этой точки, так что одно подтверждение из миллионов, которые несёт прогон, остаётся за пределами числа в каждом из них.
Числа - снимок одной машины в один день. Их переснимают вручную на машине, отданной одному этому замеру: разница, о которой говорит эта страница, меньше шума машины, которую делят с другими задачами.
Как повторить¶
Рецепт поднимает узел из docker-compose.test.yml, прогоняет оба сценария, гасит его и переписывает
docs/benchmarks/results.json тем, что намерил. Это занимает несколько минут и требует машину
целиком. Число сообщений не фиксировано: пробный прогон подбирает его так, чтобы каждый измеряемый
прогон длился не меньше пяти секунд на той машине, где его снимают.
Рецепт поднимает узел из docker-compose.test.yml, подсчитывает таблицу кода под valgrind, гасит
узел и переписывает раздел code того же документа. Это занимает меньше минуты; нужны valgrind и
исполнитель бенчмарков: cargo install --locked gungraun-runner --version =0.19.4.