Приветствуем вас во второй части нашего курса по основам ClickHouse (далее CH)! В первой статье мы разобрались, что такое ClickHouse, почему он так хорош для аналитики и как запустить его локально или в облаке. Теперь пришло время углубиться в две ключевые концепции, которые определяют, как CH хранит и обрабатывает ваши данные: типы данных ClickHouse и движки таблиц (Table Engines).
Понимание этих концепций критически важно для создания производительных решений. Неправильный выбор может привести к не оптимальной производительности и избыточному потреблению ресурсов, поэтому давайте разберем все по порядку.
Типы Данных ClickHouse: Точность и Эффективность Хранения
CH, как и любая СУБД, требует, чтобы вы указывали тип данных для каждого столбца. Однако CH предлагает множество специализированных типов данных, которые позволяют достичь высокой эффективности хранения и обработки. Выбор правильного типа данных может значительно повлиять на размер вашей базы и скорость запросов.
Числовые типы данных используемые в ClickHouse
ClickHouse предоставляет широкий спектр числовых типов, что позволяет выбрать оптимальный для памяти диапазон значений.
Целые числа: Это типы от Int8/UInt8 до Int256/UInt256. Правило простое: всегда выбирайте наименьший тип, который может вместить ваши значения. Например, для возраста человека идеально подойдет UInt8.
Числа с плавающей точкой: Float32 и Float64 используются для вычислений, где требуется высокая точность.
Десятичные числа (Decimal): Типы Decimal32, Decimal64 и другие незаменимы там, где потеря точности недопустима, например, при работе с денежными суммами.
Строковые типы
Обработка строк часто бывает ресурсоемкой, и CH предлагает несколько вариантов для оптимизации.
String: Строка произвольной длины, самый универсальный тип.
FixedString(N): Строка фиксированной длины. Идеальна для данных вроде хэшей или кодов стран.
LowCardinality(String): Это “секретное оружие” для оптимизации. Используется для столбцов с небольшим количеством уникальных значений. ClickHouse создает словарь уникальных значений и для каждой строки хранит лишь короткий числовой индекс. Это драматически улучшает сжатие и ускоряет фильтрацию и группировку.
Дата и время
Date: Хранит только дату.
DateTime: Хранит дату и время с точностью до секунды.
DateTime64(precision): Хранит дату и время с точностью до милли-, микро- или наносекунд.
Важная рекомендация: всегда храните временные метки в UTC, чтобы избежать проблем с часовыми поясами.
Прочие важные типы
Boolean: Хранится как UInt8 (0 или 1).
UUID: Для хранения универсальных уникальных идентификаторов.
IPv4, IPv6: Специализированные и эффективные типы для IP-адресов.
Array(T): Массив значений одного типа.
Nullable(T): Оборачивает любой тип, позволяя ему хранить значение NULL. Используйте его только при необходимости.
Практический пример: Выбор типов данных для ClickHouse таблиц
Давайте пересмотрим таблицу access_logs
из первой статьи и посмотрим, как типы данных влияют на нее:
CREATE TABLE access_logs ( timestamp DateTime64(3), -- Точность до миллисекунд для детального анализа событий event_type LowCardinality(String),-- Ограниченное число уникальных типов событий -> эффективно user_id UInt64, -- Большое количество пользователей, ID всегда положительный ip_address IPv4, -- Оптимизировано для хранения IP-адресов url String, -- Произвольная длина, высокая кардинальность -> String duration_ms UInt32, -- Продолжительность всегда положительная, до нескольких миллиардов миллисекунд is_mobile Nullable(UInt8) -- Добавим новое поле: может быть NULL, если неизвестно ) ENGINE = MergeTree() ORDER BY (timestamp, user_id);
Правильный выбор типов данных ClickHouse — это первое, что нужно сделать для обеспечения производительности и экономии ресурсов.
Движки Таблиц (Table Engines): Сердце Хранения Данных
Движок таблицы (Table Engine) определяет, как данные хранятся, читаются, записываются, индексируются и реплицируются. Это одна из самых мощных и отличительных особенностей CH . Выбор движка критически важен, так как он определяет производительность, надежность и функциональность вашей таблицы.
CH предлагает множество движков, но для аналитических задач наиболее важными являются движки семейства MergeTree
. Они оптимизированы для сценариев OLAP (Online Analytical Processing) и обеспечивают высокую производительность на больших объемах данных.
Семейство движков MergeTree: ваш рабочий инструмент
Движки MergeTree спроектированы для хранения огромных объемов данных и обеспечивают молниеносное выполнение аналитических запросов. Ключевые особенности MergeTree:
Колоночное хранение: Как мы уже обсуждали, данные хранятся по столбцам.
Партиционирование (Partitioning): Данные могут быть разбиты на логические части (партиции) по заданному критерию (часто по дате или месяцу). Это позволяет ClickHouse читать только те данные, которые релевантны запросу, и эффективно удалять старые данные.
Сортировка (Ordering): Данные внутри каждой партиции отсортированы по указанному ключу сортировки (ORDER BY
). Это ускоряет фильтрацию и агрегацию.
Индексирование (Indexing): Поверх отсортированных данных создается разреженный индекс (primary index), который позволяет быстро находить блоки данных.
Слияние (Merging): Данные записываются в небольшие, отсортированные части (data parts). В фоновом режиме ClickHouse периодически объединяет эти части в более крупные, что оптимизирует хранение и производительность.
Синтаксис базового движка MergeTree:
CREATE TABLE my_table ( col1 DataType, col2 DataType, -- ... ) ENGINE = MergeTree() PARTITION BY expression -- (Опционально) Выражение для партиционирования ORDER BY (col1, col2) -- Ключ сортировки (ОБЯЗАТЕЛЕН для MergeTree) PRIMARY KEY (col1) -- (Опционально) Выборка подмножества из ORDER BY для первичного индекса TTL expression -- (Опционально) Время жизни данных SETTINGS setting = value -- (Опционально) Дополнительные настройки
PARTITION BY
: Определяет, как данные будут разбиты на партиции. Чаще всего используется по дате или части даты (toYYYYMM(timestamp)
). Это наиболее важная оптимизация для работы с большими временными рядами. При запросах по диапазону дат CH будет читать только нужные партиции, игнорируя остальные.
ORDER BY
: Определяет, как данные отсортированы внутри каждой партиции. Это ваш основной индекс. Запросы с WHERE
или GROUP BY
по столбцам из ORDER BY
будут выполняться быстрее.
PRIMARY KEY
: В отличие от традиционных СУБД, PRIMARY KEY
в CH не гарантирует уникальность. Он просто определяет, какие столбцы используются для построения разреженного первичного индекса. Если не указан, по умолчанию используется ORDER BY
.
Вариации MergeTree
Помимо базового MergeTree
, существуют специализированные движки, наследующие его функциональность и добавляющие специфическое поведение:
ReplacingMergeTree
:
Назначение: Обработка дубликатов. При слиянии частей данных, если есть строки с одинаковым значением ключа сортировки (ORDER BY
), ReplacingMergeTree
оставляет только одну (последнюю по времени вставки или указанному столбцу-версии).
Пример: Для таблицы пользователей, где нужно хранить только актуальную информацию о каждом пользователе.
ENGINE = ReplacingMergeTree([ver_column])
– ver_column
опционален и указывает на столбец, по которому определяется “самая свежая” запись.
SummingMergeTree
:
Назначение: Агрегация данных “на лету” при слиянии частей. Все числовые столбцы (кроме тех, что в ORDER BY
) суммируются, если строки имеют одинаковое значение ключа сортировки.
Пример: Для агрегации метрик (просмотры, клики, суммы транзакций) по измерениям (дата, кампания, пользователь). Значительно уменьшает объем хранимых данных.
ENGINE = SummingMergeTree([columns_to_sum])
– опционально можно указать, какие столбцы суммировать. Если не указаны, суммируются все числовые.
AggregatingMergeTree
:
Назначение: Хранение предварительно агрегированных данных. Используется совместно с агрегатными функциями ClickHouse engine, которые возвращают промежуточные состояния (AggregateFunction
).
Пример: Если вам нужно часто считать uniqCombined
или quantiles
по большому объему данных, вы можете предварительно агрегировать их.
Это более продвинутый движок, требующий понимания работы с AggregateFunction
.
CollapsingMergeTree
:
Назначение: Удаление “парных” строк (например, событие “вход” и “выход”) или хранение только последнего состояния записи, используя специальный столбец Sign
.
Пример: Отслеживание сессий или состояний сущностей, где важно фиксировать изменения и убирать промежуточные состояния.
GraphiteMergeTree
:
Назначение: Оптимизирован для хранения данных временных рядов, подобных метрикам Graphite.
Replicated*MergeTree
: (Например, ReplicatedMergeTree
, ReplicatedReplacingMergeTree
и т.д.)
Назначение: Обеспечивает репликацию данных между серверами в кластере, используя Apache ZooKeeper (или ClickHouse Keeper) для координации. Это ваш выбор для production-систем, где нужна отказоустойчивость.
Практический Пример: Использование ClickHouse MergeTree engine с Партиционированием
Вернемся к нашей таблице access_logs
. Добавим партиционирование по месяцу, что очень распространено для временных рядов. Удалим старую таблицу (если она существует):
DROP TABLE IF EXISTS my_first_db.access_logs; -- Создадим новую таблицу с партиционированием CREATE TABLE my_first_db.access_logs ( timestamp DateTime64(3), event_type LowCardinality(String), user_id UInt64, ip_address IPv4, url String, duration_ms UInt32 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(timestamp) -- Партиционируем по году и месяцу из поля timestamp ORDER BY (timestamp, user_id);
Здесь toYYYYMM(timestamp)
извлекает год и месяц из временной метки (например, 202406
для июня 2024 года). ClickHouse автоматически создаст отдельные директории для каждой партиции на диске.
Вставим данные (включая данные за разные месяцы):
INSERT INTO my_first_db.access_logs (timestamp, event_type, user_id, ip_address, url, duration_ms) VALUES ('2024-05-15 10:00:00.123', 'page_view', 100, '192.168.1.1', '/old_home', 150), ('2024-05-15 10:00:01.456', 'click', 100, '192.168.1.1', '/old_button_a', 20), ('2024-06-19 10:00:00.123', 'page_view', 101, '192.168.1.1', '/home', 150), ('2024-06-19 10:00:01.456', 'click', 101, '192.168.1.1', '/button_a', 20), ('2024-06-19 10:00:02.789', 'page_view', 102, '10.0.0.5', '/products', 300), ('2024-06-20 10:00:03.000', 'page_view', 101, '192.168.1.1', '/contact', 100), ('2024-06-20 10:00:04.111', 'click', 102, '10.0.0.5', '/product_details', 50), ('2024-07-01 08:00:05.222', 'page_view', 103, '172.16.0.10', '/home', 200), ('2024-07-01 08:01:05.222', 'page_view', 104, '172.16.0.11', '/home', 250);
Проверим партиции (только для локальной инсталляции или через системные таблицы). В clickhouse-client
вы можете посмотреть, какие партиции создались:
Вы видите отдельные партиции для 202405
, 202406
и 202407
. Выполним запрос с фильтрацией по партиции. Когда вы запрашиваете данные за конкретный месяц, CH будет читать только соответствующую партицию:
Этот запрос будет очень быстрым, так как CH сразу поймет, что ему нужно работать только с партицией 202406
.
Другие категории движков (краткий обзор)
Хотя MergeTree
– ваш основной инструмент для аналитики, стоит знать о других категориях движков:
Лог-движки (Log
, TinyLog
, StripeLog
):
Простые движки для небольших объемов данных, которые записываются последовательно и не изменяются. Не поддерживают ORDER BY
, PARTITION BY
.
Когда использовать: Для временных таблиц, небольших логов, для быстрой отладки, где не требуется сложная аналитика.
Движки для внешних систем (Kafka
, MySQL
, PostgreSQL
, ODBC
, JDBC
, S3
, URL
, File
):
Позволяют ClickHouse напрямую работать с данными из внешних источников, не импортируя их.
Когда использовать: Для чтения данных из стриминговых платформ ( Kafka), PostgreSQL, S3 и других источников (более подробно посмотрим на них в Уроке 7 посвященном интеграции)
Специальные движки (Dictionary
, Distributed
, Buffer
):
Dictionary
: Для работы со словарями (маппингами), которые загружаются в оперативную память для быстрого сопоставления.
Distributed
: Не хранит данные сам по себе, а позволяет выполнять распределенные запросы по нескольким серверам CH.
Buffer
: Для временного буферизации данных перед их записью в другой движок (обычно MergeTree
).
Практическое Задание для Самостоятельного Изучения
Чтобы закрепить материал, предлагаю следующее упражнение. Выполните его в вашей локальной инсталляции Docker или в ClickHouse Cloud.
- Создайте новую базу данных с именем
my_app_data
. - Создайте таблицу
user_profiles
в этой базе данных с использованиемReplacingMergeTree
(чтобы гарантировать уникальность профилей поuser_id
при наличии дубликатов). Включите следующие столбцы:
user_id (UInt64) username (String) email (String) registration_date (Date) last_login_timestamp (DateTime) is_premium (UInt8 или Nullable(UInt8)) region (LowCardinality(String)) version (UInt64) - используйте его как столбец версии для ReplacingMergeTree.
Определите ORDER BY
для этой таблицы по user_id
и registration_date
.
Вставьте несколько записей в user_profiles
, включая:
Несколько уникальных пользователей.
Несколько записей для одного и того же user_id
, но с разными version
и last_login_timestamp
, чтобы увидеть, как ReplacingMergeTree
работает. Убедитесь, что последняя запись для пользователя имеет наибольший version
.
Проверьте данные с помощью SELECT * FROM user_profiles
.
Попробуйте вставить еще одну запись для существующего пользователя с более высоким version
и измененными данными (например, новый email
).
Снова проверьте данные и убедитесь, что ReplacingMergeTree
оставил только самую свежую версию записи для этого пользователя (возможно, вам придется подождать несколько секунд, пока произойдет слияние, или принудительно выполнить его с помощью OPTIMIZE TABLE user_profiles FINAL;
— не используйте OPTIMIZE
в продакшене без понимания его работы).
Это упражнение поможет вам на практике понять, как работают типы данных и движки MergeTree
.
Заключение
Типы данных и движки таблиц — это не просто теоретические концепции в CH; это фундаментальные строительные блоки, которые напрямую влияют на производительность, стоимость хранения и эффективность ваших аналитических решений. Правильный выбор и понимание их работы позволят вам раскрыть весь потенциал СУБД ClickHouse.
В следующей статье ( Урок 3) мы углубимся в выполнение запросов: рассмотрим базовые DML операции (вставка, выборка) и продвинутые SQL-функции, которые помогут вам извлекать максимум информации из ваших данных.
Использованные референсы и материалы
- Официальная документация ClickHouse по типам данных и движкам таблиц.
- Документация Apache Kafka и Apache ZooKeeper.