MergeTree
Движок MergeTree, а также другие движки этого семейства (*MergeTree) — это наиболее функциональные движки таблиц ClickHouse.
Основная идея, заложенная в основу движков семейства MergeTree следующая. Когда у вас есть огромное количество данных, которые должны быть вставлены в таблицу, вы должны быстро записать их по частям, а затем объединить части по некоторым правилам в фоновом режиме. Этот метод намного эффективнее, чем постоянная перезапись данных в хранилище при вставке.
Основные возможности:
- 
Хранит данные, отсортированные по первичному ключу. Это позволяет создавать разреженный индекс небольшого объёма, который позволяет быстрее находить данные. 
- 
Позволяет оперировать партициями, если задан ключ партиционирования. ClickHouse поддерживает отдельные операции с партициями, которые работают эффективнее, чем общие операции с этим же результатом над этими же данными. Также, ClickHouse автоматически отсекает данные по партициям там, где ключ партиционирования указан в запросе. Это также увеличивает эффективность выполнения запросов. 
- 
Поддерживает репликацию данных. Для этого используется семейство таблиц ReplicatedMergeTree. Подробнее читайте в разделе Репликация данных.
- 
Поддерживает сэмплирование данных. При необходимости можно задать способ сэмплирования данных в таблице. 
Движок Merge не относится к семейству *MergeTree.
Создание таблицы
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
    ...
    INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
    INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr
    [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx' [, ...] ]
    [WHERE conditions]
    [GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ] ]
[SETTINGS name=value, ...]
Описание параметров смотрите в описании запроса CREATE.
Секции запроса
- 
ENGINE— имя и параметры движка.ENGINE = MergeTree().MergeTreeне имеет параметров.
- 
ORDER BY— ключ сортировки.Кортеж столбцов или произвольных выражений. Пример: ORDER BY (CounterID, EventDate).ClickHouse использует ключ сортировки в качестве первичного ключа, если первичный ключ не задан в секции PRIMARY KEY.Чтобы отключить сортировку, используйте синтаксис ORDER BY tuple(). Смотрите выбор первичного ключа.
- 
PARTITION BY— ключ партиционирования. Необязательный параметр.Для партиционирования по месяцам используйте выражение toYYYYMM(date_column), гдеdate_column— столбец с датой типа Date. В этом случае имена парт иций имеют формат"YYYYMM".
- 
PRIMARY KEY— первичный ключ, если он отличается от ключа сортировки. Необязательный параметр.По умолчанию первичный ключ совпадает с ключом сортировки (который задаётся секцией ORDER BY.) Поэтому в большинстве случаев секциюPRIMARY KEYотдельно указывать не нужно.
- 
SAMPLE BY— выражение для сэмплирования. Необязательный параметр.Если используется выражение для сэмплирования, то первичный ключ должен содержать его. Результат выражения для сэмплирования должен быть беззнаковым целым числом. Пример: SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID)).
- 
TTL— список правил, определяющих длительности хранения строк, а также задающих правила перемещения частей на определённые тома или диски. Необязательный параметр.Выражение должно возвращать столбец DateилиDateTime. Пример:TTL date + INTERVAL 1 DAY.Тип правила DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'|GROUP BYуказывает действие, которое будет выполнено с частью: удаление строк (прореживание), перемещение (при выполнении условия для всех строк части) на определённый диск (TO DISK 'xxx') или том (TO VOLUME 'xxx'), или агрегирование данных в устаревших строках. Поведение по умолчанию соответствует удалению строк (DELETE). В списке правил может быть указано только одно выражение с поведениемDELETE.Дополнительные сведения смотрите в разделе TTL для столбцов и таблиц 
- 
SETTINGS— дополнительные параметры, регулирующие поведениеMergeTree(необязательные):- index_granularity— максимальное количество строк данных между засечками индекса. По умолчанию — 8192. Смотрите Хранение данных.
- index_granularity_bytes— максимальный размер гранул данных в байтах. По умолчанию — 10Mb. Чтобы ограничить размер гранул только количеством строк, установите значение 0 (не рекомендовано). Смотрите Хранение данных.
- min_index_granularity_bytes— минимально допустимый размер гранул данных в байтах. Значение по умолчанию — 1024b. Для обеспечения защиты от случайного создания таблиц с очень низким значением- index_granularity_bytes. Смотрите Хранение данных.
- enable_mixed_granularity_parts— включает или выключает переход к ограничению размера гранул с помощью настройки- index_granularity_bytes. Настройка- index_granularity_bytesулучшает производительность ClickHouse при выборке данных из таблиц с большими (десятки и сотни мегабайтов) строками. Если у вас есть таблицы с большими строками, можно включить эту настройку, чтобы повысить эффективность запросов- SELECT.
- use_minimalistic_part_header_in_zookeeper— Способ хранения заголовков кусков данных в ZooKeeper. Если- use_minimalistic_part_header_in_zookeeper = 1, то ZooKeeper хранит меньше данных. Подробнее читайте в описании настройки в разделе "Конфигурационные параметры сервера".
- min_merge_bytes_to_use_direct_io— минимальный объём данных при слиянии, необходимый для прямого (небуферизованного) чтения/записи (direct I/O) на диск. При слиянии частей данных ClickHouse вычисляет общий объём хранения всех данных, подлежащих слиянию. Если общий объём хранения всех данных для чтения превышает- min_bytes_to_use_direct_ioбайт, тогда ClickHouse использует флаг- O_DIRECTпри чтении данных с диска. Если- min_merge_bytes_to_use_direct_io = 0, тогда прямой ввод-вывод отключен. Значение по умолчанию:- 10 * 1024 * 1024 * 1024байтов.
- merge_with_ttl_timeout— минимальное время в секундах перед повторным слиянием для удаления данных с истекшим TTL. По умолчанию:- 14400секунд (4 часа).
- merge_with_recompression_ttl_timeout— минимальное время в секундах перед повторным слиянием для повторного сжатия данных с истекшим TTL. По умолчанию:- 14400секунд (4 часа).
- try_fetch_recompressed_part_timeout— время ожидания (в секундах) перед началом слияния с повторным сжатием. В течение этого времени ClickHouse пытается извлечь сжатую часть из реплики, которая назначила это слияние. Значение по умолчанию:- 7200секунд (2 часа).
- write_final_mark— включает или отключает запись последней засечки индекса в конце куска данных, указывающей за последний байт. По умолчанию — 1. Не отключайте её.
- merge_max_block_size— максимальное количество строк в блоке для операций слияния. Значение по умолчанию: 8192.
- storage_policy— политика хранения данных. Смотрите Хранение данных таблицы на нескольких блочных устройствах.
- min_bytes_for_wide_part,- min_rows_for_wide_part— минимальное количество байт/строк в куске данных для хранения в формате- Wide. Можно задать одну или обе настройки или не задавать ни одной. Подробнее см. в разделе Хранение данных.
- max_parts_in_total— максимальное количество кусков во всех партициях.
- max_compress_block_size— максимальный размер блоков несжатых данных перед сжатием для записи в таблицу. Вы также можете задать этот параметр в глобальных настройках (смотрите max_compress_block_size). Настройка, которая задается при создании таблицы, имеет более высокий приоритет, чем глобальная.
- min_compress_block_size— минимальный размер блоков несжатых данных, необходимых для сжатия при записи следующей засечки. Вы также можете задать этот параметр в глобальных настройках (смотрите min_compress_block_size). Настройка, которая задается при создании таблицы, имеет более высокий приоритет, чем глобальная.
- max_partitions_to_read— Ограничивает максимальное число партици й для чтения в одном запросе. Также возможно указать настройку max_partitions_to_read в глобальных настройках.
 
