Перейти к содержанию

Масштабирование PostgreSQL

Масштабируют двумя способами, и они решают разные задачи:

  • Репликация - копии одних и тех же данных на нескольких серверах. Даёт сохранность данных, отказоустойчивость и масштабирование чтения.
  • Шардирование - разные части данных на разных серверах. Даёт масштабирование объёма и записи.

Внутри одного сервера большие таблицы делят партиционированием - это не горизонтальное масштабирование, но часто его откладывает.


Репликация

Репликация - хранение копий одних и тех же данных на нескольких серверах (репликах). Главная цель - не потерять данные, если сервер выйдет из строя.

Типы репликации

Тип Как идут апдейты
С лидером (leader) Клиенты пишут только в одну реплику - лидера. Лидер рассылает апдейт остальным
Без лидера (leaderless) Клиент сам шлёт апдейт сразу во все реплики и ждёт ответов
Мультилидер (multi-leader) Каждая реплика принимает записи от своих клиентов, а потом реплики асинхронно обмениваются апдейтами

Почему мультилидер сводится к двум другим типам:

  • Асинхронность входит в его определение. Будь обмен синхронным, схема не отличалась бы от безлидерной, только с лишним шагом: клиент → реплика → все реплики вместо клиент → все реплики.
  • Из-за асинхронности возможна потеря данных: реплика приняла апдейт, не успела разослать его и упала - апдейт пропал.
  • На практике мультилидер имеет смысл, только когда каждый «лидер» - группа серверов, внутри которой настроена репликация с лидером или без лидера.

Асинхронную репликацию с лидером дальше тоже не рассматриваем: в ней теряются апдейты по той же причине. Речь только про синхронную.


Кворум

Кворум - большинство реплик: 2 из 3, 3 из 5. На нём держатся и запись, и чтение в обоих типах репликации.

  • Запись в кворум - запись успешна, когда её подтвердило большинство реплик. Одна тормозящая или упавшая реплика запись не блокирует.
  • Чтение из кворума - читаем с большинства реплик. Хотя бы одна из них точно участвовала в последней успешной записи и вернёт актуальное значение.

Работает, потому что кворумы записи и чтения пересекаются: при N репликах, кворуме записи W и кворуме чтения R нужно W + R > N. Для 3 реплик это 2 + 2 > 3.


Репликация с лидером

Запись

  1. Наивно - во все реплики. Лидер применяет апдейт на всех репликах, затем у себя и отвечает клиенту OK.
    • Если хотя бы одна реплика тормозит или недоступна, запись не проходит - низкая доступность на запись.
  2. В кворум. Лидер ждёт подтверждения от большинства: для 3 реплик это лидер + ещё одна.
    • Доступность на запись намного выше.

Чтение

С произвольной реплики читать нельзя: на ней могут быть устаревшие данные. Иногда это допустимо, но здесь цель - читать актуальное.

Способ Плюсы Минусы
Читать с лидера Просто и быстро: лидер всегда актуален Упал лидер - нельзя ни писать, ни читать
Читать с кворума Хотя бы одна реплика из кворума содержит актуальные данные Выше задержка (latency), не всегда оправдано

Обычно следят за работоспособностью лидера и читают с него.

Плюсы Минусы

Главный плюс - система ведёт себя как один компьютер: все пишут в лидера и читают с лидера, а он всегда актуален.

  • Плюсы:
    • строгая согласованность (strong consistency): если запись прошла, любой клиент, который читает после неё, увидит новое значение
    • транзакции: с системой можно делать всё то же, что с одной машиной
  • Минусы:
    • лидер - единая точка отказа. При его падении нужны выборы нового: вручную - долго, автоматически - всё равно до секунды или нескольких секунд без чтения и записи
    • низкая пропускная способность на запись: операции сериализуются в логе (см. Лог операций) и не выполняются параллельно

Пропускную способность на запись лечат шардированием: несколько независимых групп реплик, каждая со своим лидером. Единую точку отказа шардирование не лечит - пока есть лидер, есть простой на его перевыборы.


