ClickHouse/docs/ru/operations/table_engines/mergetree.md

188 lines
18 KiB
Markdown
Raw Normal View History

<a name="table_engines-mergetree"></a>
# MergeTree
2018-08-21 07:54:37 +00:00
Движок `MergeTree`, а также другие движки этого семейства (`*MergeTree`) — это наиболее функциональные движки таблиц ClickHousе.
2018-08-21 07:54:37 +00:00
!!!info
Движок [Merge](merge.md#table_engine-merge) не относится к семейству `*MergeTree`.
2018-08-21 07:54:37 +00:00
Основные возможности:
- Хранит данные, отсортированные по первичному ключу.
Это позволяет создавать разреженный индекс небольшого объёма, который позволяет быстрее находить данные.
- Позволяет оперировать партициями, если задан [ключ партиционирования](custom_partitioning_key.md#table_engines-custom_partitioning_key).
ClickHouse поддерживает отдельные операции с партициями, которые работают эффективнее, чем общие операции с этим же результатом над этими же данными. Также, ClickHouse автоматически отсекает данные по партициям там, где ключ партиционирования указан в запросе. Это также увеличивает эффективность выполнения запросов.
- Поддерживает репликацию данных.
2018-08-22 06:29:36 +00:00
Для этого используется семейство таблиц `ReplicatedMergeTree`. Подробнее читайте в разделе [Репликация данных](replication.md#table_engines-replication).
2018-08-21 07:54:37 +00:00
- Поддерживает сэмплирование данных.
При необходимости можно задать способ сэмплирования данных в таблице.
## Конфигурирование движка при создании таблицы
```
ENGINE [=] MergeTree() [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
```
**Секции ENGINE**
- `ORDER BY` — первичный ключ.
2018-08-22 06:38:01 +00:00
Кортеж столбцов или произвольных выражений. Пример: `ORDER BY (CounerID, EventDate)`.
2018-08-22 06:37:05 +00:00
Если используется ключ сэмплирования, то первичный ключ должен содержать его. Пример: `ORDER BY (CounerID, EventDate, intHash32(UserID))`.
2018-08-21 07:54:37 +00:00
2018-08-22 06:37:35 +00:00
- `PARTITION BY` — [ключ партиционирования](custom_partitioning_key.md#table_engines-custom_partitioning_key).
Для партиционирования по месяцам используйте выражение `toYYYYMM(date_column)`, где `date_column` — столбец с датой типа [Date](../../data_types/date.md#data_type-date). В этом случае имена партиций имеют формат `"YYYYMM"`.
2018-08-22 06:37:05 +00:00
- `SAMPLE BY` — выражение для сэмплирования (не обязательно). Пример: `intHash32(UserID))`.
2018-08-22 06:32:16 +00:00
- `SETTINGS` — дополнительные параметры, регулирующие поведение `MergeTree` (не обязательно):
2018-08-21 07:54:37 +00:00
- `index_granularity` — гранулярность индекса. Число строк данных между «засечками» индекса. По умолчанию — 8192.
**Пример**
```
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192
```
В примере мы устанавливаем партиционирование по месяцам.
Также мы задаем выражение для сэмплирования в виде хэша по идентификатору посетителя. Это позволяет псевдослучайным образом перемешать данные в таблице для каждого `CounterID` и `EventDate`. Если при выборке данных задать секцию [SAMPLE](../../query_language/select.md#select-section-sample) то ClickHouse вернёт равномерно-псевдослучайную выборку данных для подмножества посетителей.
`index_granularity` можно было не указывать, поскольку 8192 — это значение по умолчанию.
### Устаревший способ конфигурирования движка
!!!attention
Не используйте этот способ в новых проектах и по возможности переведите старые проекты на способ описанный выше.
```
2018-08-21 07:54:37 +00:00
ENGINE [=] MergeTree(date-column [, sampling_expression], (primary, key), index_granularity)
```
**Параметры MergeTree()**
2018-08-21 07:54:37 +00:00
- `date-column` — имя столбца с типом [Date](../../data_types/date.md#data_type-date). На основе этого столбца ClickHouse автоматически создаёт партиции по месяцам. Имена партиций имеют формат `"YYYYMM"`.
- `sampling_expression` — выражение для сэмплирования.
- `(primary, key)` — первичный ключ. Тип — [Tuple()](../../data_types/tuple.md#data_type-tuple). Может состоять из произвольных выражений, но обычно это кортеж столбцов. Обязательно должен включать в себя выражение для сэмплирования, если оно задано. Не обязан включать в себя столбец с датой `date-column`.
- `index_granularity` — гранулярность индекса. Число строк данных между «засечками» индекса. Для большинства задач подходит значение 8192.
2018-08-21 07:54:37 +00:00
**Пример**
```
MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)
```
2018-08-21 07:54:37 +00:00
Движок `MergeTree` сконфигурирован таким же образом, как и в примере выше для основного способа конфигурирования движка.
## Хранение данных
Таблица хранится блоками данных, отсортированных по первичному ключу. Каждый блок маркируется максимальной и минимальной датами хранящихся в нём записей.
При вставке в таблицу создаются отдельные блоки данных, каждый из которых лексикографически отсортирован по первичному ключу. Например, если первичный ключ — `(CounterID, Date)`, то данные в блоке будут лежать в порядке `CounterID`, а для каждого `CounterID` в порядке `Date`.
Данные, относящиеся к разным месяцам разбиваются на разные блоки. В дальнейшем ClickHouse в фоновом режиме объединяет мелкие блоки в более крупные для более эффективного хранения. Блоки, относящиеся к разным месяцам не объединяются, это локализует модификации и упрощает бэкапы. Поддерживается запрос `OPTIMIZE`, который вызывает один внеочередной шаг слияния.
Для каждого блока данных ClickHouse создаёт индексный файл, который содержит значение первичного ключа для каждой индексной строки («засечка»). Номера индексных строк определяются как `n * index_granularity`, а максимальное значение `n` равно целой части от деления общего количества строк на `index_granularity`. Для каждого столбца также пишутся «засечки» для тех же индексных строк, что и для первичного ключа, эти «засечки» позволяют находить непосредственно данные в столбцах.
Вы можете использовать одну большую таблицу, постоянно добавляя в неё данные небольшими пачками, именно для этого предназначен движок `MergeTree`.
## Первичные ключи и индексы в запросах
Рассмотрим первичный ключ — `(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.
2018-08-21 07:54:37 +00:00
Разреженный индекс допускает чтение лишних строк. При чтении одного диапазона первичного ключа, может быть прочитано до `index_granularity * 2` лишних строк в каждом блоке данных. В большинстве случаев ClickHouse не теряет производительности при `index_granularity = 8192`.
2018-08-21 07:54:37 +00:00
Разреженность индекса позволяет работать даже с очень большим количеством строк в таблицах, поскольку такой индекс всегда помещается в оперативную память компьютера.
2018-08-21 07:54:37 +00:00
ClickHouse не требует уникального первичного ключа. Можно вставить много строк с одинаковым первичным ключом.
2018-08-21 07:54:37 +00:00
### Выбор первичного ключа
2018-08-21 07:54:37 +00:00
Количество столбцов в первичном ключе не ограничено явным образом. В зависимости от структуры данных в первичный ключ можно включать больше или меньше столбцов. Это может:
2018-08-21 07:54:37 +00:00
- Увеличить эффективность индекса.
2018-08-21 07:54:37 +00:00
Пусть первичный ключ — `(a, b)`, тогда добавление ещё одного столбца `c` повысит эффективность, если выполнены условия:
2018-08-21 07:54:37 +00:00
- Есть запросы с условием на столбец `c`.
- Часто встречаются достаточно длинные (в несколько раз больше `index_granularity`) диапазоны данных с одинаковыми значениями `(a, b)`. Иначе говоря, когда добавление ещё одного столбца позволит пропускать достаточно длинные диапазоны данных.
2018-08-21 07:54:37 +00:00
- Улучшить сжатие данных.
ClickHouse сортирует данные по первичному ключу, поэтому чем выше однородность, тем лучше сжатие.
- Обеспечить дополнительную логику при слиянии в движках [CollapsingMergeTree](collapsingmergetree.md#table_engine-collapsingmergetree) и [SummingMergeTree](summingmergetree.md#table_engine-summingmergetree).
Может потребоваться иметь много полей в первичном ключе, даже если они не нужны для выполнения предыдущих пунктов.
Длинный первичный ключ будет негативно влиять на производительность вставки и потребление памяти, однако на производительность ClickHouse при запросах `SELECT` лишние столбцы в первичном ключе не влияют.
### Использование индексов и партиций в запросах
Для запросов `SELECT` ClickHouse анализирует возможность использования индекса. Индекс может использоваться, если в секции `WHERE/PREWHERE`, в качестве одного из элементов конъюнкции, или целиком, есть выражение, представляющее операции сравнения на равенства, неравенства, а также `IN` или `LIKE` с фиксированным префиксом, над столбцами или выражениями, входящими в первичный ключ или ключ партиционирования, либо над некоторыми частично монотонными функциями от этих столбцов, а также логические связки над такими выражениями.
2018-03-15 11:48:19 +00:00
Таким образом, обеспечивается возможность быстро выполнять запросы по одному или многим диапазонам первичного ключа. Например, в указанном примере будут быстро работать запросы для конкретного счётчика; для конкретного счётчика и диапазона дат; для конкретного счётчика и даты, для нескольких счётчиков и диапазона дат и т. п.
2018-08-21 07:54:37 +00:00
Рассмотрим движок сконфигурированный следующим образом:
```
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192
```
В этом случае в запросах:
```sql
SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
SELECT count() FROM table WHERE ((EventDate >= toDate('2014-01-01') AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01')) AND CounterID IN (101500, 731962, 160656) AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))
```
2018-08-21 07:54:37 +00:00
ClickHouse будет использовать индекс по первичному ключу для отсечения не подходящих данных, а также ключ партиционирования по месяцам для отсечения партиций, которые находятся в не подходящих диапазонах дат.
Запросы выше показывают, что индекс используется даже для сложных выражений. Чтение из таблицы организовано так, что использование индекса не может быть медленнее, чем full scan.
2018-08-21 07:54:37 +00:00
В примере ниже индекс не может использоваться.
```sql
SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'
```
Чтобы проверить, сможет ли ClickHouse использовать индекс при выполнении запроса, используйте настройки [force_index_by_date](../settings/settings.md#settings-settings-force_index_by_date) и [force_primary_key](../settings/settings.md#settings-settings-force_primary_key).
2018-08-21 07:54:37 +00:00
Ключ партиционирования по месяцам обеспечивает чтение только тех блоков данных, которые содержат даты из нужного диапазона. При этом блок данных может содержать данные за многие даты (до целого месяца). В пределах одного блока данные упорядочены по первичному ключу, который может не содержать дату в качестве первого столбца. В связи с этим, при использовании запроса с указанием условия только на дату, но не на префикс первичного ключа, будет читаться данных больше, чем за одну дату.
2018-08-21 07:54:37 +00:00
## Конкурентный доступ к данным
2018-08-21 07:54:37 +00:00
Для конкурентного доступа к таблице используется мультиверсионность. То есть, при одновременном чтении и обновлении таблицы, данные будут читаться из набора кусочков, актуального на момент запроса. Длинных блокировок нет. Вставки никак не мешают чтениям.
2018-08-21 07:54:37 +00:00
Чтения из таблицы автоматически распараллеливаются.