pgrust ускорил Postgres в 300 раз на аналитических запросах

Авторы pgrust, базы данных, совместимой с Postgres и написанной на Rust, выпустили версию 0.2, целиком посвящённую производительности. По их данным, новая версия в 10 раз быстрее предыдущей версии самого pgrust; на OLTP-бенчмарках она на 30% быстрее Postgres, а на Clickbench, бенчмарке для аналитических СУБД от Clickhouse, обгоняет Postgres в 300 раз, опережая при этом и сам Clickhouse. Около 10-кратного ускорения из общих 300 раз на Clickbench дал именно движок выполнения запросов (query engine), разбору его оптимизаций и посвящён пост.
Авторы объясняют разрыв возрастом архитектуры Postgres: проект восходит к 1980-м годам, когда главным узким местом СУБД был дисковый ввод-вывод. С тех пор ситуацию изменили три тренда: многие датасеты теперь целиком помещаются в оперативную память; аналитические нагрузки, сканирующие большие объёмы данных, всё чаще упираются не в диск, а в процессор или пропускную способность памяти; а сами диски (NVMe) стали на порядки быстрее жёстких дисков. В итоге CPU и память стали куда важнее для производительности, чем раньше, движок выполнения запросов как главный потребитель CPU в СУБД оптимизировали именно под это.
Чтобы показать масштаб разрыва, авторы взяли простой запрос, сумму 500 миллионов чисел. На инстансе AWS c8g.4xlarge (Graviton4, 16 vCPU) с отключёнными параллельными запросами Postgres выполняет его примерно за 20 секунд, тогда как эквивалентный цикл for на чистом Rust, за 358 мс, то есть примерно в 55 раз быстрее. Авторы оговариваются, что сравнение не вполне равноценное: Postgres проделывает намного больше внутренней работы, в том числе блокировки и разбор внутреннего формата хранения.
Дальше авторы построили миниатюрную копию движка Postgres, чтобы изолировать вклад именно этого компонента. Postgres использует классическую модель выполнения запросов «Volcano» (всего в системе более 40 типов узлов плана запроса): каждый узел реализует метод next(), возвращающий по одной строке за вызов, а выполнение запроса, это последовательные вызовы next() у корневого узла. Такая упрощённая копия на запросе «сумма 500 миллионов чисел» отрабатывает за 1,3 секунды, заметно быстрее полного Postgres, но всё ещё намного медленнее сырого цикла.
Дальше, три последовательные оптимизации той же миниатюрной копии. Батчинг: вместо одной строки за вызов next() узлы начинают отдавать пакеты по 1024 строки за раз (буфер пакета выделяется на стеке, без обращений к аллокатору памяти), время падает с 1,3 секунды до примерно 480 мс. Слияние операторов (operator fusion): отдельные узлы сканирования и суммирования заменяют одним узлом, который совмещает обе операции и устраняет лишнее копирование данных в буфер, это даёт производительность, равную обычному циклу for; в общем случае для этого нужна JIT-компиляция «на лету» под конкретный запрос, но эту тему авторы обещают раскрыть отдельным постом. SIMD: добавление векторных инструкций процессора (в примере, под архитектуру aarch64/ARM) поверх слияния операторов доводит время до 135 мс, почти втрое быстрее обычного цикла for и, по словам авторов, в 10 раз быстрее исходной небатченной Volcano-копии.
Замеры проводились на PostgreSQL 18.4 с отключёнными параллельными воркерами (max_parallel_workers_per_gather = 0), данными, прогретыми в shared buffers, медианой из 5 прогонов для Postgres и по 4 прогона на каждую Rust-реализацию, всё на одной машине в одном процессе.
Ключевые факты
- pgrust 0.2 быстрее предыдущей версии pgrust в 10 раз; Postgres на OLTP-бенчмарках, на 30%; на Clickbench (аналитика), в 300 раз, опережая при этом и сам Clickhouse
- Из общих 300 раз ускорения на Clickbench около 10-кратного вклада дал именно движок выполнения запросов
- На тестовом запросе «сумма 500 млн чисел»: Postgres ~20 секунд, сырой цикл for на Rust, 358 мс (~55x быстрее)
- Миниатюрная копия движка Postgres на том же запросе: 1,3 с без оптимизаций → 480 мс после батчинга по 1024 строки → 135 мс после добавления SIMD поверх слияния операторов
- Бенчмарки поставлены на AWS c8g.4xlarge (Graviton4, 16 vCPU), PostgreSQL 18.4; тему JIT-компиляции в pgrust авторы оставили для отдельного поста
Почему это важно
Пост показывает не просто число «в 300 раз», а откуда оно берётся: архитектура Postgres спроектирована под эпоху, когда узким местом был дисковый ввод-вывод, а сегодня для многих нагрузок дисковый ввод-вывод почти не участвует, данные помещаются в память, а узкое место сместилось к процессору и пропускной способности памяти. Это системный аргумент о том, почему СУБД тридцати-сорокалетней давности можно на порядки обогнать в аналитических сценариях, если перепроектировать движок выполнения запросов под современное железо.
Кому это важно
Инженерам, которые строят аналитическую инфраструктуру поверх Postgres и упираются в производительность запросов; разработчикам баз данных и низкоуровневых движков обработки данных; всем, кто пишет высокопроизводительный код обработки больших массивов чисел на Rust или похожих языках и интересуется, откуда берётся оверхед построчной обработки.
Как это применить
Пост раскладывает путь ускорения на три конкретных, переносимых на другие проекты приёма: батчинг (обрабатывать данные пакетами, а не по одной строке, чтобы не терять на оверхеде вызова функции и лучше использовать конвейер процессора); слияние операторов (схлопывать соседние операции в одну, когда заранее известно, что они выполняются вместе, устраняя лишнее копирование данных); и SIMD (использовать векторные инструкции процессора для обработки нескольких значений одной командой). Отдельно подчёркнута практика выделять буферы на стеке, а не через аллокатор памяти, в горячем пути.
Можно ли доверять
Источник, технический пост самой команды pgrust с разбором собственного кода и бенчмарков; сторонней проверки цифр в тексте нет. Замеры описаны подробно и воспроизводимо (конкретный инстанс AWS, версия PostgreSQL, число прогонов, отключённая параллельность), что повышает доверие к методике, но проект находится на стадии версии 0.2, и все итоговые цифры даны исключительно авторами продукта.
Риски и подводные камни
Заявленное ускорение (300x на Clickbench, 30% на OLTP) опирается на бенчмарки самой команды pgrust, а не на независимую проверку. Иллюстративный пример «358 мс против 20 секунд» сами авторы называют не вполне равноценным сравнением, поскольку Postgres выполняет намного больше внутренней работы. JIT-компиляция, которую авторы называют ключевой для обобщения operator fusion на произвольные запросы, в этом посте не реализована и не протестирована, она отложена на будущий материал. Ни имя автора, ни название компании, ни точная дата релиза версии 0.2 в тексте не указаны.