
Предисловие
PostgreSQL уже довольно давно и прочно закрепилась в топе популярных СУБД. Ресурс DB-Engines Ranking признавал ее “СУБД года” за последнее десятилетие четырежды – в 2017, 2018, 2020 и 2023 гг. Огромная популярность связана с несколькими факторами: во-первых, с полностью либеральной лицензией (PostgreSQL License), во-вторых – с расширяемостью, далее – с доказанной на практике репутацией надежной СУБД, универсальностью и т.д.
В то же время PostgreSQL иногда критикуют за излишний консерватизм, сложность добавления в upstream больших патчей и новых функций. Это приводит к формированию пула архитектурных ограничений, которые, как показывает практика, не устраняются годами.
В 2021 году Александр Коротков проводил доклад “Solving PostgreSQL wicked problems”, где подробно описывал эти нерешенные проблемы и то, как они решаются в OrioleDB. В данной статье мы расширим этот список и расскажем, каким образом над снятием архитектурных ограничений PostgreSQL работаем мы. Это первая статья из цикла, она будет скорее обобщающей, – мы опишем архитектуру в целом. А вот в следующих статьях раскроем конкретные “фичи” по отдельности.
"А не замахнуться ли нам на Вильяма нашего Шекспира?" (С)
Российский рынок СУБД и машин баз данных (МБД) по понятным причинам несколько десятилетий был поделен между крупными игроками – Oracle, Microsoft, IBM, SAP, Teradata и другими. Это не могло не выстроить вполне конкретные ожидания рынка от данного класса продуктов. Мы в «Тантор Лабс» занимаемся разработкой МБД Tantor XData c начала 2023 года, и за эти три года удалось выпустить два поколения продукта, набить шишки в реальной эксплуатации и значительно продвинуться с точки зрения функциональности, удобства и операционной эффективности. Все это время бизнес, инженеры, разработчики сравнивали наш продукт с Oracle Exadata. Что уж греха таить, сравнение было не в нашу пользу, и дело не только в ограничениях PostgreSQL. По нашей задумке, новое (третье) поколение XData должно было преодолевать все основные узкие места поколения предыдущего:
Ограничение БД одним физическим сервером, т.е. отсутствие горизонтального масштабирования;
Локальный Storage в совокупности с XDP (XData Processor) дает лучшие в своем классе характеристики по I/O (более 2.1 млн IOPS при использовании SSD), но когда Storage еще «есть», а Compute (чаще всего CPU) «закончился», — это иногда приводило к возникновению «перекосов»;
Необходимость обязательно «таскать за собой» собственную быструю (до 35ТБ/час) backup‑систему для обеспечения адекватного RTO/RPO (это особенно актуально для баз данных размером в десятки ТБ);
Полная копия данных на каждом standby‑узле (физическая репликация для кластера из 3 нод);
Деградация производительности при большом количестве активных одновременных соединений (5000+);
Проблема с традиционным XID‑based MVCC при высокой конкуренции;
Узкое место в виде журнала транзакций (WAL) при высоком commit rate;
Параллелизация запросов (parallel query) в рамках одного инстанса;
Отсутствие возможности работы как HTAP‑система, когда на одном и том же наборе данных в реальном времени одновременно должна поддерживаться транзакционная (OLTP) и аналитическая обработки (OLAP).
Да, большинство этих проблем связаны с архитектурой PostgreSQL, – ее нужно было менять в корне. И это повлекло за собой изменение всей архитектуры Tantor XData Gen3 по сравнению со вторым поколением – вплоть до железа и сети. Но самая большая проблема была в том, что наша СУБД должна была сохранить полную совместимость с обычным Tantor Postgres и всеми бизнес-приложениями, включая 1С. А это – уже задача со звездочкой, т.к. любой серьезный форк, значительно меняющий архитектуру, ставит под вопрос наследование экосистемы и расширяемости PostgreSQL. Именно поэтому мы отказались от идеи масштабирования с помощью шардинга (привет Citus), хотя, конечно, эта технология имеет в некоторых сценариях свои преимущества, например, в аналитике или тенантной модели БД. Мы начали активно смотреть в сторону реализации подходов с разделением Compute и Storage. Подобных успешных проектов на международном рынке уже достаточно, мы писали об этом в нашем блоге. Первопроходцами в теме мы, конечно же, не были.
В итоге после длительного R&D команда остановилась на проекте PolarDB for PostgreSQL от Alibaba. Нам пришлось провести полный реверс-инжиниринг кода, устранить все барьеры, мешавшие достичь нужной производительности, восполнить недостающую функциональность и создать документацию, так как оригинальная де-факто не отражает реальность и включает множество отсылок к функциональности в облаке. Вообще, документация, научные статьи, блоги по PolarDB могут свести с ума, поскольку непонятно, о какой версии PolarDB в каждом конкретном случае идет речь: open source Postgres, или MySQL, или об облачных версиях этих продуктов, или же это просто исследовательская статья, или экспериментальный функционал, которого нигде нет :) Чтобы ощутить всю прелесть, прибавьте теперь к этому всему китайский язык. В итоге мы просто начали писать свою документацию, основываясь на исходном коде, тем более, что часть функционала пришлось переписать.
Мы создали отдельный форк СУБД Tantor Polar, имеющий общие элементы и архитектуру с оригинальной открытой версией PolarDB for PostgreSQL, но представляющий собой его глубокую переработку. Далее пойдет речь именно об этой редакции, а не об open source версии PolarDB.
Архитектура СУБД Tantor Polar в Tantor XData Gen3
Compute-Storage separation
Как уже было сказано выше, Tantor Polar наследует архитектуру оригинального PolarDB, то есть, представляет собой СУБД с реализованной технологией разделения Compute и Storage. Однако Tantor Polar может работать и в режиме Compute-Storage Integration, и тогда его поведение ничем не будет отличаться от обычной Tantor Postgres или PostgreSQL. Основным требованием и особенностью функционирования Tantor Polar в режиме с shared storage является протокол RDMA (Remote Direct Memory Access) – это может быть RoCEv2 (RDMA over Converged Ethernet version 2) или Infiniband.

