Шардинг и репликация баз данных
Шардинг и репликация баз данных
1) Зачем это нужно
Когда вертикальный апгрейд БД упирается в пределы CPU/IO/RAM или один кластер становится SPOF, приходят репликация (для чтений/HA) и шардинг (для распределения записи/данных). Цели:- Пропускная способность (горизонтальный рост write QPS).
- Доступность (быстрый failover, отсутствие единой точки отказа).
- Локализация данных (мульти-регион, низкая латентность).
- Изоляция шумных соседей (hot tenants / hot keys).
2) Базовые термины и модели согласованности
Primary/Leader ↔ Replica/Follower: запись на лидере, чтение — на репликах.
Синхронная репликация: подтверждение транзакции после записи на N узлах (низкий RPO, выше латентность).
Асинхронная: лидер фиксирует коммит и отправляет лог позже (RPO>0, низкая латентность).
Кворумная (Raft/Paxos): запись на большинство узлов; один лог, автоматический лидер.
Read-after-write: гарантированное чтение своих записей (см. §5).
CAP в проде читаем так: при сетевых проблемах вы выбираете согласованность (CP) или доступность (AP) для критичных операций, часто комбинируя уровни на разных путях.
3) Репликация: варианты и практики
3.1 Физическая и логическая
Физическая (WAL/redo/binlog): ближе к блочному журналу, проста и быстра; ограничена гомогенной топологией/версией.
Логическая: поток DML/DDL на уровне строк/таблиц; позволяет частичные реплики, миграции между версиями, CDC для DWH/стриминга.
3.2 Настройка и управление
Контролируйте lag (время/байты/LSN).
Ограничьте hot-standby feedback и долгие запросы на репликах (чтобы не стопорить VACUUM/клининг).
Для MySQL — GTID и Orchestrator; для PostgreSQL — Patroni/replication slots, synchronous_standby_names.
sql
-- на лидере
ALTER SYSTEM SET synchronous_commit = 'on';
ALTER SYSTEM SET synchronous_standby_names = 'FIRST 1 (standby_a, standby_b)';
SELECT pg_reload_conf();
MySQL GTID (идентификатор транзакции):
sql
SET GLOBAL enforce_gtid_consistency = ON;
SET GLOBAL gtid_mode = ON; -- перезапуск
CHANGE MASTER TO MASTER_AUTO_POSITION=1;
3.3 Топологии
1→N (лидер → реплики) + каскады (реплика сливает дальше).
Multi-primary (active-active) — избегать в OLTP без строгого конфликт-менеджмента.
Quorum-кластер (Raft) — CockroachDB/Yugabyte/PG-Raft-надстройки.
4) Read/Write Split и маршрутизация
Пишите всегда в лидер; читайте с реплик, но учитывайте lag.
Стратегии read-after-write:1. Session stickiness: после успешной записи клиент читает с лидера в течение `ΔT`.
2. LSN/GTID-гейт: клиент сообщает «хочу не старее LSN=X», роутер отправляет на реплику, чей LSN ≥ X.
3. Stale-ok: часть запросов допускает устаревшие данные (каталоги/ленты).
Инструменты: PgBouncer/ Pgpool-II (Postgres), ProxySQL/MaxScale (MySQL), Vitess (маршрутизация шардов).
Пример LSN-гейта (идея): сохраняйте `pg_current_wal_lsn()` в HTTP-хедер/куку и требуйте от роутера реплику с `pg_last_wal_replay_lsn() ≥ LSN`.
5) Стратегии шардинга
5.1 Выбор ключа
Ключ должен обеспечивать равномерность и локальность запросов:- Hash по `tenant_id`/`user_id` — равномерно, но лишает range-сканов.
- Range по времени/ID — отлично для time-series/архива, но риск hot-shard.
- Consistent hashing — упрощает добавление/удаление шардов.
- Directory/lookup-таблица — гибко (любой алгоритм), но еще одна таблица/кэш.
5.2 Паттерны
Shared-nothing: каждый шард — отдельная БД/кластер, приложение знает маршрутизацию.
Middleware-шардинг: Vitess (MySQL), Citus (Postgres), Proxy-уровень прячет топологию.
Федерация: разделение доменов данных по сервисам (catalog, payments, auth).
5.3 Композитные ключи
Используйте ключ-пространство: `{tenant}:{entity}:{id}` и храните это же в приложении и кэше. Для Postgres — hash partitioning + LIST/RANGE subpartition.
PostgreSQL partitioning (фрагмент):sql
CREATE TABLE orders (
tenant_id int,
id bigint,
created_at timestamptz,
...,
PRIMARY KEY (tenant_id, id)
) PARTITION BY HASH (tenant_id);
CREATE TABLE orders_t0 PARTITION OF orders FOR VALUES WITH (MODULUS 16, REMAINDER 0);
--... t1..t15
6) Генерация идентификаторов
Избегайте «горячих» монотонных автоинкрементов на шардинге.
Используйте Snowflake-подобные 64-бит ID (time + region + shard + seq) или ULID/KSUID (монотонность и распределенность).
Для Postgres — sequence per shard; для MySQL — auto_increment_increment/offset (разные оффсеты на лидерах шардов).
7) Онлайн-перешардирование и миграции
Ключевые принципы: двойная запись (dual-write), идемпотентность, временная двойная маршрутизация.
Шаги (обобщенно):1. Добавьте новый шард/кластер.
2. Включите dual-read (проверка консистентности).
3. Включите dual-write (в оба шарда), фиксируйте расхождения.
4. Выполните backfill исторических данных (батчи, репликация логическая/CDC).
5. Переключите «источник правды» на новый шард; оставьте «хвостовую» синхронизацию.
6. Выключите старый.
Инструменты: Vitess Resharding, Citus move shards, pg_logical/pgoutput, Debezium (CDC), gh-ost/pt-online-schema-change (DDL без блокировок).
8) Мульти-регион и геораспределение
Leader-follower per region: локальные чтения, запись — через глобальный лидер (простая модель, но cross-region RTT).
Multi-leader: запись в обоих регионах — нужен конфликт-мерджинг (timestamp/версия/CRDT).
True distributed SQL (Raft): CockroachDB/Yugabyte — данные «приклеены» к региону, запросы идут в локальный кворум.
- Деньги/заказы — CP (кворум/лидер), каталоги/ленты — AP (кэш, eventual).
- Всегда планируйте write fencing (уникальные ключи/версионирование) при возможном split-brain.
9) Согласованность на практике
Read-your-writes: лидеру или реплике, «догнавшей» LSN/GTID.
Monotonic reads: «не старше» последнего прочитанного LSN.
Write-conflict control: `SELECT... FOR UPDATE`, версии (`xmin`/`rowversion`), UPSERT с проверкой версии.
Идемпотентность: ключи идемпотентности на оплатах/событиях.
10) Наблюдаемость, SLO и алерты
Лаг реплик: время (сек), LSN distance (bytes), seconds_behind_master (MySQL).
Вынужденные откаты/конфликты, ошибки репликации.
p95/p99 latency по route (read leader vs replica, write).
Throughput: TPS/locks/row-contended tables.
Bloat/VACUUM (PG), InnoDB buffer pool hit ratio (MySQL).
Дашборды: per-shard нагрузка, «горячие» шарды, распределение ключей.
11) Бэкапы, PITR и DR
Полный бэкап + WAL/binlog для PITR (point-in-time recovery).
Храните в другом регионе/облаке, регулярно делайте restore-тесты.
Для шардов — согласованный «срез» (координация времени/LSN) или аппликативная идемпотентность при восстановлении.
RPO/RTO прописаны и тестируются на game-days.
bash pg_basebackup -D /backups/base -X stream -C -S slot_replica_1 архивация WAL через archive_command или pgBackRest/Barman
12) Безопасность и доступ
Сегментация по VPC/ACL, mTLS на прокси.
Роли/гранты по принципу минимальных прав; отдельные пользователи на шард/роль.
Аудит DDL/DCL, лимиты на «тяжелые» запросы на репликах.
Шифрование at rest (KMS) и в транзите (TLS).
«Паника-кнопка»: глобальный `READ ONLY` на время инцидента/расследования.
13) Инструменты и кирпичики
PostgreSQL: Patroni (HA), PgBouncer (pooling/RO-routing), repmgr, pgBackRest/Barman (бэкап), Citus (шардинг), pglogical/Logical Replication, pgbadger/pg_stat_statements.
MySQL: Orchestrator (топологии/авто-failover), ProxySQL/MaxScale (маршрутизация), Percona XtraBackup (бэкап), Group Replication/InnoDB Cluster, Vitess (шардинг/ресхардинг).
Distributed SQL: CockroachDB, YugabyteDB (кворум, встроенный шардинг/геолокация).
CDC: Debezium + Kafka/Pulsar для событий/ETL.
14) Анти-паттерны
Single-primary без авто-failover и без тестов DR.
«Магический» read-split без учета lag → фантомные ошибки/подозрительные баги.
Шардинг «ради шардинга»: преждевременное усложнение вместо вертикального скейла/индексов/кэша.
Горячий диапазон (time-range) без time-bucket/hash-salt → один шард плавится.
Глобальная транзакция 2PC поверх десятков шардов в OLTP — высокие хвосты p99 и частые блокировки.
Отсутствие dual-write/dual-read при миграциях → потеря/рассинхрон.
DDL в проде без онлайн-инструментов и без фиче-флагов совместимости.
15) Чек-лист внедрения (0–60 дней)
0–15 дней
Определить SLO БД, RPO/RTO.
Включить репликацию, мониторинг lag, базовые бэкапы + PITR.
Ввести роутер (PgBouncer/ProxySQL) и политику read-after-write.
16–30 дней
Выбрать стратегию шардинга, описать ключи и схемы.
Подготовить инструменты перешардирования (Vitess/Citus/CDC).
Каталог сервисов/таблиц с пометкой «read-stale-ok» vs «strict».
31–60 дней
Запустить pilot-шард, dual-read и backfill.
Game-day: failover лидера, восстановление из PITR, переключение региона.
Автоматизировать отчеты по горячим шард-ключам и неравномерности.
16) Метрики зрелости
Replica lag p95 < целевого (например, 500 ms) для критичных чтений.
Успешные DR-тесты ≥ 1/квартал (restore ≤ RTO, потеря ≤ RPO).
Распределение нагрузки по шардам: дисбаланс < 20% по QPS/хранилищу.
Доля запросов со strict-consistency, корректно маршрутизированная, = 100%.
Zero-data-loss в инцидентах, требующих CP-гарантий (деньги/заказы).
Онлайн DDL/миграции без простоев, с флагами совместимости.
17) Примеры рецептов
Hash-salt для time-range (чтобы не греть один шард):sql
-- вычисляйте bucket = hash(user_id) % 16, храните (bucket, created_at)
PARTITION BY LIST (bucket) SUBPARTITION BY RANGE (created_at)
Read-my-writes middleware (псевдокод):
python lsn = db.leader_query("SELECT pg_current_wal_lsn()")
ctx.sticky_until = now()+5s ctx.min_lsn = lsn в роутере чтений: выбираем реплику с last_lsn >= ctx.min_lsn, иначе лидер
Vitess VSchema (фрагмент):
json
{
"tables": {
"orders": { "column_vindexes": [{ "column": "tenant_id", "name": "hash" }] }
}
}
18) Заключение
Шардинг и репликация — это не только техника, но и процессы: маршрутизация с учетом согласованности, дисциплина миграций (dual-write/read, backfill), регулярные DR-тесты и наблюдаемость lag/горячих шардов. Начинайте с простого leader→replica + read-after-write, затем добавляйте шардинг там, где его действительно требует профиль нагрузки. Используйте готовые платформы (Vitess/Citus/Distributed SQL) и держите бизнес-критичные данные в CP-режиме — так база перестанет быть бутылочным горлышком и станет предсказуемым, эластичным фундаментом платформы.