Пример задания секций
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192
В примере мы устанавливаем партиционирование по месяцам.
Также мы задаем выражение для сэмплирования в виде хэша по идентификатору посетителя. Это позволяет псевдослучайным образом перемешать данные в таблице для каждого CounterID и EventDate. Если при выборке данных задать секцию SAMPLE, то ClickHouse вернёт равномерно-псевдослучайную выборку данных для п одмножества посетителей.
index_granularity можно было не указывать, поскольку 8192 — это значение по умолчанию.
Устаревший способ создания таблицы
Не используйте этот способ в новых проектах и по возможности переведите старые проекты на способ, описанный выше.
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
) ENGINE [=] MergeTree(date-column [, sampling_expression], (primary, key), index_granularity)
Параметры MergeTree()
- date-column— имя столбца с типом Date. На основе этого столбца ClickHouse автоматически создаёт партиции по месяцам. Имена партиций имеют формат- "YYYYMM".
- sampling_expression— выражение для сэмплирования.
- (primary, key)— первичный ключ. Тип — Tuple()
- index_granularity— гранулярность индекса. Число строк данных между «засечками» индекса. Для большинства задач подходит значение 8192.
Пример
MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)
Движок MergeTree сконфигурирован таким же образом, как и в примере выше для основного способа конфигурирования движка.
Хранение данных
Таблица состоит из кусков данных (data parts), отсортированных по первичному ключу.
При вставке в таблицу создаются отдельные куски данных, каждый из которых лексикографически отсортирован по первичному ключу. Например, если первичный ключ — (CounterID, Date), то данные в куске будут лежать в порядке CounterID, а для каждого CounterID в порядке Date.
Данные, относящиеся к разным партициям, разбиваются на разные куски. В фоновом режиме ClickHouse выполняет слияния (merge) кусков данных для более эффективного хранения. Куски, относящиеся к разным партициям не объединяются. Механизм слияния не гарантирует, что все строки с одинаковым первичным ключом окажутся в одном куске.
Куски данных могут храниться в формате Wide или Compact. В формате Wide каждый столбец хранится в отдельном файле, а в формате Compact все столбцы хранятся в одном файле. Формат Compact может быть полезен для повышения производительности при частом добавлении небольших объемов данных.
Формат хранения определяется настройками движка min_bytes_for_wide_part и min_rows_for_wide_part. Если число байт или строк в куске данных меньше значения, указанного в соответствующей настройке, тогда этот кусок данных хранится в формате Compact. В противном случае кусок данных хранится в формате Wide. Если ни одна из настроек не задана, куски данных хранятся в формате Wide.
Каждый кусок данных логически делится на гранулы. Гранула — это минимальный неделимый набор данных, который ClickHouse считывает при выборке данных. ClickHouse не разбивает строки и значения и гранула всегда содержит целое число строк. Первая строка гранулы помечается значением первичного ключа для этой строки (засечка). Для каждого куска данных ClickHouse создаёт файл с засечками (индексный файл). Для каждого столбца, независимо от того, входит он в первичный ключ или нет, ClickHouse также сохраняет эти же засечки. Засечки используются для поиска данных напрямую в файлах столбцов.
Размер гранул оганичен настройками движка index_granularity и index_granularity_bytes. Количество строк в грануле лежит в диапазоне [1, index_granularity], в зависимости от размера строк. Размер гранулы может превышать index_granularity_bytes в том случае, когда размер единственной строки в грануле превышает значение настройки. В этом случае, размер гранулы равен размеру строки.
Первичные ключи и индексы в запросах
Рассмотрим первичный ключ — (CounterID, Date). В этом случае сортировку и индекс можно проиллюстрировать следующим образом:
Whole data:     [-------------------------------------------------------------------------]
CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
Marks:           |      |      |      |      |      |      |      |      |      |      |
                a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
