В данной статье будет приведена инструкция по развертыванию отказоустойчивого кластера Clickhouse на 3 ноды. В ходе развертывания будет использоваться docker compose. Это очень удобный способ для создания трех инстансов СУБД в целях тренировки.

Репозиторий с готовым docker compose проектом

После прочтения статьи у вас будет представление о том, как развернуть свой отказоустойчивый кластер хоть на 3, хоть на 5, хоть на 9 и сколько угодно узлов.

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

Предлагаю выкачать материалы из репозитория к себе и с ходом теории подглядывать на конфигурационные файлы для Clickhouse.

Что из себя представляет кластер Clickhouse

Кластер Clickhouse представляет собой группу запущенных процессов Clickhouse, объединенных сетью. В рамках кластера можно производить репликацию данных: поддерживать несколько таблиц в одинаковом состоянии. Это необходимо для обеспечения отказоустойчивости. Если один сервер стал недоступен, другой сервер продолжает предоставлять доступ пользователей с данным.

Остается ли кластер в рабочем состоянии или же переходит в режим “readonly”, зависит от того, сколько серверов оказались недоступными. Кластер Clickhouse будет оставаться в полностью рабочем состоянии до тех пор, пока будет доступно как минимум int(N / 2) + 1 серверов, где N - суммарное число серверов в кластере. То есть, если кластер состоит из 3 серверов, то он не будет переходить в режим readonly до тех пор, пока не станут недоступными 2 сервера.

int(3 / 2) + 1 -> int(1.5) + 1 -> 1 + 1 = 2 - 2 это число серверов, необходимое для штатной работы кластера на 3 сервера.

В случае кластера на 9 серверов, аналогично:

int(9 / 2) + 1 -> int(4.5) + 1 -> 4 + 1 = 5 - Кластер будет работать до тех пор, пока в нём штатно работают 5 серверов.

В Clickhouse реплицируются не базы данных, а отдельные таблицы. Они основаны на движках таблиц, поддерживающих репликацию. Движок таблицы MergeTree не может быть использован для репликации таблицы. А вот движок ReplicatedMergeTree несет в себе возможности движка MergeTreem, но такая таблица еще и может быть реплицирована.

Clickhouse Keeper, Apache Zookeeper, RAFT

Clickhouse Keeper - сервис координации, написанный разработчиками Clickhouse. Написан на C++ и представляет собой замену сервису Apache Zookeeper, написанному на Java. Разработчики Clickhouse рекомендуют использовать именно Clickhouse Keeper для развертывания кластеров СУБД. Далее в этой статье, а также в официальной документации вы можете встретить секцию параметров <zookeeper></zookeeper>, имейте ввиду, что эти параметры относятся не именно к Apache Zookeeper, а к любому из двух сервисов, смотря, какой настроите: Apache Zookeeper или Clickhouse Keeper. Далее в статье для краткости я буду писать просто Keeper, а иметь ввиду Clickhouse Keeper.

Keeper для достижения консенсуса использует протокол RAFT, в то же время Apache Zookeeper использует для этого протокол ZAB.. Ниже протокол будет рассмотрен в краткой форме. Для более подробного изучения протокола могу порекомендовать статью на хабре, интерактивную презентацию, сайт, посвещенный RAFT’у, а также научную публикацию.

Так как развернуть кластер?

Конфиг Clickhouse может храниться в нескольких xml-файлах внутри директории config.d, при описании секций следует следить, чтобы корневой секцией во всех файлах оставалась секция <clickhouse></clickhouse>

Первым делом нужно разрешить доступ к Clickhouse и Keeper из сети, для этого задается параметр listen_host.

<listen_host>0.0.0.0</listen_host>

0.0.0.0 означает, что доступ открыт для любого ip-адреса.

Запуск кластера Keeper

Для работы кластера Clickhouse требуется развернуть кластер Keeper. Keeper может работать и отдельно от СУБД. То есть имеется возможность развернуть кластер Keeper на одних машинах, а кластер СУБД на других. Развернем оба кластера на одних и тех же трех машинах (docker- контейнерах), ничего плохого в этом нет.

За запуск кластера Keeper отвечат вот этот конфиг:

<keeper_server>
        <tcp_port>9181</tcp_port>
        <server_id>1</server_id>
        <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
        <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
        <coordination_settings>
                <operation_timeout_ms>10000</operation_timeout_ms>
                <session_timeout_ms>30000</session_timeout_ms>
                <raft_logs_level>warning</raft_logs_level>
        </coordination_settings>
        <raft_configuration>
                <server>
                        <id>1</id>
                        <hostname>click1.clickcompose_default</hostname>
                        <port>9010</port>
                </server>
                <server>
                        <id>2</id>
                        <hostname>click2.clickcompose_default</hostname>
                        <port>9010</port>
                </server>
                <server>
                        <id>3</id>
                        <hostname>click3.clickcompose_default</hostname>
                        <port>9010</port>
                </server>
                <server>
                        <id>4</id>
                        <hostname>click4.clickcompose_default</hostname>
                        <port>9010</port>
                </server>
                <server>
                        <id>5</id>
                        <hostname>click5.clickcompose_default</hostname>
                        <port>9010</port>
                </server>
        </raft_configuration>
</keeper_server>
  • tcp_port - порт, на котором сидит Clickhouse Keeper, на нём он принимает клиентские подключения и также отвечает на 4 буквенные команды: ruok, srvr и прочие;
  • raft_configuration представляет собой карту кластера;
  • hostname может быть как доменным именем, так и ip-адресом;
  • port - порт для межсерверного взаимодействия узлов внутри кластера RAFT-Keeper.

Благодаря этому конфигу Clickhouse понимает, что требуется запустить сервер Keeper’а, а сам Keeper из секции <raft_configuration></raft_configuration> понимает, с кем ему предстоит работать.

Конфиг для Clickhouse, который даст информацию о том, как соединиться с

серверами Keeper

Этот конфиг говорит clickhouse о том, что необходимо сотрудничать с Keeper’ом. Секция настроек называется zookeeper, будто бы отсылая читателя-писателя конфига к Apache Zookeeper, но на самом деле секция zookeeper может описывать подключение как к Apache Zookeeper, так и к Clickhouse Keeper кластерам. Почему так сделано- тайна, покрытая мраком. Я так полагаю, сделано это потому что Clickhouse изначально мог работать только с Apache Zookeeper, Clickhouse Keeper появился позже, а наименование секции настроек решили не менять в целях обратной совместимости с уже существующими и работающими конфигурациями кластеров.

<zookeeper>
        <node>
                <host>click1</host>
                <port>9181</port>
        </node>
        <node>
                <host>click2</host>
                <port>9181</port>
        </node>
        <node>
                <host>click3</host>
                <port>9181</port>
        </node>
</zookeeper>

Кстати говоря, port - это тот самый порт, на котором Keeper ожидает клиентсктие подключения.

Создание кластера. Шардирование

В общем случае, шардирование есть разнесение одной базы данных по нескольким узлам/серверам/нодам, подставьте нужное или то, что больше нравится. В конкретных случаях в рамках используемой СУБД, смысл понятия “шардирование” будет меняться. Для одной СУБД это будет означать партицирование данных и хранение их на том же самом сервере, для другой шардирование заключается в хранении фрагментов одной таблицы на нескольких серверах. Могу порекомендовать такую статью на хабре по теме шардинга.

Как мы успели выяснить, шардирование- понятие общее. И вот, Clickhouse не видит ничего зазорного в том, чтобы требовать, чтобы реплики, хранящиее одни и те же данные, были в одном шарде. Так что не стоит ругаться на СУБД, имеет право. Итак, нас интересует отказоустойчивый кластер на 3 ноды с полным реплицированием. Чтобы такой сделать, следует определить кластер, в нём определить один шард, внутри шарда описать 3 реплики, внутри каждой реплики по яйцу, в каждом яйце по смерти Кащеевой.

<remote_servers>
        <cluster_name>
                <shard>
                        <replica>
                                <host>click1</host>
                                <port>9000</port>
                        </replica>
                        <replica>
                                <host>click2</host>
                                <port>9000</port>
                        </replica>
                        <replica>
                                <host>click3</host>
                                <port>9000</port>
                        </replica>
                </shard>
        </cluster_name>
</remote_servers>
  • cluster_name - название кластера Clickhouse;
  • port - TCP порт для коммуникации между серверами, в главном конфиге ноды (/etc/clickhouse-server/config.xml) имеет название tcp_port.

