Learning Platform
Глоссарий Troubleshooting
Урок 10.05 · 30 мин
Продвинутый
INSERTDistributedinternal_replicationdistributed_foreground_insert

Стратегии INSERT

В распределённом кластере ClickHouse есть два основных пути для записи данных: INSERT через Distributed table (который маршрутизирует данные по шардам автоматически) и прямой INSERT в локальный ReplicatedMergeTree на конкретном узле. Выбор стратегии влияет на задержку, надёжность и нагрузку на сеть.

Выполнение распределённого запроса
Клиент → Distributed tableКлиент отправляет SELECT к Distributed table на любом узле кластера (round-robin или через load balancer). Этот узел становится coordinator — он не хранит данные, только оркестрирует запрос.
Coordinator: маршрутизация по шардамCoordinator node разбирает запрос, определяет список шардов из remote_servers конфига. Для каждого шарда выбирает реплику (балансировка: random, nearest, in_order). Отправляет подзапрос каждому шарду параллельно.
Shard 1: локальный SELECTШард 1: локально выполняет WHERE/PREWHERE, GROUP BY агрегацию, применяет ORDER BY и LIMIT. Возвращает частичный (mergeable) результат coordinator-у — не финальный: агрегатные функции возвращают промежуточное состояние (-Merge compatible).
Shard 2: локальный SELECTШард 2: идентичная локальная обработка. Каждый шард читает только свои партиции, применяет sparse index pruning. Параллельное выполнение = горизонтальное масштабирование.
Coordinator: финальный mergeCoordinator получает частичные результаты от всех шардов. Выполняет финальный merge: объединяет агрегаты (MergingAggregated), применяет HAVING, финальный ORDER BY и LIMIT. Distributed LIMIT: шарды возвращают min(limit * distribution_ratio) строк для корректного global LIMIT.
РезультатФинальные строки клиентуКлиент получает результат через тот же протокол, что и при запросе к одному узлу (Native/HTTP). Для клиента запрос к Distributed table неотличим от запроса к локальной таблице — прозрачная распределённость.

INSERT через Distributed table

Distributed table принимает INSERT и автоматически распределяет строки по шардам согласно функции шардирования:

-- Distributed table создана с xxHash64(user_id) как ключом шардирования
CREATE TABLE events_dist ON CLUSTER mycluster
ENGINE = Distributed('mycluster', 'db', 'events_local', xxHash64(user_id));

-- INSERT через Distributed: строки автоматически маршрутизируются
INSERT INTO events_dist VALUES
    (1001, 'click', now()),
    (1002, 'view', now()),
    (1003, 'purchase', now());

При internal_replication=true (рекомендуемая настройка для ReplicatedMergeTree):

  1. Distributed table отправляет каждую строку ровно одной реплике выбранного шарда.
  2. ReplicatedMergeTree сам реплицирует часть на остальные реплики через лог в Keeper.
  3. Данные не дублируются на уровне сети Distributed → шарды.
<!-- remote_servers конфиг в config.xml -->
<remote_servers>
  <mycluster>
    <shard>
      <internal_replication>true</internal_replication>
      <replica>
        <host>clickhouse-01</host>
        <port>9000</port>
      </replica>
      <replica>
        <host>clickhouse-02</host>
        <port>9000</port>
      </replica>
    </shard>
  </mycluster>
</remote_servers>

INSERT в локальный ReplicatedMergeTree

Прямой INSERT в ReplicatedMergeTree на конкретном узле даёт больший контроль:

-- Прямой INSERT на конкретный узел (clickhouse-01)
INSERT INTO db.events_local VALUES
    (1001, 'click', now()),
    (1003, 'purchase', now());

-- INSERT на другой узел (clickhouse-03, другой шард)
INSERT INTO db.events_local VALUES
    (1002, 'view', now());

Этот подход требует:

  • Внешнего балансировщика нагрузки (Nginx, HAProxy, ClickHouse Keeper load balancing)
  • Логики на стороне клиента для маршрутизации строк по правильным шардам
  • Знания ключа шардирования и количества шардов

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

