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

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

Методология

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

Три цикла​

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

  • Сырой клиент. aws-sdk-kinesis напрямую: GetRecords по итератору сегмента, декодирование каждой записи, запись её контрольной точки и продление аренды по расписанию крейта.
  • Крейт брокера. Этот крейт и ничего над ним: брокер подключается, дескриптор KinesisStream открывает подписку, а цикл забирает доставки из её потока, декодирует их и подтверждает.
  • RustStream. Сервис, который пишет пользователь: обработчик #[subscriber] и приложение вокруг него.

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

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

Прогон​

  • Новый поток на каждый прогон. Каждый прогон создаёт собственный поток из одного сегмента, а сценарий с DynamoDB - собственную таблицу аренд, поэтому прогон никогда не видит того, что оставил предыдущий.
  • Потребитель подключается первым, с TRIM_HORIZON. Затем шестнадцать задач заполняют поток, по одному PutRecord на запись, и опережают потребителя не больше чем на 50 000 записей.
  • Окно открывается на первой доставке и закрывается, когда прочитано поле последней записи, во всех трёх циклах. Создание потока, взятие аренды и открытие итератора в него не входят.
  • Число записей даёт пробный замер скорости сырого цикла, поэтому измеряемый прогон на любой машине длится не меньше пяти секунд.
  • Циклы чередуются: сырой, крейт, RustStream, три круга. Каждый цикл сообщает свой лучший, медианный и худший круг.

Строка получает пометку «упирается в эмулятор», когда круговые задержки до конечной точки занимают не меньше половины времени доставки. Это арифметика. Пробный замер засекает самый маленький запрос, на который отвечает конечная точка, DescribeStreamSummary, на собственном клиенте, до публикации первой записи. Доставка стоит свою долю вызова GetRecords, который её принёс, а в сценарии с DynamoDB ещё и условную запись, которая её завершает. Документ публикует эту долю для каждой строки как round_trips_per_message. Строка получает пометку, когда произведение круговой задержки на эту долю достигает половины измеренного времени на запись. Круговая задержка публикуется вместе с машиной, так что арифметику можно проверить.

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

Каждый сценарий графика стоимости - сервис, который пишет пользователь, на KinesisBroker против той же конечной точки, собранный без фичи testing и запущенный на однопоточном рантайме. Он читает поток из одного сегмента, созданный для прогона, с TRIM_HORIZON и записывает контрольные точки через стандартное хранилище аренд. Другой поток выполнения заполняет поток до того, как открывается измеряемая область, и его работа в счёт не входит.

Инструкции даёт callgrind, выделения памяти - DHAT. Сценарий прогоняется на одной доставке, затем на 1000, затем на 2000. Разность двух последних - установившаяся стоимость записи, а одна доставка даёт холодный старт. Одно чтение делят записи, которые оно вернуло, поэтому каждая строка включает свою долю чтения: до тысячи записей на чтение в первых двух строках и один пакет из 64 в третьей.

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

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

just bench

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

just bench-code

Подсчёт кода занимает несколько минут и требует valgrind, кроме стенда. just bench-code 5000 измеряет каждый сценарий на пяти тысячах доставок вместо тысячи. just bench-code --save-baseline=main на main записывает базовый замер, а just bench-code --baseline=main на ветке сравнивает с ним.