Консенсус: Paxos и Raft

От реализации репликации с лидером нужны две нетривиальные вещи:

  1. автоматические выборы нового лидера при падении старого
  2. одинаковое состояние на кворуме реплик

Обе - задачи консенсуса: равноправные узлы без центрального арбитра должны договориться за минимум round trip'ов. Классические алгоритмы - Paxos и Raft. Их ценность в том, что корректность доказана для всех ситуаций.

Выборы лидера

Вручную всё просто: человек назначает лидером конкретную реплику. Без человека остаются равноправные узлы, которые сами должны решить, кто лидер.

Синхронизация состояния

Кажется, что если лидер выбран и пишет в кворум, состояния реплик совпадут сами. Это не так.

Пример (3 реплики, лидер - 1):

  1. Апдейт A дошёл только до реплики 2. До реплики 3 не дошёл из-за сети, на лидере не применился (лидер «моргнул»). Кворума нет, клиенту OK не вернули.
  2. Апдейт B применился на реплике 3 и на лидере. Кворум есть, клиент получил OK.
  3. Лидер падает. Реплики 2 и 3 выбирают нового лидера и видят, что у них разные операции: A и B.
  4. Правильно - оставить B (прошёл кворум и подтверждён клиенту) и отбросить A.

Таких расхождений бывает много, Paxos и Raft корректно разбирают каждое.

Лог операций

Оба алгоритма опираются на лог операций:

  • состояние реплики = лог операций + их применение
  • одинаковый лог на кворуме реплик = одинаковое состояние
  • консенсус достигается по логу: где-то лишнюю операцию выкидывают, где-то недостающие докидывают с других реплик

Следствие: операции сериализуются - идут строго одна за другой. Отсюда низкая пропускная способность на запись.

Raft и Paxos

Raft Paxos
Сложность Проще Сложнее
Кто инициирует запись Только один стабильный лидер Любая реплика может попытаться занять следующую ячейку лога через консенсус
Лог операций Есть, операции сериализуются Есть, операции тоже сериализуются

Особенность Paxos полезна для реплик в разных регионах:

  • Raft: лидер в одном регионе, клиенты из других ходят к нему через полмира - высокая задержка.
  • Paxos: клиент пишет в локальную реплику, она распространяет запись на остальные - задержка ниже.
  • Минус Paxos: если клиенты из разных регионов пишут одновременно, их реплики претендуют на одну ячейку лога. Побеждает одна, остальные зря тратят round trip'ы, откатываются и делают ретрай. Схема выгодна, только если одновременные записи из разных регионов редки.

Как поступил Google: перешёл к Paxos с одним стабильным лидером, как в Raft. Задержку решают так: отслеживают, из какого региона приходит большинство апдейтов, и переносят лидера туда. Так сделано в Google Spanner.


Репликация без лидера

Клиент шлёт апдейт одновременно во все реплики и ждёт ответа.

  • Запись - в кворум, как в лидерной схеме.
  • Чтение - только из кворума: лидера нет, и «заведомо актуальной» реплики тоже нет.

Зачем: доступность при разделении сети

Безлидерная схема нужна ради ультравысокой доступности: нет лидера - нет простоя на перевыборы, и можно писать при разделении сети.

Разделение сети (network partition) - сеть делится на части, клиенты и реплики одной части не видят другую. Широко известной проблему сделал whitepaper Amazon Dynamo.

  • С лидером: лидер оказывается в одной части, клиенты в другой части писать не могут.
  • Без лидера: пишут в любую доступную реплику, обе части продолжают принимать записи.

В меньшей части может не набраться кворума: доступна 1 реплика из 3. Варианты:

  • считать запись успешной уже на одной реплике - сильно падает durability
  • добавить реплик (например, 5) и считать запись успешной при подтверждении от двух - обе части продолжают работать без такой потери надёжности

Согласованность