СУБД Tantor Polar в режиме с shared storage следует таким принципам совместного хранения данных:
Основной узел (RW) может обрабатывать запросы на чтение и на запись. Узлы, предназначенные только для чтения (RO), могут обрабатывать только запросы на чтение;
Только RW-узел может записывать данные в общее хранилище. Таким образом, данные, которые вы читаете на RW, совпадают с данными, которые вы читаете на RO-узлах;
RW-узел записывает WAL в общее хранилище, и на RO-узлы реплицируются только метаданные WAL-записей.
RW-узел реплицирует только метаданные WAL на RO-узлы для обеспечения синхронизации страниц в памяти этих узлов с страницами в памяти RW;
RO-узлы применяют записи WAL (метаданные) для обеспечения синхронизации страниц в памяти этих узлов с страницами в памяти RW.

Архитектура заключает в себе две ключевые проблемы синхронизации в памяти узлов: проблему устаревания страниц и проблему будущих страниц. Для их решения используется специальный инвертированный индекс, который хранит сопоставление каждой страницы с записями WAL этой страницы. LogIndex — это постоянная справочная таблица, сопоставляющая идентификатор каждой страницы данных со списком LSN записей WAL, содержащих изменения для этой страницы.
Реплицирование LogIndex с узлов RW на узлы RO происходит с использованием стандартных средств репликации, то есть процессов записи/отправки WAL на узле RW и процесса приема WAL на узлах RO. Иными словами, в отличие от традиционной физической репликации PostgreSQL, основной узел не передает полные записи WAL напрямую на узлы-реплики.
Есть и проблема возникновения блокировок DDL. В архитектуре СУБД Tantor Polar с общим хранилищем RW- и RO-узлы обращаются к одним и тем же базовым файлам. RW-сервер не должен изменять файлы таблицы, пока какой-либо RO-узел продолжает их читать. Когда RW выполняет операцию DDL (например, DROP TABLE) для изменения таблицы, он получает эксклюзивную блокировку DDL на эту таблицу. Эксклюзивная блокировка DDL реплицируется на RO-узлы вместе с записями WAL. Запись информации о блокировке в WAL недостаточна: RW-узел также должен дождаться, пока RO-узлы получат блокировку (и, следовательно, ни одна реплика не будет читать эти файлы), прежде чем выполнять DDL. Для уменьшения влияния блокировок DDL в Tantor Polar есть асинхронная оптимизация DDL, которая уменьшает задержку реплик, когда реплика должна дождаться выполнения запросов прежде чем сможет получить блокировку. Подробнее об этом мы расскажем в отдельной статье о репликации.
Сочетание подхода с общим хранилищем и описанных оптимизаций значительно сокращает задержку репликации и обеспечивает следующие преимущества:
Разделение операций чтения/записи и горизонтальное масштабирование чтения: балансировка нагрузки с помощью умного прокси и RW-splitting, позволяет Tantor Polar обеспечивать архитектуру, сопоставимую с Oracle Real Application Clusters (RAC);
Высокая доступность: сокращается время, необходимое для переключения на резервный сервер;
Снижение стоимости и простота использования: работа всего кластера с одной копией БД, отсутствие репликации данных и сохранение классического пользовательского опыта работы с отказоустойчивым кластером Primary-Standby.
Репликация в Tantor Polar
RW-узел Tantor Polar поддерживает обычную репликацию PostgreSQL на узлы горячего резерва (standby) при использовании локального хранилища. В этом случае вся документация PostgreSQL по физической, логической и синхронной/асинхронной репликации будет применима и к Tantor Polar.
Ниже описана репликация на реплики с общим хранилищем (узлы RO в терминологии Tantor Polar), которая в некоторых аспектах отличается от традиционной репликации PostgreSQL. Подобно традиционному PostgreSQL, репликация Tantor Polar (процесс переноса метаданных WAL на RO-узлы) может быть как синхронной, так и асинхронной.
Синхронная репликация:
гарантирует, что узлы RO смогут восстановить самые актуальные данные по запросу;
с точки зрения согласованности кластер функционирует как одноузловая СУБД;
для некоторых рабочих нагрузок задержка записи может явиться неприемлемой.
Асинхронная репликация:
узлы RO могут отставать в обработке и подтверждении метаданных WAL;
задержка записи на узле RW не зависит от производительности узлов RO.
для обеспечения согласованности транзакций необходимо предпринимать дополнительные шаги.
Tantor Polar использует стандартный протокол потоковой физической репликации PostgreSQL, но изменяет содержимое сообщений (RW-узел отправляет только метаданные WAL) и способ обработки и подтверждения этих сообщений RO-узлами. Как и в PostgreSQL, режим синхронной репликации в Tantor Polar управляется с помощью synchronous_commit, но с другой семантикой:
synchronous_commit | WAL в shared storage | Метаданные WAL, полученные RO-узлом | Метаданные WAL, обработанные RO-узлом (хранятся в LogIndex) |
remote_apply | ✅ | ✅ | ✅ |
on | ✅ | ✅ | ❌ |
remote_write | ✅ | ✅ | ❌ |
local | ✅ | ❌ | ❌ |
off | ❌ | ❌ | ❌ |
В PostgreSQL режимы synchronous_commit = on и remote_write различаются в зависимости от того, ожидает ли основной узел flush_lsn или write_lsn реплики перед подтверждением транзакции. В Tantor Polar такого различия нет. Оба значения LSN обновляются одним и тем же числом как только поступают соответствующие метаданные WAL независимо от того, были ли они обработаны.
Massive Parallel Processing (MPP)
Tantor Polar способна совмещать транзакционную (OLTP) и аналитическую обработку (OLAP) на одном и том же наборе данных в реальном времени, то есть функционировать как реальная HTAP-СУ��Д. Для аналитической нагрузки реализуется подход, похожий на MPP-базы данных, таких как GreenplumDB, но с определенными особенностями:
все RO-узлы могут функционировать как узлы-координаторы (Сoordinators). Таким образом, производительность Tantor Polar не ограничивается наличием всего одного узла-координатора;
все RO-узлы могут функционировать как узлы-воркеры (workers);
любой SQL-запрос может выполняться параллельно на всех доступных вычислительных узлах. Это увеличивает вычислительную мощность и позволяет более гибко планировать рабочие нагрузки;
Tantor Polar можно настроить для одновременного выполнения различных типов рабочих нагрузок (OLTP или OLAP) на разных вычислительных узлах.
В кластере Tantor Polar общее хранилище распределяется между всеми вычислительными узлами, поэтому для сканирования таблиц в таких кластерах нельзя использовать метод сканирования, применяемый в обычных MPP-базах данных. Elastic Parallel Query (ePQ) — это распределенный механизм обработки запросов Tantor Polar, основанный на Greenplum (GPORCA) c доработками под общее хранилище.

