У традиционной архитектуры PostgreSQL есть фундаментальные ограничения: вертикальное масштабирование рано или поздно упирается в возможности одного сервера, а попытки масштабировать систему горизонтально приводят к репликации, шардированию и связанным с ними тупикам и компромиссам. Сколько ни улучшай PostgreSQL, сколько ни отчитывайся о реальных примерах внедрения, архитектурный потолок от этого никуда не исчезает: есть класс задач, где дальнейшее развитие в рамках традиционной архитектуры объективно затрудняется.
Именно здесь появляется Tantor Polar — распределенная СУБД с разделением вычислений и хранения и 100% совместимостью с PostgreSQL. На ее основе построена машина баз данных Tantor XData Gen3, объединяющая мощные вычислительные узлы, высокопроизводительное общее хранилище и высокоскоростную сеть c RDMA в единую систему. В этой статье мы покажем, что эта архитектура дает на практике — рассмотрим устройство синергично работающих программных и аппаратных модулей позволяющих достичь высоких показателей в популярных нагрузочных фреймворках, таких как TPC‑B (250 тыс. TPS) и TPC‑C (2,6 млн NOPM).

Архитектура Tantor Polar
В основе архитектуры распределенной СУБД — несколько вычислительных узлов, подключенных к общему хранилищу данных (shared storage). Это позволяет избежать проблем, характерных для shared memory и shared nothing архитектур, а также масштабировать необходимые типы ресурсов независимо друг от друга. Если необходимо больше процессорных ядер и/или оперативной памяти, можно добавлять вычислительные узлы. Если столкнулись с ограничениями пропускной способности системы ввода‑вывода или необходимо хранить больший объем данных, можно масштабировать хранилище.

Основной узел записывает на общее хранилище WAL и страницы данных. Реплики читают данные с того же общего хранилища. Добавить новую реплику легко, копирование данных не требуется. В качестве хранилища может быть использовано любое блочное устройство. Рекомендованный способ организации общего хранилища — Linux MD Cluster поверх NVMe‑oF с поддержкой RDMA.
Tantor Polar использует, дополняет и развивает различные механизмы оптимизации и повышения производительности PolarDB, такие как LogIndex, WAL pipelining, CSN и др.Общее хранилище снимает необходимость в классической репликации. Однако данные находящегося в оперативной памяти buffer pool на репликах устаревают после того, как основной узел производит какие‑либо изменения. Для поддержания реплик в актуальном состоянии достаточно передать на них метаданные WAL с информацией о том, какие страницы были обновлены. Метаданные имеют гораздо меньший объем, чем сами данные, а их передача осуществляется по RDMA, и это позволяет достичь высокой скорости репликации. Кроме того, реплики не проигрывают весь WAL в последовательном режиме, как это происходит в обычном PostgreSQL. Вместо этого Tantor Polar использует специальную структуру LogIndex — отображение идентификатора страницы памяти на список LSN (Log Sequence Number) ее изменений, для того, чтобы проигрывать WAL по требованию (во время чтения) и в многопоточном режиме. В результате лаг репликации составляет несколько миллисекунд.
Tantor Polar оптимизирует запись в WAL за счет конвейерной обработки. В стандартном PostgreSQL каждый процесс, фиксирующий транзакцию, выполняет запись и сброс WAL на запоминающее устройство, конкурируя за блокировки и систему ввода‑вывода. При высокой транзакционной нагрузке это становится узким местом. Конвейерная обработка позволяет нескольким потокам помещать LSN своих транзакций в очередь, после чего специальный обработчик сохраняет все содержимое очереди в пакетном режиме. Это существенно ускоряет запись WAL.
Tantor Polar использует механизм CSN для того, чтобы упростить и ускорить определение видимости при многоверсионном управлении доступом к данным (MVCC — Multi Version Currency Control). В традиционной реализации PostgreSQL для определения видимости изменений транзакции требуется получить ее статус в CLOG (Commit Log) и сравнить ее идентификатор (XID) со списком транзакций активных на момент создания снимка БД (асимптотическая сложность O(n)). При использовании механизма CSN проверка видимости сводится к простому сравнению: изменения транзакции видимы, если ее CSN больше CSN снапшота (асимптотическая сложность O(1)).
Файловая система
Основой общей системы хранения Tantor Polar является высокопроизводительная распределенная файловая система Tantor PolarFS — доработанная версия Alibaba PolarFS. PolarFS предоставляет Tantor Polar доступ к блочному устройству на уровне файловой системы. Она оптимизирована для работы с NVMe‑oF поверх RDMA. Для реализации ввода‑вывода используется асинхронная библиотека libaio и, соответственно, режим небуферизованного доступа к файлам O_DIRECT. Это позволяет исключить страничный кеш ОС и использовать до 75% RAM под buffer pool. Tantor PolarFS содержит собственную реализацию fsync, позволяющую эффективно производить надежную запись данных на произвольное блочное устройство. Существенно оптимизирован и переработан механизм межпроцессного взаимодействия (по сравнению с исходной реализацией). Группировка poll‑запросов реплик для проверки изменения метаданных позволяет минимизировать расходы на чтение данных из специального файла. Используя анонимные файлы в памяти (memfd), Tantor PolarFS получает непосредственный (zero‑copy) доступ ко всем резидентным буферам разделяемой памяти PostgreSQL — WAL buffers, buffer pool, COPY buffer, SLRU pages и так далее
Перечисленные оптимизации позволили на порядок увеличить скорость чтения и записи в Tantor PolarFS по сравнению с исходной реализацией.
Балансировка и параллелизация
Для балансировки нагрузки и пулинга соединений Tantor Polar использует Tantor Polar Proxy — прокси‑сервер, основанный на ProxySQL. Прокси‑сервер позволяет равномерно распределять читающие запросы между узлами. Также в нем реализован пул соединений.
Однако для тяжелых аналитических запросов простого распределения на уровне балансировщика может быть недостаточно. Для таких запросов архитектура Tantor Polar позволяет задействовать массивно‑параллельную обработку, благодаря которой один запрос может выполняться сразу на нескольких узлах (независимо от того, на какой узел его направил балансировщик). При этом в рамках одного узла запрос также может исполняться параллельно в несколько потоков. MPP реализована с помощью механизма эластичного распараллеливания запросов PX/ePQ (Parallel Executor / elastic Parallel Query), использующего доработанный планировщик GPORCA. Механизм называется эластичным, так как каждый узел имеет доступ ко всем данным, что дает возможность легко добавлять вычислительные узлы «эластично» увеличивая степень параллелизации выполнения запроса (DOP — degree of parallelism).
Архитектура МБД Tantor XData Gen3