Один писатель - строгая согласованность есть. Если конкретные данные в каждый момент обновляет только один клиент, запись в кворум и чтение из кворума гарантируют, что записанное прочитается.

Несколько писателей - строгой согласованности нет. Одни и те же данные одновременно обновляют клиенты с апдейтами A и B:

  1. Апдейты доходят до реплик в разном порядке: на реплике 1 - A, затем B, на реплике 2 - B, затем A.
  2. Данные на репликах расходятся.
  3. Клиенты, читая из кворума, попадают на разные пары реплик. Один получает согласованный ответ от реплик 2 и 3, другой - разные значения от реплик 1 и 3 и выбирает одно или мёржит. Два клиента видят разные данные по одному ключу.

Транзакции

Их тоже нет, по той же причине: разный порядок применения на репликах.

Пример: баланс 100, транзакция A списывает 60, транзакция B списывает 50.

  1. Реплика 1: A прошла (баланс 40), B откатилась - нельзя уйти в минус.
  2. Реплика 2: B прошла (баланс 50), A откатилась.
  3. Обе транзакции закоммичены, состояние на репликах разное, откатить закоммиченное уже нельзя.

Разрешение конфликтов

Расходящиеся данные приводят к одному значению. Запускают это двумя способами:

  1. Фоново - реплики периодически сверяются, выбирают для каждого ключа одно значение и применяют на всех.
  2. При чтении (read repair) - клиент видит разные значения с разных реплик, выбирает одно по тем же правилам и записывает его обратно во все реплики. Чинит быстрее.

Как выбрать значение:

Last Write Wins (LWW) - побеждает значение с самым большим timestamp'ом, оно перезаписывает остальные. - используется в Cassandra - минус: конкурирующие апдейты просто отбрасываются, данные теряются

Vector clock - метаданные рядом со значением на каждой реплике. Сравнив метаданные двух значений, видно: - одно значение - потомок другого: новое просто заменяет старое - значения получены независимыми апдейтами: это конфликт, значения нужно смёржить, а не выбирать одно

Мотивация - корзина покупок в первой версии Amazon Dynamo: при LWW часть добавлений товаров терялась бы.

Когда LWW безопасна (2 реплики, сеть разделилась): - пишут только в реплику 2 (A, B, C), реплика 1 просто отстала - после восстановления данные реплики 2 однозначно новее, LWW подходит - пишут в обе реплики: в реплику 2 - A, B, C, в реплику 1 - D, E, F - это независимые ветки, LWW потеряет одну из них

Итого: - LWW - когда новое значение по смыслу полностью заменяет старое. Пример: счётчик просмотров товара за текущий час. - Vector clock - во всех остальных случаях, когда терять конкурентные апдейты нельзя.

Плюсы Минусы

Плюсы Минусы
Высокая доступность Нет строгой согласованности при конкурентных записях
Запись при network partition Нет транзакций
Нет простоя на выборы лидера Нужно разрешать конфликты: LWW теряет данные, vector clock сложнее

С лидером и без лидера

С лидером Без лидера
Запись Через лидера, в кворум Клиент сам во все реплики, в кворум
Чтение актуальных данных С лидера или из кворума Только из кворума
Strong consistency Есть Нет (в общем случае)
Транзакции Есть, ACID Нет
Доступность Ниже: лидер - единая точка отказа Выше, разделение сети не страшно
Примеры Системы на Paxos / Raft, Spanner Amazon Dynamo, Cassandra

Итого: - С лидером - для сложной бизнес-логики: строгая согласованность и транзакции. Важно, чтобы под капотом был Raft / Paxos: у него есть доказательство корректности. «Что-то похожее на Raft» без доказательства и асинхронная репликация могут терять данные. - Без лидера - когда нужна максимальная доступность. Cassandra - с осторожностью, подходит не для любой бизнес-логики. Amazon Dynamo гибче благодаря vector clock.


Репликация в популярных системах