ePQ использует основанные на Greenplum планировщик (Planner) и исполнитель (Executor) запросов, поэтому, несмотря на то, что Tantor Polar и Greenplum имеют довольно разные архитектуры, есть значительное совпадение в терминологии, переменных конфигурации и документации.
Одно из фундаментальных понятий – сегмент (segment). Он представляет собой блок обработки запросов, способный выполнять части запроса (slices) параллельно с другими сегментами. Для Greenplum сегменты — по сути, отдельные экземпляры PostgreSQL, хранящие части распределенных данных, и обычно на одной физической машине работает несколько сегментов. Для Tantor Polar с ее архитектурой общего хранилища сегменты представляют собой логические единицы выполнения в рамках одного экземпляра Tantor Polar на каждом вычислительном узле, при этом все сегменты совместно используют одни и те же данные. Это означает, что сегменты Tantor Polar во всех отношениях равны, а уровень параллелизма определяется не распределением данных между сегментами, а планами запросов и конфигурацией. Это отличие позволяет решать проблему неравномерности распределения данных при шардировании в Greenplum, зачастую приводящую к неравномерной загруженности сегментов. Количество сегментов на вычислительных узлах Tantor Polar можно настроить с помощью параметра polar_px_dop_per_node.
ePQ- процесс – это процесс, участвующий в распределенном выполнении запросов. Есть два типа ePQ-процессов:
рабочий процесс координатора (
Coordinator), создаваемый узлом координатора;рабочие процессы PX (
Parallel Executor), называемые в некоторых источниках сегментными рабочими процессами. Рабочие процессы PX создаются рабочими узлами.
ePQ имеет следующую архитектуру:
координатор состоит из двух частей: потока данных (
DataThread) и потока управления (ControlThread);DataThread собирает и агрегирует кортежи;
функция
ControlThreadуправляет ходом сканирования каждого оператора сканирования;рабочий поток, сканирующий данные с высокой скоростью, может сканировать больше логических фрагментов данных;
учитывается родство (
affinity) буферов.
Срезы (slice) — это логические части плана выполнения запроса, которые могут выполняться параллельно. Например, для запроса…
SELECT c_name, SUM(o_totalprice) FROM customer INNER JOIN orders ON c_custkey = o_custkey GROUP BY c_name;
…планировщик может создать 4 сегмента:
Slice 1: Параллельное сканирование таблицы customer
Slice 2: Параллельное сканирование таблицы orders
Slice 3: Операция хеш-соединения
Slice 4: Агрегация (GROUP BY)
Результат выполнения команды EXPLAIN для данного запроса может выглядеть примерно так:
pg_tpch=# EXPLAIN SELECT c_name, SUM(o_totalprice) FROM customer JOIN orders ON c_custkey = o_custkey GROUP BY c_name; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- PX Coordinator 48:1 (slice1; segments: 48) (cost=0.00..3829.51 rows=15000000 width=27) -> Finalize HashAggregate (cost=0.00..2899.87 rows=312500 width=27) Group Key: customer.c_name -> PX Hash 48:48 (slice2; segments: 48) (cost=0.00..2856.69 rows=312500 width=27) Hash Key: customer.c_name -> Partial HashAggregate (cost=0.00..2830.28 rows=312500 width=27) Group Key: customer.c_name -> Hash Join (cost=0.00..2419.80 rows=3125000 width=27) Hash Cond: (orders.o_custkey = customer.c_custkey) -> PX Hash 48:48 (slice3; segments: 48) (cost=0.00..863.91 rows=3125000 width=12) Hash Key: orders.o_custkey -> Partial Seq Scan on orders (cost=0.00..676.78 rows=3125000 width=12) -> Hash (cost=500.21..500.21 rows=312500 width=23) -> PX Hash 48:48 (slice4; segments: 48) (cost=0.00..500.21 rows=312500 width=23) Hash Key: customer.c_custkey -> Partial Seq Scan on customer (cost=0.00..464.34 rows=312500 width=23) Optimizer: Tantor Polar PX Optimizer (17 rows)
Встроенный pooler соединений
В Tantor Polar реализован механизм пулинга соединений, портированный из старых версий PolarDB. Называется он, на наш взгляд, немного странно – Shared Server (SS). Тем не менее, реализован он очень интересно, хотя и достаточно инвазивно. Встроенный connection pooler заменяет модель "один backend-процесс на одно подключение" на “много клиентских подключений - диспетчеры - пул backend-процессов”, которые шарятся между сессиями.
Основные элементы архитектуры SS
Session Context (SC) в shared memory хранит всё состояние сессии, которое раньше лежало в памяти конкретного backend-процесса:
prepared statements и их plancache;
сессионные GUC’и;
метаданные временных таблиц;
прочий сессионный state.
Dispatcher-ы - это отдельные процессы, которые принимают клиентские TCP-подключения и выступают как прокси:
принимают и парсят протокол (libpq);
выбирают подходящий backend из пула (по “<user, database, GUCs>” ключу);
транслируют запросы/ответы между клиентом и backend.
Backend Pool-ы. У каждого диспетчера есть несколько пулов backend-ов, которые определяются ключу пула: “<user, database, GUCs>”. В пуле находится группа backend-процессов, которые разделяются между разными клиентами.
Основной особенностью встроенного пулера SS является то, что он не работает постоянно, а включается при достижении порогового значения polar_ss_client_threshold_for_shared. Пока общее количество подключений ниже порога, SS не включается (работает как обычный Postgres), чтобы не создавать дополнительный оверхед. При превышении порога новые клиенты начинают попадать в Shared-режим (через диспетчеры и пул backend-ов).
Режимы работы backend-процессов
В SS каждый backend может динамически переключаться между тремя режимами:
Native(обычный Tantor Postgres). Этот режим может использоваться, если Shared Server выключен или пользователь, установивший коннект, находится блек-листе, указанном в специальном GUC;Shared(наиболее оптимальный режим). Даёт максимальный выигрыш при большом количестве одновременных активных соединений;Dedicated(fallback / «загрязнённый» процесс). Наиболее негативный сценарий, при котором backend переводят в режим, где он работает только с одной сессией, а после disconnect он убивается. Причин перехода в Dedicated-режим может быть несколько: изменение GUC-ов, указанных в конфигурации SS, использование запрещённых расширений, DECLARE CURSOR или CURSOR WITH HOLD, операции над таблицами с ON COMMIT DELETE ROWS, пользовательские GUC-и, LOAD динамических библиотек. То есть всё, что делает per-backend state сложным или неразделяемым, вынуждает уйти в Dedicated.
Сравнение с PgBouncer
Критерий | PolarDB Shared Server | PgBouncer |
Поддержка session state | Сохраняется в shared memory (prepared plans, GUC, temp metadata) | Ограничена (особенно в transaction mode) |
Prepared statements | Поддерживаются (хранятся в Session Context) | В transaction mode ломаются |
LISTEN / NOTIFY | Поддерживается | Не работает в transaction mode |
Session advisory locks | Поддерживаются | Не поддерживаются в transaction mode |
Temp tables | Поддерживаются (с fallback в Dedicated) | Ограничено / риск конфликтов |
GUC (SET LOCAL / SESSION) | Поддерживаются | Частично, зависит от режима |
Курсоры (WITH HOLD) | Может перевести backend в Dedicated | Не поддерживаются в transaction mode |
Расширения / LOAD | Переводят backend в Dedicated | Не поддерживается |
Fallback поведение | Shared → Dedicated → Native | Нет fallback — только выбранный режим |
Режимы работы | Native / Shared / Dedicated | session / transaction / statement pooling |
Триггеры деградации | GUC, курсоры, temp tables, плагины → Dedicated-режим | Неподдерживаемые session-фичи → ошибки |
Overhead на переключение | Низкий (внутренний IPC) | Средний (сетевой hop + proxy) |
Дополнительная латентность | Небольшая | Выше (дополнительный TCP hop) |
По сути, Shared Server – это интегрированный и более совместимый с семантикой Tantor Postgres пулер, но с более сложным поведением при переключении режимов. Без этого компонента невозможно достичь высокой производительности при большом количестве одновременных активных сессий.
Другой важный механизм, позволяющий значительно увеличить производительность Tantor Polar, – это WAL pipelining.
WAL pipelining
WAL pipelining (конвейерная обработка WAL) – это оптимизация, которая разбивает процесс фиксации журнала транзакций Tantor Polar на несколько подэтапов, каждый из которых работает независимо — подобно конвейеру CPU – для повышения пропускной способности экземпляров БД при транзакционной нагрузке. Вместо того чтобы каждый бэкэнд, выполняющий операцию фиксации, самостоятельно записывал и сбрасывал WAL-файл (конкурируя за блокировки и операции ввода-вывода), выделенные рабочие потоки конвейера продвигают позицию записи, записывают WAL-файл в кэш операционной системы, сбрасывают его в постоянное хранилище и уведомляют ожидающие бэкенды. Это уменьшает конкуренцию за блокировки и позволяет лучше организовывать пакетную обработку операций ввода-вывода.
Проблема при отсутствии конвейерной обработки WAL
В PostgreSQL (или Tantor Polar с отключенной конвейерной обработкой WAL) каждый бэкенд, выполняющий фиксацию транзакции, должен участвовать в записи и сбросе WAL. Это приводит к следующему:
конкуренция за блокировки (Lock contention) – бэкенды конкурируют за
WALInsertLock,insertpos_lck,иWALWriteLockпри определении и продвижении write/flush позиции;сериализация (Serialization) – одновременно только один бэкэнд может управлять записью и сбросом WAL-файла; другие или ожидают, или повторяют попытку;
сериализация ввода-вывода (I/O serialization) – каждый commit path может запускать собственную операцию записи и fsync, что уменьшает возможности пакетной обработки.
При высокой частоте фиксации транзакций сериализация становится узким местом.
Как WAL pipelining решает эту проблему
В конвейерной обработке WAL используются выделенные рабочие потоки, которые отвечают за этапы продвижения (advance), записи (write), сброса (flush) и уведомления (notify). Бэкенды, осуществляющие фиксацию, больше не выполняют запись или сброс, они только ставят свой LSN в очередь конвейера и ждут сигнала о завершении от рабочего потока, посылающего уведомление. Это снижает конкуренцию: бэкэнды больше не конкурируют за WALWriteLock операции ввода-вывода и не управляют ими самостоятельно. Это также обеспечивает пакетную обработку: рабочие процессы записи и сброса данных могут обрабатывать смежные WAL-файлы большими блоками. Также в зависимости от режима конвейера операции продвижения, записи, сброса и уведомления могут выполняться параллельно в разных потоках.
Конвейер может работать в пяти различных конфигурациях, обеспечивая компромисс между сложностью и производительностью:
Однопоточный режим. Основной поток последовательно выполняет операции advance + write + flush + notify.
Два потока. Основной поток выполняет операции продвижения + записи + сброса , отдельный поток(и) – для уведомлений.
Три потока, основной поток выполняет запись и сброс буфера, отдельный поток выполняет продвижение и уведомляет потоки.
Три потока, основной поток выполняет операцию продвижения + записи ; отдельный поток выполняет операцию сброса буфера и уведомляет потоки.
Выделенные потоки для каждого этапа.