Marks numbers:   0      1      2      3      4      5      6      7      8      9      10
Если в запросе к данным указать:
- CounterID IN ('a', 'h'), то сервер читает данные в диапазонах засечек- [0, 3)и- [6, 8).
- CounterID IN ('a', 'h') AND Date = 3, то сервер читает данные в диапазонах засечек- [1, 3)и- [7, 8).
- Date = 3, то сервер читает данные в диапазоне засечек- [1, 10].
Примеры выше показывают, что использование индекса всегда эффективнее, чем full scan.
Разреженный индекс допускает чтение лишних строк. При чтении одного диапазона первичного ключа, может быть прочитано до index_granularity * 2 лишних строк в каждом блоке данных.
Разреженный индекс почти всегда помещается в оперативную память и позволяет работать с очень большим количеством строк в таблицах.
ClickHouse не требует уникального первичного ключа. Можно вставить много строк с одинаковым первичным ключом.
Ключ в PRIMARY KEY и ORDER BY может иметь тип Nullable. За поддержку этой возможности отвечает настройка allow_nullable_key.
При сортировке с использованием выражения ORDER BY для знач ений NULL всегда работает принцип NULLS_LAST.
Выбор первичного ключа
Количество столбцов в первичном ключе не ограничено явным образом. В зависимости от структуры данных в первичный ключ можно включать больше или меньше столбцов. Это может:
- 
Увеличить эффективность индекса. Пусть первичный ключ — (a, b), тогда добавление ещё одного столбцаcповысит эффективность, если выполнены условия:- Есть запросы с условием на столбец c.
- Часто встречаются достаточно длинные (в несколько раз больше index_granularity) диапазоны данных с одинаковыми значениями(a, b). Иначе говоря, когда добавление ещё одного столбца позволит пропускать достаточно длинные диапазоны данных.
 
- Есть запросы с условием на столбец 
- 
Улучшить сжатие данных. ClickHouse сортирует данные по первичному ключу, поэтому чем выше однородность, тем лучше сжатие. 
- 
Обеспечить дополнительную логику при слиянии кусков данных в движках CollapsingMergeTree и SummingMergeTree. В этом случае имеет смысл указать отдельный ключ сортировки, отличающийся от первичного ключа. 
Длинный первичный ключ будет негативно влиять на производительность вставки и потребление памяти, однако на производительность ClickHouse при запросах SELECT лишние столбцы в первичном ключе не влияют.
Вы можете создать таблицу без первичного ключа, используя синтаксис ORDER BY tuple(). В этом случае ClickHouse хранит данные в порядке вставки. Если вы хотите сохранить порядок данных при вставке данных с помощью запросов INSERT ... SELECT, установите max_insert_threads = 1.
Чтобы выбрать данные в первоначальном порядке, используйте однопоточные запросы `SELECT.
Первичный ключ, отличный от ключа сортировки
Существует возможность задать первичный ключ (выражение, значения которого будут записаны в индексный файл для каждой засечки), отличный от ключа сортировки (выражение, по которому будут упорядочены строки в кусках данных). Кортеж выражения первичного ключа при этом должен быть префиксом кортежа выражения ключа сортировки.
Данная возможность особенно полезна при использовании движков SummingMergeTree
и AggregatingMergeTree. В типичном сценарии использования этих движков таблица
содержит столбцы двух типов: измерения (dimensions) и меры (measures). Типичные запросы агрегируют
значения столбцов-мер с произвольной группировкой и фильтрацией по измерениям. Так как SummingMergeTree
и AggregatingMergeTree производят фоновую агрегацию строк с одинаковым значением ключа сортировки, приходится
добавлять в него все столбцы-измерения. В результате выражение ключа содержит большой список столбцов,
который приходится постоянно расширять при добавлении новых измерений.