Масштабирование PostgreSQL¶
Масштабируют двумя способами, и они решают разные задачи:
- Репликация - копии одних и тех же данных на нескольких серверах. Даёт сохранность данных, отказоустойчивость и масштабирование чтения.
- Шардирование - разные части данных на разных серверах. Даёт масштабирование объёма и записи.
Внутри одного сервера большие таблицы делят партиционированием - это не горизонтальное масштабирование, но часто его откладывает.
Note
Главы про теорию - конспект видео: «Репликация, часть 1. Репликация с лидером», «Репликация, часть 2. Репликация без лидера», «Репликация, часть 3. Обзор баз данных», «Шардирование - все самое важное с примерами».
Репликация¶
Репликация - хранение копий одних и тех же данных на нескольких серверах (репликах). Главная цель - не потерять данные, если сервер выйдет из строя.
Типы репликации¶
| Тип | Как идут апдейты |
|---|---|
| С лидером (leader) | Клиенты пишут только в одну реплику - лидера. Лидер рассылает апдейт остальным |
| Без лидера (leaderless) | Клиент сам шлёт апдейт сразу во все реплики и ждёт ответов |
| Мультилидер (multi-leader) | Каждая реплика принимает записи от своих клиентов, а потом реплики асинхронно обмениваются апдейтами |
Почему мультилидер сводится к двум другим типам:
- Асинхронность входит в его определение. Будь обмен синхронным, схема не отличалась бы от безлидерной, только с лишним шагом: клиент → реплика → все реплики вместо клиент → все реплики.
- Из-за асинхронности возможна потеря данных: реплика приняла апдейт, не успела разослать его и упала - апдейт пропал.
- На практике мультилидер имеет смысл, только когда каждый «лидер» - группа серверов, внутри которой настроена репликация с лидером или без лидера.
Асинхронную репликацию с лидером дальше тоже не рассматриваем: в ней теряются апдейты по той же причине. Речь только про синхронную.
Кворум¶
Кворум - большинство реплик: 2 из 3, 3 из 5. На нём держатся и запись, и чтение в обоих типах репликации.
- Запись в кворум - запись успешна, когда её подтвердило большинство реплик. Одна тормозящая или упавшая реплика запись не блокирует.
- Чтение из кворума - читаем с большинства реплик. Хотя бы одна из них точно участвовала в последней успешной записи и вернёт актуальное значение.
Работает, потому что кворумы записи и чтения пересекаются: при N репликах, кворуме записи W и кворуме чтения R нужно
W + R > N. Для 3 реплик это 2 + 2 > 3.
Репликация с лидером¶
Запись¶
- Наивно - во все реплики. Лидер применяет апдейт на всех репликах, затем у себя и отвечает клиенту OK.
- Если хотя бы одна реплика тормозит или недоступна, запись не проходит - низкая доступность на запись.
- В кворум. Лидер ждёт подтверждения от большинства: для 3 реплик это лидер + ещё одна.
- Доступность на запись намного выше.
Чтение¶
С произвольной реплики читать нельзя: на ней могут быть устаревшие данные. Иногда это допустимо, но здесь цель - читать актуальное.
| Способ | Плюсы | Минусы |
|---|---|---|
| Читать с лидера | Просто и быстро: лидер всегда актуален | Упал лидер - нельзя ни писать, ни читать |
| Читать с кворума | Хотя бы одна реплика из кворума содержит актуальные данные | Выше задержка (latency), не всегда оправдано |
Обычно следят за работоспособностью лидера и читают с него.
Плюсы Минусы¶
Главный плюс - система ведёт себя как один компьютер: все пишут в лидера и читают с лидера, а он всегда актуален.
- Плюсы:
- строгая согласованность (strong consistency): если запись прошла, любой клиент, который читает после неё, увидит новое значение
- транзакции: с системой можно делать всё то же, что с одной машиной
- Минусы:
- лидер - единая точка отказа. При его падении нужны выборы нового: вручную - долго, автоматически - всё равно до секунды или нескольких секунд без чтения и записи
- низкая пропускная способность на запись: операции сериализуются в логе (см. Лог операций) и не выполняются параллельно
Пропускную способность на запись лечат шардированием: несколько независимых групп реплик, каждая со своим лидером. Единую точку отказа шардирование не лечит - пока есть лидер, есть простой на его перевыборы.
Консенсус: Paxos и Raft¶
От реализации репликации с лидером нужны две нетривиальные вещи:
- автоматические выборы нового лидера при падении старого
- одинаковое состояние на кворуме реплик
Обе - задачи консенсуса: равноправные узлы без центрального арбитра должны договориться за минимум round trip'ов. Классические алгоритмы - Paxos и Raft. Их ценность в том, что корректность доказана для всех ситуаций.
Выборы лидера¶
Вручную всё просто: человек назначает лидером конкретную реплику. Без человека остаются равноправные узлы, которые сами должны решить, кто лидер.
Синхронизация состояния¶
Кажется, что если лидер выбран и пишет в кворум, состояния реплик совпадут сами. Это не так.
Пример (3 реплики, лидер - 1):
- Апдейт A дошёл только до реплики 2. До реплики 3 не дошёл из-за сети, на лидере не применился (лидер «моргнул»). Кворума нет, клиенту OK не вернули.
- Апдейт B применился на реплике 3 и на лидере. Кворум есть, клиент получил OK.
- Лидер падает. Реплики 2 и 3 выбирают нового лидера и видят, что у них разные операции: A и B.
- Правильно - оставить 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 - A, затем B, на реплике 2 - B, затем A.
- Данные на репликах расходятся.
- Клиенты, читая из кворума, попадают на разные пары реплик. Один получает согласованный ответ от реплик 2 и 3, другой - разные значения от реплик 1 и 3 и выбирает одно или мёржит. Два клиента видят разные данные по одному ключу.
Транзакции¶
Их тоже нет, по той же причине: разный порядок применения на репликах.
Пример: баланс 100, транзакция A списывает 60, транзакция B списывает 50.
- Реплика 1: A прошла (баланс 40), B откатилась - нельзя уйти в минус.
- Реплика 2: B прошла (баланс 50), A откатилась.
- Обе транзакции закоммичены, состояние на репликах разное, откатить закоммиченное уже нельзя.
Разрешение конфликтов¶
Расходящиеся данные приводят к одному значению. Запускают это двумя способами:
- Фоново - реплики периодически сверяются, выбирают для каждого ключа одно значение и применяют на всех.
- При чтении (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:
- Лидер -
primary, реплики -standby. - Лог операций - WAL. Он один и упорядочен, standby всегда хранит префикс WAL primary. Расхождения логов, как в примере с Raft, в physical replication не бывает.
- Выборы лидера - встроенных нет. Standby повышают вручную или через внешний HA-менеджер, консенсус вынесен в отдельный кластер (см. Автоматический failover).
- Запись в кворум - есть с 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 |
Все сообщения чата на одной машине |
Шардирование и репликация дополняют друг друга: каждый шард - отдельная группа реплик. Шардирование даёт объём и запись, репликация внутри шарда - сохранность данных.
Главный вопрос шардирования - как узнать, на какой машине лежат нужные данные. Два подхода.
Сервер метаданных¶
Самый интуитивный и самый популярный в БД подход.
- Клиент идёт в сервер метаданных и спрашивает, где лежат данные для ключа.
- Получает адрес машины, например «сервер 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¶
Для ситуаций, где сервер метаданных становится проблемой.
- Представляем кольцо целых чисел от 0 до максимального значения.
- Каждый сервер БД встаёт на кольцо по хэшу своего адреса.
- Для ключа считаем хэш
user_id, находим точку на кольце и идём по часовой стрелке до первого сервера - данные живут на нём. - Запрос клиента обрабатывается так же: хэш ключа → обход кольца → нужный сервер.
Добавление сервера. Новый сервер встаёт на кольцо и забирает у соседа (следующего по часовой стрелке) только тот участок ключей, который теперь попадает на него. Общается он только с этим соседом, а не с центральным сервером, поэтому можно добавлять много серверов одновременно.
Метаданных почти нет. Нужен только список серверов и их позиции на кольце, о ключах ничего хранить не надо. - этот список небольшой, его держит каждый сервер кластера - клиент может прийти в любой сервер. Если ключ принадлежит другому, текущий перенаправит клиента или сам сходит за данными - центрального сервера метаданных нет - выше доступность
Плюсы Минусы¶
- Плюсы:
- нет единой точки отказа
- нет бутылочного горлышка в виде сервера метаданных - быстрое масштабирование
- Минусы:
- нет гибкой балансировки: как серверы встали на кольце, так и поделили ключи. При перекосе нагрузки остаётся
добавлять машины или изначально продумать ключ шардирования хитрее, чем просто
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
Когда подходит: 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/p99latency,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 |
Сначала понять, что именно упёрлось в лимит, и только потом выбирать стратегию масштабирования.