Проверка работоспособности Keeper кластера и Clickhouse кластера

В принципе, всё готово к запуску. Запускаем контейнеры через docker compose, и каким угодно образом отправляем stat (чуть ниже показано, как это сделать) на TCP порт, на котором Keeper сервера ожидают клиентских подключений.

Ниже привожу ответ, который я получил от узлов в ответ на команду stat. Порты 9001, 9002, 9003 это те порты, на которые был произведен маппинг портов 9181 трех docker- контейнеров.

Нода 1:

user@vm:~/Desktop/click compose$ echo stat | nc localhost 9001
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
 172.19.0.1:47744(recved=0,sent=0)
 172.19.0.3:59438(recved=7,sent=7)

Latency min/avg/max: 0/0/1
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: leader
Node count: 4

Нода 2:

user@vm:~/Desktop/click compose$ echo stat | nc localhost 9002
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
 172.19.0.1:60756(recved=0,sent=0)
 172.19.0.4:33656(recved=7,sent=7)

Latency min/avg/max: 0/0/1
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: follower
Node count: 4

Нода 3:

user@vm:~/Desktop/click compose$ echo stat | nc localhost 9003
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Clients:
 172.19.0.1:38350(recved=0,sent=0)
 172.19.0.2:52274(recved=7,sent=7)

Latency min/avg/max: 0/1/4
Received: 7
Sent : 7
Connections: 1
Outstanding: 0
Zxid: 19
Mode: follower
Node count: 4

Может возникнуть вопрос: почему параметр Node count равен 4? Ноды то у нас всего 3. На самом деле, это не количество работающих нод кластера и даже не общее количество нод в кластере. Ради эксперимента я запустил кластер на 5 серверов, Node count все равно равен 4.

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

user@vm:~$ echo srvr | nc localhost 9001
ClickHouse Keeper version: v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
Latency min/avg/max: 0/1/10
Received: 280
Sent : 286
Connections: 2
Outstanding: 0
Zxid: 963
Mode: follower
Node count: 140

Теперь Node count и вовсе 140.

В ответ на другую команду, mntr, нода возвращает чуть другую информацию. На этот раз некий параметр zk_znode_count равен 140.

user@vm:~$ echo mntr | nc localhost 9001
zk_version      v22.2.3.1-stable-97317e8bb51d2723b539b7863dd6471a68b11d01
zk_avg_latency  0
zk_max_latency  10
zk_min_latency  0
zk_packets_received     808
zk_packets_sent 814
zk_num_alive_connections        2
zk_outstanding_requests 0
zk_server_state follower
zk_znode_count  140
zk_watch_count  6
zk_ephemerals_count     5
zk_approximate_data_size        28020
zk_key_arena_size       28672
zk_latest_snapshot_size 0
zk_open_file_descriptor_count   442
zk_max_file_descriptor_count    18446744073709551615

zk_znode_count и Node из примеров чуть выше это одна и та же метрика. Это не количество нод в кластере, это количество Z-нод внутри Clickhouse Keeper. Тут не обойтись без знаний о внутреннем устройстве Keeper’а. Мда уж… А документации то нет на Keeper. Ну прям вот нет, все, что говорится о Clickhouse Keeper на сайте Clickhouse, можно уместить на 4-5 листов А4. Как же быть…

Решение есть! Как же сразу такая идея не пришла в голову? Нужно посмотреть документацию на Apache Zookeeper, чтобы понять, как работает Clickhouse Keeper. Чудесно же!

Итак. Шутки в сторону. Где- то внутри себя Zookepeer содержит иерархическое пространство имен, похожее на распределенную файловую систему. Её можно представить как вывод директорий командой tree, но я вам такого не говорил. Каждый узел в этом дереве содержит какие- то данные, а также наследников. Такие узлы и называются Node’ами или znode’ами. Таким образом, чем больше данных в кластере, тем больше узлов становится. По этой причине в моем тестовом кластере произошло увеличение числа узлов от 4 до 140.

Заключение

В статье я постарался описать свой собственный опыт по конфигурации отказоустойчивого кластера Clickhouse. Надеюсь, что эта статья оказалась для вас полезной.

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

Создание реплицируемой таблицы на кластере описано в этом посте.