Шардинг і реплікація баз даних
Шардинг і реплікація баз даних
1) Навіщо це потрібно
Коли вертикальний апгрейд БД впирається в межі CPU/IO/RAM або один кластер стає SPOF, приходять реплікація (для читань/НА) і шардинг (для розподілу запису/даних). Цілі:- Пропускна здатність (горизонтальне зростання 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, Vuster itess (шардинг/ресхардинг).
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-режимі - так база перестане бути пляшковим горлечком і стане передбачуваним, еластичним фундаментом платформи.