Перейти к содержанию

Бенчмарки

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

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

Прослойка между клиентом Redis и типом сообщения стоит времени на каждой доставке: чтение, декодирование, ack. Эта страница говорит, сколько именно. Точкой отсчёта служит та же работа, написанная руками на fred.

Один процесс выполняет сценарий тремя способами. Сырой клиент - цикл, написанный руками на fred. ruststream-fred - такой же цикл, написанный руками на этом крейте: брокер, подписка, поток доставок и ack, без сервиса, без обработчика и без диспетчеризации. Сервис RustStream - сервис, который пишет пользователь, запущенный через настоящий рантайм.

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

Всё остальное держится одинаковым: пул и его размер, группа потребителей и имя потребителя, команды чтения с их COUNT и BLOCK, место подтверждения, декодирование в тот же тип, байты тела, рантайм tokio и сборка. Процедура принадлежит самому фреймворку и описана в разделе методики; здесь опубликовано то, что она дала на этой машине.

Измеряются три формы потребителя, по одной на каждую форму доставки этого крейта: группа потребителей Redis Streams с подтверждением каждой записи, надёжная очередь заданий на списке и канал Pub/Sub. Каждая снимается против трёх форм сервера, к которым умеет подключаться крейт: одиночный сервер, кластер и мастер за Sentinel. Подписка на всех трёх одна и та же, различается только маршрутизация клиента под ней, поэтому разница между такими строками говорит именно о ней. Единственное намеренное исключение: строка Pub/Sub на кластере шардированная, SSUBSCRIBE и SPUBLISH, то есть та форма, с которой кластер и используют, тогда как классический PUBLISH рассылается на каждый узел.

Числа

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

Таблица читается из документа, указанного ниже, при каждом открытии страницы, поэтому показывает последний прогон и ничего кроме него.

Подтверждение доставки в Redis стоит отдельной команды - XACK для записи потока, LREM для записи списка, - а на сервере, до которого идут через локальную петлю, такая команда стоит десятки микросекунд. Это на порядок больше того, что доставка проводит внутри этого крейта. Потребитель проводит своё окно в ожидании сокета, поэтому обе эти строки несут отметку «ограничено брокером»: всё, что лежит над сокетом, делает свою работу внутри ожидания, за которое потребитель платил и так. Для потребителя, который подтверждает каждое сообщение, это настоящий результат и одновременно нижняя граница этих расходов, а не их измерение.

Pub/Sub ничего не подтверждает, и это та строка, где работе над сокетом есть где проявиться: доставка там - это чтение из сокета, снятие конверта и декодирование, и стоит она на порядок меньше доставки из потока.

Машинная форма того же прогона, из которой сайт фреймворка строит сводную таблицу по брокерам, лежит по адресу benchmarks/results.json.

Собственный код крейта

Вторая таблица - собственная цена сообщения в этом крейте, подсчитанная, а не засечённая по часам: инструкции под callgrind и выделения памяти под DHAT. Каждый сценарий - сервис в том виде, в каком его пишет пользователь: он построен на RedisBroker и запущен против одиночного сервера стенда, поэтому каждая его команда - та, что отправляет настоящий сервис. Сценарий потока читает группу потребителей RedisStream командой XREADGROUP, подтверждает доставку XACK и отправляет ответ XADD через RedisPublish. Надёжный список переносит каждую запись командой LMOVE и подтверждает её LREM. Сценарий Pub/Sub читает канал или шаблон и ничего не подтверждает. Сценарий с .pipeline() отправляет подтверждения одной выборки вместе, а его обработчик добавляет по одной своей команде INCR на доставку. .pipeline().atomic() заключает подтверждение каждой доставки в собственный MULTI / EXEC.

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

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

Сервис говорит с настоящим сервером, поэтому подсчёт между прогонами немного плавает: за шесть прогонов итоговые инструкции сценария расходились не больше чем на 0,4%, а выделения - на несколько блоков. Поэтому порог каждого сценария - наибольшее увиденное число плюс запас в 0,1%. just bench-code падает, если выделений больше объявленного сценарием порога, а с --baseline=main - ещё и на приросте инструкций больше двух процентов; запрос на слияние, который меняет цену, приводит свои числа.

Машина

Флаги сборки публикуются вместе с числами, потому что они эти числа меняют. Бинарник, собранный с -C target-cpu=native, даёт результат, который не воспроизвести на другой машине, поэтому рецепт очищает переменную перед сборкой.

Чего эти числа не значат

Это один потребитель, один ключ, маленькое тело и сервер на локальной петле. Измеряется то, во что доставка обходится внутри этого крейта и в рантайме над ним, а не то, сколько способен пронести Redis. Строку отсюда нельзя сравнивать со строкой, опубликованной для другого брокера: транспорты выполняют на сообщение разную работу.

Окно прогона открывается на первой доставке и закрывается на последней, одинаково во всех трёх циклах, поэтому одно подтверждение за весь прогон остаётся за пределами числа везде.

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

Серверы стенда работают в сети хоста и без сохранения на диск: между клиентом и сервером нет прокси порта, нет журнала append-only, нет снимков. Измеряется стоимость доставки, а не мост перед сервером и не диск под ним. Сервис, который оставляет сохранение включённым, платит за него, и платит одинаково с обеих сторон.

Число Pub/Sub снято при издателе, который никогда не ждёт потребителя. Redis Pub/Sub выбрасывает то, что потребитель не готов взять, а не ставит в очередь, поэтому эта строка говорит о доставках в секунду у насыщенного потребителя.

Числа - снимок одной машины в один день. Их переснимают вручную на машине, отданной одному этому замеру: разница, о которой говорит эта страница, меньше шума машины, которую делят с другими задачами.

Как повторить

just bench

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

just bench-code

Рецепт поднимает тот же стенд, подсчитывает таблицу кода под valgrind против его одиночного сервера, гасит стенд и переписывает раздел code того же документа. Ему нужны valgrind и исполнитель бенчмарков: cargo install --locked gungraun-runner --version =0.19.4.