Система Тип Механизм Надёжность и замечания
Kafka С лидером ISR для данных, KRaft (Raft) для метаданных ✅ Без потерь при acks=all, min.insync.replicas и запрете unclean leader election
MongoDB С лидером Протокол на основе Raft, но не Raft ⚠️ Доказательства нет. Потери данных находили и чинили, сейчас известных сценариев нет, но и гарантии нет
Elasticsearch С лидером Primary-replica, свой алгоритм координации ⚠️ Нет доказательства, в старых версиях находили потери данных
CockroachDB С лидером Raft строго по спецификации ✅ Надёжно
Redis С лидером Асинхронная репликация, «похоже на Raft» для выборов ❌ Лидером может стать отстающая реплика, данные теряются
Amazon DynamoDB С лидером Paxos ✅ Надёжно
etcd С лидером Raft ✅ Надёжно, координационный сервис / lock service
ZooKeeper С лидером ZAB (ZooKeeper Atomic Broadcast) ✅ Надёжно, координационный сервис как etcd
Google Spanner С лидером Paxos со стабильным лидером ✅ Надёжно
PostgreSQL С лидером WAL-поток, выборы через внешний сервис ✅ Надёжно с HA-менеджером, см. Репликация в PostgreSQL
ClickHouse С лидером ClickHouse Keeper (Raft) или ZooKeeper ✅ Надёжно, есть запись в кворум (insert_quorum)
Cassandra Без лидера Last write wins ⚠️ Высокая доступность, нет strong consistency и транзакций
Amazon Dynamo Без лидера Vector clock ⚠️ Высокая доступность, гибкое разрешение конфликтов

Note

Amazon Dynamo и Amazon DynamoDB - разные системы. Dynamo - внутренняя система Amazon из whitepaper 2007 года, безлидерная, вдохновила Cassandra. DynamoDB - managed-сервис AWS. Вопреки распространённому мнению, его репликация не безлидерная: у каждой партиции лидер, выбранный через Paxos.

Note

С лекции: Kafka «использует Raft», Elasticsearch - «асинхронная репликация», ClickHouse - «etcd». Точнее так:

  • в Kafka на Raft (KRaft) работают только метаданные кластера. Данные партиций реплицируются через ISR (in-sync replicas): лидер ждёт подтверждения от in-sync реплик при acks=all
  • в Elasticsearch запись подтверждается после применения на всех in-sync копиях шарда, то есть синхронно. Слабое место - координация кластера и выбор primary, формального доказательства у них нет
  • ClickHouse для репликации использует ZooKeeper или свой ClickHouse Keeper на Raft, а не etcd

Репликация в PostgreSQL

PostgreSQL - репликация с лидером. Как теория ложится на Postgres:

  1. Лидер - primary, реплики - standby.
  2. Лог операций - WAL. Он один и упорядочен, standby всегда хранит префикс WAL primary. Расхождения логов, как в примере с Raft, в physical replication не бывает.
  3. Выборы лидера - встроенных нет. Standby повышают вручную или через внешний HA-менеджер, консенсус вынесен в отдельный кластер (см. Автоматический failover).
  4. Запись в кворум - есть с PG 10: synchronous_standby_names = 'ANY 2 (s1, s2, s3)'. При failover повышать нужно реплику, на которой есть все подтверждённые транзакции, - с наибольшим LSN.

Note

С лекции: Postgres не умеет писать в кворум, потому что у него нет консенсуса по логу, и вынужден писать во все реплики синхронно - отсюда низкая доступность на запись. Это неточно: логи у standby не расходятся (WAL - единый поток), а кворумная синхронная репликация ANY n есть с PG 10. Задача при отказе другая - не потерять подтверждённое при выборе нового primary, её решает HA-менеджер.


Physical replication

Physical replication - репликация всего инстанса на уровне WAL и файлового состояния.

  • File-based log shipping - WAL архивируется и копируется файлами, затем применяется на standby
  • Streaming replication - WAL идёт потоком почти в реальном времени
  • Cascading replication - standby получает WAL не только от primary, но и от другой standby