По умолчанию используется второй режим.
Конвейерная обработка WAL повышает пропускную способность, когда узким местом является конкуренция за запись или сброс WAL. Преимущества зависят от рабочей нагрузки: наибольший прирост наблюдается при высоком уровне фиксации транзакций и большом количестве одновременно работающих бэкэндов, поскольку бэкэнды больше не конкурируют за операции ввода – WALWriteLock вывода и не управляют ими самостоятельно. Конвейер позволяет объединять операции записи и сброса в пакеты и может параллельно выполнять этапы продвижения, записи, сброса и уведомления (в зависимости от режима конвейера).
CSN (Commit Sequence Number)
Во время тестирования нашей МБД Tantor XData Gen2 у одного из клиентов мы столкнулись с деградацией производительности СУБД. При детальном анализе выяснилось, что бизнес-приложение активно использует SAVEPOINT-ы, что приводит к существенному росту структуры ProcArray и ожиданиях на блокировках ProcArrayLock. Это и в самом деле реальное узкое место масштабируемости в традиционном MVCC PostgreSQL. Каждый раз, когда транзакции требуется снимок, она должна получить ProcArrayLock и пройтись по всем активным бэкендам, чтобы собрать их идентификаторы транзакций. Эта операция становится все более затратной по мере роста числа одновременных соединений — при тысячах соединений конкуренция за блокировку может серьезно ограничить пропускную способность. CSN (Commit Sequence Number) устраняет это узкое место, заменяя сканирование ProcArray атомарным чтением переменных, что делает получение снимков по сути O(1) не зависящим от количества соединений.
CSN — это монотонно возрастающее 64-битное число, присваиваемое каждой транзакции в момент её фиксации. Оно представляет собой логический порядок фиксации транзакций в БД — если транзакция A фиксируется раньше транзакции B, то CSN транзакции A будет меньше, чем CSN транзакции B.
Идея CSN заключается в упрощении решений, касающихся видимости транзакций. В традиционном PostgreSQL MVCC определение видимости транзакции требует проверки ее статуса фиксации в CLOG и сравнения идентификаторов транзакций со списком выполняющихся транзакций, зафиксированных в снимке. С CSN это сводится к простому числовому сравнению: транзакция видна, если ее CSN меньше или равен CSN снимка.
CSN выгоден для рабочих нагрузок с высокой степенью параллелизма и множеством коротких транзакций. Получение моментальных снимков без блокировок хорошо масштабируется при тысячах одновременных подключений, а устранение конфликтов переполнения подтранзакций помогает при сценариях, использующих точки сохранения (SAVEPOINT) или обработку исключений PL/pgSQL. Однако использование CSN сопряжено со значительными компромиссами, связанными с длительными транзакциями.
Более подробно о том, как в Tantor Polar устроен CSN, мы расскажем в отдельной статье.
Распределенная файловая система PFS в Tantor XData Gen3
PolarFS – это высокопроизводительная распределенная файловая система, используемая в СУБД Tantor Polar. В исходном решении от Alibaba эта часть PolarDB выглядит наиболее непонятной и запутанной. Общедоступная документация по PolarFS крайне скудна. Помимо файла README проекта и руководства по PFS есть крайне мало материалов, описывающих конструкцию или поведение системы. Основной опубликованный источник информации — статья VLDB «PolarFS: An Ultra-low Latency and Failure Resilient Distributed File System for Shared Storage Cloud Database». В ней описывается версия PolarFS для Alibaba Cloud, которая отличается по архитектуре и функциональности от версии с открытым исходным кодом. Эта версия построена на основе PolarStore, ChunkServers, ParallelRaft и PolarCtrl и не была опубликована в открытом доступе; смысловое соответствие с кодовой базой с открытым исходным кодом имеет только описание API libpfs. Другие материалы, такие как презентация FAST'20 о PolarFS и вычислительном хранилище ScaleFlux, ссылаются на дополнительные проприетарные варианты и не относятся к открытой версии PolarFS.
Общий вид вычислительного уровня Tantor Polar выглядит следующим образом (еще раз напомним, что используемая нами архитектура отличается от той, которая описана в статье о VLDB).
На каждом вычислительном узле Tantor Polar использует API libpfsd для хранения и извлечения данных;
libpfsd взаимодействует с pfsd (демоном PFS) через разделяемую память;
В свою очередь, pfsd преобразует запросы из libpfsd в libpfs;
Наконец, libpfs преобразует запросы от pfsd в API POSIX и передает их базовому блочному устройству (в нашем случае nvmedisk01).

