awesome-everything EN
↑ Обратно к восхождению

Базы данных

Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованию

Суть Ко-локация — одинаковый ключ шарда означает одного воркера — держит joins в рамках клиента на одном узле. Типы таблиц Citus (distributed, reference, local) и его планировщик запросов явно разграничивают одношардовые и fan-out запросы.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

Команда мигрирует на Citus и рада, что запросы для отдельных клиентов так же быстры, как раньше. Потом они добавляют новую таблицу без колонки tenant_id, и новый join-запрос занимает 500 мс вместо 5 мс. Ревью схемы выявляет причину: одна таблица не была ко-локирована.

Архитектура Citus

Citus — расширение Postgres, превращающее кластер экземпляров Postgres в одну логическую шардированную базу данных:

  • Координатор: один узел, хранящий метаданные кластера (карта шардов, информация о распределении таблиц), разбирающий запросы, планирующий распределённое выполнение и возвращающий результаты клиенту.
  • Воркеры: N узлов, хранящих фактические данные (шарды). Воркеры выполняют Postgres-запросы локально и возвращают результаты координатору.
  • Клиент: подключается к координатору как к обычному Postgres. Изменения драйвера не требуются.
Клиент → Координатор (держит метаданные, планирует, маршрутизирует)
              ↓               ↓               ↓
          Воркер 0        Воркер 1        Воркер 2
        (шарды 0-10)   (шарды 11-21)  (шарды 22-31)

Три типа таблиц

ТипЖивёт наДля чегоСтоимость join
DistributedВоркеры, разбита по ключу шардаВсе таблицы в рамках клиента (orders, users, projects, …)Одного узла при ко-локации; fan-out без неё
ReferenceПолная копия на каждом воркереНебольшие, преимущественно читаемые lookup-таблицы (plans, feature_flags, countries)Всегда локальный; запись через 2PC (медленно)
LocalТолько координаторТаблицы admin/control plane (список клиентов, workers_meta)Нельзя объединять с таблицами воркеров в одном запросе
-- Распределить таблицы в рамках клиента по tenant_id
SELECT create_distributed_table('users',       'tenant_id');
SELECT create_distributed_table('projects',    'tenant_id', colocate_with => 'users');
SELECT create_distributed_table('tasks',       'tenant_id', colocate_with => 'users');
SELECT create_distributed_table('comments',    'tenant_id', colocate_with => 'users');
SELECT create_distributed_table('attachments', 'tenant_id', colocate_with => 'users');

-- Реплицировать небольшие lookup-таблицы на каждый воркер
SELECT create_reference_table('plans');
SELECT create_reference_table('feature_flags');
SELECT create_reference_table('countries');

-- Таблица tenants остаётся локальной на координаторе (control plane)

Ко-локация: центральный инвариант

Ко-локация означает, что таблицы, распределённые по одному ключу, имеют соответствующие шарды на одном физическом воркере. Если orders и payments обе распределены по tenant_id, то все заказы клиента 42 и все его платежи живут на воркере 1.

Запрос, соединяющий orders и payments с фильтром по tenant_id = 42:

  • С ко-локацией: планировщик отправляет join на воркер 1, который выполняет его как обычный Postgres join — одна машина, полное использование индексов, однозначные миллисекунды.
  • Без ко-локации (например, payments распределена по payment_id): координатор должен собрать частичные результаты со всех воркеров и объединить их. P99 задержка = самый медленный воркер. Каждый воркер участвует. В 10–100× медленнее.

Ко-локация — не оптимизация производительности — это проектный контракт, благодаря которому шардированная система ведёт себя как единственный Postgres для 99% случаев.

Кросс-шард запросы и их смягчение

Запросы без фильтра по ключу шарда расходятся на все шарды. Примеры:

  • «Список всех пользователей с email, заканчивающимся на @enterprise.com» (нет tenant_id)
  • «Подсчёт всех заказов за сегодня по всем клиентам» (кросс-клиентская аналитика)
  • «Найти пользователя по email для входа» (email, не tenant_id)

Для OLTP:

  1. Переработать API: почти все OLTP-запросы должны нести tenant_id. Lookup при входе по email требует отдельного индекса или небольшого lookup-сервиса, разрешающего email → tenant_id сначала.
  2. CDC в OLAP: кросс-клиентская аналитика должна выполняться на отдельном аналитическом хранилище (ClickHouse, BigQuery), пополняемом через Change Data Capture. Никогда не запускайте глобальные агрегаты на OLTP-кластере.
  3. Rate-limit fan-out endpoints: для редких легитимных кросс-шард операций — rate-limit и документируйте их стоимость.

Опытная метрика: кросс-шард запросы должны составлять <1–2% OLTP-трафика. Выше этого — схема ушла от ко-локации и требует ревью.

Почему это работает

Почему Citus по умолчанию создаёт 32 шарда на таблицу даже на кластере из 4 воркеров? Шарды — единица ребалансировки: больше шардов означает более точную ребалансировку и более плавное распределение при добавлении воркеров. При 32 шардах на 4 воркерах каждый воркер получает 8 шардов; добавление 5-го воркера позволяет ребалансировщику перемещать шарды без разрезания шарда пополам. Команды часто поднимают это до 64 или 128 при планировании под большие кластеры. Значение по умолчанию 32 — отправная точка, а не мандат.

Викторина

В чём преимущество маркировки таблицы как reference table Citus вместо distributed table?

Викторина

Новый инженер добавляет таблицу 'audit_log', распределённую по 'id' (не tenant_id), в кластер Citus, шардированный по клиентам. Что ломается?

Вспомните перед уходом
  1. 01
    Объясните, что означает ко-локация в Citus и что происходит с производительностью запросов при её нарушении.
  2. 02
    Каковы три типа таблиц Citus и какой случай использования обслуживает каждый?
  3. 03
    Как нужно обрабатывать кросс-клиентский запрос вроде 'подсчитать все заказы за сегодня' в кластере Citus, шардированном по клиентам?
Итог

Citus добавляет координатор (метаданные, планирование, маршрутизация) и N воркеров (хранилище шардов и выполнение) к кластеру Postgres, делая его похожим на единственную базу данных. Таблицы классифицируются как distributed (шардированные по ключу, на воркерах), reference (полная копия на каждом воркере для локальных joins) или local (только координатор для control-plane данных). Ко-локация — каждая таблица в рамках клиента распределена по одному ключу, чтобы совпадающие шарды попадали на один воркер — это инвариант, удерживающий клиентские joins на одном узле. Нарушение ко-локации (распределение таблицы по другому ключу) превращает каждый join с ней в кросс-шард fan-out: N× работы, P99 = самый медленный воркер. Кросс-шард аналитика должна выгружаться в выделенное OLAP-хранилище через CDC, а не запускаться на OLTP-кластере.

Связанные уроки
встречается в258
Продолжить восхождение ↑Режим отказа hot shard: обнаружение, изоляция и долгосрочная политика
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.