Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Движки таблиц — основы
table_engines_basic

Движки таблиц — основы

Что такое движки таблиц, MergeTree, Log, Memory, File, Table, Null, различия и применение

Движки таблиц — основы

Как движок определяет хранение, фоновые слияния и цену запроса

#Результат урока

Вы свяжете ORDER BY, партиции, части и фоновые merge с ценой вставки и чтения, а затем защитите DDL под заданные запросы.

#Что такое движок таблиц

Движок таблиц (table engine) в ClickHouse определяет:

  • Как данные хранятся на диске
  • Как данные вставляются и читаются
  • Какие индексы поддерживаются
  • Возможность репликации и шардирования
  • Поддержку транзакций (вернее, её отсутствие)

Синтаксис:

CREATE TABLE table_name (columns) ENGINE = EngineName(parameters);

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

СемействоНазначениеProduction
MergeTreeОсновное семейство для аналитикиДа
LogПростые временные таблицыНет
MemoryДанные в RAMНет
ИнтеграцияВнешние источникиЗависит от движка
СпециальныеNull, File, URLЗависит от движка

#MergeTree — основной движок

#Почему MergeTree

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

Возможности:

  • Индексы и пропуск ненужных гранул
  • Партиционирование
  • Репликация и шардирование
  • TTL и мутации
  • Сжатие данных

#Базовый синтаксис

CREATE TABLE events ( event_time DateTime, user_id UInt64, event_type String, value Decimal(10, 2) ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time) ORDER BY (event_time, user_id) PRIMARY KEY (event_time, user_id) SETTINGS index_granularity = 8192;

#Ключевые компоненты

#1. ORDER BY — ключ сортировки

Обязательный параметр! Определяет порядок сортировки данных внутри партиции.

ORDER BY (event_time, user_id)

Важно:

  • Данные физически сортируются по этим колонкам
  • Первичный индекс по умолчанию совпадает с ORDER BY
  • От порядка колонок в ORDER BY зависит производительность запросов

Правила выбора ORDER BY:

  1. Первыми указывайте колонки для фильтрации в WHERE
  2. Затем — для группировки GROUP BY
  3. Затем — для сортировки ORDER BY в запросах
-- Хороший ORDER BY для типичных запросов: ORDER BY (event_date, user_id, event_type) -- Запросы будут быстрыми: WHERE event_date = '2026-03-01' WHERE event_date = '2026-03-01' AND user_id = 123 WHERE event_date = '2026-03-01' AND user_id = 123 AND event_type = 'click'

#2. PARTITION BY — разбиение на партиции

PARTITION BY toYYYYMM(event_time) -- По месяцам PARTITION BY toYYYYMMDD(event_time) -- По дням PARTITION BY (year, month) -- По нескольким колонкам

Зачем нужно:

  • Быстрое удаление старых данных: ALTER TABLE DROP PARTITION
  • Пропуск нерелевантных партиций при чтении
  • Параллельная обработка партиций
  • Упрощение бэкапов и восстановления

Рекомендации:

  • Для событий: toYYYYMM(event_time) — по месяцам
  • Не создавайте высокую кардинальность партиций; оцените число активных партиций и частей на своём потоке вставки
  • Не используйте UNIQUE ID для партиционирования

#3. PRIMARY KEY — первичный индекс

PRIMARY KEY (event_time, user_id)

В ClickHouse первичный ключ:

  • НЕ гарантирует уникальность (в отличие от PostgreSQL/MySQL)
  • Это индекс для ускорения поиска по диапазонам
  • По умолчанию совпадает с ORDER BY
  • Создает разреженный индекс (каждая N-я строка)

Как работает:

Данные отсортированы:
event_time    | user_id | ...
2026-03-01    | 1       | ...
2026-03-01    | 100     | ...
2026-03-01    | 200     | ...  ← Индекс: (2026-03-01, 200) → гранула 1
2026-03-01    | 300     | ...
2026-03-01    | 400     | ...  ← Индекс: (2026-03-01, 400) → гранула 2
...

Запрос: WHERE event_time = '2026-03-01' AND user_id BETWEEN 250 AND 350
→ Индекс пропускает гранулы 1 и 3+, читает только гранулу 2

#4. index_granularity — размер гранулы

SETTINGS index_granularity = 8192 -- По умолчанию

Гранула — минимальная единица чтения данных:

  • 8192 строки на гранулу
  • Индекс указывает на начало каждой гранулы
  • Меньшая гранулярность = больше индекс, точнее поиск
  • Большая гранулярность = меньше индекс, больше чтение

Когда менять:

  • Уменьшить (4096): для частых точечных запросов
  • Увеличить (16384): для последовательного чтения больших данных

#Пример: таблица событий

CREATE TABLE page_views ( event_time DateTime DEFAULT now(), user_id UInt64, session_id UInt64, page_url String, referer String, country LowCardinality(String), city LowCardinality(String), screen_width UInt16, screen_height UInt16, is_mobile UInt8, duration_ms UInt32 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time) ORDER BY (event_time, user_id, session_id) PRIMARY KEY (event_time, user_id, session_id) SETTINGS index_granularity = 8192;