Основной механизм для high availability, read-only реплик и failover.

Команды:

-- на primary: какие standby подключены, их режим и отставание
SELECT application_name, state, sync_state, replay_lag FROM pg_stat_replication;

-- на любом узле: это standby?
SELECT pg_is_in_recovery();

-- повысить standby до primary вручную (PG 12+)
SELECT pg_promote();


Синхронная и асинхронная

Режим подтверждения commit в streaming replication:

Режим Как работает Плюсы Минусы
asynchronous primary не ждёт подтверждения standby на каждый commit меньше задержка записи при failover можно потерять последние транзакции
synchronous primary ждёт подтверждения standby выше надёжность больше latency на запись

Кого ждать, задаёт synchronous_standby_names: - FIRST 1 (s1, s2) - ждать одну standby по приоритету - ANY 2 (s1, s2, s3) - ждать любые две из трёх, это запись в кворум - список всех standby - ждать все, тогда одна упавшая standby блокирует запись

SHOW synchronous_standby_names;
SHOW synchronous_commit;   -- on, remote_apply, remote_write, local, off

Warm и hot standby

Это не способ передачи WAL, а режим использования standby.

Режим Что это значит
warm standby standby держится в актуальном состоянии, но для запросов не используется
hot standby на standby можно подключаться и выполнять read-only запросы

Logical replication

Logical replication - репликация логических изменений на уровне таблиц и строк, а не файлового состояния кластера.

  • реплицирует часть данных, а не весь сервер
  • переносит данные между разными кластерами и версиями PostgreSQL
  • раздаёт выбранные таблицы в несколько кластеров (selective fan-out)
  • требует wal_level = logical
-- на источнике
CREATE PUBLICATION orders_pub FOR TABLE orders;

-- на приёмнике
CREATE SUBSCRIPTION orders_sub
    CONNECTION 'host=src dbname=shop user=repl'
    PUBLICATION orders_pub;

Для чего её используют на практике - см. Logical replication для selective scaling.


Автоматический failover

PostgreSQL сам умеет быть primary и standby, реплицировать WAL и повышать standby. Автоматическую координацию делает внешний HA-менеджер: Patroni, repmgr, pg_auto_failover.

Консенсус по выбору лидера вынесен во внешний координационный сервис - etcd, Consul или ZooKeeper (внутри них Raft или ZAB):

  • все узлы Postgres держат связь с сервисом. Лидер - тот, кто держит lock (leader key с TTL)
  • лидер упал и перестал продлевать lock - одна из standby забирает lock и становится primary
  • данные не теряются, если новый primary - синхронная standby, на которой есть все подтверждённые транзакции

Что делает HA-менеджер: - следит, жив ли текущий primary - решает, кто имеет право стать новым primary - запускает promotion standby - не даёт случиться split-brain: двум primary одновременно - иногда bootstrap'ит узлы и управляет конфигом PostgreSQL


Точка входа: прокси и пулер

Pgpool-II - прокси между клиентом и несколькими серверами PostgreSQL. - единая точка входа: клиент подключается к Pgpool-II, тот решает, куда отправить запрос - отправляет SELECT на standby, запись на primary, балансирует read-трафик - участвует в failover-сценариях

HAProxy - сетевой прокси и балансировщик. - держит единый endpoint - проверяет backend'ы через health checks и отправляет трафик только на живые узлы - ролями PostgreSQL не управляет, новый primary не назначает - это работа HA-менеджера

PgBouncer - пулер соединений. Держит мало реальных соединений к PostgreSQL и раздаёт их множеству клиентов.

Типичный production-стек, когда нужны uptime и read scaling:

приложение → HAProxy → PgBouncer → PostgreSQL primary + streaming standby
                                        ↑
                          Patroni на каждом узле + etcd
  • проще сопровождать, чем distributed cluster
  • закрывает и HA, и read scaling при одном writer
  • подходит, пока distributed writes не нужны

Шардирование

