Распределённые базы данных: принципы, архитектуры и практики

Курс раскрывает основы проектирования и эксплуатации распределённых баз данных: от теории согласованности и репликации до выбора архитектуры и обеспечения надёжности. Уделяется внимание компромиссам CAP, стратегиям шардирования, транзакциям и практикам мониторинга в продакшене.

1. Основы распределённых систем и CAP-теорема

Основы распределённых систем и CAP-теорема

Зачем вообще нужны распределённые базы данных

Распределённая база данных (РБД) — это система хранения данных, где данные и вычисления намеренно размещены на нескольких узлах (серверах, виртуальных машинах, контейнерах), соединённых сетью, и совместно выполняют функции единой базы.

Распределение выбирают не из эстетики, а из практических причин:

  • Масштабирование: один узел не справляется по CPU, памяти, диску или пропускной способности.
  • Отказоустойчивость: падение одного узла не должно останавливать систему.
  • География: пользователи по всему миру ожидают низкую задержку.
  • Изоляция отказов: сбой в одном домене (стойка, зона доступности) не должен «класть» всё.
  • Но за преимущества платят сложностью: в распределённых системах появляются частичные отказы, сетевые задержки, рассогласование реплик и необходимость выбирать компромиссы.

    Что такое распределённая система

    Распределённая система — это набор независимых узлов, которые обмениваются сообщениями по сети и для клиента выглядят как единая система.

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

    Почему «сеть ненадёжна» — это не лозунг, а реальность

    Ключевые факты, которые отличают распределённые системы от монолитных:

  • Задержки переменные: сообщение может прийти через 1 мс, а может через 1 секунду.
  • Сообщения могут теряться и дублироваться: повторные отправки создают дубликаты.
  • Узлы могут быть недоступны частично: один сервис видит другой, а третий — не видит.
  • Разделение сети (partition): сеть раскалывается на части, которые не могут общаться.
  • Следствие: «если запрос не вернулся, мы не знаем — он не был выполнен или был выполнен, но ответ потерялся». Это влияет на поведение баз данных и транзакций.

    !Иллюстрация сетевого разделения: части кластера живы, но не могут обмениваться сообщениями

    Базовые понятия: репликация, согласованность и отказ

    Репликация

    Репликация — это хранение копий данных на нескольких узлах. Она нужна для:

  • Доступности: если одна реплика упала, запросы идут в другую.
  • Производительности чтения: можно читать с ближайшей или менее нагруженной реплики.
  • Устойчивости: данные не теряются при сбое диска/узла.
  • Но репликация порождает вопрос: как гарантировать, что реплики не расходятся.

    Согласованность как свойство наблюдаемого поведения

    В контексте распределённых БД слово consistency часто путают:

  • В ACID-транзакциях согласованность — это про соблюдение ограничений данных (инвариантов, внешних ключей и т. п.).
  • В CAP-теореме согласованность — это про единое наблюдение данных: видят ли разные клиенты одну и ту же «истину» в один момент.
  • Чтобы избежать путаницы, дальше под согласованностью (C) будем иметь в виду именно CAP-смысл.

    Частичный отказ

    В распределённых системах «сломано» может быть не всё и не ничего, а что-то конкретное:

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

    CAP-теорема: суть и определения

    CAP-теорема — это утверждение о фундаментальном компромиссе в распределённых системах хранения.

    Классическая формулировка стала популярной после обсуждений Эрика Брюэра и последующего формального доказательства.

    > “In the presence of a network partition, a distributed system must choose between consistency and availability.” Brewer, CAP Twelve Years Later

    Формальная сторона CAP обычно связывается с работой:

  • Gilbert, Lynch — Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services
  • Три свойства CAP

    CAP говорит о трёх свойствах:

  • Consistency (C) — согласованность: все корректные узлы возвращают одинаковый результат для одного и того же запроса чтения после завершившейся записи (упрощённо: чтение видит «самое свежее» согласованное состояние).
  • Availability (A) — доступность: каждый запрос к неупавшему узлу получает ответ (не обязательно успешный по смыслу данных, но не «молчание»).
  • Partition tolerance (P) — устойчивость к разделению сети: система продолжает работать, даже если сеть разделилась на компоненты, которые не могут обмениваться сообщениями.
  • Удобно запомнить: P — это не «опция», а характеристика реального мира. Если система распределённая и общается по сети, разделение сети возможно, и проектировать систему так, будто его не бывает, — опасно.

    !Треугольник CAP и интуитивное объяснение компромисса

    Что CAP на самом деле утверждает

    CAP часто пересказывают как «нельзя иметь C, A и P одновременно». В разговорной форме это помогает, но важно понимать точнее:

  • Если возникло разделение сети (partition), и система должна продолжать обслуживать запросы, то она вынуждена выбрать:
  • 1. либо сохранять согласованность (C) и тогда часть запросов не сможет получить ответ (падает доступность A), 2. либо сохранять доступность (A) и тогда ответы могут быть несогласованными между компонентами (падает C).
  • Если разделения сети нет, система может обеспечивать и C, и A одновременно (по крайней мере с точки зрения CAP), поэтому «CA» возможно только в условиях надёжной связи. В реальных геораспределённых системах это предположение хрупкое.
  • Иными словами, CAP — это не «про то, какая база хорошая», а про выбор поведения системы при сетевой аварии.

    CP и AP: как выглядит выбор на практике

    CP-системы (Consistency + Partition tolerance)

    CP-подход: при сетевом разделении система предпочитает не отдавать потенциально неверные данные.

    Типичное поведение:

  • запись/чтение могут ошибиться или ждать, пока восстановится связь и будет подтверждено единое состояние;
  • клиент может получить timeout или явный отказ.
  • Интуиция: лучше «не ответить», чем «ответить неправильно».

    AP-системы (Availability + Partition tolerance)

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

    Типичное поведение:

  • чтение всегда возвращает что-то;
  • запись принимается локально;
  • после восстановления связи система выполняет схождение (convergence) и разрешение конфликтов.
  • Интуиция: лучше «ответить возможно устаревшим», чем «не ответить вообще».

    Таблица: как свойства проявляются в пользовательском опыте

    | Сценарий при partition | Выбор | Что увидит клиент | Типичный риск | |---|---|---|---| | Узлы не могут согласовать единое состояние | CP | Ошибка/ожидание/таймаут для части операций | Простой функций, ухудшение UX | | Узлы продолжают обслуживать запросы автономно | AP | Ответы есть, но данные могут различаться | Конфликты, устаревшие чтения, «двойные продажи» |

    Распространённые ошибки понимания CAP

    Ошибка: «P можно отключить»

    В распределённой системе по сети нельзя гарантировать, что разделений не будет. Можно снизить вероятность (лучшие сети, несколько каналов, одна зона доступности), но не убрать.

    Практический вывод: если система реально распределена, при проектировании всегда рассматривайте поведение при partition.

    Ошибка: «CAP — единственный закон распределённых данных»

    CAP описывает компромисс в условиях partition. Но в обычной жизни системы чаще живут в режиме «задержки растут, но сеть не упала полностью». Поэтому часто обсуждают более прикладные расширения, например PACELC: если partition нет (Else), всё равно выбираем между задержкой (Latency) и согласованностью (Consistency).

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

  • Designing Data-Intensive Applications (сайт книги)
  • Ошибка: «AP означает eventual consistency всегда и везде»

    AP означает, что при partition система продолжает отвечать, но это не обязывает её быть «всегда eventual». Многие системы позволяют настраивать уровни согласованности: например, для части операций можно требовать более строгих гарантий ценой задержек или отказов.

    CAP и распределённые базы данных: как читать характеристики продуктов

    Маркетинговые описания («строго консистентная», «высокодоступная») полезны только в связке с вопросом:

  • Что делает система, когда кластер разделился?
  • Чтобы перевести CAP в инженерную плоскость, задайте продукту/архитектуре вопросы:

  • Где находится источник истины: один лидер (leader) или несколько?
  • Может ли запись быть принята в двух компонентах кластера одновременно?
  • Как определяется «успешная запись»: сколько реплик должно подтвердить?
  • Что делает система при невозможности связаться с большинством реплик?
  • Как разрешаются конфликты после восстановления связи?
  • Ответы на эти вопросы обычно и определяют, ближе ли система к CP или к AP в условиях аварий.

    Мини-кейс: один и тот же бизнес-требование, разные решения

    Представим два продукта:

  • Интернет-банк: критично не показать клиенту «баланс, которого нет».
  • Лента новостей: лучше показать посты с задержкой/частично, чем не показать ничего.
  • В терминах CAP-поведения при partition:

  • интернет-банк часто выбирает CP-поведение для операций с деньгами (лучше отказ, чем неверная операция);
  • лента новостей часто выбирает AP-поведение для чтений (лучше доступность, чем строгая свежесть).
  • При этом даже внутри одного продукта возможны разные режимы для разных сущностей: платежи — ближе к CP, рекомендации — ближе к AP.

    Практические выводы для инженера распределённых БД

  • CAP — это инструмент описания компромисса при сетевом разделении, а не универсальный рейтинг баз данных.
  • В распределённой системе P практически неизбежно, поэтому ключевой выбор — поведение между C и A в аварии.
  • Выбор CP/AP должен быть привязан к цене ошибки:
  • - цена неверных данных высока → склоняемся к CP; - цена простоя высока → склоняемся к AP.
  • В следующих темах курса мы будем разбирать, как именно базы достигают нужного поведения: репликация, кворумы, лидерство, консенсус и модели согласованности.
  • 2. Репликация, согласованность и модели чтения/записи

    Репликация, согласованность и модели чтения/записи

    Как эта тема связана с CAP

    В предыдущей статье мы разобрали CAP как выбор поведения при сетевом разделении: в присутствии partition система вынуждена жертвовать либо согласованностью (C), либо доступностью (A).

    Теперь углубимся в механику, которая стоит за этими словами в реальных распределённых БД:

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

    Репликация — это хранение нескольких копий одних и тех же данных на разных узлах.

    Ключевые цели репликации:

  • отказоустойчивость: пережить падение узла или диска без потери данных;
  • доступность: продолжать обслуживать запросы при частичных отказах;
  • масштабирование чтений: распределять нагрузку чтения по репликам;
  • географическая близость: обслуживать пользователей из ближайшего региона.
  • Цена репликации: нужно договориться, какая копия является актуальной, что делать при задержках сети и как разрешать конфликты.

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

    На практике репликация почти всегда сводится к одному из трёх семейств топологий.

    !Сравнение leader-based, multi-leader и leaderless репликации

    Репликация с лидером (leader-based, primary-replica)

    Один узел — лидер — принимает записи. Остальные — реплики (часто говорят followers) — получают изменения от лидера.

    Свойства:

  • запись обычно проще: один упорядоченный поток изменений;
  • чтения можно делать с реплик, но тогда появляется риск устаревшего чтения;
  • при падении лидера нужен failover: выбор нового лидера.
  • Где встречается: классическая схема во многих SQL БД и в ряде распределённых систем.

    Репликация с несколькими лидерами (multi-leader)

    Несколько узлов могут принимать записи, обычно в разных дата-центрах. Затем изменения реплицируются между лидерами.

    Свойства:

  • высокая локальная доступность записи в каждом регионе;
  • почти неизбежны конфликты (два лидера приняли разные записи для одного объекта);
  • требуется стратегия разрешения конфликтов.
  • Часто применяется в сценариях с офлайн-режимом и межрегиональной записью.

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

    Нет выделенного лидера. Клиент (или координатор) отправляет запись сразу на несколько реплик.

    Свойства:

  • при отказе части реплик система всё равно может принимать запись (зависит от настроек);
  • согласованность достигается через кворумы и механизмы ремонта (подробнее ниже);
  • сложнее рассуждать о «самом свежем» значении без дополнительных протоколов.
  • Ассоциируется с рядом систем семейства Dynamo-подхода.

    Синхронная и асинхронная репликация

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

    Асинхронная репликация

    Лидер подтверждает запись клиенту, не дожидаясь, пока все реплики применят изменение.

    Плюсы:

  • ниже задержки записи;
  • выше доступность при временных проблемах сети.
  • Минусы:

  • при падении лидера можно потерять часть подтверждённых записей, если они не успели дойти до реплик.
  • Синхронная репликация

    Запись подтверждается только после того, как изменение записано на нескольких узлах.

    Плюсы:

  • меньше риск потери подтверждённых данных;
  • можно получить более строгие гарантии чтения.
  • Минусы:

  • задержка записи растёт;
  • при недоступности части реплик система может начать отказывать в записи.
  • Практический компромисс: часто делают подтверждение после записи на часть реплик (например, «большинство»), а оставшиеся догоняют асинхронно.

    Что такое согласованность в распределённых БД

    В CAP мы говорили о согласованности как об единости наблюдаемого состояния при сбоях сети. В инженерной практике согласованность удобнее обсуждать как модель согласованности — набор гарантий о том, какие результаты чтений возможны при конкурирующих операциях и репликации.

    Важно: разные системы (и даже одна система в разных режимах) могут давать разные гарантии.

    Модели согласованности: от строгих к слабым

    Ниже — распространённые модели, упорядоченные по интуитивной «строгости».

    Линеаризуемость (linearizability)

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

    Практический смысл:

  • если клиент получил подтверждение записи, то последующее чтение (откуда бы оно ни было) увидит эту запись;
  • хорошо подходит для денег, блокировок, уникальности.
  • Цена:

  • обычно требует координации и ожидания подтверждений от кворума/лидера;
  • может ухудшать доступность при partition (ближе к CP-поведению).
  • Справка: определение и примеры доступны в Linearizability на Wikipedia.

    Последовательная согласованность (sequential consistency)

    Интуиция: существует некоторый единый порядок операций, который видят все, но он не обязан соответствовать реальному времени.

    Отличие от линеаризуемости:

  • линеаризуемость привязана к реальному времени (если A завершилось до начала B, порядок должен это отражать);
  • sequential consistency такого требования не даёт, поэтому может быть проще/быстрее в реализации.
  • Справка: Sequential consistency на Wikipedia.

    Причинная согласованность (causal consistency)

    Интуиция: если одно событие причинно зависит от другого (например, комментарий зависит от поста), то все наблюдатели увидят их в правильном порядке. Независимые события могут быть видны в разном порядке.

    Практический смысл:

  • полезна в социальных продуктах и коллаборации;
  • обычно даёт лучшие задержки, чем линеаризуемость, при разумных гарантиях.
  • Справка: Causal consistency на Wikipedia.

    Eventual consistency (итоговая согласованность)

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

    Практический смысл:

  • высокая доступность при сбоях;
  • чтения могут быть устаревшими, а при конфликтующих записях нужен механизм разрешения конфликтов.
  • Справка: Eventual consistency на Wikipedia.

    Источник для системного понимания компромиссов репликации и согласованности: Designing Data-Intensive Applications.

    Модели чтения: какие гарантии получает клиент

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

    Устаревшее чтение (stale read)

    Чтение возвращает данные, которые не включают последние подтверждённые записи (например, реплика ещё не догнала лидера).

    Причины:

  • асинхронная репликация;
  • чтение из географически ближней, но отстающей реплики;
  • partition или временные сетевые задержки.
  • Read-your-writes

    Если клиент записал значение и получил подтверждение, то его последующие чтения должны видеть эту запись.

    Как обычно достигается:

  • чтение только с лидера после записи;
  • привязка клиента к «свежей» реплике (session stickiness);
  • использование меток версии (например, LSN/offset), чтобы читать не ниже нужной позиции.
  • Monotonic reads

    Если клиент однажды увидел новую версию данных, он не должен позже увидеть более старую.

    Частая проблема без этой гарантии:

  • пользователь обновил страницу и внезапно увидел «прошлое» состояние из другой, отстающей реплики.
  • Consistent prefix reads

    Клиент не должен видеть эффекты операций в «перемешанном» порядке (например, увидеть ответ на сообщение, не увидев само сообщение).

    Особенно актуально при шардинге и репликации логов, когда порядок внутри потока важен.

    Модели записи: что значит «запись успешна»

    У записи есть два ключевых аспекта:

  • долговечность: записалось ли значение так, что оно не пропадёт при сбое;
  • видимость: когда другие клиенты смогут это прочитать.
  • В leader-based архитектуре это часто выражается настройками уровня подтверждения:

  • подтверждать после записи на лидере (быстро, но риск потери при failover);
  • подтверждать после записи на лидере и части реплик (компромисс);
  • подтверждать после записи на всех репликах (медленно и хрупко к отказам).
  • В leaderless архитектуре обычно говорят языком кворумов.

    Кворумы в leaderless репликации: N, R, W

    Представим, что объект хранится на репликах.

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

    Смысл формулы:

  • левая часть — суммарное число реплик, участвующих в чтении и записи;
  • правая часть — число всех реплик;
  • знак означает, что множества реплик, участвовавших в чтении и записи, обязаны пересечься хотя бы в одной реплике.
  • Почему это важно: если чтение и запись пересекаются по хотя бы одной реплике, чтение может увидеть запись (при условии, что мы умеем выбирать самую новую версию среди ответов).

    Пример настройки кворума

    Если , то можно выбрать:

  • и , тогда .
  • Интуитивно:

  • запись переживёт отказ одной реплики (две всё равно подтвердят);
  • чтение спросит две реплики и, скорее всего, увидит актуальное значение.
  • Ограничение: даже при выполнении система не автоматически становится линеаризуемой. Нужны дополнительные условия: корректная версионность, разрешение конфликтов, отсутствие «раздвоенных» записей и аккуратная работа при таймаутах.

    !Пересечение кворумов чтения и записи

    Конфликты при репликации и как их понимают

    Конфликт возникает, когда система допускает конкурентные записи, и нет единственного очевидного порядка.

    Типичные источники конфликтов:

  • multi-leader: два лидера принимают запись для одной сущности;
  • leaderless: две реплики приняли записи параллельно при проблемах сети.
  • Частые стратегии разрешения конфликтов:

  • last write wins: выбрать значение с «самой новой» меткой времени;
  • слияние по бизнес-правилам: например, суммировать счётчики, объединять множества;
  • ручное разрешение: сохранить обе версии и просить приложение выбрать.
  • Важно: стратегия разрешения конфликтов — это не только про технику, но и про бизнес-семантику. Для денег и инвариантов last write wins часто недопустим.

    Как репликация и модели согласованности связаны с CP и AP

    Связь с CAP становится практической, если задать вопрос: что делает система при partition?

  • Системы, которые требуют координации (например, подтверждения от большинства) и при невозможности собрать подтверждения начинают отказывать, тяготеют к CP-поведению.
  • Системы, которые продолжают принимать чтения/записи в изолированных компонентах, а потом «сводят» изменения и решают конфликты, тяготеют к AP-поведению.
  • При этом один и тот же продукт может комбинировать подходы:

  • строгое поведение (ближе к CP) для критичных сущностей;
  • более доступное (ближе к AP) для вторичных данных, кешей, аналитики.
  • Практические вопросы, которые нужно задать при выборе режима чтения/записи

  • Откуда читаем: с лидера, с ближайшей реплики, с кворума?
  • Что важнее: свежесть или низкая задержка?
  • Какой ущерб от устаревшего чтения в вашем домене?
  • Что считается успешной записью: запись на одном узле или на большинстве?
  • Допустимы ли конфликты, и если да, то как они разрешаются?
  • Нужны ли клиентские гарантии: read-your-writes, monotonic reads?
  • Итоги

  • Репликация повышает доступность и отказоустойчивость, но требует явных решений по согласованности.
  • Архитектуры репликации обычно укладываются в leader-based, multi-leader и leaderless, каждая со своими рисками.
  • Модели согласованности описывают, какие результаты чтения возможны при конкуренции и репликации: от линеаризуемости до eventual consistency.
  • Клиентские гарантии чтения (read-your-writes, monotonic reads и другие) часто важнее абстрактных ярлыков и напрямую влияют на UX.
  • Кворумы , , — практический инструмент балансировки между отказоустойчивостью, задержками и шансом увидеть актуальные данные, но они не решают все проблемы автоматически.
  • 3. Шардирование, партиционирование и маршрутизация запросов

    Шардирование, партиционирование и маршрутизация запросов

    Как эта тема связана с CAP и репликацией

    В предыдущих статьях мы разобрали:

  • почему в распределённых системах неизбежны частичные отказы и сетевые разделения (partition);
  • как репликация повышает доступность и отказоустойчивость, но заставляет выбирать модели согласованности и режимы чтения/записи.
  • Теперь добавим ещё один базовый механизм масштабирования распределённых баз данных: шардирование.

    Ключевая идея:

  • репликация отвечает на вопрос как сделать копии данных надёжнее и доступнее;
  • шардирование отвечает на вопрос как разделить данные, чтобы хранить и обрабатывать их на многих узлах параллельно.
  • На практике почти всегда используется комбинация: каждый шард реплицируется (например, leader-based внутри шарда), а шардов становится много.

    Зачем нужно шардирование: пределы одной реплицируемой группы

    Если у вас один набор данных и вы просто реплицируете его на несколько узлов, вы получаете:

  • отказоустойчивость (данные не пропадают при падении узла);
  • масштабирование чтения (можно читать с реплик);
  • иногда ускорение записи за счёт батчинга и параллелизма на уровне диска.
  • Но есть ограничения:

  • объём данных может не помещаться на один узел (или на одну реплицируемую группу);
  • запись часто упирается в лидера (в leader-based репликации) или в кворумы и сетевые RTT (в leaderless);
  • многие операции (индексация, компакция, бэкапы) становятся тяжелее по мере роста одного «монолита данных».
  • Шардирование решает это разделением данных на части, которые можно хранить и обслуживать независимо.

    Термины: партиционирование, партиция, шард

    В разговорной практике термины иногда смешивают, но полезно развести их смысл.

  • Партиционирование — способ разделить логическую таблицу/коллекцию на части по правилу.
  • Партиция — одна такая часть (логическая единица разбиения).
  • Шард — партиция (или набор партиций), закреплённая за конкретной группой узлов/реплик и физически обслуживаемая ими.
  • В документации и статьях часто используют слово shard как для логической части, так и для физической. Если хочется формальности, можно ориентироваться на определения из материалов по Shard (database architecture)) и Partition (database)).

    Таблица: чем шардирование отличается от репликации

    | Механизм | Что происходит с данными | Основная цель | Основная цена | |---|---|---|---| | Репликация | Одна и та же запись хранится в нескольких копиях | Доступность, отказоустойчивость, ускорение чтений | Согласованность, задержки, конфликты | | Шардирование | Набор записей делится на непересекающиеся части | Масштабирование объёма и пропускной способности | Сложность запросов, ребалансировка, кросс-шард операции |

    Ключ партиционирования: главный выбор, который определяет всё

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

    Что такое ключ партиционирования

    Ключ партиционирования — это значение, по которому вы определяете, в какой партиции/шарде хранить запись.

    Примеры:

  • user_id для данных пользователей;
  • tenant_id в multi-tenant системах;
  • (country, user_id) для географического разделения;
  • timestamp (осторожно) для временных рядов.
  • Свойства хорошего ключа

    Хороший ключ партиционирования обычно:

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

  • Партиционирование по монотонно растущему ключу (например, created_at): новые записи всегда попадают в «последнюю» партицию, создавая hot shard.
  • Партиционирование по низкокардинальному полю (например, status): слишком мало корзин, распределение плохое.
  • Партиционирование по полю, которое часто меняется (например, user_region, если пользователь переезжает): дорогое перемещение данных.
  • Стратегии партиционирования

    Ниже — базовые стратегии. На практике многие системы комбинируют их.

    !Диаграмма, показывающая разницу между диапазонным и хеш-партиционированием

    Диапазонное (range) партиционирование

    Данные делятся по диапазонам ключа, например:

  • user_id 0–999999 на шарде A
  • user_id 1000000–1999999 на шарде B
  • Плюсы:

  • эффективны диапазонные запросы: WHERE user_id BETWEEN ... или временные окна для рядов;
  • удобно делать «локальные» сканы и сортировки внутри диапазона.
  • Минусы:

  • риск hot shard, если новые данные всегда попадают в один конец диапазона (частая проблема для времени);
  • ребалансировка может означать перерезание диапазонов и миграцию больших кусков данных.
  • Хеш-партиционирование

    Шард выбирается по значению хеш-функции от ключа, например концептуально: shard = hash(key) -> bucket.

    Плюсы:

  • более равномерное распределение записей и нагрузки;
  • меньше шанс «естественных» перекосов из-за структуры ключей.
  • Минусы:

  • диапазонные запросы по ключу теряют смысл: чтобы найти диапазон, часто приходится обращаться ко многим шардам;
  • сложнее делать операции, которые зависят от «соседства» ключей.
  • Консистентное хеширование

    Консистентное хеширование — подход, при котором при добавлении/удалении узлов перемещается только часть ключей, а не почти все.

    Это особенно полезно, когда:

  • шарды сопоставляются узлам через «кольцо»;
  • состав кластера меняется (масштабирование, аварии, плановые работы).
  • Концепцию обычно описывают через хеш-кольцо и виртуальные узлы. См. Consistent hashing.

    !Иллюстрация хеш-кольца и эффекта минимального перемещения ключей

    Комбинированные схемы

    Чтобы совместить преимущества разных подходов, используют комбинации, например:

  • география → хеш: сначала выбираем регион (чтобы данные были ближе), затем внутри региона распределяем по хешу user_id;
  • время → хеш: для временных рядов — партиции по окнам времени (день/час), а внутри окна — хеш по идентификатору источника.
  • Комбинации почти всегда появляются из-за реальных запросов: нужен и контроль локальности, и защита от горячих точек.

    Ребалансировка: что происходит, когда данных и узлов становится больше

    Шардирование редко бывает «настроил и забыл». Данные растут, узлы добавляются, нагрузка меняется.

    Ребалансировка — это перераспределение партиций/шардов между узлами.

    Типовые операции:

  • Split: горячую или слишком большую партицию делят на две.
  • Merge: мелкие партиции объединяют.
  • Move: партицию переносят на другой узел или в другую реплицируемую группу.
  • Почему ребалансировка сложна

    Проблемы, которые нужно решить одновременно:

  • не потерять данные и не нарушить долговечность;
  • не сломать маршрутизацию (клиенты должны понимать, куда идти за данными);
  • пережить конкурирующие записи во время миграции;
  • не уронить латентность из-за массового копирования и догоняющей репликации.
  • Часто миграция идёт как управляемый процесс:

  • копирование данных в новое место;
  • догоняющая синхронизация изменений;
  • переключение маршрутизации на новое место;
  • очистка старой копии.
  • Если при этом внутри шарда используется репликация, то миграция обычно переносит не «один узел», а целую реплицируемую группу или её лидера, сохраняя выбранные гарантии чтения/записи из предыдущей статьи.

    Маршрутизация запросов: как запрос попадает в нужный шард

    Шардирование добавляет обязательный этап: определить, где лежит нужная запись.

    Под маршрутизацией запросов обычно понимают:

  • вычисление шарда по ключу;
  • поиск актуального лидера/реплик шарда (если используется репликация);
  • обработку случаев, когда метаданные устарели или шард мигрирует.
  • Основные архитектуры маршрутизации

    | Подход | Где живёт логика маршрутизации | Плюсы | Минусы | |---|---|---|---| | Клиентская | В библиотеке/SDK приложения | Нет лишнего хопа, меньше задержка | Сложнее обновлять всех клиентов, риск расхождения метаданных | | Прокси/роутер | Отдельный слой между клиентом и БД | Централизованный контроль, проще миграции | Дополнительная точка отказа и задержка, нужна масштабируемость прокси | | Координатор в БД | Внутри кластера (координатор принимает запрос) | Клиент проще, единые правила | Координатор может стать узким местом, усложнение системы |

    Метаданные партиций

    Чтобы маршрутизировать запрос, нужны метаданные:

  • список партиций (диапазоны или сегменты хеш-кольца);
  • сопоставление партиции → шард/группа реплик;
  • информация о лидере (если есть лидер) и о доступных репликах.
  • Критический момент: метаданные тоже распределены и могут устаревать.

    Типичные механизмы защиты:

  • версии/эпохи метаданных и проверка, что клиент работает с актуальной;
  • перенаправление (redirect) на правильный узел;
  • ретраи с обновлением маршрута;
  • запрет «долгих» кешей маршрута у клиентов.
  • Маршрутизация чтений и записей при репликации

    Связь с предыдущей статьёй о моделях чтения/записи выглядит так:

  • записи обычно идут к лидеру шарда (в leader-based) или к набору реплик (в leaderless с кворумами);
  • чтения могут идти:
  • - к лидеру (свежее, но иногда дороже по задержке), - к ближайшей реплике (быстрее, но возможны stale reads), - к нескольким репликам/кворуму (дороже, но может дать лучшие гарантии).

    То есть маршрутизатор выбирает не только шард, но и реплику/лидера в зависимости от требуемой согласованности.

    Что усложняется в запросах: fan-out и кросс-шард операции

    В идеально спроектированном приложении большая часть запросов — один ключ → один шард.

    Но реальность приносит запросы, которые затрагивают много шардов:

  • поиск по полю, которое не является ключом партиционирования;
  • агрегации по всем пользователям;
  • сортировка по глобальному признаку;
  • соединения (join) между сущностями, разложенными по разным ключам.
  • Scatter-gather (fan-out)

    Распространённый паттерн:

  • координатор отправляет запрос на множество шардов;
  • каждый шард выполняет свою часть;
  • координатор объединяет результаты.
  • Это почти всегда увеличивает задержку, потому что общий ответ ограничен самым медленным шардом, а также увеличивает нагрузку из-за параллельных запросов.

    Как с этим живут

    Практические приёмы:

  • проектировать данные так, чтобы основной доступ был по ключу шарда;
  • денормализовать (хранить нужные поля рядом, чтобы не делать кросс-шард join);
  • делать предагрегации и материализованные представления;
  • выделять отдельные системы для аналитики, где fan-out ожидаем и приемлем.
  • Индексы и уникальность в шардированном мире

    Локальные и глобальные индексы

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

  • сама должна быть масштабируемой и отказоустойчивой;
  • может требовать координации (а значит, влиять на CAP-поведение в авариях);
  • усложняет запись: нужно обновлять и данные, и индекс.
  • Уникальные ограничения

    Уникальность вроде email или username становится сложной, если данные шардированы по другому ключу.

    Типовые решения:

  • шардировать по уникальному полю (если это согласуется с запросами);
  • держать отдельный «каталог» (сервис/таблицу), который резервирует уникальные значения;
  • применять бизнес-компромиссы (например, уникальность в пределах tenant_id).
  • Практические рекомендации

  • Выбирайте ключ партиционирования от основных запросов и профиля нагрузки, а не только от структуры данных.
  • Планируйте, что ребалансировка неизбежна: закладывайте миграции, версии метаданных и безопасные ретраи.
  • Избегайте fan-out на критическом пути: он почти всегда ухудшает p95/p99 задержки.
  • Комбинируйте шардирование и репликацию осознанно: шардирование даёт масштаб, репликация даёт устойчивость, а согласованность и маршрутизация определяют пользовательские гарантии.
  • Итоги

  • Шардирование (партиционирование) делит данные на части, чтобы масштабировать объём и пропускную способность.
  • Ключ партиционирования определяет, будут ли запросы точечными (в один шард) или распылёнными (fan-out).
  • Основные стратегии — диапазонная, хеш- и консистентное хеширование; у каждой свои риски (hot shard, сложность range-запросов, миграции).
  • Маршрутизация — обязательный слой логики: найти шард и выбрать реплику/лидера с нужными гарантиями чтения/записи.
  • Кросс-шард запросы и глобальные индексы — основные источники сложности и неожиданных задержек в шардированных системах.
  • 4. Распределённые транзакции, консенсус и отказоустойчивость

    Распределённые транзакции, консенсус и отказоустойчивость

    Как эта тема связана с предыдущими

    В первых статьях курса мы собрали базовую картину:

  • CAP объясняет, почему при сетевом разделении (partition) система вынуждена выбирать поведение между согласованностью и доступностью.
  • Репликация показывает, как держать несколько копий данных и какие модели чтения/записи (кворумы, устаревшие чтения) из этого следуют.
  • Шардирование добавляет масштабирование по объёму и пропускной способности, но приводит к кросс-шард запросам и усложняет поддержание инвариантов.
  • Теперь мы соединяем эти темы в инженерную «сердцевину» распределённых БД:

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

    В одной машине транзакция часто выглядит просто: есть журнал (WAL), блокировки или MVCC, и система контролирует порядок операций.

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

    Из этого рождаются типичные сложности:

  • частичный успех: один узел применил изменения, другой нет, а координатор потерялся;
  • непонятный исход: клиент не знает, была ли операция выполнена, если ответ не пришёл;
  • split brain: два узла считают себя «главными» и принимают записи параллельно;
  • кросс-шард инварианты: например, «перевод денег» почти всегда трогает минимум две сущности, которые могут лежать на разных шардах.
  • Практический вывод: распределённая транзакция — это всегда протокол, который должен правильно отрабатывать отказы.

    Что такое распределённая транзакция

    Распределённая транзакция — это транзакция, которая для достижения своих гарантий должна координировать изменения состояния на нескольких узлах (часто на нескольких шардах и/или реплика-группах).

    В простейшем виде цель звучит так:

  • либо все участники зафиксировали изменения,
  • либо никто не зафиксировал,
  • и система может это доказуемо восстановить после сбоев.
  • Эта цель обычно конфликтует с высокой доступностью при partition, поэтому в реальных системах часто встречаются гибридные подходы:

  • строгие транзакции для критичных данных (ближе к CP при авариях);
  • саги, идемпотентность, компенсации и итоговая согласованность для менее критичных операций.
  • Двухфазный коммит (2PC): классическая схема распределённой транзакции

    Один из базовых протоколов координации — Two-Phase Commit (2PC). В нём есть:

  • координатор (transaction coordinator);
  • участники (participants), каждый из которых хранит часть данных.
  • Идея 2PC

    2PC делит фиксацию транзакции на две фазы:

  • Prepare (голосование): координатор спрашивает участников, готовы ли они зафиксировать изменения.
  • Commit/Abort (решение): если все ответили «готов», координатор рассылает commit, иначе abort.
  • !Диаграмма обмена сообщениями в 2PC

    Что значит «prepare» на практике

    Чтобы ответить «YES», участник обычно должен:

  • проверить ограничения (например, достаточно ли денег на счёте);
  • зафиксировать в своём журнале намерение коммита (чтобы пережить перезапуск);
  • удержать необходимые блокировки или версию (чтобы не нарушить изоляцию до финального решения).
  • Это делает 2PC сильным по гарантиям, но дорогим по ресурсам.

    Главная проблема 2PC: блокирующее поведение

    2PC может стать блокирующим:

  • если координатор упал после того, как участники ответили «YES», участники могут остаться в состоянии «готов, но не знаю итог»;
  • пока не станет ясно, commit или abort, участник часто обязан удерживать блокировки/ресурсы.
  • Именно поэтому 2PC в чистом виде плохо сочетается с требованием высокой доступности: при ряде отказов система вынуждена ждать восстановления координатора или вмешательства.

    Полезная справка: Two-phase commit protocol.

    Почему «таймаут» не решает 2PC автоматически

    Можно подумать: «Если координатор не отвечает, давайте просто откатим». Но участники не всегда могут безопасно откатить, потому что:

  • координатор мог уже отправить commit, но сообщение потерялось;
  • разные участники примут разные решения, и вместо атомарности получится расхождение.
  • Поэтому в строгой модели участникам нужен источник истины о решении (лог координатора или внешний консенсус).

    3PC и практическая реальность

    Существует Three-Phase Commit (3PC), который пытается убрать блокировку при некоторых предположениях о сети. Но в реальных сетях с произвольными задержками и разделениями 3PC используется редко.

    В современных распределённых БД чаще идут другим путём: делают решение «commit/abort» частью консенсуса (чтобы оно не зависело от одного координатора как точки отказа).

    Консенсус: как узлы договариваются об одном решении

    Консенсус — это класс протоколов, которые позволяют нескольким узлам согласовать одно значение или последовательность значений, даже если часть узлов падает или сеть временно работает плохо.

    Типичные задачи, решаемые консенсусом в БД:

  • выбрать лидера (leader election);
  • согласовать порядок операций (replicated log);
  • хранить метаданные кластера (кто лидер, где шард, какая эпоха);
  • сделать так, чтобы «решение о коммите» не потерялось при падении одного узла.
  • Классические семейства протоколов:

  • Paxos: Paxos Made Simple
  • Raft: In Search of an Understandable Consensus Algorithm (Raft)
  • Почему консенсус почти всегда завязан на кворум

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

    Если в группе узлов, то размер большинства часто выражают как:

    Где:

  • — общее число узлов в группе репликации;
  • — целая часть от половины ;
  • — минимальное число узлов, которое гарантирует пересечение любых двух «большинств» хотя бы в одном узле.
  • Пересечение важно потому, что оно предотвращает ситуацию, когда два разных решения «легально» принимаются двумя непересекающимися группами.

    Raft в контексте базы данных: реплицируемый лог и машина состояний

    Инженерно полезная модель консенсуса для БД — State Machine Replication:

  • узлы согласуют один и тот же лог команд;
  • затем применяют команды к локальной машине состояний (например, к B-Tree или LSM-структурам);
  • если лог одинаковый и порядок одинаковый, состояние сходится.
  • Роли в Raft

  • Leader принимает записи от клиентов и реплицирует их.
  • Followers принимают записи от лидера.
  • Candidate появляется на выборах лидера.
  • Как запись становится «зафиксированной»

    Упрощённая картина:

  • лидер добавляет запись в свой лог;
  • рассылает её follower-ам;
  • когда запись сохранена на большинстве узлов, лидер считает её committed;
  • committed-запись можно применять к состоянию и отдавать клиенту подтверждение (в строгой конфигурации).
  • !Схема лидера, кворума и коммита записи в Raft

    Связь с репликацией из прошлой статьи

    Если в прошлой статье мы говорили «leader-based репликация», то Raft можно понимать как способ сделать leader-based репликацию безопасной:

  • лидерство не «назначается вручную», а выбирается протоколом;
  • порядок записей фиксируется через согласованный лог;
  • при падении лидера новый лидер выбирается так, чтобы не потерять уже зафиксированные данные.
  • Как консенсус помогает отказоустойчивости

    Отказоустойчивость в распределённой БД — это не только «есть реплики», а ещё и ответ на вопрос:

  • кто имеет право принимать запись прямо сейчас?
  • Иначе легко получить split brain.

    Split brain и «фальшивый лидер»

    Split brain — это ситуация, когда из-за сетевого разделения два узла (или две группы узлов) считают себя лидером и принимают записи.

    Защита обычно строится вокруг двух идей:

  • кворум: лидер может быть лидером, только если он общается с большинством;
  • эпохи/термы: каждое лидерство имеет номер (term/epoch), который монотонно растёт.
  • Fencing tokens: как защититься от «зомби-координатора»

    Распространённый практический приём — fencing token (токен ограждения):

  • при выдаче «права быть лидером» система возвращает монотонно растущий номер (эпоху);
  • все операции лидера должны нести этот номер;
  • если старый лидер ожил и пытается писать со старым номером, получатель откажет.
  • Это особенно важно, когда есть внешние системы (хранилище, очередь, сторонний сервис), которые должны уметь сказать: «этот лидер устарел».

    Распределённые транзакции в шардированной базе

    Если база шардирована, одна транзакция может затронуть несколько шардов. Тогда появляются два уровня распределённости:

  • внутри каждого шарда данные обычно реплицированы (и часто используют консенсус для порядка записей);
  • между шардами нужно согласовать общий результат транзакции.
  • Распространённый современный паттерн

  • Каждый шард — это реплика-группа (часто с консенсусом), которая надёжно фиксирует локальные изменения.
  • Для транзакции на несколько шардов используется координация уровня 2PC, но ключевые решения фиксируются так, чтобы координатор не был «одной точкой правды».
  • Такой подход встречается в системах, которые стремятся дать строгие транзакции поверх распределённой архитектуры.

    Пример направления идей: распределённые транзакции и синхронизация времени в Spanner описаны в статье Spanner: Google’s Globally-Distributed Database.

    Цена кросс-шард транзакций

    Кросс-шард транзакции почти всегда ухудшают:

  • задержку (нужно несколько сетевых раундов);
  • отказоустойчивость (больше компонентов должны быть доступны одновременно);
  • пропускную способность (координаторы, блокировки, ретраи, конфликтующие записи).
  • Поэтому рекомендации из статьи про шардирование остаются практическим правилом: критический путь лучше держать в формате «один ключ → один шард».

    Когда строгие распределённые транзакции не подходят: саги и компенсации

    Если бизнес допускает модель «в итоге сойдётся», часто используют Saga:

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

  • выше доступность и масштабируемость;
  • меньше блокировок и глобальной координации.
  • Минусы:

  • сложнее корректно проектировать компенсации;
  • клиент может наблюдать промежуточные состояния;
  • нужны продуманные инварианты и обработка повторов.
  • В терминах CAP это часто сознательное движение в сторону AP-поведения для части сценариев.

    Повторы, идемпотентность и миф «exactly-once»

    В распределённой системе нормальная ситуация: клиент не получил ответ и повторил запрос. Это означает, что ваша система должна быть готова к:

  • дублированию команд;
  • изменению порядка доставки;
  • многократному выполнению попыток.
  • Частый практический подход:

  • делать операции идемпотентными (повтор не меняет результат);
  • использовать idempotency key или уникальный идентификатор операции;
  • хранить факт обработки (dedup) там, где это надёжно и проверяемо.
  • Это напрямую связано с отказоустойчивостью: система не должна превращать сетевой таймаут в двойное списание.

    Практический чек-лист для проектирования отказоустойчивых транзакций

  • Определите, где проходит граница «строгости»: какие сущности требуют строгой согласованности, а какие могут быть итоговыми.
  • Минимизируйте кросс-шард транзакции на критическом пути.
  • Если используете 2PC, заранее решите, как хранится решение commit/abort и как система восстанавливается после падения координатора.
  • Опирайтесь на консенсус для лидерства и порядка операций внутри реплика-группы.
  • Защититесь от split brain через кворумы, эпохи и при необходимости fencing tokens.
  • Спроектируйте ретраи как нормальный путь: идемпотентность, backoff, дедупликация.
  • Итоги

  • 2PC даёт понятную модель атомарности между узлами, но может быть блокирующим и плохо переносит ряд отказов без дополнительной инфраструктуры.
  • Консенсус (Paxos/Raft) — базовый инструмент, который позволяет надёжно выбрать лидера и согласовать порядок записей, формируя устойчивый реплицируемый лог.
  • В шардированных системах строгие транзакции часто строятся как «координация между реплика-группами», а цена кросс-шард транзакций почти всегда высока.
  • Отказоустойчивость — это не только реплики, но и корректное поведение при таймаутах, повторах, split brain и восстановлении.
  • 5. Проектирование, безопасность и эксплуатация в продакшене

    Проектирование, безопасность и эксплуатация в продакшене

    Зачем эта тема в курсе про распределённые БД

    В предыдущих статьях мы разобрали фундаментальные компромиссы и механизмы:

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

    Цель этой статьи: собрать практики проектирования, безопасности и эксплуатации так, чтобы распределённая база данных была управляемой и доверенной в бою.

    !Пирамида показывает, что продакшен-качества опираются на архитектурные решения

    Продакшен-мышление: что мы на самом деле гарантируем

    Распределённая БД в продакшене должна отвечать на три вопроса.

    Какие гарантии получает клиент

    Нужно явно зафиксировать контракт:

  • что означает успешная запись (например, подтверждение от лидера или от кворума);
  • какие чтения допустимы (например, чтение с реплики может быть устаревшим);
  • какие инварианты защищаются строго (например, баланс не может стать отрицательным);
  • что происходит при сбоях сети (CP-поведение: ошибки/ожидание; AP-поведение: принимаем и потом сводим изменения).
  • Эти пункты напрямую связывают продакшен-ожидания с CAP, репликацией и транзакциями.

    Какие цели по надёжности мы принимаем

    В эксплуатации обычно работают через три термина:

  • SLO (Service Level Objective) — целевая планка, например: 99.95% успешных чтений за 28 дней.
  • SLI (Service Level Indicator) — измеряемая метрика, например: доля запросов GET с кодом 2xx и задержкой меньше 200 мс.
  • Error budget — допустимый объём "ошибок" относительно SLO, который расходуется инцидентами и рисковыми изменениями.
  • Определения и подход хорошо изложены в Google SRE Book.

    > "Site Reliability Engineering is what happens when you ask a software engineer to design an operations function." Google SRE Book

    Как система деградирует

    Распределённые системы почти никогда не падают целиком — они деградируют частично. Хороший дизайн заранее определяет, что будет происходить при:

  • недоступности меньшинства реплик;
  • потере связи между зонами доступности;
  • перегрузке отдельных шардов;
  • исчерпании диска или росте лагов репликации;
  • частичных отказах зависимостей (KMS, DNS, сервис обнаружения узлов).
  • Проектирование для эксплуатации: чтобы система была управляемой

    Таймауты, ретраи и идемпотентность

    В распределённых системах таймаут — это не исключение, а часть нормального пути. Поэтому проектирование API и клиентского поведения должно включать:

  • таймауты на каждый сетевой вызов;
  • повторы (retries) с exponential backoff и jitter (случайной добавкой), чтобы не устроить самоускоряющуюся перегрузку;
  • идемпотентность критичных операций.
  • Идемпотентная операция — операция, которую можно выполнить повторно без изменения результата. Практический паттерн:

  • клиент генерирует idempotency_key для записи;
  • сервер хранит факт обработки (например, в таблице дедупликации или в журнале);
  • повтор с тем же ключом возвращает тот же результат.
  • Это напрямую защищает от двойных списаний и "дубликатов" при сетевых сбоях.

    Версионирование протоколов и обратная совместимость

    Продакшен-обновления часто происходят по частям: часть узлов уже новая, часть ещё старая. Поэтому протоколы и форматы должны быть:

  • backward compatible (новые узлы понимают старые сообщения);
  • forward compatible на период раскатки (старые узлы терпят новые поля, которые могут игнорировать).
  • Практики:

  • добавлять поля как необязательные;
  • не переиспользовать смысл полей;
  • поддерживать версии протокола и чёткую политику жизненного цикла.
  • Управление конфигурацией и безопасные переключатели

    В распределённой БД важно уметь быстро и безопасно менять поведение без перекомпиляции.

    Полезные механизмы:

  • feature flags для включения новых путей чтения/записи;
  • переключатели уровня согласованности (например, читать только с лидера для критичных запросов);
  • ограничители нагрузки (rate limiting) и предохранители (circuit breakers).
  • Наблюдаемость: чтобы понимать, что происходит

    Наблюдаемость обычно складывают из трёх сигналов:

  • метрики (числа во времени);
  • логи (события);
  • трейсы (связанные события по пути запроса).
  • Ключевая продакшен-идея: нельзя управлять тем, что нельзя измерить.

    Метрики, которые особенно важны для распределённых БД

    Таблица ниже — минимальный набор, который помогает диагностировать сбои репликации, шардирования и консенсуса.

    | Область | Метрика | Что означает | Почему важно | |---|---|---|---| | Репликация | lag реплики | насколько реплика отстаёт | растут устаревшие чтения и риск потери данных при failover | | Консенсус | время выборов лидера | сколько длится лидерская смена | напрямую влияет на окна недоступности записи | | Запись | доля таймаутов/ошибок | сколько операций не завершилось | индикатор partition, перегрузки или проблем диска | | Шардирование | перекос нагрузки по шардам | hot shard | приводит к p99 задержкам и каскадной деградации | | Хранилище | заполнение диска, скорость компакции | здоровье LSM/B-Tree обслуживания | дисковая деградация часто маскируется до критического момента | | Сеть | RTT и потери пакетов между узлами | качество связи | CAP-компромиссы начинают проявляться из-за "серой" деградации сети |

    Корреляция запросов и трассировка

    Чтобы разбирать инциденты, нужна связь между:

  • входным запросом приложения;
  • роутером/координатором;
  • конкретным шардом и реплика-группой;
  • внутренними RPC и ретраями.
  • Практика: единый request_id/trace_id в логах и трейсе на всех слоях.

    Бэкапы и восстановление: репликация не заменяет резервное копирование

    Репликация защищает от падения узла, но не спасает от:

  • логических ошибок (удалили не те строки);
  • багов приложения, записавших мусор;
  • компрометации учётных данных;
  • "тихой" порчи данных, размноженной на все реплики.
  • Поэтому нужны бэкапы и проверяемая процедура восстановления.

    Виды бэкапов

  • Полный бэкап: снимок данных целиком.
  • Инкрементальный: сохраняем изменения с момента прошлого бэкапа.
  • Point-in-time recovery (PITR): восстановление на конкретный момент времени с помощью журнала изменений.
  • Пример хорошо документированного PITR-подхода можно посмотреть в разделе PostgreSQL про непрерывное архивирование и PITR: PostgreSQL Documentation: Continuous Archiving and Point-in-Time Recovery (PITR).

    Что нужно решить для шардированной базы

    В шардированной архитектуре возникает вопрос: как получить согласованный бэкап сразу по всем шардам.

    Варианты:

  • бэкап "как есть" по каждому шарду отдельно, принимая, что глобально снимки могут быть на чуть разные моменты;
  • координированный снимок (дороже и сложнее), если нужны строгие межшардовые инварианты;
  • логическая репликация/журналы как источник для восстановлений и переигрывания.
  • !Схема шагов восстановления и переключения

    Регулярные учения восстановления

    Бэкап без проверяемого восстановления — это надежда, а не план.

    Минимальные практики:

  • периодические тестовые восстановления в изолированной среде;
  • измерение RPO и RTO:
  • - RPO (Recovery Point Objective) — сколько данных можно потерять по времени (например, до 5 минут); - RTO (Recovery Time Objective) — сколько времени допустимо восстанавливаться (например, 60 минут);
  • автоматизация runbook восстановления.
  • Безопасность: защита данных и управление доступом

    Безопасность распределённой БД — это не одна настройка, а система слоёв.

    Модель угроз: от чего защищаемся

    Перед выбором механизмов полезно явно зафиксировать:

  • кто атакующий (внешний злоумышленник, внутренний пользователь, компрометированный сервис);
  • что является целью (данные, учётные данные, доступ к админ-операциям);
  • какие каналы атаки (сеть, ключи доступа, уязвимости приложения, цепочка поставки).
  • Это помогает отличить "хорошую практику" от реально нужного контроля.

    Аутентификация и авторизация

  • Аутентификация отвечает на вопрос: кто ты?
  • Авторизация отвечает на вопрос: что тебе можно?
  • Практики для БД:

  • принцип наименьших привилегий: выдавать минимум прав, необходимый для роли;
  • разделение ролей: чтение, запись, администрирование, бэкапы;
  • короткоживущие токены и автоматическая ротация;
  • запрет прямого доступа к админ-интерфейсам из публичных сетей.
  • Шифрование: в канале и на диске

  • Шифрование в канале (in transit): защищает трафик между клиентами и узлами, а также межузловой трафик.
  • - обычно используется TLS; - для сервис-сервис часто применяют mTLS (взаимная аутентификация сертификатами).
  • Шифрование на диске (at rest): защищает данные на носителе (диски, снапшоты, бэкапы).
  • Важно понимать границу: шифрование на диске не защищает от запроса, который уже прошёл авторизацию.

    Управление ключами и секретами

    Ключи шифрования и секреты доступа нельзя "просто положить в конфиг".

    Ожидаемый минимум:

  • хранить ключи в KMS/HSM (или эквиваленте вашей платформы);
  • автоматическая ротация ключей;
  • аудит доступа к ключам;
  • разделение обязанностей: администратор БД не должен автоматически иметь доступ к ключам уровня платформы.
  • Сегментация сети и контроль периметра

    В распределённых БД межузловой трафик критичен, и его нужно защищать.

    Практики:

  • отдельные подсети для кластеров;
  • firewall/security groups по принципу "разрешено только необходимое";
  • запрет прямого доступа к узлам данных из пользовательских подсетей;
  • bastion/jump-host для админ-доступа с MFA.
  • Аудит и неизменяемые журналы

    Для расследований и соответствия требованиям часто нужен аудит:

  • кто читал и менял данные;
  • кто менял схемы и конфигурации;
  • кто выполнял административные операции.
  • Полезная цель: аудит-лог должен быть трудно подделать (например, отправка в отдельное хранилище/систему логирования с ограничениями на изменение).

    Уязвимости уровня приложения

    Даже идеально защищённая БД не спасёт, если приложение делает небезопасные запросы. Базовые рекомендации хорошо систематизированы в OWASP Top Ten.

    Эксплуатация кластера: обновления, миграции, инциденты

    Обновления без простоя: rolling upgrade

    Rolling upgrade — обновление узлов по одному (или небольшими партиями), сохраняя работоспособность кластера.

    Чтобы это было возможно:

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

    В распределённой среде миграции часто опаснее, чем кажется.

    Практики:

  • миграции делать двухфазными:
  • - сначала добавить новое поле/индекс, не ломая старый код; - затем перевести приложение на новый путь; - только потом удалять старое.
  • избегать долгих блокировок на больших таблицах;
  • иметь план отката и понимание, что "откат" иногда означает ещё одну миграцию вперёд.
  • Ребалансировка и перемещение шардов

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

  • лимитировать параллелизм миграций, чтобы не убить p99;
  • уметь ставить миграцию на паузу;
  • наблюдать прогресс и влияние на лаги репликации;
  • защищаться от устаревших метаданных маршрутизации (версии/эпохи, редиректы).
  • Инцидент-менеджмент и runbooks

    Runbook — пошаговая инструкция для типового инцидента.

    Хороший runbook отвечает:

  • как распознать проблему по метрикам и симптомам;
  • какие проверки сделать (например, жив ли лидер, есть ли кворум);
  • какие действия безопасны, а какие запрещены;
  • как проверить, что система восстановилась.
  • Полезная практика: game days — регулярные учения аварий (например, выключить зону доступности в тестовой среде) и обновление runbook по результатам.

    Для проверки корректности систем под отказами популярны методологии и инструменты типа Jepsen: Jepsen.

    Производительность и ёмкость: чтобы не превратить рост в аварию

    Планирование ёмкости (capacity planning)

    Нужно заранее понимать, как рост данных и нагрузки влияет на:

  • размер шардов и необходимость split/merge;
  • объём журналов и место под бэкапы;
  • время восстановления;
  • стоимость межрегиональной репликации.
  • Хорошая практика: планировать не только среднюю нагрузку, но и пики, учитывая деградацию при отказах (когда часть узлов недоступна, оставшиеся берут нагрузку на себя).

    Защита от перегрузки

    Перегрузка опаснее "обычного" отказа: она растёт каскадно и вызывает лавину ретраев.

    Инженерные инструменты:

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

    Таблица ниже помогает быстро оценить, закрыты ли основные риски.

    | Область | Вопрос | Минимально приемлемый ответ | |---|---|---| | Контракты | Что гарантирует чтение/запись при сбоях? | Документированы уровни согласованности и поведение при partition | | Наблюдаемость | Можно ли быстро локализовать проблему? | Метрики репликации/консенсуса/шардов + трассы + корреляция логов | | Восстановление | Проверяли ли вы восстановление? | Регулярные тестовые restores + измерены RPO/RTO | | Обновления | Можно ли обновляться без простоя? | Rolling upgrade с совместимостью протоколов и контролем кворума | | Безопасность | Кто и как получает доступ? | Least privilege, TLS/mTLS, KMS, аудит, сегментация сети | | Инциденты | Есть ли инструкции и учения? | Runbooks + game days + постмортемы без поиска виноватых |

    Итоги

  • Продакшен-качества распределённой БД начинаются с явного контракта согласованности и поведения при отказах (связь с CAP, репликацией и транзакциями).
  • Наблюдаемость должна покрывать репликацию, консенсус, шардирование и сетевые характеристики, иначе отладка превращается в гадание.
  • Репликация повышает доступность, но не заменяет бэкапы и регулярно проверяемое восстановление.
  • Безопасность — это слои: аутентификация, авторизация, TLS/mTLS, шифрование на диске, KMS, сегментация сети, аудит.
  • Эксплуатация требует совместимости версий, безопасных обновлений, аккуратных миграций и готовности к инцидентам через runbooks и учения.