Обоснование:

  • PARTITION BY toYYYYMM — по месяцам, удобно для TTL
  • ORDER BY (event_time, user_id, session_id) — типичные запросы по времени и пользователю
  • LowCardinality(String) для country/city — сжатие

#Log-семейство

Движки Log подходят для простых временных таблиц. Для рабочей аналитической нагрузки обычно нужен MergeTree.

#Log

CREATE TABLE temp_logs ( timestamp DateTime, message String ) ENGINE = Log;

Характеристики:

  • Нет индексов: при чтении приходится сканировать таблицу целиком
  • Нет сортировки и партиционирования
  • Нет репликации
  • Небольшие накладные расходы при вставке
  • Подходит для временных данных

Когда использовать:

  • Временные таблицы для промежуточных вычислений
  • Логирование отладочной информации
  • Быстрая загрузка данных перед обработкой

#StripeLog и TinyLog

ENGINE = StripeLog; -- Общий индекс для всех колонок ENGINE = TinyLog; -- Без индексов вообще

Различия:

  • Log — отдельный файл индекса на колонку
  • StripeLog — один файл индекса на таблицу
  • TinyLog — нет индексов, мало служебных данных

#Memory — данные в RAM

CREATE TABLE cache_table ( key String, value String ) ENGINE = Memory;

Характеристики:

  • Данные находятся только в оперативной памяти
  • После перезапуска сервера данные пропадут
  • Индексов нет
  • Чтение и запись не требуют обращения к диску
  • Сжатия нет

Когда использовать:

  • Кэширование промежуточных результатов
  • Временные справочники
  • Тестирование и отладка

#Специальные движки

#Null

CREATE TABLE sink_table ( any_column String ) ENGINE = Null;

Назначение:

  • Таблица-«чёрная дыра» — данные исчезают при вставке
  • Для тестирования производительности вставки
  • Для отладки пайплайнов данных

#File

CREATE TABLE file_table ( col1 UInt32, col2 String ) ENGINE = File(TabSeparated);

Назначение:

  • Чтение/запись файлов как таблиц
  • Поддерживаемые форматы: TabSeparated, CSV, JSON, etc.
  • Для импорта/экспорта данных

#Table Function

-- Чтение из файла как из таблицы SELECT * FROM file('data.csv', 'CSV', 'col1 UInt32, col2 String'); -- Чтение из URL SELECT * FROM url('https://example.com/data.json', 'JSON');

Назначение:

  • Одноразовый импорт данных
  • Интеграция с внешними источниками

#Сравнение движков

ХарактеристикаMergeTreeLogMemory
ИндексыДаНетНет
СортировкаДаНетНет
ПартиционированиеДаНетНет
РепликацияДаНетНет
СжатиеДаМинимальноеНет
Сохранение на дискДаДаНет
Производительность вставкиВысокаяОчень высокаяМаксимальная
Производительность чтенияОчень высокаяНизкаяВысокая
Для рабочей нагрузкиДаНетНет

#Как проверить выбор движка

Используйте MergeTree, если:

  • Рабочая аналитическая нагрузка
  • Нужны индексы для быстрого чтения
  • Данные должны переживать перезапуск
  • Планируется репликация или шардирование

Используйте Log, если:

  • Временные данные для обработки
  • Важна скорость вставки, а чтение вторично

Используйте Memory, если:

  • Данные допустимо потерять при перезапуске
  • Кэширование промежуточных результатов
  • Тестирование и отладка

#Практические примеры

#Таблица для аналитики событий

CREATE TABLE analytics_events ( event_time DateTime DEFAULT now(), event_date Date DEFAULT toDate(event_time), user_id UInt64, event_type LowCardinality(String), platform LowCardinality(String), country LowCardinality(String), properties Map(String, String) ) ENGINE = MergeTree() PARTITION BY event_date ORDER BY (event_date, user_id, event_type) SETTINGS index_granularity = 8192;

#Таблица для метрик

CREATE TABLE system_metrics ( timestamp DateTime DEFAULT now(), host LowCardinality(String), metric_name LowCardinality(String), value Float64 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(timestamp) ORDER BY (timestamp, host, metric_name) SETTINGS index_granularity = 8192;

#Временная таблица для ETL

CREATE TABLE staging_data ( id UInt64, raw_data String, loaded_at DateTime DEFAULT now() ) ENGINE = Log; -- После обработки данные перемещаются в основную таблицу INSERT INTO main_table SELECT * FROM staging_data WHERE ...;

#Практика: два ключа сортировки

Создайте две таблицы событий с разным ORDER BY, загрузите одинаковые данные и выполните запросы по времени, арендатору и пользователю. Сравните EXPLAIN indexes = 1, read_rows, read_bytes, размер на диске и число активных частей. Победитель может быть разным для разных запросов — это и должно быть отражено в выводе.

Дальше эта модель усложнится: ReplacingMergeTree, SummingMergeTree и AggregatingMergeTree меняют не только хранение, но и смысл результата до завершения слияний.

Далее: Движки таблиц — продвинутые