Машина баз данных Tantor XData Gen3 на аппаратном уровне обеспечивает поддержку реализации архитектурных возможностей СУБД Tantor Polar, таких как разделение и независимое масштабирование подсистем вычислений и хранения.
В основе XData Gen3:
мощные вычислительные серверы на многоядерных процессорах AMD для запуска экземпляров Tantor Polar (основного узла и реплик);
высокопроизводительные (NVMe) запоминающие устройства, подключенные к серверам системы хранения;
сетевые коммутаторы Infiniband/RoCE v2 от 100 Гбит и выше для высокоростного внутреннего взаимодействия между узлами через RDMA.

На каждом узле хранения несколько локальных NVMe‑устройств объединяются в RAID 0 с помощью mdadm. Том RAID 0 каждого узла доступен на других узлах через выделенную многоканальную сеть NVMe‑oF с использованием RDMA. Таким образом, каждый узел хранения видит все тома других узлов (fullу connected mesh). На каждом узле хранения с помощью Linux MD Cluster формируется кластерное устройство RAID 1, состоящее локального и удаленных томов. Локальный RAID 0 поддерживает запись со скоростью 30 Гбит/с. При наличии трех узлов хранения зеркалирование генерирует трафик 60 Гбит/с. Для изолированной передачи этого трафика используется выделенный канал 100 ГБит/сек.
Подробнее об устройстве и отличительных особенностях МБД Tantor XData Gen3 можно прочесть в этой статье.
Как все работает на практике
Рассмотрим, каких практических результатов позволяют достичь описанные выше архитектурные особенности МБД Tantor XData Gen3 и СУБД Tantor Polar. Для генерации нагрузки использовались тесты TPC: TPC‑B, TPC‑C и TPC‑E. Эти тесты позволяют проверить работу СУБД под OLTP‑нагрузкой в виде большого количества одновременных SQL‑запросов.
Все тесты выполнялись многократно, приведенные ниже результаты являются усредненными.
Конфигурация тестового стенда
Менеджмент (1 сервер) |
Процессоры: 2 x AMD 192 ядер, 3.2 ГГц Оперативная память: 1 Тб, DDR5 Диск: SSD SAS/SATA 30 Тб Сеть: 2 x Infiniband ConnectX-6 100 Гбит |
Вычислительные узлы (3 сервера) |
Процессоры: 2 x AMD 192 ядер, 3.2 ГГц Оперативная память: 1 Тб, DDR5 Диск: SSD SAS/SATA 560 Тб Сеть: 2 x Infiniband ConnectX-6 100 Гбит |
Хранилище (3 сервера) |
Процессоры: 2 x AMD 32 ядра, 3.65 ГГц Оперативная память: 256 Гб, DDR5 Диск: SSD NVMe 91 Тб Сеть: 2 x Infiniband ConnectX-6 100 Гбит |
Коммутаторы |
Данные: 2 x Infiniband 100 Гбит Управление: 48 x 1 Гбит RJ45, 4 × 10/25 Гбит, 2 x 40 Гбит |
Основные настройки
Репликация |
асинхронная |
shared buffers |
350 Гб |
huge pages |
206 000 страниц по 2 Мб |
CSN |
включен |
Тест TPC‑B
Тест TPC‑B предназначен для измерения транзакционной нагрузки (OLTP), которую способна обработать СУБД. Тест многократно выполняет транзакцию, состоящую из трех UPDATE, одного SELECT и одного INSERT операторов. Результаты теста измеряются в транзакциях в секунду (TPS — Transactions Per Second). БД состоит из четырех таблиц. Для выполнения теста использовалась утилита pgbench, входящая в стандартную инсталляцию PostgreSQL.
Т.к. под shared buffers отведено 350 Гб, тест будем проводить на БД размером ~1 Тб, чтобы исключить возможность ее полного кеширования в оперативной памяти. Для ее генерации необходимо задать коэффициент масштаба 100 000 (ключ ‑scale). Базу данных можно создать следующим образом:
PGPASSWORD=postgres pgbench \ --initialize \ --scale=100000 \ --host=localhost \ --port=5432 \ --username=postgres \ my_tpcb_db
Запуск теста
PGPASSWORD=postgres pgbench \ --protocol=prepared \ --time=300 \ --host=localhost \ --port=5432 \ --progress=1 \ --client=1000 \ --jobs=256 \ --username=postgres \ my_tpcb_db
Здесь:
|
использовать extended protocol и prepared statements |
|
эмулировать параллельную работу тысячи клиентов |
|
использовать 256 потоков для отправки запросов |
|
выводить сообщения о ходе теста раз в секунду |
Результат выполнения теста — в среднем около 250 тыс. TPS.
Пример запуска:
Скрытый текст
pgbench (PostgreSQL 15.18 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-14), 64-bit) starting vacuum...end. progress: 1.1 s, 0.0 tps, lat 0.000 ms stddev 0.000, 0 failed progress: 2.0 s, 217013.3 tps, lat 4.606 ms stddev 19.863, 0 failed progress: 3.0 s, 284939.5 tps, lat 3.507 ms stddev 3.297, 0 failed progress: 4.0 s, 288413.8 tps, lat 3.434 ms stddev 3.016, 0 failed progress: 5.0 s, 286961.1 tps, lat 3.519 ms stddev 3.697, 0 failed progress: 6.0 s, 286891.6 tps, lat 3.485 ms stddev 3.105, 0 failed progress: 7.0 s, 278812.3 tps, lat 3.586 ms stddev 3.339, 0 failed progress: 8.0 s, 266805.4 tps, lat 3.741 ms stddev 4.375, 0 failed progress: 9.0 s, 283594.2 tps, lat 3.525 ms stddev 3.314, 0 failed progress: 10.0 s, 270787.6 tps, lat 3.600 ms stddev 3.840, 0 failed progress: 11.0 s, 266464.5 tps, lat 3.846 ms stddev 4.477, 0 failed progress: 12.0 s, 281500.2 tps, lat 3.524 ms stddev 3.980, 0 failed progress: 13.0 s, 274039.1 tps, lat 3.588 ms stddev 3.664, 0 failed progress: 14.0 s, 272739.7 tps, lat 3.762 ms stddev 4.659, 0 failed progress: 15.0 s, 270624.8 tps, lat 3.683 ms stddev 3.815, 0 failed progress: 16.0 s, 281179.8 tps, lat 3.563 ms stddev 3.675, 0 failed progress: 17.0 s, 273686.6 tps, lat 3.651 ms stddev 4.096, 0 failed progress: 18.0 s, 260651.1 tps, lat 3.835 ms stddev 4.325, 0 failed progress: 19.0 s, 279534.8 tps, lat 3.578 ms stddev 3.688, 0 failed progress: 20.0 s, 280565.7 tps, lat 3.557 ms stddev 3.574, 0 failed progress: 21.0 s, 277141.9 tps, lat 3.609 ms stddev 3.966, 0 failed progress: 22.0 s, 278798.8 tps, lat 3.586 ms stddev 4.184, 0 failed progress: 23.0 s, 274611.7 tps, lat 3.635 ms stddev 3.853, 0 failed progress: 24.0 s, 258635.1 tps, lat 3.823 ms stddev 4.598, 0 failed progress: 25.0 s, 268608.7 tps, lat 3.679 ms stddev 4.221, 0 failed progress: 26.0 s, 268584.2 tps, lat 3.812 ms stddev 4.895, 0 failed progress: 27.0 s, 271344.6 tps, lat 3.688 ms stddev 4.155, 0 failed progress: 28.0 s, 275260.7 tps, lat 3.632 ms stddev 3.477, 0 failed progress: 29.0 s, 260328.4 tps, lat 3.839 ms stddev 4.276, 0 failed progress: 30.0 s, 277137.3 tps, lat 3.608 ms stddev 3.990, 0 failed progress: 31.0 s, 278381.8 tps, lat 3.586 ms stddev 3.514, 0 failed progress: 32.0 s, 270456.7 tps, lat 3.699 ms stddev 4.771, 0 failed progress: 33.0 s, 264088.1 tps, lat 3.770 ms stddev 4.328, 0 failed progress: 34.0 s, 266508.6 tps, lat 3.770 ms stddev 4.371, 0 failed progress: 35.0 s, 277826.4 tps, lat 3.598 ms stddev 4.268, 0 failed progress: 36.0 s, 262102.9 tps, lat 3.814 ms stddev 5.686, 0 failed progress: 37.0 s, 254985.3 tps, lat 3.916 ms stddev 4.777, 0 failed progress: 38.0 s, 282909.6 tps, lat 3.535 ms stddev 3.874, 0 failed progress: 39.0 s, 269163.8 tps, lat 3.716 ms stddev 4.527, 0 failed progress: 40.0 s, 268108.2 tps, lat 3.719 ms stddev 4.677, 0 failed progress: 41.0 s, 274462.2 tps, lat 3.645 ms stddev 3.798, 0 failed progress: 42.0 s, 271685.6 tps, lat 3.686 ms stddev 3.417, 0 failed progress: 43.0 s, 277169.8 tps, lat 3.606 ms stddev 3.589, 0 failed progress: 44.0 s, 275183.8 tps, lat 3.629 ms stddev 3.868, 0 failed progress: 45.0 s, 270375.2 tps, lat 3.698 ms stddev 4.011, 0 failed progress: 46.0 s, 271277.7 tps, lat 3.684 ms stddev 3.595, 0 failed progress: 47.0 s, 268475.7 tps, lat 3.724 ms stddev 2.865, 0 failed progress: 48.0 s, 251619.5 tps, lat 3.970 ms stddev 4.475, 0 failed progress: 49.0 s, 268261.2 tps, lat 3.707 ms stddev 4.110, 0 failed progress: 50.0 s, 270641.3 tps, lat 3.719 ms stddev 3.654, 0 failed progress: 51.0 s, 275049.5 tps, lat 3.636 ms stddev 4.121, 0 failed progress: 52.0 s, 264599.8 tps, lat 3.775 ms stddev 3.874, 0 failed progress: 53.0 s, 268659.3 tps, lat 3.723 ms stddev 4.293, 0 failed progress: 54.0 s, 273813.9 tps, lat 3.651 ms stddev 4.045, 0 failed progress: 55.0 s, 270361.2 tps, lat 3.697 ms stddev 4.200, 0 failed progress: 56.0 s, 266858.1 tps, lat 3.745 ms stddev 3.665, 0 failed progress: 57.0 s, 259759.5 tps, lat 3.850 ms stddev 4.425, 0 failed progress: 58.0 s, 277792.6 tps, lat 3.595 ms stddev 4.119, 0 failed progress: 59.0 s, 261231.3 tps, lat 3.831 ms stddev 3.779, 0 failed progress: 60.0 s, 246105.5 tps, lat 4.055 ms stddev 4.821, 0 failed progress: 61.0 s, 273969.0 tps, lat 3.654 ms stddev 3.347, 0 failed progress: 62.0 s, 264187.7 tps, lat 3.779 ms stddev 3.984, 0 failed progress: 63.0 s, 244876.8 tps, lat 4.082 ms stddev 3.861, 0 failed progress: 64.0 s, 266836.1 tps, lat 3.752 ms stddev 4.030, 0 failed progress: 65.0 s, 265177.0 tps, lat 3.757 ms stddev 3.597, 0 failed progress: 66.0 s, 269024.7 tps, lat 3.723 ms stddev 3.545, 0 failed progress: 67.0 s, 246314.2 tps, lat 4.056 ms stddev 3.796, 0 failed progress: 68.0 s, 261654.5 tps, lat 3.821 ms stddev 3.817, 0 failed progress: 69.0 s, 261046.6 tps, lat 3.835 ms stddev 3.818, 0 failed progress: 70.0 s, 234287.9 tps, lat 4.267 ms stddev 5.708, 0 failed progress: 71.0 s, 191669.4 tps, lat 4.385 ms stddev 8.077, 0 failed progress: 72.0 s, 235132.4 tps, lat 4.924 ms stddev 14.576, 0 failed progress: 73.0 s, 176064.2 tps, lat 5.686 ms stddev 18.263, 0 failed progress: 74.0 s, 179057.2 tps, lat 5.584 ms stddev 19.776, 0 failed progress: 75.0 s, 224393.9 tps, lat 4.461 ms stddev 9.783, 0 failed progress: 76.0 s, 224639.1 tps, lat 4.434 ms stddev 8.549, 0 failed progress: 77.0 s, 238755.0 tps, lat 4.194 ms stddev 5.369, 0 failed progress: 78.0 s, 241211.8 tps, lat 4.149 ms stddev 7.398, 0 failed progress: 79.0 s, 228743.9 tps, lat 4.277 ms stddev 6.648, 0 failed progress: 80.0 s, 221481.4 tps, lat 4.610 ms stddev 7.678, 0 failed progress: 81.0 s, 250906.7 tps, lat 3.985 ms stddev 5.667, 0 failed progress: 82.0 s, 243364.6 tps, lat 4.109 ms stddev 5.834, 0 failed progress: 83.0 s, 235682.7 tps, lat 4.238 ms stddev 5.729, 0 failed progress: 84.0 s, 233014.4 tps, lat 4.293 ms stddev 6.414, 0 failed progress: 85.0 s, 248087.8 tps, lat 4.025 ms stddev 5.274, 0 failed progress: 86.0 s, 231095.2 tps, lat 4.322 ms stddev 6.101, 0 failed progress: 87.0 s, 227428.4 tps, lat 4.406 ms stddev 5.752, 0 failed progress: 88.0 s, 255638.5 tps, lat 3.891 ms stddev 5.334, 0 failed progress: 89.0 s, 244763.4 tps, lat 4.104 ms stddev 5.289, 0 failed progress: 90.0 s, 229885.2 tps, lat 4.347 ms stddev 5.189, 0 failed progress: 91.0 s, 218177.2 tps, lat 4.184 ms stddev 6.915, 0 failed progress: 92.0 s, 260283.8 tps, lat 4.176 ms stddev 7.161, 0 failed progress: 93.0 s, 239232.2 tps, lat 4.152 ms stddev 6.047, 0 failed progress: 94.0 s, 234495.8 tps, lat 4.287 ms stddev 6.850, 0 failed progress: 95.0 s, 259313.1 tps, lat 3.852 ms stddev 4.029, 0 failed progress: 96.0 s, 241072.1 tps, lat 4.154 ms stddev 4.595, 0 failed progress: 97.0 s, 239695.0 tps, lat 4.105 ms stddev 6.522, 0 failed progress: 98.0 s, 245813.6 tps, lat 4.124 ms stddev 6.016, 0 failed progress: 99.0 s, 245729.2 tps, lat 4.076 ms stddev 6.668, 0 failed progress: 100.0 s, 239022.2 tps, lat 4.181 ms stddev 4.849, 0 failed progress: 101.0 s, 252464.6 tps, lat 3.956 ms stddev 4.701, 0 failed progress: 102.0 s, 262157.0 tps, lat 3.800 ms stddev 4.208, 0 failed progress: 103.0 s, 246570.6 tps, lat 4.070 ms stddev 4.583, 0 failed progress: 104.0 s, 234252.2 tps, lat 4.266 ms stddev 7.021, 0 failed progress: 105.0 s, 217362.4 tps, lat 4.578 ms stddev 8.443, 0 failed progress: 106.0 s, 261793.6 tps, lat 3.841 ms stddev 4.618, 0 failed progress: 107.0 s, 246110.6 tps, lat 4.056 ms stddev 4.768, 0 failed progress: 108.0 s, 233725.9 tps, lat 4.278 ms stddev 5.542, 0 failed progress: 109.0 s, 261310.3 tps, lat 3.822 ms stddev 4.290, 0 failed progress: 110.0 s, 255688.2 tps, lat 3.871 ms stddev 4.512, 0 failed progress: 111.0 s, 249768.7 tps, lat 4.050 ms stddev 5.068, 0 failed progress: 112.0 s, 247486.8 tps, lat 4.039 ms stddev 4.528, 0 failed progress: 113.0 s, 259301.0 tps, lat 3.851 ms stddev 3.713, 0 failed progress: 114.0 s, 245150.8 tps, lat 4.079 ms stddev 4.110, 0 failed progress: 115.0 s, 235689.9 tps, lat 4.245 ms stddev 5.324, 0 failed progress: 116.0 s, 259283.0 tps, lat 3.858 ms stddev 4.991, 0 failed progress: 117.0 s, 258534.1 tps, lat 3.868 ms stddev 5.381, 0 failed progress: 118.0 s, 247524.4 tps, lat 4.035 ms stddev 4.083, 0 failed progress: 119.0 s, 252071.5 tps, lat 3.963 ms stddev 4.800, 0 failed progress: 120.0 s, 250938.7 tps, lat 3.922 ms stddev 4.663, 0 failed transaction type: <builtin: TPC-B (sort of)> scaling factor: 65535 query mode: prepared number of clients: 1000 number of threads: 256 maximum number of tries: 1 duration: 120 s number of transactions actually processed: 30507016 number of failed transactions: 0 (0.000%) latency average = 3.897 ms latency stddev = 5.569 ms initial connection time = 1073.452 ms tps = 256435.810898 (without initial connection time)
Тест TPC‑C
Тест TPC‑C также предназначен для измерения транзакционной нагрузки. Считается, что он более приближен к реальным условиям, чем TPC‑B. В TPC‑C параллельно выполняются пять транзакций различной сложности. БД состоит из девяти таблиц с колонками различных типов и со значительно различающимся количеством записей. TPC‑C условно имитирует работу оптового поставщика. Результаты теста измеряются в количестве созданных заказов в минуту (NOPM — New Orders Per Minute). NOPM для TPC‑C можно получить из TPS по формуле:
где n — NOPM, t — TPS.
Стандартная утилита pgbench не позволяет запускать тест TPC‑C (имеется возможность запуска произвольных пользовательских SQL‑скриптов, но встроенная возможность непосредственного запуска TPC‑C и создания БД для него отсутствует). Для запуска теста была выбрана утилита HammerDB, в которой реализован удобный консольный интерфейс. Для проведения теста вновь создадим БД размером около 1 ТБ (10000 складов). В качестве способа реализации скриптов выберем хранимые процедуры, так как считаем, что этот вариант ближе к устройству реальных системы.
Создание базы данных
Команда |
Пояснение |
|
установить тип СУБД PostgreSQL |
|
установить тип теста TPC‑C |
|
|
параметры подключения |
|
количество складов (от этого зависит размер БД) |
|
количество потоков создания БД |
|
использовать хранимые процедуры |
|
не скрывать текст ошибок при их возникновении |
|
запустить создание БД |
Запуск теста
Команда |
Пояснение |
|
ограничить выполнение теста по времени |
|
время «прогрева» БД в минутах |
|
общее время теста БД в минутах |
|
закрыть подключения к БД от предыдущего теста (если он был) |
|
задать количество одновременных клиентских подключений |
|
создать подключения к БД |
|
запустить тест |
Тест запускался для 10, 50, 100, 200, 300, 400, 500, 1000 и 5000 клиентов (параметр vu, см. выше). Большинство результатов запусков дает результат в промежутке от 1 до 1,6 млн NOPM на 50–1000 клиентах, чуть меньше 1 млн (800–900 тыс) NOPM на 5000 клиентах и 0,5 млн NOPM на 10 клиентах. Максимальное значение, полученное на 1000 клиентах, — 2,6 млн NOMP. Запуск на 5000 клиентах производился через Polar Proxy с размером пула 1000 соединений.
Полученные результаты значительно превышают показатели, полученные на альтернативных платформах, включая Oracle Exadata X8M/X9M:

