Хочу показать простой и повторяемый способ запустить шардированный, реплицируемый кластер ClickHouse на ноутбуке с помощью Docker Compose — чтобы учиться, экспериментировать и безопасно ломать/чинить без влияния на прод.

Зачем и кому это нужно

  • Быстро развернуть учебный кластер на локальной машине.

  • На практике потрогать репликацию (replication), шардирование (sharding), балансировку и проверить отказоустойчивость.

  • Экспериментировать без риска: всё в контейнерах; если что-то пошло не так — снесли и подняли заново.

Почему Docker Compose? Повторяемо, прозрачно, наглядно, легко снести и поднять заново — для обучения самое то.

Как устроен кластер ClickHouse (в двух словах)

В ClickHouse репликация и согласование происходят на уровне таблиц, а не всего сервера. Таблицы на движках Replicated*MergeTree используют ClickHouse Keeper или ZooKeeper для координации операций (создание, вставка, слияние, удаление партиций и т. д.). Таблицы без репликации (MergeTree и др.) этих сервисов не требуют.

Кластеры описываются в конфигурации сервера; одну и ту же ноду можно включать в несколько логических «раскладок» (кластеров) с разным числом шардов и реплик.

В учебном стенде для простоты используем одиночный ZooKeeper. В продакшене для новых инсталляций официально рекомендуют ClickHouse Keeper в составе трёх нод: он быстрее и проще в эксплуатации. ZooKeeper остаётся поддерживаемым и часто используется там, где уже есть опыт и инфраструктура под него.

Репозиторий с примером

Полный проект с Compose-файлом и конфигами:
github.com/dementev-dev/clickhouse-learning-cluster

Состав стенда

  • 1× ZooKeeper (учебный, одиночный).

  • 4× ClickHouse Server (версия 25.1).

  • 1× HAProxy для балансировки 8123/9000.

Рис. 1. Схема кластера

Схема кластера ClickHouse
Схема кластера ClickHouse

Базовый шаблон сервиса ClickHouse

x-configs: &ch-default-configs
  image: clickhouse/clickhouse-server:25.1
  environment:
    TZ: "Europe/Moscow"
  ulimits:
    nproc: 65535
    nofile:
      soft: 262144
      hard: 262144
  networks:
    - ch_learning_net
  depends_on:
    - zookeeper

Зачем ulimits:

  • nofile=262144 — рекомендация ClickHouse для запаса по файловым дескрипторам (много фоновых задач и соединений). В тестовой среде можно меньше, но в продакшене лучше держать рекомендуемое.

  • nproc=65535 — достаточно для учебного стенда; если словите «Maximum number of threads is lower than 30000», поднимайте лимит — в контейнерах это частая причина проблем.

Координатор (ZooKeeper)

Для простоты — одиночный ZooKeeper.

services:
  zookeeper:
    image: zookeeper:3.9.3
    networks: [ch_learning_net]
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
      - ZOOKEEPER_CLIENT_PORT=2181
      - TZ=Europe/Moscow
    ports:
      - "2182:2181"  # наружу
      - "2888:2888"
      - "3888:3888"

⚠️ В продакшене анонимный доступ нельзя. Здесь он только ради простоты учебного стенда.

Первая нода ClickHouse

Остальные (click2…click4) аналогично, меняются только макросы.

  click1:
    <<: *ch-default-configs
    volumes:
      - ./configs/default_user.xml:/etc/clickhouse-server/users.d/default_user.xml
      - ./configs/z_config.xml:/etc/clickhouse-server/config.d/z_config.xml
      - ./configs/macros_ch1.xml:/etc/clickhouse-server/config.d/macros.xml
      # сюда кладем все то, что потом хотим загрузить с файловой системы в ClickHouse
      - ./data:/var/lib/clickhouse/user_files/data
    ports:
      - "8002:9000"  # native для отладки
      - "9123:8123"  # http для отладки

Что в этих файлах конфигурации

users.d/default_user.xml — задаём пароль и базовые права пользователю default.

<clickhouse>
  <users>
    <default>
      <password>yourStrongPass</password>
      <access_management>1</access_management>
    </default>
  </users>
</clickhouse>

Пользователь default — фактически суперпользователь. В реальных средах лучше создать отдельного пользователя и роль-модель, а default ограничить/отключить.

config.d/z_config.xml — где искать ZooKeeper + описание кластеров.

<clickhouse>
  <zookeeper>
    <node>
      <host>zookeeper</host>
      <port>2181</port>
    </node>
  </zookeeper>

  <remote_servers>
    <c2sh2rep>
      <shard>
        <replica><host>click1</host><port>9000</port></replica>
        <replica><host>click2</host><port>9000</port></replica>
      </shard>
      <shard>
        <replica><host>click3</host><port>9000</port></replica>
        <replica><host>click4</host><port>9000</port></replica>
      </shard>
    </c2sh2rep>
  </remote_servers>
</clickhouse>

config.d/macros.xml — уникальные макросы на каждую ноду (для путей в Keeper/ZooKeeper и имён реплик).