Шардирование - основной способ горизонтально масштабировать хранилище бэкенда: данные распределяются между несколькими серверами БД (шардами) по ключу шардирования.

Зачем: миллиард пользователей не помещается на один сервер БД, и один сервер не выдерживает нагрузку на чтение и запись.

Система Ключ шардирования Что получаем
Instagram (посты) user_id автора Все посты пользователя на одной машине
Мессенджер (сообщения) chat_id Все сообщения чата на одной машине

Шардирование и репликация дополняют друг друга: каждый шард - отдельная группа реплик. Шардирование даёт объём и запись, репликация внутри шарда - сохранность данных.

Главный вопрос шардирования - как узнать, на какой машине лежат нужные данные. Два подхода.


Сервер метаданных

Самый интуитивный и самый популярный в БД подход.

  1. Клиент идёт в сервер метаданных и спрашивает, где лежат данные для ключа.
  2. Получает адрес машины, например «сервер 3», и идёт за данными туда.

Диапазоны вместо отдельных ключей. Маппинг user_id → сервер для миллиарда пользователей не поместится на сервер метаданных. Поэтому хранят диапазоны ключей: «от X до Y - на сервере 1, от Y до Z - на сервере 2». Один диапазон покрывает огромное число ключей, метаданных мало.

Балансировка нагрузки. Сервер метаданных централизованный, поэтому в современных БД он балансирует нагрузку: - диапазон разросся - делит его надвое и раскладывает части по разным серверам - сервер БД перегружен - переносит часть его диапазонов на другие серверы

Плюсы Минусы

  • Плюсы:
    • гибкое распределение нагрузки между серверами БД
    • эффективные range-запросы: «ключи от A до B» попадают в один-два диапазона, то есть на небольшое число машин
  • Минусы:
    • единая точка отказа. Метаданные реплицируют, но в репликации есть лидер. Пока выбирается новый (до секунды, в худшем случае несколько), клиент без закэшированных метаданных не узнает, где данные. При разделении сети клиенты могут не достучаться до сервера метаданных
    • бутылочное горлышко: при резком добавлении множества машин сервер метаданных перегружается, быстро масштабироваться нельзя. Пример, где это критично, - Discord, когда из-за событий резко приходит толпа пользователей и нужно срочно добавлять машины

Где используется

Система Замечание
Google Bigtable Первая такая распределённая БД (статья 2006 года), с неё всё началось
HBase Open-source версия Bigtable
Google Spanner Тот же подход
MongoDB Сервер метаданных называется config server
PostgreSQL + Citus Встроенного шардирования в Postgres нет, Citus хранит метаданные на coordinator-узле
Amazon DynamoDB Границы диапазонов - хэши ключа партиции. Нагрузка ровнее, но range-запросы работают только внутри одного ключа партиции (по sort key)
CockroachDB Идейный наследник Spanner, тот же подход

Consistent hashing

Для ситуаций, где сервер метаданных становится проблемой.

  1. Представляем кольцо целых чисел от 0 до максимального значения.
  2. Каждый сервер БД встаёт на кольцо по хэшу своего адреса.
  3. Для ключа считаем хэш user_id, находим точку на кольце и идём по часовой стрелке до первого сервера - данные живут на нём.
  4. Запрос клиента обрабатывается так же: хэш ключа → обход кольца → нужный сервер.

Добавление сервера. Новый сервер встаёт на кольцо и забирает у соседа (следующего по часовой стрелке) только тот участок ключей, который теперь попадает на него. Общается он только с этим соседом, а не с центральным сервером, поэтому можно добавлять много серверов одновременно.

Метаданных почти нет. Нужен только список серверов и их позиции на кольце, о ключах ничего хранить не надо. - этот список небольшой, его держит каждый сервер кластера - клиент может прийти в любой сервер. Если ключ принадлежит другому, текущий перенаправит клиента или сам сходит за данными - центрального сервера метаданных нет - выше доступность

