ClickHouse: От новичка до Senior архитектора

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

1. Введение в архитектуру ClickHouse, установка и основы работы с SQL

Введение в архитектуру ClickHouse, установка и основы работы с SQL

Добро пожаловать в курс «ClickHouse: От новичка до Senior архитектора». Мы начинаем наше путешествие с фундаментальных основ. Если вы привыкли работать с классическими реляционными базами данных, такими как PostgreSQL или MySQL, то ClickHouse заставит вас пересмотреть многие привычные паттерны проектирования.

В этой статье мы разберем, почему ClickHouse настолько быстр, чем колоночная СУБД отличается от строковой, как установить сервер и выполнить свои первые SQL-запросы.

Что такое ClickHouse и зачем он нужен?

ClickHouse — это колоночная система управления базами данных (СУБД) для онлайн-обработки аналитических запросов (OLAP). Она была разработана в Яндексе для задачи Яндекс.Метрики — второго по величине веб-аналитического сервиса в мире.

Главная особенность ClickHouse — способность обрабатывать петабайты данных и отвечать на аналитические запросы за миллисекунды. Там, где обычная база данных будет «думать» минуты или часы, ClickHouse выдает результат мгновенно.

OLAP против OLTP

Чтобы понять архитектуру ClickHouse, нужно разграничить два типа нагрузок:

  • OLTP (Online Transaction Processing). Это классические транзакции: «пользователь положил товар в корзину», «обновился баланс». Здесь важна атомарность, быстрые точечные записи и обновления. Примеры: PostgreSQL, MySQL, Oracle.
  • OLAP (Online Analytical Processing). Это аналитика: «посчитать средний чек по всем магазинам за год», «найти топ-10 товаров по регионам». Здесь важна скорость чтения огромных массивов данных. ClickHouse — король в этой нише.
  • Архитектура: Колоночное хранение

    Секрет скорости ClickHouse кроется в том, как он хранит данные на диске. Большинство традиционных БД хранят данные построчно. Это значит, что все данные одной строки (ID, Имя, Дата, Сумма) лежат на диске рядом.

    ClickHouse хранит данные поколоночно. Значения из колонки «Дата» хранятся вместе, значения из колонки «Сумма» — отдельно.

    !Сравнение строкового и колоночного хранения данных: для аналитики колоночный подход позволяет читать с диска в разы меньше информации.

    Почему это эффективно?

    Представим, что у нас есть таблица с 100 колонками и миллиардом строк. Нам нужно посчитать среднюю сумму заказа.

    В строковой БД (MySQL) системе придется прочитать с диска всю строку целиком (все 100 колонок), даже если нам нужна только одна. Это создает огромную нагрузку на ввод-вывод (I/O).

    Математически время чтения данных можно выразить формулой:

    Где — время чтения, — объем считываемых данных, — скорость чтения диска.

    В колоночной БД (ClickHouse) мы читаем только файлы, относящиеся к колонке «Сумма». Если эта колонка занимает 1% от объема таблицы, то уменьшается в 100 раз, и, следовательно, также уменьшается в 100 раз.

    Сжатие данных

    Второй важный аспект — сжатие. Поскольку в одной колонке хранятся однотипные данные (например, только даты или только числа), они сжимаются гораздо лучше, чем разнородные данные в строке. ClickHouse использует различные кодеки (LZ4, ZSTD, Delta, Gorilla), что позволяет уменьшить объем данных на диске в 10–100 раз по сравнению с сырыми данными.

    Векторное выполнение запросов

    ClickHouse не просто хранит данные по колонкам, он и обрабатывает их векторами (батчами). Вместо того чтобы вызывать функцию сложения для каждой строки отдельно (что создает оверхед на вызов функции), ClickHouse загружает массив данных в процессор и использует SIMD-инструкции (Single Instruction, Multiple Data).

    Установка ClickHouse

    ClickHouse работает только на Linux (нативная поддержка) и macOS (через Docker или специальные сборки). Для Windows рекомендуется использовать WSL 2 или Docker.

    Самый простой и безопасный способ начать эксперименты — использовать Docker. Это изолирует среду и позволит вам быстро запустить сервер.

    Запуск через Docker

    Убедитесь, что у вас установлен Docker, и выполните следующую команду в терминале:

    Разберем флаги: * -d: запуск в фоновом режиме. * --name: имя контейнера. * --ulimit nofile: ClickHouse требует много открытых файловых дескрипторов, так как работает с большим количеством файлов колонок. * -p 8123:8123: HTTP порт (для JDBC, веб-интерфейсов). * -p 9000:9000: Нативный TCP порт (для clickhouse-client).

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

    Вы увидите приглашение командной строки :) — это фирменный знак ClickHouse.

    Основы SQL в ClickHouse

    Язык запросов в ClickHouse — это диалект SQL. Он очень похож на стандартный ANSI SQL, но имеет свои расширения и ограничения.

    Создание базы данных

    Создание таблицы и движок MergeTree

    В ClickHouse концепция «движков таблиц» (Table Engines) является ключевой. От выбора движка зависит, где и как хранятся данные, поддерживается ли репликация и как работают индексы.

    Самый универсальный и мощный движок — MergeTree. Он предназначен для вставки больших объемов данных и последующей аналитики.

    Важные моменты:

  • Типы данных: UInt64, DateTime, String, Float32. В ClickHouse строгая типизация. UInt означает Unsigned Integer (беззнаковое целое).
  • ENGINE = MergeTree(): Мы явно указываем движок.
  • ORDER BY: Это обязательный элемент для MergeTree. В ClickHouse первичный ключ (Primary Key) по умолчанию совпадает с ключом сортировки. Данные на диске будут физически отсортированы по event_time, а затем по id.
  • > В отличие от классических БД, первичный ключ в ClickHouse не гарантирует уникальность. Он нужен для быстрого поиска и сжатия данных (разреженный индекс).

    Вставка данных

    ClickHouse оптимизирован для вставки данных большими пачками (батчами). Вставка по одной строке — это антипаттерн, который убьет производительность.

    В реальных проектах данные вставляются пачками по 10 000 – 100 000 строк за раз.

    Выборка данных (SELECT)

    Запросы на чтение выглядят привычно:

    Обратите внимание на функцию count(). В ClickHouse она работает быстрее, чем count(*), хотя делает то же самое.

    Особенности работы с данными

    Отсутствие UPDATE и DELETE (в привычном виде)

    ClickHouse — это append-only система (ориентированная на добавление). Операции изменения (UPDATE) и удаления (DELETE) здесь называются Mutations (мутации). Они выполняются асинхронно и являются «тяжелыми» операциями, так как требуют перезаписи целых кусков данных (партов).

    Пример мутации:

    Этот запрос не удалит данные мгновенно. Он лишь запланирует удаление, которое произойдет в фоне.

    Итоговая согласованность (Eventual Consistency)

    Из-за особенностей архитектуры MergeTree, данные в ClickHouse не всегда доступны мгновенно после вставки для всех реплик, и процесс слияния (merge) кусков данных происходит в фоне. Это плата за высокую скорость записи и чтения.

    Заключение

    Мы рассмотрели базовую архитектуру ClickHouse. Теперь вы знаете, что:

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

    2. Глубокое погружение в движки семейства MergeTree и эффективная вставка данных

    Глубокое погружение в движки семейства MergeTree и эффективная вставка данных

    В предыдущей статье мы установили ClickHouse, создали первую таблицу и выполнили базовые запросы. Мы коснулись движка MergeTree лишь поверхностно. Однако, чтобы стать Senior-архитектором ClickHouse, необходимо понимать, как этот «зверь» устроен внутри.

    Почему данные не удаляются сразу? Что такое «разреженный индекс»? Как правильно нарезать данные на разделы (партиции)? И почему вставка по одной строке — это смертный грех для ClickHouse?

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

    Анатомия MergeTree: Как хранятся данные

    Название MergeTree (Дерево слияния) происходит от принципа работы, похожего на LSM-деревья (Log-Structured Merge-tree). Идея проста: мы пишем новые данные быстро, создавая новые файлы, а затем в фоне «сливаем» (merge) эти файлы в более крупные и упорядоченные структуры.

    Куски данных (Data Parts)

    Каждая вставка (INSERT) в ClickHouse создает новую директорию на диске, которая называется кусок (part). Если вы вставите 10 раз по одной строке, вы создадите 10 маленьких файлов. Это плохо, потому что файловая система не любит миллионы мелких файлов.

    В фоновом режиме ClickHouse выбирает несколько кусков и объединяет их в один большой. Именно в этот момент происходят:

  • Сортировка данных по первичному ключу.
  • Применение логики движка (например, удаление дублей или суммирование).
  • Удаление данных, помеченных на удаление.
  • !Визуализация процесса фонового слияния мелких кусков данных в один крупный.

    Первичный ключ и Разреженный индекс

    В классических базах данных (MySQL, PostgreSQL) индекс — это обычно B-Tree, которое хранит указатель на каждую строку. Если у вас миллиард строк, индекс будет огромным.

    ClickHouse использует разреженный индекс (Sparse Index). Он хранит значение ключа не для каждой строки, а только для каждой -й строки. Это число называется гранулярностью индекса (index granularity) и по умолчанию равно 8192.

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

    Математически эффективность памяти для индекса можно оценить так:

    Где: * — объем оперативной памяти, занимаемый индексом. * — общее количество строк в таблице. * — гранулярность (по умолчанию 8192). * — размер первичного ключа в байтах.

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

    !Принцип работы разреженного индекса: метки указывают только на начало групп строк (гранул).

    Семейство движков MergeTree

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

    ReplacingMergeTree: Борьба с дубликатами

    ClickHouse не поддерживает уникальные ключи (UNIQUE KEY) в традиционном понимании. Если вы вставите две строки с одинаковым ID, они обе сохранятся.

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

    Важные нюансы:

  • Асинхронность: Удаление дублей происходит только во время слияния (merge). До этого момента вы можете видеть дубликаты в выборке.
  • Аргумент версии: В примере выше мы указали updated_at. Если придут две строки с одинаковым id, ClickHouse оставит ту, у которой updated_at больше.
  • FINAL: Чтобы гарантированно получить данные без дублей прямо сейчас, нужно использовать модификатор SELECT ... FINAL, но это медленная операция.
  • SummingMergeTree: Готовые агрегаты

    Идеален для хранения счетчиков. Если у вас есть таблица с ключом (SiteID, Date) и метриками Views, Clicks, то при вставке новых данных с тем же ключом движок автоматически сложит значения метрик.

    Это позволяет экономить место на диске и ускорять запросы, так как данные пре-агрегируются на лету.

    AggregatingMergeTree

    Более мощная версия SummingMergeTree. Позволяет хранить не просто суммы, а сложные состояния агрегатных функций (например, уникальные посетители через HyperLogLog или квантили). Работает в связке с типами данных AggregateFunction и материализованными представлениями (Materialized Views), о которых мы поговорим в будущих статьях.

    Партиционирование (Partitioning)

    Многие новички путают Первичный ключ (ORDER BY) и Ключ партиционирования (PARTITION BY).

    * ORDER BY: Определяет, как данные сортируются внутри куска и как работает индекс. Это главное средство ускорения запросов. * PARTITION BY: Определяет, как данные физически разделяются по папкам на диске.

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

    Зачем нужно партиционирование?

    Главная цель — управление данными, а не ускорение SELECT. Партиции позволяют мгновенно удалять старые данные:

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

    Никогда не делайте партиционирование по часам, минутам или id с высокой кардинальностью.

    Если вы создадите тысячи партиций, ClickHouse будет вынужден держать открытыми тысячи файлов, а процесс слияния (merge) станет неэффективным. Рекомендуемый размер партиции — от 1 до 300 ГБ. Обычно партиционирования по месяцам или дням достаточно.

    Эффективная вставка данных

    ClickHouse — это аналитическая СУБД. Она оптимизирована для чтения, но запись должна быть специфической.

    Правило №1: Вставляйте батчами

    Каждый INSERT создает новый кусок данных (папку с файлами). Если вы будете делать 100 инсертов в секунду по одной строке, вы создадите 100 кусков. Фоновый процесс слияния не будет успевать их объединять, и вы получите ошибку Too many parts.

    Как надо: Накапливайте данные в буфере вашего приложения (например, в оперативной памяти или Kafka) и отправляйте их в ClickHouse раз в секунду или когда накопится 10 000 - 100 000 строк.

    Асинхронная вставка (Async Insert)

    Если вы не можете буферизовать данные на стороне приложения (например, вы пишете прямо из PHP-скрипта, который сразу умирает), используйте настройку async_insert.

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

    Идемпотентность вставки

    Что будет, если сеть моргнула во время вставки? Вы не знаете, записались данные или нет. Если повторить вставку, могут появиться дубли.

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

    Заключение

    Мы погрузились в недра MergeTree. Теперь вы понимаете, что:

  • Данные хранятся в кусках, которые сливаются в фоне.
  • Индекс разреженный и помещается в RAM.
  • ReplacingMergeTree помогает с дублями, но с оговоркой на «итоговую согласованность».
  • Партиционирование нужно использовать осторожно (не мельчить).
  • Вставка должна быть большими пачками.
  • В следующей статье мы разберем типы данных ClickHouse: от простых чисел до массивов и вложенных структур, а также узнаем, как кодеки сжатия позволяют хранить терабайты данных на дешевых дисках.

    3. Проектирование схемы данных, использование индексов и материализованных представлений

    Проектирование схемы данных, использование индексов и материализованных представлений

    В предыдущих статьях мы разобрали фундамент ClickHouse: колоночное хранение, движок MergeTree и важность вставки данных большими пачками. Теперь пришло время перейти от «механики» к «архитектуре».

    Как спроектировать таблицу так, чтобы запросы летали? Почему привычная нормализация данных из мира PostgreSQL здесь вредит? Как работают «пропускающие» индексы и почему материализованные представления (Materialized Views) — это главное оружие архитектора ClickHouse?

    В этой статье мы научимся думать категориями ClickHouse, проектировать эффективные схемы и настраивать автоматическую агрегацию данных.

    Философия схемы данных: Денормализация

    Если вы пришли из мира OLTP (MySQL, PostgreSQL), ваш рефлекс — нормализовать данные. Вы создаете таблицу users, таблицу products, таблицу orders и соединяете их через JOIN.

    В ClickHouse JOIN — это дорогая операция. Хотя ClickHouse умеет делать джойны, они требуют пересылки огромных объемов данных между узлами кластера или загрузки правых таблиц в оперативную память.

    Широкие таблицы (Wide Tables)

    Золотой стандарт ClickHouse — максимальная денормализация. Мы стараемся собрать все необходимые данные в одну широкую таблицу.

    Вместо того чтобы хранить user_id и джойнить его с таблицей имен при каждом запросе, мы часто записываем user_name прямо в таблицу событий. Да, это дублирование данных. Да, это увеличивает объем на диске. Но благодаря колоночному сжатию ClickHouse, текстовые колонки с повторяющимися значениями сжимаются невероятно эффективно.

    Типы данных имеют значение

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

  • Nullable. Старайтесь избегать Nullable полей, если это возможно. Для хранения Nullable ClickHouse создает отдельный файл с маской NULL-значений. Это лишний IO (ввод-вывод) и замедление запросов. Лучше использовать значения по умолчанию (пустая строка, 0, 1970-01-01).
  • LowCardinality. Если у вас есть строковая колонка, где значений мало, а строк много (например, «Тип устройства»: Mobile, Desktop, Tablet), используйте LowCardinality(String). ClickHouse внутри создаст локальный словарь, заменяя строки на числа. Это ускоряет фильтрацию и группировку.
  • Первичный ключ: Искусство выбора

    Мы уже знаем, что ORDER BY в MergeTree определяет первичный ключ. Это не ключ уникальности, а ключ сортировки. Данные физически лежат на диске отсортированными по этому ключу.

    Как выбирать порядок колонок?

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

    Представьте, что у нас есть таблица логов:

    В этом случае данные сначала сортируются по времени. Если мы делаем запрос с фильтром WHERE event_time > now() - INTERVAL 1 HOUR, ClickHouse сразу найдет нужный диапазон файлов и проигнорирует старые данные.

    Если же мы сделаем ORDER BY (user_id, event_time), то данные одного пользователя будут лежать рядом. Это идеально для запроса «история действий пользователя Вася», но ужасно для запроса «что происходило на сайте за последний час», так как данные за последний час будут размазаны по всему диску.

    Математически эффективность поиска зависит от объема сканируемых данных:

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

    Data Skipping Indexes (Индексы пропуска данных)

    Иногда первичного ключа недостаточно. Например, данные отсортированы по времени, но нам часто нужно искать по url. Сканировать всю таблицу дорого. Здесь на помощь приходят вторичные индексы, которые в ClickHouse называются Data Skipping Indexes.

    Они не указывают на конкретную строку (как B-Tree), а говорят: «В этом блоке данных точно нет искомого значения, пропускай его».

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

    Типы индексов

  • minmax. Хранит минимальное и максимальное значение выражения для блока. Идеально для дат или чисел, которые слабо коррелируют с первичным ключом.
  • set(k). Хранит множество всех уникальных значений в блоке (до штук). Если значений больше , индекс отключается для этого блока. Хорошо подходит для колонок с низкой кардинальностью (статусы, типы).
  • bloom_filter. Использует структуру данных «Фильтр Блума». Позволяет с высокой вероятностью сказать, есть ли строка в блоке. Идеально для поиска по строкам, ID или элементам массивов.
  • Пример создания индекса:

    Здесь 0.01 — это допустимая вероятность ложноположительного срабатывания.

    Вероятность ложноположительного срабатывания фильтра Блума описывается формулой:

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

    Материализованные представления (Materialized Views)

    Это, пожалуй, самая мощная и недооцененная фича ClickHouse. В отличие от классических VIEW (которые просто сохраненный SQL-запрос), Materialized View (MV) в ClickHouse хранит физические данные.

    Как это работает?

    MV в ClickHouse — это триггер на вставку (INSERT trigger).

  • Вы вставляете данные в таблицу A (Source).
  • Триггер MV срабатывает, берет этот блок данных.
  • Применяет к нему SELECT запрос, написанный в определении MV.
  • Результат записывает в таблицу B (Target).
  • Важно: MV обрабатывает только новые данные в момент вставки. Она не пересчитывает старые данные.

    Сценарий использования: Агрегация логов

    У вас есть таблица raw_logs с миллиардами строк. Вы хотите строить графики посещаемости по часам. Делать GROUP BY по миллиарду строк каждый раз — долго.

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

  • Целевая таблица (где храним результат):
  • Материализованное представление (сам процесс):
  • Теперь, когда вы пишете в raw_logs, ClickHouse автоматически обновляет hourly_stats. Данные в hourly_stats занимают в сотни раз меньше места, и запросы к ней выполняются за миллисекунды.

    Эффективность сжатия данных при агрегации можно выразить коэффициентом:

    Где — коэффициент агрегации, — количество сырых строк, — количество строк после агрегации. Для временных рядов этот коэффициент часто достигает значений 100–1000.

    POPULATE

    При создании MV можно указать ключевое слово POPULATE, чтобы ClickHouse обработал уже существующие данные в таблице. Однако, не рекомендуется использовать это на работающем проде под нагрузкой, так как процесс вставки блокируется на время начального заполнения.

    Словари (Dictionaries)

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

    Для этого ClickHouse использует Словари. Это структуры данных, которые хранятся в оперативной памяти (полностью или частично) и оптимизированы для точечного поиска (key-value).

    Функция dictGet работает быстрее, чем JOIN.

    Заключение

    Проектирование в ClickHouse — это баланс между скоростью записи, скоростью чтения и объемом диска.

  • Денормализуйте данные в широкие таблицы.
  • Выбирайте Первичный ключ под самые частые фильтры.
  • Используйте Skipping Indexes для вторичных колонок, но помните, что они не панацея.
  • Используйте Materialized Views с движками SummingMergeTree или AggregatingMergeTree для прекалькуляции тяжелых отчетов.
  • В следующей части курса мы разберем работу с кластером: шардирование, репликацию и как обеспечить отказоустойчивость вашей системы.

    4. Построение распределенного кластера: репликация, шардирование и ClickHouse Keeper

    Построение распределенного кластера: репликация, шардирование и ClickHouse Keeper

    В предыдущих статьях мы научились выжимать максимум из одного сервера ClickHouse: проектировали схемы, использовали индексы и материализованные представления. Но что делать, если данные перестали помещаться на один диск? Или что еще страшнее — что будет, если единственный сервер сгорит?

    В этой статье мы переходим на уровень Senior-архитектора. Мы разберем, как превратить одиночный сервер в отказоустойчивый и масштабируемый кластер. Мы узнаем, чем репликация отличается от шардирования, зачем нам ClickHouse Keeper и как правильно настроить Distributed таблицы.

    Масштабирование: Вертикальное vs Горизонтальное

    Когда сервер перестает справляться с нагрузкой, у нас есть два пути:

  • Вертикальное масштабирование (Scale Up): Добавить больше процессорных ядер, оперативной памяти и дисков в текущий сервер. Это просто, но имеет физический предел и стоит дорого.
  • Горизонтальное масштабирование (Scale Out): Добавить больше серверов и распределить данные между ними. ClickHouse создан именно для этого сценария.
  • В мире распределенных систем есть две главные концепции: Репликация и Шардирование.

    !Визуализация различия между копированием данных (репликация) и их разделением (шардирование).

    Репликация: Защита данных

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

    Главная цель репликации — отказоустойчивость (High Availability). Если один сервер выйдет из строя, запросы продолжит обслуживать его копия (реплика).

    Математика надежности

    Допустим, вероятность отказа одного сервера в течение года составляет 10% (или 0.1). Если у нас данные хранятся только на одном сервере, риск потери данных велик.

    При наличии репликации вероятность того, что все серверы выйдут из строя одновременно, рассчитывается по формуле:

    Где: * — вероятность отказа всей системы (потери данных). * — вероятность отказа одного сервера (например, 0.1). * — количество реплик.

    Если у нас 3 реплики, то , то есть 0.1%. Надежность системы возрастает экспоненциально с добавлением каждой новой реплики.

    ReplicatedMergeTree

    В ClickHouse репликация работает на уровне таблиц, а не всего сервера. Для этого используется семейство движков ReplicatedMergeTree.

    Чтобы таблица стала реплицируемой, нужно:

  • Использовать движок ReplicatedMergeTree вместо обычного MergeTree.
  • Иметь внешний сервис координации (ZooKeeper или ClickHouse Keeper).
  • Синтаксис создания:

    Обратите внимание на два аргумента в движке:

  • Путь в Keeper: /clickhouse/tables/{shard}/hits. Это виртуальная папка, где реплики будут находить друг друга. Все реплики одной таблицы должны иметь одинаковый путь.
  • Имя реплики: {replica}. Уникальное имя конкретной копии. Обычно совпадает с именем хоста.
  • > В конфигурации ClickHouse часто используют макросы {shard} и {replica}, чтобы на всех серверах выполнять одну и ту же команду CREATE TABLE, а подстановка значений происходила автоматически из конфига.

    ClickHouse Keeper: Мозг кластера

    Для работы репликации серверам нужно договариваться: «Я записал кусок данных, теперь ты скачай его у меня». Для этой координации исторически использовался Apache ZooKeeper.

    Однако ZooKeeper написан на Java, требует JVM и сложен в администрировании. Поэтому команда ClickHouse разработала свою альтернативу — ClickHouse Keeper.

    ClickHouse Keeper — это система координации, написанная на C++, полностью совместимая с протоколом ZooKeeper, но работающая быстрее и потребляющая меньше памяти. Она может быть встроена прямо в бинарный файл clickhouse-server или запускаться отдельно.

    Keeper использует алгоритм консенсуса Raft для гарантии того, что все узлы имеют согласованное представление о состоянии кластера.

    Шардирование: Масштабирование емкости

    Шардирование — это разделение данных на части (шарды).

    Главная цель шардирования — производительность записи и чтения огромных объемов данных. Если у вас есть 1 Петабайт данных, а диск сервера вмещает только 10 Терабайт, вам нужно минимум 100 шардов.

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

    Архитектура кластера

    Типичный кластер ClickHouse состоит из шардов, каждый из которых имеет реплики.

    Например, кластер «2 шарда по 2 реплики» состоит из 4 серверов: * Шард 1: * Сервер A (Реплика 1) * Сервер B (Реплика 2) * Шард 2: * Сервер C (Реплика 1) * Сервер D (Реплика 2)

    !Схема кластера с двумя шардами, где каждый шард имеет по две реплики для надежности.

    Движок Distributed

    Сам по себе ClickHouse на конкретном узле ничего не знает о других шардах. Чтобы работать с кластером как с единым целым, используется специальный движок таблицы — Distributed.

    Важно: Таблица Distributed не хранит данные. Это просто «прокси» или «маршрутизатор», который перенаправляет ваши запросы на локальные таблицы ReplicatedMergeTree на удаленных серверах.

    Аргументы:

  • my_cluster: Имя кластера из конфигурационного файла XML.
  • default: Имя базы данных.
  • hits: Имя локальной таблицы, где реально лежат данные.
  • rand(): Ключ шардирования. Определяет, на какой шард полетят данные при записи.
  • Как происходит запись и чтение

    Чтение (SELECT)

    Когда вы делаете SELECT из Distributed таблицы:
  • Сервер, принявший запрос, смотрит в конфиг кластера.
  • Он отправляет подзапросы на все шарды (выбирая одну из доступных реплик для каждого шарда).
  • Шарды выполняют обработку локально.
  • Инициатор запроса собирает результаты и отдает их клиенту.
  • Запись (INSERT)

    Есть два способа записи в кластер:

  • Прямая запись в шарды (Рекомендуется). Ваше приложение само знает, какие данные куда класть, и пишет напрямую в локальные ReplicatedMergeTree таблицы на нужных узлах. Это снимает нагрузку с ClickHouse.
  • Запись через Distributed таблицу. Вы пишете в Distributed таблицу, а она сама нарезает данные и рассылает их по шардам асинхронно. Это проще, но создает оверхед на сеть и CPU координатора.
  • Ключ шардирования (Sharding Key)

    В примере выше мы использовали rand() — случайное распределение. Это обеспечивает равномерное заполнение дисков, но делает невозможным локальные джойны.

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

    Формула распределения выглядит так:

    Где: * — номер шарда, куда попадут данные. * — числовое значение хеш-функции от ключа шардирования (например, ID пользователя). * — операция взятия остатка от деления на , где — количество шардов.

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

    Заключение

    Построение кластера ClickHouse — это переход на новый уровень инженерной зрелости.

    Мы выяснили, что:

  • Репликация (через ReplicatedMergeTree) спасает от потери данных и требует ClickHouse Keeper.
  • Шардирование позволяет хранить петабайты данных, распределяя их по узлам.
  • Таблица Distributed — это маршрутизатор, связывающий шарды воедино.
  • Теперь вы обладаете знаниями для проектирования высоконагруженных систем. В следующей статье мы рассмотрим вопросы мониторинга и оптимизации производительности кластера, чтобы ваша система работала как швейцарские часы.

    5. Эксплуатация Production-среды: мониторинг, безопасность и оптимизация сложных запросов

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

    Поздравляю! Вы прошли путь от установки локального сервера до проектирования распределенного кластера с шардированием и репликацией. Теперь вы — архитектор системы, способной переваривать петабайты данных. Но построить систему — это только половина дела. Вторая половина — заставить её работать стабильно 24/7, защитить от злоумышленников (и неопытных коллег) и оптимизировать запросы, которые «съедают» всю оперативную память.

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

    Мониторинг: Глаза и уши архитектора

    ClickHouse — это «черный ящик», пока вы не настроите метрики. В отличие от простых приложений, где достаточно знать загрузку CPU, здесь нам важно понимать состояние внутренних процессов: слияния (merges), репликации и потребления памяти.

    Системные таблицы

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

  • system.metrics: Содержит мгновенные значения метрик (например, сколько сейчас выполняется запросов Query или сколько соединений открыто TCPConnection).
  • system.events: Содержит накопительные счетчики с момента запуска сервера (например, SelectQuery — сколько всего было SELECT-запросов, InsertedRows — сколько строк вставлено).
  • system.asynchronous_metrics: Метрики, которые обновляются периодически (например, использование RAM, размер очередей репликации).
  • Логирование запросов (Query Log)

    Самая полезная таблица для отладки — system.query_log. Если в конфигурации включен параметр log_queries, ClickHouse записывает туда информацию о каждом выполненном запросе: кто запустил, сколько времени заняло, сколько памяти съело и какая ошибка возникла.

    Пример поиска самых тяжелых запросов за последний час:

    Внешний мониторинг (Prometheus + Grafana)

    Смотреть в таблицы глазами неудобно. Стандарт индустрии — использование clickhouse-exporter, который забирает данные из системных таблиц и отдает их в Prometheus. Затем эти данные визуализируются в Grafana.

    !Пример дашборда мониторинга, показывающего ключевые метрики здоровья кластера.

    Критические метрики, за которыми нужно следить: * MaxPartCountForPartition: Если это число растет (больше 300), значит, ClickHouse не успевает сливать куски данных (merges). Это приведет к отказу в записи (Too many parts). * ReplicationQueue: Если очередь растет, реплики отстают друг от друга. * MemoryTracking: Общее потребление памяти сервером.

    Безопасность: RBAC и Квоты

    По умолчанию в ClickHouse есть пользователь default без пароля. В продакшене это недопустимо. ClickHouse поддерживает полноценную модель управления доступом на основе ролей (RBAC — Role-Based Access Control), похожую на ту, что есть в PostgreSQL.

    Создание пользователей и ролей

    Никогда не давайте аналитикам доступ на запись. Разделяйте права.

  • Создаем роль для аналитиков (только чтение):
  • Создаем пользователя и назначаем роль:
  • Квоты и ограничения (Settings Profiles)

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

    Создадим профиль, который запрещает запросам потреблять больше 10 ГБ RAM и выполняться дольше 60 секунд:

    Теперь, если john_doe попытается сделать SELECT * из петабайтной таблицы, ClickHouse убьет этот запрос при превышении лимитов, спасая сервер для остальных пользователей.

    Оптимизация сложных запросов

    Даже с индексами и мощным железом запросы могут тормозить. Разберем главные техники оптимизации.

    PREWHERE: Секретное оружие

    В обычных SQL-базах есть только WHERE. В ClickHouse есть PREWHERE.

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

    Обычно ClickHouse сам автоматически переносит условия из WHERE в PREWHERE, но иногда полезно указать это явно, особенно для тяжелых колонок.

    EXPLAIN: Разбор плана запроса

    Чтобы понять, как ClickHouse выполняет запрос, используйте EXPLAIN.

    Вывод покажет дерево выполнения. Ищите там: * Expression: Вычисления. * Filter: Фильтрация строк. * ReadFromMergeTree: Чтение с диска. Обратите внимание на количество просканированных гранул (Granules). Если индекс работает, это число должно быть маленьким.

    Оптимизация JOIN

    JOIN в ClickHouse — это сложная операция. По умолчанию ClickHouse пытается загрузить правую таблицу в оперативную память (в хеш-таблицу), а левую таблицу стримить через эту хеш-таблицу.

    Золотое правило JOIN: Справа всегда должна быть меньшая таблица.

    Если вы делаете big_table JOIN small_table, все работает быстро. Если small_table JOIN big_table — сервер может упасть с ошибкой Memory limit exceeded.

    Оценка потребления памяти для JOIN можно выразить формулой:

    Где: * — объем оперативной памяти, необходимый для выполнения JOIN. * — количество строк в правой таблице после фильтрации. * — средний размер одной строки в байтах (в несжатом виде). * — накладные расходы хеш-таблицы на каждую строку (обычно несколько десятков байт).

    Если превышает доступную RAM, запрос упадет. В таких случаях используйте JOIN с внешним хранилищем (параметр max_bytes_before_external_join), который сбрасывает временные данные на диск, но это сильно замедляет работу.

    Управление жизненным циклом данных (TTL)

    В аналитике данные часто имеют срок годности. Логи пятилетней давности обычно не нужны на быстрых SSD дисках. ClickHouse позволяет автоматизировать удаление или перемещение данных с помощью TTL (Time To Live).

    Удаление старых данных

    В этом примере ClickHouse будет автоматически удалять строки, которые старше 1 месяца. Это происходит в фоне во время слияния кусков (merges).

    Иерархическое хранение (Tiered Storage)

    Вы можете настроить ClickHouse так, чтобы свежие данные (Hot) лежали на быстрых NVMe SSD, а старые (Cold) автоматически переезжали на медленные HDD или даже в S3 (объектное хранилище).

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

    Заключение курса

    Мы прошли большой путь. Теперь вы знаете, что ClickHouse — это не просто база данных, а мощный конструктор для аналитики.

    Мы изучили:

  • Архитектуру: Колоночное хранение и векторные вычисления.
  • MergeTree: Как данные живут на диске и сливаются в фоне.
  • Схемы: Почему денормализация — это хорошо, а JOIN — дорого.
  • Кластер: Как масштабироваться с помощью шардирования и репликации.
  • Эксплуатацию: Как мониторить, защищать и чистить систему.
  • Теперь вы готовы к роли Senior-архитектора. ClickHouse продолжает развиваться, добавляя поддержку JSON, векторный поиск для AI и интеграцию с Data Lake. Но фундамент, который вы получили в этом курсе, останется неизменным. Удачи в построении высоконагруженных систем!