PolarFS выполняет все операции ввода-вывода с помощью O_DIRECT. Одним из важных следствий безусловного O_DIRECT использования pfsdявляется отсутствие кэша операционной системы (так называемого страничного кэша) в операциях ввода-вывода Tantor Polar, в отличие от обычного PostgreSQL. Это означает, что типичные рекомендации по shared_buffers конфигурации (например, 25-40% оперативной памяти) неприменимы.
Для Tantor Polar типичные рекомендации по настройке shared_buffers будут похожи на рекомендации для InnoDB, то есть не менее 75% доступной оперативной памяти для экземпляров с объемом оперативной памяти более 64 ГБ.
Открытая версия PFS к промышленной эксплуатации и высоким нагрузкам непригодна. В нашей реализации Tantor PFS разработан другой механизм межпроцессного взаимодействия между libpfsd и pfsd, который заменяет старый исходя из соображений производительности. Также реализован ряд других оптимизаций, которые позволили достичь нужных нам показателей производительности и утилизации ресурсов сервера.
PFS – важный компонент в архитектуре Tantor XData Gen3, и он имеет большой потенциал для дальнейшего развития. В частности, в данный момент мы ведем исследование с целью реализовать более умный Storage, чем простое блочное устройство. В архитектуре PFS заложена возможность расширения собственными device types, которые могут реализовывать иной, более продвинутый уровень хранения и интеграцию с СУБД.
Proxy и Read/Write splitting в Tantor XData Gen3
Описанная архитектура масштабирования за счет реплик имеет два основных недостатка: во-первых, единственный RW-узел, который может стать узким местом, а во-вторых, – необходимость балансировки нагрузки между RW-и RO-узлами. Первое решается рядом доработок, причем они разнятся для OLTP- и OLAP-нагрузки, и о них мы рассчитываем рассказать в будущих статьях. Вторая же проблема решается с помощью прокси, который обеспечивает read/write splitting. В своем облаке Alibaba имеет оригинальный прокси, который поддерживает read/write splitting, consistency levels и transaction splitting. В open source подобное отсутствует, поэтому нам пришлось создать собственную реализацию с поддержкой этого функционала. Вначале мы сопоставили возможности существующих прокси и провели нагрузочные тесты.
Функция | PolarProxy | pgpool-II | pgbouncer | pgcat | odyssey | ProxySQL |
✅ | ✅ | ❌ | ✅ | ✅ | ✅ | |
✅ | ❌ | ❌ | ❌ | ❌ | ❌ | |
✅ | ❌ | ❌ | ❌ | ❌ | ❌ | |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
В итоге выбор пал на ProxySQL, который мы доработали с точки зрения поддержки уровней консистентности.
На текущий момент прокси в числе прочего поддерживает консистентность на уровне сессий (session consistency) при разделении пишущих и читающих запросов. В СУБД Tantor Polar реализован специальный протокол, который позволяет обмениваться информацией с ProxySQL о том, какой LSN на каком из узлов применен. Прокси отслеживает WAL, применяемые на каждом узле, и сохраняет порядковый номер каждого журнала (LSN). Когда данные обновляются на RW-узле, LSN записывается, как сессионный LSN. Если поступает запрос на чтение, прокси сравнивает сессионный LSN с LSN на RO-узлах и для обеспечения согласованности на уровне сессии перенаправляет запрос на RO-узел, где LSN больше или равен сессионному LSN.
ProxySQL имеет богатый функционал, позволяющий обрабатывать сложные сценарии балансировки нагрузки. Только он позволил обрабатывать сотни тысяч TPS без деградации, а также добавлять RO-узлы “на лету” с перебалансировкой без рестарта. Еще раз подчеркнем, что подробнее опишем данный элемент архитектуры МБД Tantor XData Gen3 в следующих статьях.
Synchronous storage cluster в Tantor XData Gen3
Один из самых интересных “частей” машины баз данных – уровень хранения. В Oracle Exadata этот элемент архитектуры применял ряд оптимизаций, которые позволяли достигать лучшей производительности в своем классе. В Alibaba Cloud реализованы различные варианты хранения, такие как PolarStore, ChunkServers, которые реализуют более “умное” хранилище по сравнению с обычным блочным устройством. Мы же в XData пошли путем создания высокопроизводительной отказоустойчивой архитектуры хранения данных, состоящей из трехузлового синхронного кластера хранения на основе кластерного RAID1, обеспечивающего единое блочное устройство для многоу��лового кластера БД через NVMe-oF по сети RDMA. Конструкция оптимизирована для записи с чрезвычайно низкой задержкой с одного основного сервера БД и высокопроизводительного распределенного чтения с нескольких реплик.