<clickhouse>
  <macros>
    <cluster>c2sh2rep</cluster>
    <shard_c2sh2rep>01</shard_c2sh2rep>
    <replica_c2sh2rep>01</replica_c2sh2rep> <!-- на click2 будет 02 и т. д. -->
  </macros>
</clickhouse>

Почему важна уникальность? Первый аргумент ReplicatedMergeTree — путь в Keeper/ZooKeeper. Он должен быть уникален для каждой таблицы в пределах шарда. Удобно собирать путь из макросов {shard}, {database}, {table} — так не перепутаете реплики при рестартах/переименованиях. Имя реплики уникально внутри шарда.

Балансировщик HAProxy

Простая отказоустойчивость и распределение подключений. Здесь проверки только TCP-доступности (8123 — HTTP, 9000 — native), чего достаточно для учебного стенда.

haproxy.cfg:

global
  log stdout format raw local0

defaults
  log global
  mode tcp
  timeout connect 5s
  timeout client  1m
  timeout server  1m

frontend ch_native
  bind *:9000
  default_backend ch_native_pool

backend ch_native_pool
  balance roundrobin
  server ch1 click1:9000 check
  server ch2 click2:9000 check
  server ch3 click3:9000 check
  server ch4 click4:9000 check

frontend ch_http
  bind *:8123
  default_backend ch_http_pool

backend ch_http_pool
  balance roundrobin
  server ch1 click1:8123 check
  server ch2 click2:8123 check
  server ch3 click3:8123 check
  server ch4 click4:8123 check

Сервис в Compose:

  haproxy:
    image: haproxy:2.9
    volumes:
      - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    ports: ["9000:9000", "8123:8123"]
    depends_on: [click1, click2, click3, click4]
    networks: [ch_learning_net]

Запуск и быстрая проверка

docker compose up -d

Проверка соединения:

-- через любой узел (или через HAProxy на 8123/9000)
SELECT version();

SHOW CLUSTERS;

SELECT cluster,
       groupArray(concat(host_name,':',toString(port))) AS hosts
FROM system.clusters
GROUP BY cluster
ORDER BY cluster;

Реплицируемая таблица + шардирование через Distributed

-- очищаем предыдущую версию, если была
DROP TABLE IF EXISTS user_scores_rep ON CLUSTER c2sh2rep SYNC;
DROP TABLE IF EXISTS user_scores          ON CLUSTER c2sh2rep SYNC;

CREATE TABLE user_scores_rep ON CLUSTER c2sh2rep (
    user_id    UInt32,
    avg_score  Float32,
    created_at DateTime
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/shard_{shard_c2sh2rep}/{database}/{table}',
    '{replica_c2sh2rep}'
)
ORDER BY (user_id);

CREATE TABLE user_scores ON CLUSTER c2sh2rep
AS user_scores_rep
ENGINE = Distributed(c2sh2rep, default, user_scores_rep, user_id);

?Используем 'SYNC' для удаления таблицы без задержки. Без модификора SYNC удаление роисходит асинхронно — таблица помечается как удалённая, а физическое удаление данных и метаданных происходит позже, в фоне. Использование SYNC важно, если нужно быть уверенным, что таблица действительно удалена к моменту завершения запроса — например, перед созданием новой таблицы с тем же именем или для последовательных операций с метаданными.

Заливаем данные:

INSERT INTO user_scores
SELECT
    number % 100000 + 1                         AS user_id,
    toFloat32(rand() % 50 + rand() % 50) / 10   AS avg_score,
    now() - (number * 86400 / 1000)             AS created_at
FROM numbers(100000);

SELECT count() FROM user_scores;  -- sanity-check

Как понять, куда легли данные

SELECT shardNum() AS shard_id,
       count()    AS rows_per_shard
FROM user_scores
GROUP BY shard_id
ORDER BY shard_id;

shardNum() работает при обращении к Distributed-таблице или при ON CLUSTER. Если выполнить на локальной таблице — будет ошибка (функция не определена в локальном контексте).

Проверка репликации:

SELECT table, is_leader, total_replicas, active_replicas, replica_delay
FROM system.replicas
WHERE database = 'default' AND table = 'user_scores_rep';

system.replicas показывает состояние локальных реплицируемых таблиц текущей ноды. В нашем примере пары (click1/click2) и (click3/click4) — это два разных шарда, так что смотрите по одной ноде из каждой пары.

Частые вопросы

Keeper или ZooKeeper?
Для новых прод-кластеров рекомендуют ClickHouse Keeper (встроен, проще, быстрее). ZooKeeper остаётся поддерживаемым и уместен там, где он уже развёрнут.

Сколько нод координации в проде?
Обычно минимум 3 (и для Keeper, и для ZooKeeper). В учебке — одна нода для простоты.

Обязательно ли ulimit nofile=262144?
Это рекомендация ClickHouse. На малых стендах может работать и меньше, но лучше сразу «как в доке».

А nproc?
Если встретили «Maximum number of threads is lower than 30000», поднимайте лимит nproc.

Что почитать дальше

  • Репликация в ClickHouse: уникальные пути, макросы и пр.

  • Масштабирование и шаблоны раскладок (1×2, 2×1, 2×2)

  • Балансировка и прокси (HAProxy на TCP-уровне)

Что дальше

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

Комментарии (0)