Недостатки: сложнее клиентский код, нет автоматической ребалансировки при добавлении шарда.


Sync vs Async: distributed_foreground_insert

Параметр distributed_foreground_insert (ранее insert_distributed_sync) управляет режимом отправки данных от Distributed узла к шардам:

Sync vs Async INSERT через Distributed
Sync (=1)distributed_foreground_insert=1 (sync): клиент ждёт подтверждения от всех шардов до завершения INSERT. Данные гарантированно доставлены на момент возврата ответа. Более высокая задержка (latency), но предсказуемая видимость данных. Используйте для критичных данных.
Клиент ждёт все шардыКлиент → Distributed → Shard 1 (подтверждение) + Shard 2 (подтверждение) → Клиент получает OK. Блокирующий вызов: INSERT завершается только когда все шарды подтвердили получение.
Async (=0, default)distributed_foreground_insert=0 (async, default): Distributed таблица буферизует данные локально во временных файлах, INSERT возвращается немедленно. Фоновый поток отправляет данные шардам позже. Низкая задержка записи, но данные временно видны только на координирующем узле.
Буфер → фоновая отправкаКлиент → Distributed (буферизует во временный файл) → OK сразу. Фоновый поток: читает буфер → отправляет шардам. Задержка между INSERT и видимостью данных на шардах = 100-500ms при нормальной нагрузке.
-- Синхронный INSERT (ждёт подтверждения от шардов)
INSERT INTO events_dist SETTINGS distributed_foreground_insert = 1
VALUES (1001, 'click', now());

-- Асинхронный INSERT (буферизует локально, быстрее)
INSERT INTO events_dist SETTINGS distributed_foreground_insert = 0
VALUES (1001, 'click', now());

WARNING

Ловушка: internal_replication и дублирование данных

Критически важно правильно понимать internal_replication:

  • internal_replication=true: Distributed table записывает данные одной реплике каждого шарда. ReplicatedMergeTree сам реплицирует через Keeper. Правильный вариант с ReplicatedMergeTree.
  • internal_replication=false: Distributed table записывает данные всем репликам каждого шарда напрямую. При использовании с ReplicatedMergeTree это приводит к дублированию данных (дедупликация снимает часть дублей, но не гарантирована при сбоях).

Всегда устанавливайте internal_replication=true, если локальная таблица — ReplicatedMergeTree.

WARNING

Изменение дефолтов в ClickHouse 26.2+

В версии 26.2 изменились параметры дедупликации, что влияет на поведение при миграции с 25.x:

  • replicated_deduplication_window увеличен с 1000 до 10000 записей
  • Введён новый параметр deduplicate_insert, который переопределяет insert_deduplicate=0
  • В сочетании с цепочкой Materialized Views это может создавать до 70x нагрузку на Keeper из-за увеличенного окна дедупликации

При миграции с 25.x на 26.x: явно проверьте настройки дедупликации и нагрузку на Keeper после обновления.


Ключевые выводы

  1. INSERT через Distributed удобен для клиентов: не нужно знать топологию шардов. Достаточно писать в одну точку.
  2. INSERT напрямую в ReplicatedMergeTree даёт больший контроль и меньше задержки, но требует клиентской логики маршрутизации.
  3. internal_replication=true обязателен при использовании ReplicatedMergeTree — иначе Distributed будет дублировать записи.
  4. distributed_foreground_insert=0 (async, default) снижает задержку записи за счёт буферизации. Для гарантированной видимости — =1.
  5. 26.2+: проверьте дедупликационные настройки при миграции — replicated_deduplication_window теперь 10000, а не 1000.
Batch-обработка: окна, расписание, идемпотентность Logical replication в PostgreSQL: publication, subscription, слоты

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 3. Кластер настроен с internal_replication=true, два шарда по две реплики каждый. Клиент выполняет INSERT в Distributed table. Сколько реплик каждого шарда получат данные непосредственно от Distributed table?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 11