В основе сервиса лежит кластер хранения данных из трех узлов, и каждый узел идентичен по своей аппаратной и программной конфигурации.
Локальный уровень (RAID0): На каждом узле хранения несколько локальных NVMe-накопителей объединяются в один высокопроизводительный логический том RAID0.
Внутрикластерная связь: том RAID0 с каждого узла экспортируется на два других узла через выделенную многоканальную сеть NVMe-oF. Это создает полную сетку, в которой каждый узел может видеть свой собственный локальный RAID0 и два удаленных тома RAID0.
Уровень кластерного резервирования (кластерный RAID1): на каждом узле из трех элементов формируется кластерное устройство MD RAID1 ( ):/dev/md0
Локальный RAID0-том узла;
Два удалённых тома RAID0, помеченные флагом writemostly. Этот флаг имеет решающее значение, поскольку он указывает драйверу MD использовать эти удалённые элементы только для записи, гарантируя, что все запросы на чтение обрабатываются локально для минимизации задержки.
Сеть – важнейший компонент для обеспечения максимальной производительности, низких задержек и предсказуемости поведения:
Протокол RDMA: Вся сеть использует протокол RDMA с низкой задержкой, либо RoCEv2 (RDMA поверх конвергентного Ethernet) , либо InfiniBand , как для передачи данных от клиента к хранилищу, так и для репликации данных во внутреннем хранилище.
Изоляция трафика: Сеть разделена для предотвращения конфликтов. Клиенты БД используют три выделенных канала по 100 Гбит/с (красный-зеленый-синий на диаграмме), а отдельный канал 100 Гбит/с (желтый) зарезервирован исключительно для трафика синхронной репликации между узлами хранения. Согласно fio-тестам, локальный RAID0 на одном узле может поддерживать скорость записи приблизительно 30 Гбит/с. Для поддержания трехстороннего зеркалирования требуется репликация данных на два узла, что генерирует исходящий трафик около 60 Гбит/с. Выделенный канал со скоростью 100 Гбит/с обеспечивает достаточную пропускную способность для этого трафика репликации, не влияя на клиентскую сеть.
Целевая конфигурация:
Единая подсистема: Все три узла хранения экспортируют свои RAID1(/dev/md0) устройства как часть единой логической подсистемы NVMe, идентифицируемой одним и тем же квалифицированным именем NVMe (NQN);
Раздельные очереди ввода-вывода: Чтобы предотвратить конфликты ввода-вывода на уровне целевого устройства, служба NVMe-oF настроена с отдельными очередями отправки и завершения для операций чтения и записи.
В будущем мы планируем отказаться от использования блочного устройства и заменить его на key-value программно-аппаратное устройство. По нашей оценке, это позволит существенно повысить производительность СУБД как для транзакционной, так и для аналитической нагрузки.
Что в итоге?
В итоге мы получили настоящую машину баз данных, глубоко интегрированную на аппаратном уровне с разработанным программным обеспечением. По нашим тестам, которые мы опубликуем чуть позже, в третьем поколении XData по производительности в несколько раз превзошла показатели предыдущего поколения как для OLTP-, так и для OLAP-нагрузок.
Упрощенная архитектура МБД Tantor XData Gen.3 состоит из двух вычислительных узлов, коммутаторов RDMA и трех узлов хранения. Причем можно масштабировать отдельно вычислительные узлы и отдельно узлы хранения, снимая таким образом проблемы “перекосов” и горизонтального масштабирования.

