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.