Плюсы Минусы

  • Плюсы:
    • нет единой точки отказа
    • нет бутылочного горлышка в виде сервера метаданных - быстрое масштабирование
  • Минусы:
    • нет гибкой балансировки: как серверы встали на кольце, так и поделили ключи. При перекосе нагрузки остаётся добавлять машины или изначально продумать ключ шардирования хитрее, чем просто user_id
    • нет эффективных range-запросов: из-за хэширования ключи разбросаны по кольцу случайно

Где используется

Система Замечание
Amazon Dynamo Первая промышленная БД с этим подходом. Цель - ультравысокая доступность: не терять ни одного добавления в корзину
Cassandra «Настоящий» consistent hashing
Redis Cluster Не consistent hashing, но ближе к нему: 16384 hash slot'а распределены по узлам, центрального сервера метаданных нет. Слоты не перераспределяются сами, добавление машины требует ручного решардинга
ClickHouse Похоже на Redis: распределение задаётся конфигурацией кластера

Сервер метаданных и consistent hashing

Сервер метаданных Consistent hashing
Как найти данные Спросить сервер метаданных (диапазоны ключей) Хэш ключа → обход кольца
Балансировка нагрузки ✅ Гибкая: деление и перенос диапазонов ❌ Нет
Range-запросы ✅ Эффективные, если диапазоны по ключам, а не по хэшам ❌ Неэффективные
Доступность ⚠️ Ниже - единая точка отказа ✅ Выше - можно идти в любой сервер
Масштабируемость ⚠️ Хуже - сервер метаданных бутылочное горлышко ✅ Лучше - новый сервер общается только с соседом
Примеры Bigtable, HBase, Spanner, MongoDB, Citus, DynamoDB, CockroachDB Dynamo, Cassandra (Redis, ClickHouse - похоже)

Итого: - Сервер метаданных - сбалансированная нагрузка и range-запросы ценой доступности и масштабируемости. - Consistent hashing - доступность и масштабируемость ценой range-запросов и баланса нагрузки.


Партиционирование и шардирование в PostgreSQL

Partitioning внутри одного кластера

Если проблема в размере таблиц, вакууме, индексах и сканах, а не в числе write-нод, часто достаточно партиционирования.

  • declarative partitioning по дате, по tenant_id, по region
  • виды: range / list / hash
  • partition pruning: планировщик читает только нужные партиции
  • локальные индексы на партициях
  • старые данные удаляются целой партицией (DROP / DETACH), без тяжёлого DELETE и вакуума
CREATE TABLE events (
    id         bigint,
    created_at timestamptz,
    payload    jsonb
) PARTITION BY RANGE (created_at);

CREATE TABLE events_2026_10 PARTITION OF events
    FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');

Когда подходит: time-series, ledger и event-таблицы, большие append-heavy таблицы, retention.

Это не горизонтальный scale-out между серверами, но часто сильно снимает нагрузку и надолго откладывает шардирование.


Шардирование в PostgreSQL

Встроенного шардирования в PostgreSQL нет. Варианты:

  • application-level sharding - приложение само выбирает шард по ключу
  • Citus - расширение: coordinator хранит метаданные о шардах (подход сервера метаданных), worker-узлы хранят данные
  • схемы ключа: tenant-based, hash, geo
-- Citus: распределить таблицу по tenant_id
SELECT create_distributed_table('orders', 'tenant_id');

Когда подходит: write-heavy OLTP, multi-tenant SaaS, объём данных больше, чем комфортно держать на одной ноде, нужен рост throughput при добавлении узлов.

  • Плюсы: масштабирует запись и общий объём данных - реальный scale-out, когда одна нода не справляется.
  • Минусы: сложнее schema design, cross-shard JOIN и cross-shard transactions, rebalancing, backups и migrations. Это отдельный уровень операционной сложности.

Стратегия масштабирования PostgreSQL

Горизонтальное масштабирование почти никогда не начинается с «добавим ещё 5 нод». Сначала выжимают максимум из одного узла, находят реальное узкое место и масштабируют именно тот слой, который упёрся в лимит.

