ClickHouse: введение¶
Зачем это DE?¶
ClickHouse — колоночная СУБД для аналитики, которая обрабатывает миллиарды строк за секунды. Если PostgreSQL — надёжный седан для OLTP, то ClickHouse — болид для OLAP. Data Engineer использует его для логов, метрик, аналитических витрин и real-time дашбордов.
ClickHouse vs PostgreSQL vs Greenplum¶
| Параметр | PostgreSQL | Greenplum | ClickHouse |
|---|---|---|---|
| Тип | OLTP (строковая) | OLAP (MPP) | OLAP (колоночная) |
| 1 млрд строк SELECT | Минуты | Секунды | Миллисекунды |
| INSERT одной строки | Быстро | Медленно | Медленно (батчами!) |
| Транзакции | ACID | ACID | Eventual consistency |
| UPDATE/DELETE | Быстро | Средне | Медленно (мутации) |
| Лучше для | CRUD, API, транзакции | DWH, SQL-аналитика | Логи, метрики, real-time |
Архитектура¶
Колоночное хранение¶
Строковое (PostgreSQL): Колоночное (ClickHouse):
┌──────┬─────┬────────┐ ┌──────────────────┐
│ id │ name│ amount │ │ id: 1, 2, 3, ... │ ← столбец
├──────┼─────┼────────┤ ├──────────────────┤
│ 1 │ Ann │ 100 │ │ name: Ann, Bob...│ ← столбец
│ 2 │ Bob │ 200 │ ├──────────────────┤
│ 3 │ Cat │ 300 │ │ amount: 100, 200 │ ← столбец
└──────┴─────┴────────┘ └──────────────────┘
SELECT SUM(amount): SELECT SUM(amount):
→ читает ВСЕ столбцы → читает ТОЛЬКО amount
→ 3 столбца × N строк → 1 столбец × N строк
→ сжатие 10-50x
Почему быстрее для аналитики:
- Читает только нужные столбцы (не все 100)
- Данные одного типа сжимаются в 10-50 раз (LZ4, ZSTD)
- Векторное выполнение: обработка пачками по 8192 строк
MergeTree — основной движок¶
MergeTree — основное семейство движков ClickHouse. ORDER BY определяет порядок хранения данных на диске и заменяет индекс.
Создание таблицы¶
CREATE TABLE events (
event_date Date,
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String), -- оптимизация для enum-like
page_url String,
duration_ms UInt32
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date) -- партиции по месяцам
ORDER BY (event_date, user_id) -- первичный ключ (сортировка на диске)
TTL event_date + INTERVAL 1 YEAR -- автоудаление через год
SETTINGS index_granularity = 8192; -- гранулярность индекса
ORDER BY — это индекс¶
В отличие от PostgreSQL, ClickHouse не имеет вторичных индексов в привычном смысле. ORDER BY определяет порядок хранения данных → это и есть primary index.
-- Быстро: фильтр по первым столбцам ORDER BY
SELECT COUNT(*) FROM events
WHERE event_date = '2025-01-15' AND user_id = 42;
-- Медленно: фильтр по столбцу НЕ из ORDER BY
SELECT COUNT(*) FROM events
WHERE page_url = '/checkout'; -- full scan
Порядок столбцов в ORDER BY критичен
ORDER BY (date, user_id) → фильтр по date быстрый, по user_id — только после date. Как составной индекс в PostgreSQL.
Вставка данных¶
Батчами, не по одной строке!¶
# ❌ Медленно: INSERT по одной строке
for row in data:
client.execute("INSERT INTO events VALUES", [row])
# ✅ Быстро: батч 10,000+ строк
client.execute(
"INSERT INTO events VALUES",
data, # список кортежей или dict'ов
types_check=True
)
Не делай INSERT по одной строке
ClickHouse создаёт парт (часть данных) на каждый INSERT. 1000 INSERT'ов → 1000 партов → деградация. Батчи от 10K строк.
Из файлов¶
-- Загрузка CSV
INSERT INTO events FORMAT CSVWithNames
-- (через clickhouse-client)
clickhouse-client --query="INSERT INTO events FORMAT CSVWithNames" < events.csv
-- Из S3
INSERT INTO events
SELECT * FROM s3(
'https://s3.amazonaws.com/bucket/events/*.parquet',
'key', 'secret',
'Parquet'
);
Материализованные представления¶
В ClickHouse materialized view — это trigger на INSERT: каждая вставка в исходную таблицу автоматически агрегируется и записывается в целевую.
-- Исходная таблица (детальные события)
CREATE TABLE events_raw (
event_date Date,
user_id UInt64,
event_type String,
amount Decimal(12,2)
) ENGINE = MergeTree()
ORDER BY (event_date, user_id);
-- Целевая таблица (агрегат)
CREATE TABLE daily_revenue (
event_date Date,
total_amount AggregateFunction(sum, Decimal(12,2)),
user_count AggregateFunction(uniq, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY event_date;
-- Materialized View — автозаполнение
CREATE MATERIALIZED VIEW mv_daily_revenue
TO daily_revenue
AS SELECT
event_date,
sumState(amount) AS total_amount,
uniqState(user_id) AS user_count
FROM events_raw
GROUP BY event_date;
-- Чтение с финализацией агрегатов
SELECT
event_date,
sumMerge(total_amount) AS revenue,
uniqMerge(user_count) AS users
FROM daily_revenue
GROUP BY event_date;
ReplacingMergeTree — дедупликация¶
CREATE TABLE users (
user_id UInt64,
name String,
email String,
updated_at DateTime
) ENGINE = ReplacingMergeTree(updated_at) -- оставит последнюю версию
ORDER BY user_id;
-- INSERT дубли — ClickHouse в фоне оставит строку с max(updated_at)
-- Для гарантии при чтении:
SELECT * FROM users FINAL WHERE user_id = 42;
-- FINAL форсирует дедупликацию при чтении
Типы данных¶
| Тип | Описание | Пример |
|---|---|---|
UInt8/16/32/64 |
Целые без знака | user_id UInt64 |
Int8/16/32/64 |
Целые со знаком | amount Int32 |
Float32/64 |
Числа с плавающей точкой | price Float64 |
Decimal(P,S) |
Точные числа | amount Decimal(12,2) |
String |
Строка (любая длина) | name String |
FixedString(N) |
Строка фиксированной длины | country_code FixedString(2) |
Date |
Дата (без времени) | event_date Date |
DateTime |
Дата и время (секунды) | created_at DateTime |
LowCardinality(T) |
Словарное кодирование | status LowCardinality(String) |
Nullable(T) |
Допускает NULL | email Nullable(String) |
Array(T) |
Массив | tags Array(String) |
LowCardinality вместо Enum
Для столбцов с малым числом уникальных значений (status, country) используй LowCardinality(String) — сжатие + скорость, но гибче Enum.
Python-клиент¶
import clickhouse_connect
client = clickhouse_connect.get_client(
host="localhost",
port=8123,
username="default",
password=""
)
# Чтение
result = client.query("SELECT event_date, COUNT(*) FROM events GROUP BY event_date")
for row in result.result_rows:
print(row)
# DataFrame
df = client.query_df("SELECT * FROM events WHERE event_date = '2025-01-15'")
# Вставка
client.insert("events", data, column_names=["event_date", "user_id", "event_type"])
Когда ClickHouse, когда PostgreSQL¶
| Сценарий | Выбор | Почему |
|---|---|---|
| CRUD-API для приложения | PostgreSQL | Транзакции, UPDATE, индексы |
| Аналитика по 1 млрд строк | ClickHouse | Скорость, сжатие |
| Логи и метрики | ClickHouse | TTL, партиции, сжатие |
| DWH с Star Schema | Greenplum / ClickHouse | Зависит от нагрузки |
| Real-time дашборды | ClickHouse | Миллисекундные ответы |
| CDC + медленно меняющиеся данные | PostgreSQL | ACID, UPDATE |
Что запомнить¶
| Концепция | Ключевое |
|---|---|
| Колоночное хранение | Читает только нужные столбцы, сжатие 10-50x |
| ORDER BY = индекс | Порядок столбцов определяет скорость фильтрации |
| Батчевая вставка | Минимум 10K строк за INSERT |
| MergeTree | Основной движок: партиции, TTL, ORDER BY |
| Materialized Views | Автоагрегация при INSERT |
| ReplacingMergeTree | Дедупликация по версии |