Нагрузка и используемые во время теста ресурсы (главный вычислительный узел):






Tantor Polar Proxy Transaction Splitting
Одна из наиболее мощных функциональных возможностей Tantor Polar Proxy — выполнение транзакции на нескольких узлах (transaction splitting). Т.е. прокси сервер может выполнять на репликах SELECT‑запросы (производить offloading) встречающиеся внутри транзакций. Даже если это пишущая транзакция. Причем offloading выполняется не только для SELECT‑запросов идущих вначале транзакции, но и для тех SELECT‑запросов, которые идут после DML‑операций.
Транзакция рассмотренного выше теста TPC‑B содержит тривиальные запросы, большинство из которых — UPDATE/INSERT. Это делает его не самым удачным кандидатом для проверки работы механизма transaction splitting. Однако, если транзакцию слегка модифицировать, уровня количество записей и чтений, то offloading можно наблюдать и на этом тесте.
Стандартная реализация TPC‑B содержит следующие запросы:
\set aid random(1, 100000 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10 * :scale) \set delta random(-5000, 5000) BEGIN; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); END;
Модифицированный TPC‑B-like тест для проверки работы transaction splitting может выглядеть следующим образом:
\set aid random(1, 100000 * :scale) \set bid random(1, 1 * :scale) \set tid random(1, 10 * :scale) \set delta random(-5000, 5000) BEGIN; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid + 1; SELECT abalance FROM pgbench_accounts WHERE aid = :aid + 2; UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; --UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); END;
Для запуска pgbench с измененным SQL‑скриптом используется ключ ‑file. Также в этом случае необходимо явно задать коэффициент масштаба (ключ ‑scale). Для корректной работы transaction splitting необходимо использовать протокол simple (ключ ‑protocol). В остальном запуск pgbench через прокси ничем не отличается от обычного запуска (за исключением того, что надо указать адрес, порт и учетную запись Tantor Polar Proxy). Команда может выглядеть так:
PGPASSWORD=postgres pgbench \ --file=tpcb-like.sql \ --scale=100000 --protocol=simple \ --time=120 \ --host=localhost \ --port=6133 \ --progress=1 \ --client=1000 \ --jobs=256 \ --username=postgres \ my_db
Выполнив следующий запрос в административной консоли Polar Proxy, можно увидеть распределение запросов по кластеру:
SELECT hostgroup, srv host, Queries, Latency_us FROM stats_pgsql_connection_pool;
hostgroup | srv_host | Queries | Latency_us -----------+--------------+-----------+------------ 10 | 192.168.6.10 | 122437440 | 170 20 | 192.168.6.11 | 22208236 | 208 20 | 192.168.6.12 | 22340367 | 178 (3 rows)
Здесь 192.168.6.10 — мастер и 192.168.6.11, 192.168.6.12 — реплики. Распределение не очень равномерное, так как на данном типе нагрузки не для каждого запроса успевает отрабатывать WAL flush. Тем не менее можно видеть, что на реплики уходит довольно большое количество запросов и механизм transaction splitting работает даже для случая, когда читающие запросы идут после модифицирующих.
Тест TPC‑E
TPC‑E — еще один тест для измерения транзакционой нагрузки. Это самый сложный и приближенный к реальности из рассматриваемых тестов. В TPC‑E выполняются двенадцать параллельных транзакций разных типов и сложности. База данных состоит из тридцати трех таблиц с колонками различных типов данных и кардинальности и независимыми коэффициентами масштабирования. TPC‑E условно имитирует работу брокерской организации на бирже. Результаты измеряются в количестве транзакций выполненных за тест.
Этот тест гораздо лучше подходит для демонстрации работы Tantor Polar Proxy и Transaction Splitting, в частности, потому, что он содержит порядка 75% читающей нагрузки. Тест проводился с помощью утилиты DBT-5 (документацию можно найти здесь). Для возможности использования Tantor Polar Proxy утилита была доработана, в нее была добавлена возможность использования simple протокола.
База данных для проведения тестов была создана следующим образом:
dbt5 build --tpcetools=egen -t 7000 -f 500 -w 300 pgsql my_tpce_db
Здесь:
|
путь к генератору данных |
|
количество клиентов биржи |
|
количество клиентов биржи на результат сделки |
|
период торговли в днях |
|
тип СУБД (PostgreSQL) |
|
наименование базы данных |
Запуск теста:
DURATION=1200 USERS=1000 PGUSER=postgres PGPASSWORD=postgres \ dbt5 run --client-side \ --dbaas \ --tpcetools=egen \ -h localhost \ -p 6133 \ -n my_tpce_db \ -u ${USERS} \ -d ${DURATION} \ -z "Users: ${USERS}, duration: ${DURATION}" \ pgsql \ result
Здесь:
|
не используем хранимые процедуры (для проверки Transaction Splitting) |
|
не запускать сервис СУБД (он уже запущен) |
|
путь к генератору данных |
|
хост подключения |
|
порт подключения (указываем порт Tantor Polar Proxy) |
|
наименование базы данных |
|
количество одновременных клиентских подключений |
|
длительность теста в секундах |
|
сообщение‑комментарий |
|
тип СУБД (PostgreSQL) |
|
каталог для сохранения результата |
После выполнения теста можно увидеть, что распределение запросов по узлам значительно улучшилось:
hostgroup | srv_host | Queries | Latency_us -----------+--------------+-----------+------------ 10 | 192.168.6.10 | 249571149 | 107 20 | 192.168.6.11 | 365686660 | 147 20 | 192.168.6.12 | 365400134 | 118 (3 rows)
Тест запускался для 10, 50, 100, 200, 300, 400, 500, 1000 и 5000 одновременных клиентских подключений. Результаты теста через прокси в сравнении с прямым подключением к главному узлу:

Как видно из гистограммы, с ростом количества подключений растет и эффект от применения Transaction Splitting.
Эффективный Transaction Splitting
Более явного эффекта от применения Transaction Splitting можно достичь используя для теста пользовательские скрипты.
Создание базы данных:
DROP TABLE IF EXISTS accounts; CREATE TABLE accounts ( account_id bigint PRIMARY KEY, region_id integer NOT NULL, balance numeric(18,2) NOT NULL, status integer NOT NULL DEFAULT 0, payload text NOT NULL ); DROP TABLE IF EXISTS operations; CREATE TABLE operations ( operation_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, account_id bigint NOT NULL REFERENCES accounts(account_id), amount numeric(18,2) NOT NULL, operation_ts timestamp NOT NULL, operation_type integer NOT NULL, payload text NOT NULL ); CREATE INDEX accounts_region_idx ON accounts(region_id); CREATE INDEX operations_account_idx ON operations(account_id); CREATE INDEX operations_ts_idx ON operations(operation_ts); CREATE INDEX operations_account_ts_idx ON operations(account_id, operation_ts);
Заполнение базы данных:
INSERT INTO accounts SELECT i, (i % 100) + 1, (10000 + random() * 100000)::numeric(18,2), 0, repeat(md5(i::text), 4) FROM generate_series(1, 1000000) AS g(i); INSERT INTO operations (account_id, amount, operation_ts, operation_type, payload) SELECT (random() * 999999)::bigint + 1, (random() * 10000)::numeric(18,2), now() - random() * interval '365 days', (random() * 4)::integer, repeat(md5(i::text), 2) FROM generate_series(1, 20000000) AS g(i); VACUUM (ANALYZE) accounts; VACUUM (ANALYZE) operations;
Бизнес логика (banking.sql):
BEGIN; SELECT a.region_id, o.operation_type, count(*) AS operation_count, sum(o.amount) AS total_amount, avg(o.amount) AS avg_amount FROM operations o JOIN accounts a ON a.account_id = o.account_id WHERE o.operation_ts >= now() - interval '30 days' AND a.region_id = ( SELECT region_id FROM accounts WHERE account_id = :account_id) GROUP BY a.region_id, o.operation_type; SELECT count(*) AS operation_count, coalesce(sum(amount), 0) AS total_amount, coalesce(avg(amount), 0) AS avg_amount, coalesce(max(amount), 0) AS max_amount FROM operations WHERE account_id = :account_id AND operation_ts >= now() - interval '180 days'; SELECT account_id, region_id, balance, status, length(payload) FROM accounts WHERE account_id = :account_id; UPDATE account SET balance = balance + 1.00, status = status + 1 WHERE account_id = :account_id; SELECT account_id, balance, status FROM accounts WHERE account_id = :account_id; SELECT a.region_id, count(*) AS operation_count, sum(o.amount) AS total_amount, avg(o.amount) AS avg_amount, min(o.amount) AS min_amount, max(o.amount) AS max_amount FROM operations o JOIN accounts a ON a.account_id = o.account_id WHERE o.operation_ts >= now() - interval '30 days' AND a.region_id = ( SELECT region_id FROM accounts WHERE account_id = :account_id) GROUP BY a.region_id; COMMIT;
Запуск теста без прокси:
PGPASSWORD=postgres pgbench --file=banking.sql --host=localhost --protocol=prepared --progress=1 --time=300 --client=1000 --jobs=128 --username=postgres --port=5432 my_db
Результат одного из запусков:
transaction type: routing.sql scaling factor: 1 query mode: prepared number of clients: 1000 number of threads: 128 maximum number of tries: 1 duration: 300 s number of transactions actually processed: 71573 number of failed transactions: 0 (0.000%) latency average = 3925.378 ms latency stddev = 9731.581 ms initial connection time = 1050.291 ms tps = 235.925803 (without initial connection time) statement latencies in milliseconds and failures: 0.048 0 \set account_id random(1, 1000000) 2964.291 0 BEGIN; 489.044 0 SELECT 0.860 0 SELECT 0.504 0 SELECT 0.799 0 UPDATE accounts 0.330 0 SELECT 467.913 0 SELECT 1.696 0 COMMIT;
Запуск теста через прокси:
PGPASSWORD=postgres pgbench --file=banking.sql --host=localhost --protocol=simple --progress=1 --time=300 --client=1000 --jobs=128 --username=postgres --port=6133 my_db
Результат одного из запусков:
transaction type: routing.sql scaling factor: 1 query mode: simple number of clients: 1000 number of threads: 128 maximum number of tries: 1 duration: 300 s number of transactions actually processed: 169934 number of failed transactions: 0 (0.000%) latency average = 1761.360 ms latency stddev = 1774.200 ms initial connection time = 1046.546 ms tps = 566.430824 (without initial connection time) statement latencies in milliseconds and failures: 0.095 0 \set account_id random(1, 1000000) 12.761 0 BEGIN; 146.804 0 SELECT 1.453 0 SELECT 1.040 0 SELECT 5.141 0 UPDATE accounts 1.957 0 SELECT 1582.483 0 SELECT 9.650 0 COMMIT;
Реплики забирают на себя тяжелые аналитические запросы, поэтому на этом тесте наблюдаем кратный рост нагрузки при использовании Tantor Polar Proxy (более чем в два раза):

Дальше — больше!
Машина баз данных Tantor XData Gen 3 на основе СУБД Tantor Polar — прорывное решение для российского рынка. Мощная современная аппаратная архитектура, существенные доработки и экстремальные оптимизации известных программных продуктов, максимально согласованная работа всех используемых модулей в рамках единого ПАК и 100% совместимость с PostgreSQL, приложениями и расширениями позволяют получить невероятно эффективное и универсальное решение для промышленной обработки больших объемов данных «под ключ».
В статье нарочно не раскрыта тема тестирования производительности системы под OLAP‑ и HTAP‑ нагрузками. Эту часть мы рассмотрим 10 сентября в нашем докладе на Tantor JAM 2026. Регистрируйтесь, приходите, будет интересно!
Sleuthhound
Любопытная железка. Сколько хоть примерно будет стоить набор железа + лиценции описанные в даном тесте?