В архитектуре могут также присутствовать дополнительные узлы управления и прокси в отказоустойчивом режиме. Тема обеспечения отказоустойчивости (high availability) и катастрофоустойчивости (disaster recovery) осталась за рамками данной статьи, но в будущем мы обязательно это разберем.
Помимо использования сети RDMA, нам пришлось отказаться от процессоров Intel в пользу AMD. Выбор связан с несколькими факторами: большее количество ядер в одном процессоре, больше доступных линий PCIe, более простая архитектура сервера по сравнению с Intel.
Сравнение с Exadata
В завершении нашей первой статьи про устройство МБД Tantor XData Gen3 сделаем сравнение с Oracle Exadata 8M в части подходов и технологий. Это сравнение призвано проиллюстровать разницу подходов к решению вендорами одних и тех же задач в своих продуктах. Специально приводим на английском языке, чтобы избежать разночтений в трактовках.
Architecture
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
Core database engine | Oracle Database 19c/21c (enterprise-grade RDBMS | Tantor + with PX engine (RDBMS with distributed enhancements) |
Processing model | Shared-everything via RAC: Tightly integrated | Shared storage, one-write-many-read: RW primary coordinates cluster-wide PX Engine execution across RO replicas, on commodity hardware |
Storage model | Hybrid Columnar Compression (HCC) + ASM (optimized for analytics with triple mirroring) | PFS over NVMe-oF: 3-node InfiniBand cluster exports unified block storage with triplicate data |
Networking | RDMA over Converged Ethernet (RoCE) (low-latency, high-throughput interconnect) | RDMA over InfiniBand (ultra-low latency for compute-storage and inter-node communication) |
Scaling model | Scale-out via RAC: coupled compute/storage expansion within Exadata racks | Disaggregated scaling: Independent compute (RW + RO nodes) and storage (NVMe-oF nodes) growth |
Read/Write architecture | Smart storage offloading + Exadata cells (storage executes queries, reduces compute load) | One-write-many-read: RW primary writes, RO replicas read via SQL proxy, shared PFS storage |
Performance and scalability
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
Parallel query qxecution | Yes: Smart scan + RAC parallel execution (storage and compute parallelism) | Yes: PX Engine with cluster-wide MPP (distributed across RW primary and RO replicas) |
Columnar storage support | Yes: HCC (native columnar format for analytics) | Partial: Columnar TAM |
Compression efficiency | High: HCC optimized for analytics (significant storage savings) | Moderate: PostgreSQL-native + PFS compression (functional but less analytics-focused) |
I/O performance | Excellent: direct RDMA to NVMe storage (low-latency, high-throughput I/O) | Excellent: RDMA over InfiniBand to NVMe-oF (low-latency, high-throughput shared storage) |
Compute scalability | Up to 400 cores/rack via RAC (scales with Exadata hardware) | High: Add RO nodes for read scalability and MPP execution (commodity hardware) |
Storage scalability | Petabyte-scale via ASM (tightly integrated with Exadata cells) | Petabyte-scale via NVMe-oF: add storage nodes independently of compute |
High availability and fault Tolerance
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
High availability (HA) | Active-Active via RAC (multiple nodes serve requests concurrently) | One-write-many-read cluster: |
Data redundancy | Triple mirroring in Exadata storage cells (hardware-level redundancy) | Triple replication in NVMe-oF storage (software-defined, shared-storage redundancy) |
Failover mechanism | RAC node failover + data guard (seamless, automated failover) | OnlinePromote: RO replica promoted to RW primary with minimal RTO, no restart needed |
Disaster recovery (DR) | Active data guard, GoldenGate (real-time replication, advanced DR) | Multi-replica with RDMA sync: high-speed replication across InfiniBand. DR via DataMax. |
Query processing and analytics
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
Massively parallel processing (MPP) | Yes: RAC + smart scan (compute and storage-level parallelism) | Yes: PX Engine with cluster-wide MPP (compute-driven parallelism across RW + RO nodes) |
Vectorized execution | Yes: vector aggregates (optimized for in-memory analytics) | Partial: Lacks native vectorized implementation (columnar only) |
Indexing and optimizations | Advanced: bitmap, adaptive, auto-indexing (self-tuning for performance) | Solid: B-Tree, GIN/BRIN, distributed index creation (PX engine accelerates builds) |
Query optimizer | Cost-based optimizer + auto-Indexing (highly adaptive to workloads) | PX Optimizer: extended GPORCA for shared-storage MPP (optimized for distributed queries) |
HTAP, mixed workloads
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
OLTP performance | Best-in-class: RAC + smart flash cache (optimized for transactions) | High: RW primary with RDMA-optimized PFS (strong, but single-writer vs. RAC active-active) |
OLAP performance | Excellent: HCC + smart scan (storage-driven analytics) | Excellent: cluster-wide MPP (compute-driven, scalable with RO nodes) |
HTAP capability | Optimized: seamless OLTP/OLAP integration (smart scan + RAC) | Strong: HTAP via one-write-many-read MPP (flexible scaling, no storage offloading now) |
Big data processing | Good: data lake + big data SQL (hadoop integration) | Good: MPP + external table support (flexible for big data workflows) |
Read/Write optimization
Feature | Oracle Exadata X8 | Tantor XData Gen3 (PX engine, RDMA, NVMeOF, PFS) |
Read-write split | Yes: RAC load balancing (dynamic read/write distribution) | Yes: SQL proxy (writes to RW primary, reads to RO replicas) |
Query offloading to storage | Yes: Exadata smart scan (storage executes lightweight queries) | No: PFS is passive (roadmap) |
Вместо заключения
Это первая статья из цикла о СУБД Tantor Polar и машине баз данных Tantor XData Gen3. Далее мы планируем раскрыть составляющие подробнее, в том числе вопросы, которые здесь вышли за рамки. Архитектура машины и СУБД настолько сложно переплетена, что зачастую сложно отделить одно от другого, но на наш взгляд, новый продукт реализует качественно новый уровень развития PostgreSQL, адаптации его к тяжелым нагрузкам и современным подходам к масштабированию. Кому интересна наша разработка - о новых статьях будем оповещать в своем телеграм-канале.
Наша компания – генеральный партнер конференции PG BootCamp Russia 2026, где на своем стенде (вне основной программы мероприятия) мы планируем впервые представить МБД Tantor XData Gen3 широкой аудитории. Пользуясь случаем, приглашаем на конференцию и на наш стенд 19 марта!