Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.
Числа
Что измеряют числа бенчмарков ruststream-sqlx, что по-прежнему стоит каждому сообщению и каких
выводов эти числа сделать не позволяют.
Сравнение с сырым циклом sqlx
Три цикла одной строки сравнимы между собой: они шли в одном процессе, чередуясь, на одной базе данных. Со строкой, которую публикует другой крейт брокера, строка не сравнима: очередь в таблице выполняет на сообщение другую работу, чем брокер сообщений, и каждое её сообщение - это захват, завершение и фиксация.
Каждое число включает эти обмены с базой данных, во всех трёх циклах. В строке «упирается в базу данных» они заняли хотя бы половину времени сообщения, поэтому разница между циклами - нижняя граница того, что делают крейт и рантайм, а не её измерение. Большинство строк inbox отмечены как «неразличимо»: работа крейта меньше разброса между кругами цикла, который ждёт сервер.
Строки outbox измеряют другое: стоимость отслеживания. Отслеживаемое сообщение стоит вставки перед публикацией, а на стороне получателя - получения записи и отметки, и каждый из этих операторов выполняется на собственном соединении. Поэтому outbox по сравнению с вариантом без него в несколько раз медленнее, и так задумано. Крейт отвечает за разрыв с outbox, написанным вручную, который выполняет те же операторы.
Сетка пропускной способности
С одним воркером три цикла делают одну и ту же работу в одном и том же порядке. С несколькими сырой цикл запускает n захватчиков, каждый - задача, которая сама захватывает и удаляет, а цикл крейта запускает n подписок, каждую на собственном брокере. Сервис запускает одну подписку на n своих воркеров, и на PostgreSQL и MySQL она держит захват в работе на каждого свободного воркера. Поэтому столбец крейта рядом со столбцом сырого цикла показывает, во что крейт обходится при той же степени параллельности, а разрыв от крейта до сервиса при четырёх и восьми воркерах - это то, как захватывает одна подписка, а не диспетчеризация. Пакет берёт за один захват до своего размера, поэтому один захват обслуживает столько же доставок, и три цикла остаются близки.
На SQLite подписка держит в работе один захват, потому что SQLite допускает одного писателя за раз, и этот захват берёт по строке на каждый воркер. n захватчиков сырого цикла на одном файле SQLite берут вместо этого по одной строке за захват, поэтому при четырёх и восьми воркерах сервис захватывает те же строки за меньшее число захватов, чем сырой цикл.
Стоимость кода
Счёт инструкций включает рантайм, этот крейт и драйвер sqlx на потоке сервиса: кодирование каждого оператора, обмен с сервером, декодирование каждой строки. Счёт сырого цикла рядом с ним - доля драйвера, поэтому разность - это крейт и рантайм. Тот же счёт стоит больше времени там, где код промахивается мимо кэша, поэтому счёт инструкций - не время.
Каждое число - установившийся режим, разность между прогоном на 1000 доставок и прогоном на 2000. Открытие соединений пула, проверки брокера при старте и первая доставка - холодный старт, который оплачивается один раз. Читайте число инструкций с точностью до нескольких процентов, а не до цифры: сервис, который работает с настоящим сервером, меняется от прогона к прогону.
Цена, которая остаётся
Крейт соблюдает правило семейства: на горячем пути нет выделений памяти, динамических вызовов, лишних атомарных операций и копий на сообщение сверх тех, что делает сам sqlx. То, что остаётся, перечислено здесь, каждое там, где оно применяется:
- Обработчик, который читает транзакцию своей доставки,
Tx, платит одним увеличением счётчика ссылок на доставку, и столько же платит обработчик, который просит пул черезCtx<keys::Pool>. Подписка, которая их не использует, не платит ничего. - Публикация через
Repositoryили маршрут читает флаг закрытия брокера, одна атомарная загрузка, и будит подписки своей таблицы в процессе, по одной атомарной операции на каждую. Routedищет имя каждого сообщения, одним поиском в хеш-таблице для точного имени, и делает один динамический вызов вPublishстроки.Repositoryназывает свою таблицу во время компиляции и не делает ни того, ни другого.- Подписка по имени над строкой, события которой - собственные события сервиса, платит одной доставкой в куче и одним future завершения в куче на сообщение.
- Outbox читает пул, заданный после старта, через
OnceLock, одна атомарная загрузка на отслеживаемое сообщение, и выделяет память под текст заголовка с id каждого отслеживаемого сообщения. - Формы блокировки строк и рекомендательной блокировки держат одно соединение пула на каждое сообщение или пакет в работе. Это цена блокировки, которую база данных снимает, когда её держатель аварийно завершается.
- Публикация не из обработчика, через издателя запущенного приложения, кодирует каждое сообщение в собственный буфер; сырой цикл держит один буфер на все сообщения.
Чего они не значат
Стенд запускает PostgreSQL и MySQL в Docker в сети хоста той машины, на которой идёт бенчмарк, и
без гарантий сохранности: без fsync и без сброса журнала при фиксации. SQLite работает на
локальном файле в режиме WAL с synchronous = NORMAL. Измеряется цена сообщения, а не диск под
сервером. Сервис, который держит сохранность включённой, платит за неё, и платит одинаково во всех
циклах. Сервер на другом хосте отвечает на вопрос о сети, а не об этом крейте.
Строка - это одна таблица, небольшое тело JSON и один сервер на loopback. Она измеряет, во что сообщение обходится в этом крейте, а не сколько способны пропустить PostgreSQL, MySQL или SQLite. Окно закрывается, когда засчитана последняя доставка, до её завершения, во всех трёх циклах, поэтому одно завершение каждого прогона везде остаётся за пределами числа.
Числа - снимок одной машины в один день, а рядом с ними записана сборка. Их перемеряют вручную, на машине, отданной только прогону: различия, которые они показывают, меньше шума общей машины.