Шаг 0. Найти узкое место

Без измерений легко масштабировать не то место. Смотрят:

  • топ медленных и самых частых запросов - pg_stat_statements
  • I/O и WAL-нагрузку - встроенная статистика, включая pg_stat_io (PG 16+)
  • блокировки и long transactions
  • bloat и autovacuum lag
  • p95 / p99 latency, TPS / QPS
  • active connections, cache hit ratio, replication lag
-- топ запросов по суммарному времени
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Шаг 1. Выжать максимум из одного узла

  • тюнинг запросов и индексов
  • убрать N+1 и лишние JOIN
  • нормальный autovacuum / vacuum
  • connection pooling через PgBouncer
  • партиционирование очень больших таблиц
  • вынос тяжёлой аналитики и отчётов из OLTP

Очень часто это даёт больший эффект, чем раннее шардирование.


Шаг 2. Выбрать схему

Read scaling: primary + read replicas

Первый и самый частый шаг, если узкое место - чтение.

  • один primary для записи, physical streaming replicas для чтения
  • read-only трафик уходит на standby через отдельный read endpoint или прокси

Когда подходит: каталог, лента, история операций, профили - много SELECT, мало INSERT / UPDATE.

  • Плюсы: простая топология, хорошо снимает read-нагрузку, строится на штатных возможностях.
  • Ограничения: запись горизонтально не масштабируется. Есть replication lag, приложение должно учитывать stale reads. При асинхронном failover возможна потеря последних транзакций.

HA + read scaling

Если нужен не только performance, но и uptime - типичный production-стек: streaming replication + Patroni + HAProxy + PgBouncer. Подходит, когда нужен один writer, а основная проблема - connections, failover и read-трафик.

Partitioning

Если упираются большие таблицы, вакуум и сканы - см. Partitioning внутри одного кластера.

Logical replication для selective scaling

Когда нужно реплицировать не весь инстанс, а отдельные таблицы или домены:

  • отделить reporting cluster
  • вынести billing / reporting / search feeder
  • выделить hot tables или отдельные сервисы при переходе от монолита
  • миграции с минимальным downtime

Механизм - см. Logical replication.

  • Плюсы: гибче physical replication, удобно для эволюции архитектуры.
  • Ограничения: выше операционная сложность, проблему записи в один primary не решает.

Sharding / distributed PostgreSQL

Если узкое место уже в записи, общем throughput одного primary или объёме данных - см. Шардирование в PostgreSQL.

Separate OLTP / OLAP

Если primary страдает от отчётов и тяжёлой аналитики:

  • OLTP остаётся на основном PostgreSQL
  • аналитика уходит в отдельный кластер или warehouse
  • BI и тяжёлые JOIN / агрегации убирают с транзакционной базы

OLTP и аналитика перестают мешать друг другу, отчётный контур масштабируется отдельно.

Cache + async processing

Если база не справляется из-за повторяющихся чтений или тяжёлой синхронной логики:

  • Redis / cache layer для hot reads
  • очереди и асинхронная обработка
  • materialized projections, read models
  • idempotent consumers

Не заменяет масштабирование PostgreSQL, но часто резко снижает давление на primary.


Выбор по симптомам

Симптом Что делать в первую очередь
CPU уходит в сложные SELECT индексы, планы, partitioning, потом read replicas
много чтения read replicas
слишком много connections PgBouncer
I/O и autovacuum упираются в огромные таблицы partitioning, retention, schema redesign
упёрлись в запись одного primary sharding / distributed PostgreSQL, а не новые read replicas
длинные отчёты мешают OLTP отделить OLAP от OLTP
нужен uptime и быстрый failover streaming replication + Patroni + HAProxy + PgBouncer
нужно реплицировать только часть данных logical replication / выделение сервиса
повторяющиеся чтения и тяжёлая синхронная логика cache + async processing

Сначала понять, что именно упёрлось в лимит, и только потом выбирать стратегию масштабирования.