> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> Движки таблиц семейства `MergeTree` предназначены для высокой скорости приёма данных и очень больших объёмов данных.

# Движок таблицы MergeTree

export const CloudNotSupportedBadge = () => {
  return <div className="cloudNotSupportedBadge">
            <div className="cloudNotSupportedIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path strokeWidth="1.5" d="M6.33366 12.6666L12.3739 12.6667C13.6593 12.6667 14.7073 11.6187 14.7073 10.3334C14.7073 9.04804 13.6593 8.00003 12.3739 8.00003C12.3739 8.00003 12.3337 7.66659 12.0003 7.33325M10.667 5.33322C8.00033 2.33325 4.45395 4.78537 4.14195 6.68203C2.55728 6.7627 1.29395 8.06203 1.29395 9.6667C1.29395 11.3234 2.66699 12.6666 4.00033 12.6666" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.5" d="M2.66699 14L12.0003 4.66663" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
            </svg>

        </div>
            Не поддерживается в ClickHouse Cloud
        </div>;
};

export const ExperimentalBadge = () => {
  return <div className="experimentalBadge">
            <div className="experimentalIcon">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path strokeWidth="1.25" d="M5.5 2H10.5" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M9.50015 2V6.19625L13.4283 12.7425C13.4738 12.8183 13.4985 12.9049 13.4996 12.9934C13.5008 13.0818 13.4785 13.169 13.435 13.246C13.3914 13.323 13.3283 13.3871 13.2519 13.4317C13.1755 13.4764 13.0886 13.4999 13.0002 13.5H3.00015C2.91164 13.5 2.8247 13.4766 2.74822 13.432C2.67174 13.3874 2.60847 13.3233 2.56487 13.2463C2.52126 13.1693 2.49889 13.082 2.50004 12.9935C2.50119 12.905 2.52582 12.8184 2.5714 12.7425L6.50015 6.19625V2" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
                <path strokeWidth="1.25" d="M4.47656 9.56754C5.30344 9.41254 6.47656 9.47942 7.99969 10.25C10.0153 11.2707 11.4216 11.0569 12.2184 10.7282" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
        </div>
            Экспериментальная возможность. <u><a href="/docs/docs/beta-and-experimental-features#experimental-features">Подробнее.</a></u>
        </div>;
};

Движок `MergeTree` и другие движки семейства `MergeTree` (например, `ReplacingMergeTree`, `AggregatingMergeTree`) — наиболее широко используемые и самые надёжные движки таблиц в ClickHouse.

Движки таблиц семейства `MergeTree` рассчитаны на высокую скорость приёма данных и очень большие объёмы данных.
Операции вставки создают части таблицы, которые затем фоновый процесс объединяет с другими частями таблицы.

Основные возможности движков таблиц семейства `MergeTree`.

* Первичный ключ таблицы определяет порядок сортировки внутри каждой части таблицы (кластерный индекс). При этом первичный ключ ссылается не на отдельные строки, а на блоки по 8192 строк, называемые гранулами. Благодаря этому первичные ключи даже для очень больших наборов данных остаются достаточно компактными, чтобы помещаться в оперативной памяти, и при этом обеспечивают быстрый доступ к данным на диске.

* Таблицы можно разбивать на партиции с помощью произвольного выражения партиционирования. Отсечение партиций позволяет не читать партиции, если это допускает запрос.

* Данные могут реплицироваться между несколькими узлами кластера для высокой доступности, переключения при сбоях и обновлений без простоя. См. [Репликация данных](/docs/ru/reference/engines/table-engines/mergetree-family/replication).

* Движки таблиц `MergeTree` поддерживают различные виды статистики и методы сэмплирования, помогающие оптимизации запросов.

<Note>
  Несмотря на похожее название, движок [Merge](/docs/ru/reference/engines/table-engines/special/merge) отличается от движков `*MergeTree`.
</Note>

<div id="table_engine-mergetree-creating-a-table">
  ## Создание таблиц
</div>

```sql theme={null}
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [[NOT] NULL] [DEFAULT|MATERIALIZED|ALIAS|EPHEMERAL expr1] [COMMENT ...] [CODEC(codec1)] [STATISTICS(stat1)] [TTL expr1] [PRIMARY KEY] [SETTINGS (name = value, ...)],
    name2 [type2] [[NOT] NULL] [DEFAULT|MATERIALIZED|ALIAS|EPHEMERAL expr2] [COMMENT ...] [CODEC(codec2)] [STATISTICS(stat2)] [TTL expr2] [PRIMARY KEY] [SETTINGS (name = value, ...)],
    ...
    INDEX index_name1 expr1 TYPE type1(...) [GRANULARITY value1],
    INDEX index_name2 expr2 TYPE type2(...) [GRANULARITY value2],
    ...
    PROJECTION projection_name_1 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY]),
    PROJECTION projection_name_2 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY])
) 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 TABLE](/docs/ru/reference/statements/create/table)

<div id="mergetree-query-clauses">
  ### Секции запроса
</div>

<div id="engine">
  #### ENGINE
</div>

`ENGINE` — имя и параметры движка. `ENGINE = MergeTree()`. У движка `MergeTree` нет параметров.

<div id="order_by">
  #### ORDER BY
</div>

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

Кортеж из имён столбцов или произвольных выражений. Пример: `ORDER BY (CounterID + 1, EventDate)`.

Если первичный ключ не определён (то есть `PRIMARY KEY` не был указан), ClickHouse использует ключ сортировки в качестве первичного ключа.

Если сортировка не нужна, можно использовать синтаксис `ORDER BY tuple()`.
Либо, если настройка `create_table_empty_primary_key_by_default` включена, в команды `CREATE TABLE` неявно добавляется `ORDER BY ()`. См. [Выбор первичного ключа](#selecting-a-primary-key).

<div id="partition-by">
  #### PARTITION BY
</div>

`PARTITION BY` — [ключ партиционирования](/docs/ru/reference/engines/table-engines/mergetree-family/custom-partitioning-key). Необязателен. В большинстве случаев ключ партиционирования не нужен, а если партиционирование всё же требуется, то, как правило, ключ партиционирования с детализацией мельче месяца тоже не нужен. Партиционирование не ускоряет запросы (в отличие от выражения ORDER BY). Никогда не используйте слишком мелкое партиционирование. Не разбивайте данные на партиции по идентификаторам или именам клиентов (вместо этого сделайте идентификатор или имя клиента первым столбцом в выражении ORDER BY).

Для партиционирования по месяцам используйте выражение `toYYYYMM(date_column)`, где `date_column` — столбец с датой типа [Date](/docs/ru/reference/data-types/date). Имена партиций здесь имеют формат `"YYYYMM"`.

<div id="primary-key">
  #### PRIMARY KEY
</div>

`PRIMARY KEY` — первичный ключ, если он [отличается от ключа сортировки](#choosing-a-primary-key-that-differs-from-the-sorting-key). Необязателен.

При указании ключа сортировки (с помощью предложения `ORDER BY`) первичный ключ задаётся неявно.
Обычно отдельно указывать первичный ключ помимо ключа сортировки не требуется.

<div id="sample-by">
  #### SAMPLE BY
</div>

`SAMPLE BY` — выражение для семплирования. Необязательный параметр.

Если оно указано, то должно входить в первичный ключ.
Выражение для семплирования должно возвращать беззнаковое целое число.

Пример: `SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))`.

<div id="ttl">
  #### TTL
</div>

`TTL` — список правил, задающих срок хранения строк и логику автоматического перемещения частей [между дисками и томами](#table_engine-mergetree-multiple-volumes). Необязательный параметр.

Выражение должно возвращать `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 для столбцов и таблиц](#table_engine-mergetree-ttl)

<div id="settings">
  #### НАСТРОЙКИ
</div>

См. [настройки MergeTree](/docs/ru/reference/settings/merge-tree-settings).

**Пример для настройки Sections**

```sql theme={null}
ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192
```

В примере мы настраиваем партиционирование по месяцам.

Мы также задаём выражение для семплирования в виде хеша по идентификатору пользователя. Это позволяет псевдослучайно распределить данные в таблице для каждого `CounterID` и `EventDate`. Если при выборке данных указать предложение [SAMPLE](/docs/ru/reference/statements/select/sample), ClickHouse вернёт равномерную псевдослучайную выборку данных для подмножества пользователей.

Настройку `index_granularity` можно опустить, поскольку 8192 — значение по умолчанию.

<details markdown="1">
  <summary>Устаревший метод создания таблицы</summary>

  <Note>
    Не используйте этот метод в новых проектах. По возможности переведите старые проекты на метод, описанный выше.
  </Note>

  ```sql theme={null}
  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](/docs/ru/reference/data-types/date). ClickHouse автоматически создаёт партиции по месяцам на основе этого столбца. Имена партиций имеют формат `"YYYYMM"`.
  * `sampling_expression` — Выражение для семплирования.
  * `(primary, key)` — Первичный ключ. Тип: [Tuple()](/docs/ru/reference/data-types/tuple)
  * `index_granularity` — Гранулярность индекса. Количество строк данных между "метками" индекса. Значение 8192 подходит для большинства задач.

  **Пример**

  ```sql theme={null}
  MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)
  ```

  Движок `MergeTree` настраивается так же, как и в приведённом выше примере для основного способа настройки движка.
</details>

<div id="mergetree-data-storage">
  ## Хранение данных
</div>

Таблица состоит из частей данных, отсортированных по первичному ключу.

При вставке данных в таблицу создаются отдельные части данных, и каждая из них лексикографически упорядочивается по первичному ключу. Например, если первичный ключ — `(CounterID, Date)`, данные в части сортируются по `CounterID`, а внутри каждого `CounterID` — по `Date`.

Данные, относящиеся к разным партициям, разделяются на разные части. В фоновом режиме ClickHouse выполняет слияние частей данных для более эффективного хранения. Части, относящиеся к разным партициям, не сливаются. Механизм слияния не гарантирует, что все строки с одинаковым первичным ключом окажутся в одной и той же части данных.

Части данных могут храниться в формате `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`, если размер одной строки больше значения этой настройки. В этом случае размер гранулы равен размеру строки.

<div id="primary-keys-and-indexes-in-queries">
  ## Первичные ключи и индексы в запросах
</div>

Рассмотрим в качестве примера первичный ключ `(CounterID, Date)`. В этом случае сортировку и индекс можно представить следующим образом:

```text theme={null}
Все данные:     [---------------------------------------------]
CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
Метки:           |      |      |      |      |      |      |      |      |      |      |
                a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
Номера меток:    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]`.

Приведённые выше примеры показывают, что использовать индекс всегда эффективнее, чем выполнять полное сканирование.

Разреженный индекс допускает чтение дополнительных данных. При чтении одного диапазона первичного ключа в каждом блоке данных может быть прочитано до `index_granularity * 2` дополнительных строк.

Разреженные индексы позволяют работать с очень большим количеством строк таблицы, потому что в большинстве случаев такие индексы помещаются в оперативную память компьютера.

ClickHouse не требует уникального первичного ключа. Вы можете вставлять несколько строк с одинаковым первичным ключом.

Вы можете использовать выражения типа `Nullable` в секциях `PRIMARY KEY` и `ORDER BY`, но делать это настоятельно не рекомендуется. Чтобы включить эту возможность, включите настройку [allow\_nullable\_key](/docs/ru/reference/settings/merge-tree-settings#allow_nullable_key). Для значений `NULL` в секции `ORDER BY` применяется принцип [NULLS\_LAST](/docs/ru/reference/statements/select/order-by#sorting-of-special-values).

<div id="selecting-a-primary-key">
  ### Выбор первичного ключа
</div>

Количество столбцов в первичном ключе явно не ограничено. В зависимости от структуры данных вы можете включить в первичный ключ больше или меньше столбцов. Это может:

* Повысить производительность индекса.

  Если первичный ключ — `(a, b)`, то добавление ещё одного столбца `c` повысит производительность, если выполняются следующие условия:

  * Есть запросы с условием по столбцу `c`.
  * Часто встречаются длинные диапазоны данных (в несколько раз длиннее, чем `index_granularity`) с одинаковыми значениями `(a, b)`. Иными словами, если добавление ещё одного столбца позволяет пропускать достаточно длинные диапазоны данных.

* Улучшить сжатие данных.

  ClickHouse сортирует данные по первичному ключу, поэтому чем выше упорядоченность данных, тем лучше сжатие.

* Обеспечить дополнительную логику при слиянии частей данных в движках [CollapsingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/collapsingmergetree) и [SummingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/summingmergetree).

  В этом случае имеет смысл указать *ключ сортировки*, который отличается от первичного ключа.

Длинный первичный ключ отрицательно скажется на производительности вставки и потреблении памяти, но дополнительные столбцы в первичном ключе не влияют на производительность ClickHouse при выполнении запросов `SELECT`.

Вы можете создать таблицу без первичного ключа, используя синтаксис `ORDER BY tuple()`. В этом случае ClickHouse хранит данные в порядке их вставки. Если вы хотите сохранить порядок данных при вставке с помощью запросов `INSERT ... SELECT`, установите [max\_insert\_threads = 1](/docs/ru/reference/settings/session-settings#max_insert_threads).

Чтобы выбирать данные в исходном порядке, используйте [однопоточные](/docs/ru/reference/settings/session-settings#max_threads) запросы `SELECT`.

<div id="choosing-a-primary-key-that-differs-from-the-sorting-key">
  ### Выбор первичного ключа, отличающегося от ключа сортировки
</div>

Можно указать первичный ключ (выражение со значениями, которые записываются в индексный файл для каждой метки), отличный от ключа сортировки (выражения для сортировки строк в частях данных). В этом случае кортеж выражения первичного ключа должен быть префиксом кортежа выражения ключа сортировки.

Эта возможность полезна при использовании движков таблиц [SummingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/summingmergetree) и
[AggregatingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/aggregatingmergetree). В типичном случае при использовании этих движков таблица содержит два типа столбцов: *измерения* и *меры*. Обычно запросы агрегируют значения столбцов-мер с произвольным `GROUP BY` и фильтрацией по измерениям. Поскольку SummingMergeTree и AggregatingMergeTree агрегируют строки с одинаковым значением ключа сортировки, логично включить в него все измерения. В результате выражение ключа представляет собой длинный список столбцов, и этот список приходится часто обновлять по мере добавления новых измерений.

В этом случае имеет смысл оставить в первичном ключе только несколько столбцов, которые обеспечат эффективное сканирование диапазонов, а остальные столбцы измерений добавить в кортеж ключа сортировки.

[ALTER](/docs/ru/reference/statements/alter/index) ключа сортировки — лёгкая операция, потому что, когда новый столбец одновременно добавляется в таблицу и в ключ сортировки, существующие части данных не нужно изменять. Поскольку старый ключ сортировки является префиксом нового ключа сортировки и в только что добавленном столбце ещё нет данных, в момент изменения таблицы данные уже отсортированы и по старому, и по новому ключу сортировки.

<div id="use-of-indexes-and-partitions-in-queries">
  ### Использование индексов и партиций в запросах
</div>

Для запросов `SELECT` ClickHouse анализирует, можно ли использовать индекс. Индекс может быть использован, если в условии `WHERE/PREWHERE` есть выражение (либо как один из элементов конъюнкции, либо целиком), представляющее собой операцию сравнения на равенство или неравенство, либо если в нём есть `IN` или `LIKE` с фиксированным префиксом для столбцов или выражений, входящих в первичный ключ или ключ партиционирования, а также для некоторых частично повторяющихся функций от этих столбцов или логических связей этих выражений.

Таким образом, можно быстро выполнять запросы по одному или нескольким диапазонам первичного ключа. В этом примере запросы будут выполняться быстро для конкретного тега отслеживания, для конкретного тега и диапазона дат, для конкретного тега и даты, для нескольких тегов в диапазоне дат и так далее.

Давайте рассмотрим движок, настроенный следующим образом:

```sql theme={null}
ENGINE MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate)
SETTINGS index_granularity=8192
```

В таком случае в запросах:

```sql theme={null}
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'))
```

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

Приведенные выше запросы показывают, что индекс используется даже для сложных выражений. Чтение из таблицы организовано так, что использование индекса не может быть медленнее полного сканирования.

В примере ниже индекс использовать нельзя.

```sql theme={null}
SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'
```

Чтобы проверить, может ли ClickHouse использовать индекс при выполнении запроса, воспользуйтесь настройками [force\_index\_by\_date](/docs/ru/reference/settings/session-settings#force_index_by_date) и [force\_primary\_key](/docs/ru/reference/settings/session-settings#force_primary_key).

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

<div id="use-of-index-for-deterministic-expressions-in-primary-keys">
  ### Использование индекса для детерминированных выражений в первичных ключах
</div>

Первичный ключ может содержать не только имена столбцов, но и выражения. Эти выражения не ограничиваются простыми цепочками функций: это могут быть произвольные деревья выражений (например, вложенные функции и составные выражения), если они детерминированы.

Выражение считается **детерминированным**, если оно всегда возвращает один и тот же результат для одних и тех же входных значений (например: `length()`, `toDate()`, `lower()`, `left()`, `cityHash64()`, `toUUID()`; в отличие от `now()` или `rand()`). Если первичный ключ содержит детерминированные выражения, ClickHouse может применить их к константным значениям из запроса и использовать результат для построения условий по индексу первичного ключа. Это позволяет пропускать данные для таких предикатов, как `=`, `IN` и `has`.

Распространённый сценарий — сделать первичный ключ компактным (например, хранить хеш вместо длинной `String`), при этом сохранив возможность использовать индекс для предикатов по исходному столбцу.

Пример детерминированного (но не инъективного) первичного ключа:

```sql theme={null}
ENGINE = MergeTree()
ORDER BY length(user_id)
```

Примеры предикатов, для которых может использоваться индекс:

```sql theme={null}
SELECT * FROM table WHERE user_id = 'alice';
SELECT * FROM table WHERE user_id IN ('alice', 'bob');
SELECT * FROM table WHERE has(['alice', 'bob'], user_id);
```

В этих случаях ClickHouse вычисляет `length('alice')` (и другие константы) один раз и использует значения длины, чтобы сузить диапазоны в индексе первичного ключа. Поскольку длина строки **неинъективна**, разные строки `user_id` могут иметь одинаковую длину, поэтому индекс может считывать лишние гранулы (ложноположительные срабатывания). При этом результат остаётся корректным, поскольку после чтения всё равно применяется исходный предикат (`user_id = ...`, `IN` и т. д.).

Если детерминированное выражение также **инъективно** (разные входные данные не могут давать один и тот же результат для используемых типов аргументов), ClickHouse также может эффективно использовать индекс для отрицательных форм: `!=`, `NOT IN` и `NOT has(...)`. Например, `reverse(p)` и `hex(p)` инъективны для `String`.

Пример инъективного первичного ключа:

```sql theme={null}
ENGINE = MergeTree()
ORDER BY hex(p)
```

Поддерживаются и более сложные инъективные выражения, например:

```sql theme={null}
ENGINE = MergeTree()
ORDER BY reverse(tuple(reverse(p), hex(p)))
```

Примеры предикатов, для которых можно использовать индекс:

```sql theme={null}
SELECT * FROM table WHERE p != 'abc';
SELECT * FROM table WHERE p NOT IN ('abc', '12345');
SELECT * FROM table WHERE NOT has(['abc', '12345'], p);
```

<div id="use-of-index-for-partially-monotonic-primary-keys">
  ### Использование индекса для частично-монотонных первичных ключей
</div>

Рассмотрим, например, дни месяца. В пределах одного месяца они образуют [монотонную последовательность](https://en.wikipedia.org/wiki/Monotonic_function), но на более длительных промежутках уже не являются монотонными. Это частично-монотонная последовательность. Если пользователь создает таблицу с частично-монотонным первичным ключом, ClickHouse, как обычно, создает разреженный индекс. При выборке данных из такой таблицы ClickHouse анализирует условия запроса. Если нужно получить данные между двумя метками индекса и обе эти метки находятся в пределах одного месяца, ClickHouse может использовать индекс в этом конкретном случае, поскольку может вычислить расстояние между параметрами запроса и метками индекса.

ClickHouse не может использовать индекс, если значения первичного ключа в диапазоне параметров запроса не образуют монотонную последовательность. В этом случае ClickHouse выполняет полное сканирование.

ClickHouse применяет эту логику не только к последовательностям дней месяца, но и к любому первичному ключу, представляющему собой частично-монотонную последовательность.

<div id="table_engine-mergetree-data_skipping-indexes">
  ### Индексы пропуска данных
</div>

Объявление индекса находится в разделе столбцов запроса `CREATE`.

```sql theme={null}
INDEX index_name expr TYPE type(...) [GRANULARITY granularity_value]
```

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

Эти индексы агрегируют информацию о заданном выражении по блокам, состоящим из `granularity_value` гранул (размер гранулы задаётся настройкой `index_granularity` в движке таблицы). Затем эти агрегаты используются в запросах `SELECT`, чтобы уменьшить объём данных, считываемых с диска, пропуская большие блоки данных, для которых условие `where` не может быть выполнено.

Предложение `GRANULARITY` можно опустить; значение `granularity_value` по умолчанию равно 1.

**Пример**

```sql theme={null}
CREATE TABLE table_name
(
    u64 UInt64,
    i32 Int32,
    s String,
    ...
    INDEX idx1 u64 TYPE bloom_filter GRANULARITY 3,
    INDEX idx2 u64 * i32 TYPE minmax GRANULARITY 3,
    INDEX idx3 u64 * length(s) TYPE set(1000) GRANULARITY 4
) ENGINE = MergeTree()
...
```

ClickHouse может использовать индексы из примера, чтобы сократить объём данных, считываемых с диска, в следующих запросах:

```sql theme={null}
SELECT count() FROM table WHERE u64 == 10;
SELECT count() FROM table WHERE u64 * i32 >= 1234
SELECT count() FROM table WHERE u64 * length(s) == 1234
```

Индексы пропуска данных также можно создавать по составным столбцам:

```sql theme={null}
-- для столбцов типа Map:
INDEX map_key_index mapKeys(map_column) TYPE bloom_filter
INDEX map_value_index mapValues(map_column) TYPE bloom_filter

-- для столбцов типа JSON:
INDEX json_paths_index JSONAllPaths(json_column) TYPE bloom_filter

-- для столбцов типа Tuple:
INDEX tuple_1_index tuple_column.1 TYPE bloom_filter
INDEX tuple_2_index tuple_column.2 TYPE bloom_filter

-- для столбцов типа Nested:
INDEX nested_1_index col.nested_col1 TYPE bloom_filter
INDEX nested_2_index col.nested_col2 TYPE bloom_filter
```

<div id="skip-index-types">
  ### Типы индексов пропуска данных
</div>

Движок таблицы `MergeTree` поддерживает следующие типы индексов пропуска данных.
Дополнительные сведения об использовании индексов пропуска данных для оптимизации производительности
см. в статье ["Принципы работы индексов пропуска данных в ClickHouse"](/docs/ru/concepts/features/performance/skip-indexes/skipping-indexes).

* индекс [`MinMax`](#minmax)
* индекс [`Set`](#set)
* индекс [`bloom_filter`](#bloom-filter)
* индекс [`ngrambf_v1`](#n-gram-bloom-filter) *(Устарело)*
* индекс [`tokenbf_v1`](#token-bloom-filter) *(Устарело)*
* индекс [`text`](#text)
* индекс [`vector_similarity`](#vector-similarity)

<div id="minmax">
  #### Индекс пропуска данных MinMax
</div>

Для каждой гранулы индекса хранятся минимальное и максимальное значения выражения.
(Если выражение имеет тип `tuple`, для каждого элемента кортежа хранятся минимальное и максимальное значения.)

```text title="Syntax" theme={null}
minmax
```

<div id="set">
  #### Set
</div>

Для каждой гранулы индекса хранится не более `max_rows` уникальных значений указанного выражения.
`max_rows = 0` означает "хранить все уникальные значения".

```text title="Syntax" theme={null}
set(max_rows)
```

<div id="bloom-filter">
  #### Bloom-фильтр
</div>

Для каждой гранулы индекса хранится [bloom-фильтр](https://en.wikipedia.org/wiki/Bloom_filter) для указанных столбцов.

```text title="Syntax" theme={null}
bloom_filter([false_positive_rate])
```

Параметр `false_positive_rate` может принимать значение от 0 до 1 (по умолчанию — `0.025`) и задаёт вероятность ложноположительного срабатывания (что увеличивает объём читаемых данных).

Поддерживаются следующие типы данных:

* `(U)Int*`
* `Float*`
* `Enum`
* `Date`
* `DateTime`
* `String`
* `FixedString`
* `Array`
* `LowCardinality`
* `Nullable`
* `UUID`
* `Map`

<Info>
  **Тип данных Map: создание индекса по ключам или значениям**

  Для типа данных `Map` client может указать, должен ли индекс создаваться по ключам или по значениям, с помощью функций [`mapKeys`](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapKeys) или [`mapValues`](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapValues).
</Info>

<Info>
  **Тип данных JSON: индексация JSON-путей**

  Для типа данных [`JSON`](/docs/ru/reference/data-types/newjson) можно создать bloom-фильтр индекс по набору путей с помощью функции [`JSONAllPaths`](/docs/ru/reference/functions/regular-functions/json-functions#JSONAllPaths). Это позволяет пропускать гранулы, в которых отсутствует запрашиваемый JSON-путь. Подробности см. в разделе [Индексы пропуска данных для JSON](/docs/ru/reference/data-types/newjson#data-skipping-indexes-for-json).
</Info>

<div id="n-gram-bloom-filter">
  #### N-граммный bloom-фильтр *(Устарело)*
</div>

<Note>
  Начиная с ClickHouse 26.2, когда индекс `text` получил статус General Availability (GA), индекс `ngrambf_v1` больше не рекомендуется использовать для полнотекстового поиска.

  Подробности см. на странице ["Полнотекстовый поиск с помощью текстовых индексов"](/docs/ru/reference/engines/table-engines/mergetree-family/textindexes).
</Note>

Для каждой гранулы индекса хранится [bloom-фильтр](https://en.wikipedia.org/wiki/Bloom_filter) для [n-грамм](https://en.wikipedia.org/wiki/N-gram) указанных столбцов.

```text title="Syntax" theme={null}
ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
```

| Параметр                        | Описание                                                                                                                          |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `n`                             | размер n-граммы                                                                                                                   |
| `size_of_bloom_filter_in_bytes` | Размер bloom-фильтра в байтах. Здесь можно использовать большое значение, например `256` или `512`, так как он хорошо сжимается). |
| `number_of_hash_functions`      | Количество хеш-функций, используемых в bloom-фильтре.                                                                             |
| `random_seed`                   | seed для хеш-функций bloom-фильтра.                                                                                               |

Этот индекс работает только со следующими типами данных:

* [`String`](/docs/ru/reference/data-types/string)
* [`FixedString`](/docs/ru/reference/data-types/fixedstring)
* [`Map`](/docs/ru/reference/data-types/map)

Чтобы оценить параметры `ngrambf_v1`, можно использовать следующие [пользовательские функции (UDF)](/docs/ru/reference/statements/create/function).

```sql title="UDFs for ngrambf_v1" theme={null}
CREATE FUNCTION bfEstimateFunctions [ON CLUSTER cluster]
AS
(total_number_of_all_grams, size_of_bloom_filter_in_bits) -> round((size_of_bloom_filter_in_bits / total_number_of_all_grams) * log(2));

CREATE FUNCTION bfEstimateBmSize [ON CLUSTER cluster]
AS
(total_number_of_all_grams, probability_of_false_positives) -> ceil((total_number_of_all_grams * log(probability_of_false_positives)) / log(1 / pow(2, log(2))));

CREATE FUNCTION bfEstimateFalsePositive [ON CLUSTER cluster]
AS
(total_number_of_all_grams, number_of_hash_functions, size_of_bloom_filter_in_bytes) -> pow(1 - exp(-number_of_hash_functions/ (size_of_bloom_filter_in_bytes / total_number_of_all_grams)), number_of_hash_functions);

CREATE FUNCTION bfEstimateGramNumber [ON CLUSTER cluster]
AS
(number_of_hash_functions, probability_of_false_positives, size_of_bloom_filter_in_bytes) -> ceil(size_of_bloom_filter_in_bytes / (-number_of_hash_functions / log(1 - exp(log(probability_of_false_positives) / number_of_hash_functions))))
```

Чтобы использовать эти функции, необходимо указать как минимум два параметра:

* `total_number_of_all_grams`
* `probability_of_false_positives`

Например, в грануле содержится `4300` n-грамм, и вы ожидаете, что вероятность ложноположительных срабатываний будет меньше `0.0001`.
Остальные параметры затем можно оценить, выполнив следующие запросы:

```sql theme={null}
--- estimate number of bits in the filter
SELECT bfEstimateBmSize(4300, 0.0001) / 8 AS size_of_bloom_filter_in_bytes;

┌─size_of_bloom_filter_in_bytes─┐
│                         10304 │
└───────────────────────────────┘

--- estimate number of hash functions
SELECT bfEstimateFunctions(4300, bfEstimateBmSize(4300, 0.0001)) as number_of_hash_functions

┌─number_of_hash_functions─┐
│                       13 │
└──────────────────────────┘
```

Конечно, вы также можете использовать эти функции, чтобы оценить параметры для других случаев.
Приведённые выше функции отсылают к калькулятору bloom-фильтра [здесь](https://hur.st/bloomfilter).

<div id="token-bloom-filter">
  #### Токенный bloom-фильтр
</div>

<Note>
  Начиная с версии ClickHouse 26.2, в которой индекс `text` стал общедоступным (GA), индекс `tokenbf_v1` больше не рекомендуется для полнотекстового поиска.

  Подробнее см. на странице ["Полнотекстовый поиск с помощью текстовых индексов"](/docs/ru/reference/engines/table-engines/mergetree-family/textindexes).
</Note>

```text title="Syntax" theme={null}
tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
```

<div id="sparse-grams-bloom-filter">
  #### Bloom-фильтр для sparse grams
</div>

Bloom-фильтр для sparse grams похож на `ngrambf_v1`, но использует [токены sparse grams](/docs/ru/reference/functions/regular-functions/string-functions#sparseGrams) вместо n-грамм.

```text title="Syntax" theme={null}
sparse_grams(min_ngram_length, max_ngram_length, min_cutoff_length, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
```

<div id="text">
  ### Текстовый индекс
</div>

Создаёт инвертированный индекс по токенизированным строковым данным, обеспечивая эффективный и детерминированный полнотекстовый поиск. Подробности см. [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/textindexes).

<div id="vector-similarity">
  #### Векторное сходство
</div>

Поддерживается приближенный поиск ближайших соседей; подробности см. [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/annindexes).

<div id="functions-support">
  ### Поддержка функций
</div>

Условия в предложении `WHERE` содержат вызовы функций, работающих со столбцами. Если столбец входит в индекс, ClickHouse пытается использовать этот индекс при выполнении этих функций. ClickHouse поддерживает разные подмножества функций для использования индексов.

Индексы типа `set` могут использоваться всеми функциями. Другие типы индексов поддерживаются следующим образом:

| Функция (оператор) / индекс                                                                                                            | первичный ключ | minmax | ngrambf\_v1 | tokenbf\_v1 | bloom\_filter | sparse\_grams | text |
| -------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------ | ----------- | ----------- | ------------- | ------------- | ---- |
| [равно (=, ==)](/docs/ru/reference/functions/regular-functions/comparison-functions#equals)                                                 | ✔              | ✔      | ✔           | ✔           | ✔             | ✔             | ✔    |
| [notEquals(!=, \<>)](/docs/ru/reference/functions/regular-functions/comparison-functions#notEquals)                                         | ✔              | ✔      | ✔           | ✔           | ✔             | ✔             | ✗    |
| [like](/docs/ru/reference/functions/regular-functions/string-search-functions#like)                                                         | ✔              | ✔      | ✔           | ✔           | ✗             | ✔             | ✔    |
| [notLike](/docs/ru/reference/functions/regular-functions/string-search-functions#notLike)                                                   | ✔              | ✔      | ✔           | ✔           | ✗             | ✔             | ✗    |
| [match](/docs/ru/reference/functions/regular-functions/string-search-functions#match)                                                       | ✗              | ✗      | ✔           | ✔           | ✗             | ✔             | ✔    |
| [startsWith](/docs/ru/reference/functions/regular-functions/string-functions#startsWith)                                                    | ✔              | ✔      | ✔           | ✔           | ✗             | ✔             | ✔    |
| [endsWith](/docs/ru/reference/functions/regular-functions/string-functions#endsWith)                                                        | ✗              | ✗      | ✔           | ✔           | ✗             | ✔             | ✔    |
| [multiSearchAny](/docs/ru/reference/functions/regular-functions/string-search-functions#multiSearchAny)                                     | ✗              | ✗      | ✔           | ✗           | ✗             | ✗             | ✔    |
| [multiSearchAnyUTF8](/docs/ru/reference/functions/regular-functions/string-search-functions#multiSearchAnyUTF8)                             | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [multiMatchAny](/docs/ru/reference/functions/regular-functions/string-search-functions#multiMatchAny)                                       | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [in](/docs/ru/reference/functions/regular-functions/in-functions)                                                                           | ✔              | ✔      | ✔           | ✔           | ✔             | ✔             | ✔    |
| [notIn](/docs/ru/reference/functions/regular-functions/in-functions)                                                                        | ✔              | ✔      | ✔           | ✔           | ✔             | ✔             | ✗    |
| [less (`<`)](/docs/ru/reference/functions/regular-functions/comparison-functions#less)                                                      | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [greater (`>`)](/docs/ru/reference/functions/regular-functions/comparison-functions#greater)                                                | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [lessOrEquals (`<=`)](/docs/ru/reference/functions/regular-functions/comparison-functions#lessOrEquals)                                     | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [greaterOrEquals (`>=`)](/docs/ru/reference/functions/regular-functions/comparison-functions#greaterOrEquals)                               | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [empty](/docs/ru/reference/functions/regular-functions/array-functions#empty)                                                               | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [notEmpty](/docs/ru/reference/functions/regular-functions/array-functions#notEmpty)                                                         | ✗              | ✔      | ✗           | ✗           | ✗             | ✔             | ✗    |
| [has](/docs/ru/reference/functions/regular-functions/array-functions#has)                                                                   | ✔              | ✔      | ✔           | ✔           | ✔             | ✔             | ✔    |
| [hasAny](/docs/ru/reference/functions/regular-functions/array-functions#hasAny)                                                             | ✗              | ✗      | ✔           | ✔           | ✔             | ✔             | ✗    |
| [hasAll](/docs/ru/reference/functions/regular-functions/array-functions#hasAll)                                                             | ✗              | ✗      | ✔           | ✔           | ✔             | ✔             | ✗    |
| [hasToken](/docs/ru/reference/functions/regular-functions/string-search-functions#hasToken)                                                 | ✗              | ✗      | ✗           | ✔           | ✗             | ✗             | ✔    |
| [hasTokenOrNull](/docs/ru/reference/functions/regular-functions/string-search-functions#hasTokenOrNull)                                     | ✗              | ✗      | ✗           | ✔           | ✗             | ✗             | ✔    |
| [hasTokenCaseInsensitive (`*`)](/docs/ru/reference/functions/regular-functions/string-search-functions#hasTokenCaseInsensitive)             | ✗              | ✗      | ✗           | ✔           | ✗             | ✗             | ✗    |
| [hasTokenCaseInsensitiveOrNull (`*`)](/docs/ru/reference/functions/regular-functions/string-search-functions#hasTokenCaseInsensitiveOrNull) | ✗              | ✗      | ✗           | ✔           | ✗             | ✗             | ✗    |
| [hasAnyTokens](/docs/ru/reference/functions/regular-functions/string-search-functions#hasAnyTokens)                                         | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [hasAllTokens](/docs/ru/reference/functions/regular-functions/string-search-functions#hasAllTokens)                                         | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [pointInPolygon](/docs/ru/reference/functions/regular-functions/geo/coordinates#pointinpolygon)                                             | ✔              | ✔      | ✗           | ✗           | ✗             | ✗             | ✗    |
| [mapContains (mapContainsKey)](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapContainsKey)                           | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [mapContainsKeyLike](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapContainsKeyLike)                                 | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [mapContainsValue](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapContainsValue)                                     | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |
| [mapContainsValueLike](/docs/ru/reference/functions/regular-functions/tuple-map-functions#mapContainsValueLike)                             | ✗              | ✗      | ✗           | ✗           | ✗             | ✗             | ✔    |

Функции с константным аргументом, который меньше размера n-граммы, не могут использоваться `ngrambf_v1` для оптимизации запросов.

(\*) Чтобы `hasTokenCaseInsensitive` и `hasTokenCaseInsensitiveOrNull` работали эффективно, индекс `tokenbf_v1` должен быть создан по данным, приведённым к нижнему регистру, например `INDEX idx (lower(str_col)) TYPE tokenbf_v1(512, 3, 0)`.

<Note>
  Bloom-фильтры могут давать ложноположительные срабатывания, поэтому индексы `ngrambf_v1`, `tokenbf_v1`, `sparse_grams` и `bloom_filter` не могут использоваться для оптимизации запросов, в которых ожидается, что результат функции будет ложным.

  Например:

  * Могут быть оптимизированы:
    * `s LIKE '%test%'`
    * `NOT s NOT LIKE '%test%'`
    * `s = 1`
    * `NOT s != 1`
    * `startsWith(s, 'test')`
  * Не могут быть оптимизированы:
    * `NOT s LIKE '%test%'`
    * `s NOT LIKE '%test%'`
    * `NOT s = 1`
    * `s != 1`
    * `NOT startsWith(s, 'test')`
</Note>

<div id="projections">
  ## Проекции
</div>

Проекции похожи на [materialized views](/docs/ru/reference/statements/create/view), но определяются на уровне частей данных. Они обеспечивают гарантии согласованности и автоматически используются в запросах.

<Note>
  При использовании проекций следует также учитывать настройку [force\_optimize\_projection](/docs/ru/reference/settings/session-settings#force_optimize_projection).
</Note>

Проекции не поддерживаются в запросах `SELECT` с модификатором [FINAL](/docs/ru/reference/statements/select/from#final-modifier).

<div id="projection-query">
  ### Запрос проекции
</div>

Запрос проекции определяет проекцию. Он неявно выбирает данные из родительской таблицы.
**Синтаксис**

```sql theme={null}
SELECT <column list expr> [GROUP BY] <group keys expr> [ORDER BY] <expr>
```

Проекции можно изменять или удалять с помощью оператора [ALTER](/docs/ru/reference/statements/alter/projection).

<div id="projection-index">
  ### Индексы проекций
</div>

Индексы проекций расширяют подсистему проекций, предоставляя лёгкий и явный способ определять индексы на уровне проекций.
Внешне индекс проекции по-прежнему остаётся проекцией, но с упрощённым синтаксисом и более ясным назначением: он определяет выражение, предназначенное для фильтрации, а не для хранения материализованных данных.
Внутри индекс проекции не материализует исходную таблицу в переставленном порядке строк, как обычная проекция.
Вместо этого перестановка хранится в виде числового столбца `_part_offset`, то есть `SELECT _part_offset ORDER BY <index_expr>`.

<div id="projection-index-syntax">
  #### Синтаксис
</div>

```sql theme={null}
PROJECTION <name> INDEX <index_expr> TYPE <index_type>
```

Пример:

```sql theme={null}
CREATE TABLE example
(
    id UInt64,
    region String,
    user_id UInt32,
    PROJECTION region_proj INDEX region TYPE basic,
    PROJECTION uid_proj INDEX user_id TYPE basic
)
ENGINE = MergeTree
ORDER BY id;
```

<div id="projection-index-types">
  #### Типы индексов
</div>

В настоящее время поддерживаются:

* **basic**: эквивалентен обычному индексу MergeTree по выражению.

В будущем этот механизм позволит добавлять новые типы индексов.

<div id="projection-storage">
  ### Хранение проекций
</div>

Проекции хранятся внутри каталога части. Это похоже на индекс, но здесь есть подкаталог, в котором хранится часть анонимной таблицы `MergeTree`. Эта таблица создается на основе запроса, задающего определение проекции. Если есть секция `GROUP BY`, нижележащий движок хранения становится [AggregatingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/aggregatingmergetree), а все агрегатные функции преобразуются в `AggregateFunction`. Если есть секция `ORDER BY`, таблица `MergeTree` использует ее как выражение первичного ключа. При слиянии часть проекции объединяется с помощью процедуры слияния соответствующего хранилища. Контрольная сумма части родительской таблицы объединяется с контрольной суммой части проекции. Остальные задачи обслуживания аналогичны индексам пропуска.

<div id="projection-query-analysis">
  ### Анализ запроса
</div>

1. Проверьте, можно ли использовать проекцию для выполнения данного запроса, то есть даст ли она тот же результат, что и запрос к базовой таблице.
2. Выберите наилучший подходящий вариант, для которого требуется прочитать наименьшее число гранул.
3. Конвейер запроса, использующий проекции, будет отличаться от того, который использует исходные части. Если в некоторых частях проекция отсутствует, можно добавить конвейер, который построит её на лету.

<div id="concurrent-data-access">
  ## Одновременный доступ к данным
</div>

Для одновременного доступа к таблице используется многоверсионность. Иными словами, когда таблицу одновременно читают и обновляют, данные читаются из набора частей, актуального на момент выполнения запроса. Длительные блокировки отсутствуют. Операции вставки не мешают чтению.

Чтение из таблицы автоматически распараллеливается.

<div id="table_engine-mergetree-ttl">
  ## TTL для столбцов и таблиц
</div>

Определяет срок хранения значений.

Клаузу `TTL` можно задать для всей таблицы и для каждого отдельного столбца. `TTL` на уровне таблицы также может задавать логику автоматического перемещения данных между дисками и томами или повторного сжатия частей, для которых срок хранения всех данных уже истёк.

Выражения должны иметь тип данных [Date](/docs/ru/reference/data-types/date), [Date32](/docs/ru/reference/data-types/date32), [DateTime](/docs/ru/reference/data-types/datetime) или [DateTime64](/docs/ru/reference/data-types/datetime64).

<Tip>
  **Избегайте недетерминированных функций в TTL-выражениях**

  TTL вычисляется во время фоновых слияний, а не в момент вставки.
  Такие функции, как `rand()`, `now()` или `now64()`, будут вычисляться заново при каждом слиянии, что приведёт к непредсказуемому поведению при удалении.
  ClickHouse блокирует выражения, которые вообще не зависят от столбцов, но в настоящее время не отклоняет недетерминированные функции, используемые вместе со ссылкой на столбец (например, `ts + rand()`). Для предсказуемых результатов TTL-выражения должны основываться исключительно на детерминированных значениях, производных от столбцов.
</Tip>

**Синтаксис**

Задание time-to-live для столбца:

```sql theme={null}
TTL time_column
TTL time_column + interval
```

Чтобы задать `interval`, используйте операторы [для работы с временными интервалами](/docs/ru/reference/operators/index#operators-for-working-with-dates-and-times), например:

```sql theme={null}
TTL date_time + INTERVAL 1 MONTH
TTL date_time + INTERVAL 15 HOUR
```

<div id="mergetree-column-ttl">
  ### TTL для столбца
</div>

Когда срок хранения значений в столбце истекает, ClickHouse заменяет их значениями по умолчанию для типа данных этого столбца. Если в части данных истекает срок хранения всех значений столбца, ClickHouse удаляет этот столбец из части данных в файловой системе.

Клаузу `TTL` нельзя использовать для столбцов ключа.

**Примеры**

<div id="creating-a-table-with-ttl">
  #### Создание таблицы с `TTL`:
</div>

```sql theme={null}
CREATE TABLE tab
(
    d DateTime,
    a Int TTL d + INTERVAL 1 MONTH,
    b Int TTL d + INTERVAL 1 MONTH,
    c String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d;
```

<div id="adding-ttl-to-a-column-of-an-existing-table">
  #### Добавление TTL к столбцу существующей таблицы
</div>

```sql theme={null}
ALTER TABLE tab
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 DAY;
```

<div id="altering-ttl-of-the-column">
  #### Изменение TTL для столбца
</div>

```sql theme={null}
ALTER TABLE tab
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 MONTH;
```

<div id="mergetree-table-ttl">
  ### TTL таблицы
</div>

Таблица может иметь выражение для удаления строк с истёкшим TTL и несколько выражений для автоматического перемещения частей между [дисками или томами](#table_engine-mergetree-multiple-volumes). Когда у строк в таблице истекает TTL, ClickHouse удаляет все соответствующие строки. Для перемещения или повторного сжатия части необходимо, чтобы все её строки удовлетворяли условиям выражения `TTL`.

```sql theme={null}
TTL expr
    [DELETE|RECOMPRESS codec_name1|TO DISK 'xxx'|TO VOLUME 'xxx'][, DELETE|RECOMPRESS codec_name2|TO DISK 'aaa'|TO VOLUME 'bbb'] ...
    [WHERE conditions]
    [GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ]
```

После каждого выражения TTL можно указать тип правила TTL. Он определяет действие, которое должно быть выполнено, когда выражение срабатывает (достигает текущего времени):

* `DELETE` - удалить истёкшие строки (действие по умолчанию);
* `RECOMPRESS codec_name` - повторно сжать часть данных с помощью `codec_name`;
* `TO DISK 'aaa'` - переместить часть на диск `aaa`;
* `TO VOLUME 'bbb'` - переместить часть на диск `bbb`;
* `GROUP BY` - агрегировать истёкшие строки.

Действие `DELETE` можно использовать вместе с условием `WHERE`, чтобы удалять только часть истёкших строк на основе условия фильтрации:

```sql theme={null}
TTL time_column + INTERVAL 1 MONTH DELETE WHERE column = 'value'
```

Выражение `GROUP BY` должно быть префиксом первичного ключа таблицы.

Если столбец не входит в выражение `GROUP BY` и не задан явно в предложении `SET`, то в результирующей строке он содержит произвольное значение из сгруппированных строк (как если бы к нему была применена агрегатная функция `any`).

**Примеры**

<div id="creating-a-table-with-ttl">
  #### Создание таблицы с `TTL`:
</div>

```sql theme={null}
CREATE TABLE tab
(
    d DateTime,
    a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH DELETE,
    d + INTERVAL 1 WEEK TO VOLUME 'aaa',
    d + INTERVAL 2 WEEK TO DISK 'bbb';
```

<div id="altering-ttl-of-the-table">
  #### Изменение `TTL` у таблицы:
</div>

```sql theme={null}
ALTER TABLE tab
    MODIFY TTL d + INTERVAL 1 DAY;
```

Создание таблицы, в которой срок хранения строк составляет один месяц. Просроченные строки, даты которых приходятся на понедельник, удаляются:

```sql theme={null}
CREATE TABLE table_with_where
(
    d DateTime,
    a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH DELETE WHERE toDayOfWeek(d) = 1;
```

<div id="creating-a-table-where-expired-rows-are-recompressed">
  #### Создание таблицы, в которой для истёкших строк выполняется повторное сжатие:
</div>

```sql theme={null}
CREATE TABLE table_for_recompression
(
    d DateTime,
    key UInt64,
    value String
) ENGINE MergeTree()
ORDER BY tuple()
PARTITION BY key
TTL d + INTERVAL 1 MONTH RECOMPRESS CODEC(ZSTD(17)), d + INTERVAL 1 YEAR RECOMPRESS CODEC(LZ4HC(10))
SETTINGS min_rows_for_wide_part = 0, min_bytes_for_wide_part = 0;
```

Создание таблицы, в которой агрегируются истекшие строки. В результирующих строках `x` содержит максимальное значение среди сгруппированных строк, `y` — минимальное значение, а `d` — любое произвольное значение из сгруппированных строк.

```sql theme={null}
CREATE TABLE table_for_aggregation
(
    d DateTime,
    k1 Int,
    k2 Int,
    x Int,
    y Int
)
ENGINE = MergeTree
ORDER BY (k1, k2)
TTL d + INTERVAL 1 MONTH GROUP BY k1, k2 SET x = max(x), y = min(y);
```

<div id="mergetree-removing-expired-data">
  ### Удаление устаревших данных
</div>

Данные с истёкшим `TTL` удаляются, когда ClickHouse выполняет слияние частей данных.

Когда ClickHouse обнаруживает, что срок действия данных истёк, он выполняет внеплановое слияние. Чтобы управлять частотой таких слияний, можно задать `merge_with_ttl_timeout`. Если значение слишком низкое, будет выполняться много внеплановых слияний, которые могут потреблять много ресурсов.

Если выполнить запрос `SELECT` между слияниями, можно получить устаревшие данные. Чтобы этого избежать, используйте запрос [OPTIMIZE](/docs/ru/reference/statements/optimize) перед `SELECT`.

**См. также**

* настройка [ttl\_only\_drop\_parts](/docs/ru/reference/settings/merge-tree-settings#ttl_only_drop_parts)

<div id="disk-types">
  ## Типы дисков
</div>

Помимо локальных блочных устройств, ClickHouse поддерживает следующие типы хранилищ:

* [`s3` для S3 и MinIO](#table_engine-mergetree-s3)
* [`gcs` для GCS](/docs/ru/integrations/connectors/data-sources/gcs#creating-a-disk)
* [`blob_storage_disk` для Azure Blob Storage](/docs/ru/concepts/features/configuration/server-config/storing-data#azure-blob-storage)
* [`hdfs` для HDFS](/docs/ru/reference/engines/table-engines/integrations/hdfs)
* [`web` для доступа только для чтения через веб](/docs/ru/concepts/features/configuration/server-config/storing-data#web-storage)
* [`cache` для локального кэширования](/docs/ru/concepts/features/configuration/server-config/storing-data#using-local-cache)
* [`s3_plain` для резервных копий в S3](/docs/ru/concepts/features/backup-restore/local-disk)
* [`s3_plain_rewritable` для неизменяемых нереплицируемых таблиц в S3](/docs/ru/concepts/features/configuration/server-config/storing-data#s3-plain-rewritable-storage)

<div id="table_engine-mergetree-multiple-volumes">
  ## Использование нескольких блочных устройств для хранения данных
</div>

<div id="introduction">
  ### Введение
</div>

Движки таблиц семейства `MergeTree` могут хранить данные на нескольких блочных устройствах. Например, это может быть полезно, когда данные определённой таблицы естественным образом делятся на «горячие» и «холодные». Самые свежие данные запрашиваются регулярно, но занимают сравнительно немного места. Напротив, исторические данные с длинным хвостом запрашиваются редко. Если доступно несколько дисков, «горячие» данные можно размещать на быстрых дисках (например, NVMe SSD или в памяти), а «холодные» — на относительно медленных (например, HDD).

Это применимо ко всем типам дисков, включая S3 и другие диски объектного хранилища. Например, вы можете распределить данные по нескольким S3 бакетам в пределах одного тома или создать многоуровневые политики, которые перемещают данные с локальных дисков в S3. Подробнее см. в разделе [Использование S3-дисков с несколькими томами](#s3-multiple-volumes).

Часть — это минимальная перемещаемая единица для таблиц с движком `MergeTree`. Данные, относящиеся к одной части, хранятся на одном диске. Части могут перемещаться между дисками в фоновом режиме (в соответствии с пользовательскими настройками), а также с помощью запросов [ALTER](/docs/ru/reference/statements/alter/partition).

<div id="terms">
  ### Термины
</div>

* Диск — блочное устройство, смонтированное в файловую систему.
* Диск по умолчанию — диск, соответствующий пути, указанному в настройке сервера [path](/docs/ru/reference/settings/server-settings/settings#path).
* Том — упорядоченный набор одинаковых дисков (аналогично [JBOD](https://en.wikipedia.org/wiki/Non-RAID_drive_architectures)).
* Политика хранения — набор томов и правил перемещения данных между ними.

Имена, присвоенные описанным объектам, можно найти в системных таблицах [system.storage\_policies](/docs/ru/reference/system-tables/storage_policies) и [system.disks](/docs/ru/reference/system-tables/disks). Чтобы назначить таблице одну из настроенных политик хранения, используйте настройку `storage_policy` таблиц семейства движков `MergeTree`.

<div id="table_engine-mergetree-multiple-volumes_configure">
  ### Конфигурация
</div>

Диски, тома и политики хранения должны быть объявлены внутри тега `<storage_configuration>` в одном из файлов каталога `config.d`.

<Tip>
  Диски также можно объявить в разделе `SETTINGS` запроса. Это полезно
  для ad hoc-анализа, чтобы временно подключить диск, который, например, размещён по URL.
  Подробнее см. в разделе [динамическое хранилище](/docs/ru/concepts/features/configuration/server-config/storing-data#dynamic-configuration).
</Tip>

Структура конфигурации:

```xml theme={null}
<storage_configuration>
    <disks>
        <disk_name_1> <!-- имя диска -->
            <path>/mnt/fast_ssd/clickhouse/</path>
        </disk_name_1>
        <disk_name_2>
            <path>/mnt/hdd1/clickhouse/</path>
            <keep_free_space_bytes>10485760</keep_free_space_bytes>
        </disk_name_2>
        <disk_name_3>
            <path>/mnt/hdd2/clickhouse/</path>
            <keep_free_space_bytes>10485760</keep_free_space_bytes>
        </disk_name_3>

        ...
    </disks>

    ...
</storage_configuration>
```

Теги:

* `<disk_name_N>` — имя диска. Имена всех дисков должны отличаться.
* `path` — путь, по которому сервер будет хранить данные (папки `data` и `shadow`); должен оканчиваться на '/'.
* `keep_free_space_bytes` — объём свободного места на диске, который нужно зарезервировать.

Порядок описания дисков не важен.

Разметка конфигурации политик хранения:

```xml theme={null}
<storage_configuration>
    ...
    <policies>
        <policy_name_1>
            <volumes>
                <volume_name_1>
                    <disk>disk_name_from_disks_configuration</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                    <load_balancing>round_robin</load_balancing>
                </volume_name_1>
                <volume_name_2>
                    <!-- конфигурация -->
                </volume_name_2>
                <!-- другие тома -->
            </volumes>
            <move_factor>0.2</move_factor>
        </policy_name_1>
        <policy_name_2>
            <!-- конфигурация -->
        </policy_name_2>

        <!-- другие политики -->
    </policies>
    ...
</storage_configuration>
```

Теги:

* `policy_name_N` — имя политики. Имена политик должны быть уникальными.
* `volume_name_N` — имя тома. Имена томов должны быть уникальными.
* `disk` — диск внутри тома.
* `max_data_part_size_bytes` — максимальный размер части, которая может храниться на любом из дисков тома. Если предполагаемый размер слитой части превышает `max_data_part_size_bytes`, эта часть будет записана на следующий том. По сути, эта возможность позволяет хранить новые/небольшие части на горячем томе (SSD) и перемещать их на холодный том (HDD), когда они становятся большими. Не используйте этот параметр, если в вашей политике только один том.
* `move_factor` — когда объем доступного пространства становится меньше этого коэффициента, данные автоматически начинают перемещаться на следующий том, если он есть (по умолчанию 0.1). ClickHouse сортирует существующие части по размеру от большего к меньшему (по убыванию) и выбирает части, суммарный размер которых достаточен для выполнения условия `move_factor`. Если суммарного размера всех частей недостаточно, будут перемещены все части.
* `perform_ttl_move_on_insert` — отключает TTL move при INSERT части. По умолчанию (если параметр включен), если вставляется часть, которая уже подпадает под правило TTL move, она сразу записывается на том/диск, указанный в правиле перемещения. Это может существенно замедлить вставку, если том/диск пункта назначения медленный (например, S3). Если параметр отключен, уже подпадающая под TTL часть записывается на том по умолчанию, а затем сразу перемещается на TTL-том.
* `load_balancing` - политика балансировки дисков: `round_robin` или `least_used`.
* `least_used_ttl_ms` - настраивает тайм-аут (в миллисекундах) обновления доступного пространства на всех дисках (`0` - обновлять всегда, `-1` - никогда не обновлять, значение по умолчанию — `60000`). Обратите внимание: если диск может использоваться только ClickHouse и не подвержен изменению размера файловой системы в режиме online, можно использовать `-1`; во всех остальных случаях это не рекомендуется, так как со временем это приведет к некорректному распределению пространства.
* `prefer_not_to_merge` — этот параметр не следует использовать. Он отключает слияние частей на этом томе (это вредно и приводит к снижению производительности). Когда этот параметр включен (не делайте этого), слияние данных на этом томе запрещено (а это плохо). Это позволяет (хотя вам это не нужно) контролировать (если вам хочется что-то контролировать, вы, скорее всего, делаете что-то не так), как ClickHouse работает с медленными дисками (но ClickHouse знает лучше, поэтому, пожалуйста, не используйте этот параметр).
* `volume_priority` — определяет приоритет (порядок), в котором заполняются тома. Меньшее значение означает более высокий приоритет. Значения параметра должны быть натуральными числами и в совокупности покрывать диапазон от 1 до N (где N соответствует наименьшему приоритету) без пропуска чисел.
  * Если помечены *все* тома, приоритет назначается им в указанном порядке.
  * Если помечены только *некоторые* тома, тома без метки получают наименьший приоритет, а между собой упорядочиваются в том порядке, в котором они заданы в config.
  * Если не помечен *ни один* том, их приоритет определяется порядком, в котором они объявлены в конфигурации.
  * Два тома не могут иметь одинаковое значение приоритета.

Примеры конфигурации:

```xml theme={null}
<storage_configuration>
    ...
    <policies>
        <hdd_in_order> <!-- policy name -->
            <volumes>
                <single> <!-- название тома -->
                    <disk>disk1</disk>
                    <disk>disk2</disk>
                </single>
            </volumes>
        </hdd_in_order>

        <moving_from_ssd_to_hdd>
            <volumes>
                <hot>
                    <disk>fast_ssd</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                </hot>
                <cold>
                    <disk>disk1</disk>
                </cold>
            </volumes>
            <move_factor>0.2</move_factor>
        </moving_from_ssd_to_hdd>

        <small_jbod_with_external_no_merges>
            <volumes>
                <main>
                    <disk>jbod1</disk>
                </main>
                <external>
                    <disk>external</disk>
                </external>
            </volumes>
        </small_jbod_with_external_no_merges>
    </policies>
    ...
</storage_configuration>
```

В приведённом примере политика `hdd_in_order` реализует подход [round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling). Таким образом, эта политика определяет только один том (`single`), а части хранятся на всех его дисках циклически. Такая политика может быть весьма полезной, если в системе смонтировано несколько одинаковых дисков, но RAID не настроен. Имейте в виду, что каждый отдельный диск сам по себе ненадёжен, и это может потребовать коэффициента репликации 3 или выше.

Если в системе доступны разные типы дисков, вместо неё можно использовать политику `moving_from_ssd_to_hdd`. Том `hot` состоит из SSD-диска (`fast_ssd`), а максимальный размер части, которая может храниться на этом томе, составляет 1 ГБ. Все части размером более 1 ГБ будут сразу сохраняться на томе `cold`, который содержит HDD-диск `disk1`.
Кроме того, как только диск `fast_ssd` заполнится более чем на 80%, данные будут перенесены на `disk1` фоновым процессом.

Порядок перечисления томов в рамках политики хранения важен, если хотя бы для одного из перечисленных томов явно не задан параметр `volume_priority`.
Когда том переполняется, данные перемещаются на следующий. Порядок перечисления дисков также важен, поскольку данные записываются на них по очереди.

При создании таблицы к ней можно применить одну из настроенных политик хранения:

```sql theme={null}
CREATE TABLE table_with_non_default_policy (
    EventDate Date,
    OrderID UInt64,
    BannerID UInt64,
    SearchPhrase String
) ENGINE = MergeTree
ORDER BY (OrderID, BannerID)
PARTITION BY toYYYYMM(EventDate)
SETTINGS storage_policy = 'moving_from_ssd_to_hdd'
```

Политика хранения `default` предполагает использование только одного тома, состоящего из одного диска, указанного в `<path>`.
Вы можете изменить политику хранения после создания таблицы с помощью запроса \[ALTER TABLE ... MODIFY SETTING]; новая политика должна включать все прежние диски и тома с теми же именами.

Количество потоков, выполняющих фоновое перемещение частей, можно изменить с помощью настройки [background\_move\_pool\_size](/docs/ru/reference/settings/server-settings/settings#background_move_pool_size).

<div id="details">
  ### Подробности
</div>

В случае таблиц `MergeTree` данные записываются на диск разными способами:

* В результате вставки (запрос `INSERT`).
* Во время фоновых слияний и [мутаций](/docs/ru/reference/statements/alter/index#mutations).
* При загрузке с другой реплики.
* В результате заморозки партиции [ALTER TABLE ... FREEZE PARTITION](/docs/ru/reference/statements/alter/partition#freeze-partition).

Во всех этих случаях, кроме мутаций и заморозки партиции, часть сохраняется на томе и диске в соответствии с заданной политикой хранения:

1. Выбирается первый том (в порядке объявления), на котором достаточно места для хранения части (`unreserved_space > current_part_size`) и который допускает хранение частей такого размера (`max_data_part_size_bytes > current_part_size`).
2. Внутри этого тома выбирается диск, следующий за тем, который использовался для хранения предыдущего фрагмента данных, и на котором свободного места больше, чем размер части (`unreserved_space - keep_free_space_bytes > current_part_size`).

На уровне реализации мутации и заморозка партиции используют [жесткие ссылки](https://en.wikipedia.org/wiki/Hard_link). Жесткие ссылки между разными дисками не поддерживаются, поэтому в таких случаях результирующие части сохраняются на тех же дисках, что и исходные.

В фоновом режиме части перемещаются между томами в зависимости от объема свободного места (параметр `move_factor`) в соответствии с порядком, в котором тома объявлены в файле конфигурации.
Данные никогда не переносятся с последнего тома и не переносятся на первый. Для отслеживания фоновых перемещений можно использовать системные таблицы [system.part\_log](/docs/ru/reference/system-tables/part_log) (поле `type = MOVE_PART`) и [system.parts](/docs/ru/reference/system-tables/parts) (поля `path` и `disk`). Кроме того, подробную информацию можно найти в серверных журналах.

Пользователь может принудительно переместить часть или партицию с одного тома на другой с помощью запроса [ALTER TABLE ... MOVE PART|PARTITION ... TO VOLUME|DISK ...](/docs/ru/reference/statements/alter/partition); при этом учитываются все ограничения для фоновых операций. Запрос сам инициирует перемещение и не дожидается завершения фоновых операций. Пользователь получит сообщение об ошибке, если свободного места недостаточно или если не выполнено какое-либо из необходимых условий.

Перемещение данных не влияет на репликацию. Поэтому для одной и той же таблицы на разных репликах можно задавать разные политики хранения.

После завершения фоновых слияний и мутаций старые части удаляются только спустя некоторое время (`old_parts_lifetime`).
В течение этого времени они не перемещаются на другие тома или диски. Поэтому, пока части не будут окончательно удалены, они по-прежнему учитываются при расчете занятого дискового пространства.

Пользователь может равномерно распределять новые крупные части по разным дискам тома [JBOD](https://en.wikipedia.org/wiki/Non-RAID_drive_architectures) с помощью настройки [min\_bytes\_to\_rebalance\_partition\_over\_jbod](/docs/ru/reference/settings/merge-tree-settings#min_bytes_to_rebalance_partition_over_jbod).

<div id="table_engine-mergetree-s3">
  ## Использование внешнего хранилища для хранения данных
</div>

Движки таблиц семейства [MergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree) могут хранить данные в `S3`, `AzureBlobStorage` и `HDFS`, используя диски типов `s3`, `azure_blob_storage` и `hdfs` соответственно. Подробнее см. в разделе [настройка параметров внешнего хранилища](/docs/ru/concepts/features/configuration/server-config/storing-data#configuring-external-storage).

Пример использования [S3](https://aws.amazon.com/s3/) в качестве внешнего хранилища с диском типа `s3`.

Конфигурация:

```xml theme={null}
<storage_configuration>
    ...
    <disks>
        <s3>
            <type>s3</type>
            <support_batch_delete>true</support_batch_delete>
            <endpoint>https://clickhouse-public-datasets.s3.amazonaws.com/my-bucket/root-path/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
            <region></region>
            <header>Authorization: Bearer SOME-TOKEN</header>
            <server_side_encryption_customer_key_base64>your_base64_encoded_customer_key</server_side_encryption_customer_key_base64>
            <server_side_encryption_kms_key_id>your_kms_key_id</server_side_encryption_kms_key_id>
            <server_side_encryption_kms_encryption_context>your_kms_encryption_context</server_side_encryption_kms_encryption_context>
            <server_side_encryption_kms_bucket_key_enabled>true</server_side_encryption_kms_bucket_key_enabled>
            <proxy>
                <uri>http://proxy1</uri>
                <uri>http://proxy2</uri>
            </proxy>
            <connect_timeout_ms>10000</connect_timeout_ms>
            <request_timeout_ms>5000</request_timeout_ms>
            <retry_attempts>10</retry_attempts>
            <single_read_retries>4</single_read_retries>
            <min_bytes_for_seek>1000</min_bytes_for_seek>
            <metadata_path>/var/lib/clickhouse/disks/s3/</metadata_path>
            <skip_access_check>false</skip_access_check>
        </s3>
        <s3_cache>
            <type>cache</type>
            <disk>s3</disk>
            <path>/var/lib/clickhouse/disks/s3_cache/</path>
            <max_size>10Gi</max_size>
        </s3_cache>
    </disks>
    ...
</storage_configuration>
```

См. также [настройку параметров внешнего хранилища](/docs/ru/concepts/features/configuration/server-config/storing-data#configuring-external-storage).

<div id="s3-multiple-volumes">
  ### Использование S3-дисков с несколькими томами
</div>

S3-диски (и другие диски объектного хранилища) можно использовать в политиках хранения с несколькими дисками и томами так же, как и локальные диски. Это позволяет распределять данные по нескольким S3 бакетам в рамках одного тома (по принципу JBOD) или настраивать политики многоуровневого хранения с S3-томами.

Например, чтобы распределять данные между двумя S3 бакетами по очереди:

```xml theme={null}
<storage_configuration>
    <disks>
        <s3_bucket1>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/bucket-1/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_bucket1>
        <s3_bucket2>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/bucket-2/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_bucket2>
    </disks>
    <policies>
        <s3_multi_bucket>
            <volumes>
                <main>
                    <disk>s3_bucket1</disk>
                    <disk>s3_bucket2</disk>
                </main>
            </volumes>
        </s3_multi_bucket>
    </policies>
</storage_configuration>
```

Вы также можете объединить локальные тома и тома S3 в многоуровневой политике, например перемещать данные с локального SSD в S3 по мере их старения:

```xml theme={null}
<storage_configuration>
    <disks>
        <local_ssd>
            <path>/mnt/fast_ssd/clickhouse/</path>
        </local_ssd>
        <s3_cold>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/cold-storage/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_cold>
    </disks>
    <policies>
        <local_to_s3>
            <volumes>
                <hot>
                    <disk>local_ssd</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                </hot>
                <cold>
                    <disk>s3_cold</disk>
                </cold>
            </volumes>
            <move_factor>0.2</move_factor>
        </local_to_s3>
    </policies>
</storage_configuration>
```

<Note>
  При использовании `use_environment_credentials` для аутентификации в S3 учетные данные окружения (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`) используются всеми S3-дисками совместно. Использовать разные учетные данные окружения для разных дисков нельзя. Если для каждого S3-диска нужны свои учетные данные, вместо этого укажите явные настройки `access_key_id` и `secret_access_key` для каждого диска.
</Note>

Можно настроить таблицы MergeTree без репликации для сценария с одним пишущим узлом и множеством читающих узлов на общем хранилище. Это обеспечивается автоматическим обновлением списка частей, которое можно настроить на читающих узлах. Обратите внимание, что для этого нужны общие метаданные файловой системы для всех реплик (или `table_disk = true` с локальным для таблицы диском). См. [refresh\_parts\_interval and table\_disk](/docs/ru/concepts/features/configuration/server-config/storing-data#refresh-parts-interval-and-table-disk).

<Info>
  **конфигурация кэша**

  В версиях ClickHouse с 22.3 по 22.7 используется другая конфигурация кэша; если вы используете одну из этих версий, см. раздел [использование локального кэша](/docs/ru/concepts/features/configuration/server-config/storing-data#using-local-cache).
</Info>

<div id="virtual-columns">
  ## Виртуальные столбцы
</div>

* `_part` — Имя части.
* `_part_index` — Последовательный индекс части в результате запроса.
* `_part_starting_offset` — Накопительный номер начальной строки части в результате запроса.
* `_part_offset` — Номер строки в части.
* `_part_granule_offset` — Номер гранулы в части.
* `_partition_id` — Имя партиции.
* `_part_uuid` — Уникальный идентификатор части (если включена настройка MergeTree `assign_part_uuids`).
* `_part_data_version` — Версия данных части (минимальный номер блока или версия мутации).
* `_partition_value` — Значения (кортеж) выражения `partition by`.
* `_sample_factor` — Коэффициент выборки (из запроса).
* `_block_number` — Исходный номер блока для строки, назначенный при вставке; сохраняется при слияниях, когда включена настройка `enable_block_number_column`.
* `_block_offset` — Исходный номер строки в блоке, назначенный при вставке; сохраняется при слияниях, когда включена настройка `enable_block_offset_column`.
* `_disk_name` — Имя диска, используемого для хранения.

<div id="column-statistics">
  ## Статистика столбцов
</div>

Статистика объявляется в разделе столбцов запроса `CREATE` для таблиц семейства `*MergeTree*`:

```sql theme={null}
CREATE TABLE tab
(
    a Int64 STATISTICS(tdigest, uniq),
    b Float64
)
ENGINE = MergeTree
ORDER BY a
```

Статистикой также можно управлять с помощью команд `ALTER`:

```sql theme={null}
ALTER TABLE tab ADD STATISTICS b TYPE tdigest, uniq;
ALTER TABLE tab DROP STATISTICS a;
```

Эти легковесные статистики содержат агрегированную информацию о распределении значений в столбцах. Статистики хранятся в каждой части и обновляются при каждой вставке.
Их можно использовать для оптимизации PREWHERE только при включении `set use_statistics = 1`.

<div id="part-pruning-with-statistics">
  #### Отсечение частей на основе статистики
</div>

Если включен параметр `use_statistics_for_part_pruning`, для отсечения частей можно использовать статистику.
В настоящее время отсечение частей поддерживается только статистикой `basic` (и устаревшей статистикой `minmax`). Когда такая статистика определена для столбца, ClickHouse отслеживает минимальное и максимальное значения этого столбца в каждой части.
Отсечение частей позволяет пропускать чтение целых частей данных, если условие фильтра запроса не может соответствовать ни одной строке в такой части.

**Пример:**

```sql theme={null}
-- Create a table with basic statistics on the 'value' column
CREATE TABLE test_stats
(
    id UInt64,
    value Int64 STATISTICS(basic)
)
ENGINE = MergeTree
ORDER BY id;

SYSTEM STOP MERGES test_stats;

-- Insert data in separate inserts to create multiple parts
INSERT INTO test_stats SELECT number, number FROM numbers(1000); -- Part 1: value range [0, 999]
INSERT INTO test_stats SELECT number, number + 10000 FROM numbers(1000); -- Part 2: value range [10000, 10999]

SET use_statistics_for_part_pruning = 1;

-- This query will skip Part 1 entirely because its max value (999) < 5000
SELECT count() FROM test_stats WHERE value > 5000;

-- Use EXPLAIN to see the pruning effect
EXPLAIN indexes = 1 SELECT count() FROM test_stats WHERE value > 5000;
-- The output will show "Parts: 1/2" indicating one part was pruned
```

<div id="available-types-of-column-statistics">
  ### Доступные типы статистики столбцов
</div>

* `basic`

  Компактный набор однозначных сводных характеристик, вычисляемых по столбцу. В зависимости от типа столбца заполняются следующие компоненты:

  * для любого столбца, значения которого представлены числами (целые числа, числа с плавающей точкой, `Decimal*`, `Date*`, `DateTime*`, `Enum*`, `IPv4`, ...): минимальное и максимальное значения, которые позволяют оценивать селективность фильтра диапазона и выполнять отсечение частей;
  * для столбцов `String` и `FixedString`: суммарная длина в байтах всех значений, отличных от `NULL` (на её основе можно вычислить среднюю длину строки);
  * для столбцов `Nullable` и `LowCardinality(Nullable)`: количество значений `NULL`, которое оптимизатор использует, чтобы исключать строки с `NULL` из оценок селективности.

    Одна статистика `basic` может одновременно заполнять несколько таких компонентов — например, для столбца `Nullable(UInt32)` она отслеживает и числовые минимум/максимум, и количество значений `NULL`. По сравнению с `minmax`, `basic` дополнительно работает со столбцами `String` / `FixedString` и может быть объявлена для обёрток `Nullable` типов вроде `UUID` или `IPv6` исключительно для отслеживания количества значений `NULL`.

* `minmax` (устарело)

<Note>
  Статистика `minmax` устарела, и её больше нельзя создавать (`CREATE TABLE ... STATISTICS(minmax)` и `ALTER TABLE ... ADD/MODIFY STATISTICS ... TYPE minmax` возвращают ошибку). Существующие таблицы и части со статистикой `minmax` продолжают работать. Вместо неё используйте статистику `basic`.
</Note>

* `tdigest`

<Warning>
  Статистика типа `tdigest` требует больших затрат на создание и может замедлять приём данных.
</Warning>

Скетчи [TDigest](https://github.com/tdunning/t-digest), которые позволяют вычислять приблизительные перцентили (например, 90-й перцентиль) для числовых столбцов.

* `uniq`

  Скетчи [BJKST](https://people.iith.ac.in/aravind/Files-CS5120/pc-lec14-BJKST.pdf), которые позволяют оценить количество различных значений в столбце. Внутри используется [`uniq`](/docs/ru/reference/functions/aggregate-functions/uniq).

* `uniq_v2`

  Аналогично `uniq`, но внутри используется [`uniqCombined`](/docs/ru/reference/functions/aggregate-functions/uniqCombined)`(12)` (вариант [HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog)). Потребляет меньше памяти, чем `uniq`, и может строиться быстрее.

* `countmin`

<Warning>
  Статистика типа `countmin` требует больших затрат на создание и может замедлять приём данных.
</Warning>

Скетчи [CountMin](https://en.wikipedia.org/wiki/Count%E2%80%93min_sketch), которые позволяют приблизительно оценить частоту каждого значения в столбце.

<div id="supported-data-types">
  ### Поддерживаемые типы данных
</div>

|          | (U)Int\*, Float\*, Decimal(*), Date*, булевый, Enum\* | IPv4 | String или FixedString |
| -------- | ----------------------------------------------------- | ---- | ---------------------- |
| basic    | ✔                                                     | ✔    | ✔                      |
| countmin | ✔                                                     | ✔    | ✔                      |
| minmax   | ✔                                                     | ✔    | ✗                      |
| tdigest  | ✔                                                     | ✗    | ✗                      |
| uniq     | ✔                                                     | ✔    | ✔                      |
| uniq\_v2 | ✔                                                     | ✔    | ✔                      |

Для всего перечисленного выше также поддерживаются обертки `Nullable` и `LowCardinality(Nullable)` указанных типов. `Basic` также можно объявлять для оберток `Nullable` типов вроде `UUID` или `IPv6` исключительно для отслеживания количества `NULL`.

<div id="supported-operations">
  ### Поддерживаемые операции
</div>

|          | Фильтры равенства (==) | Фильтры диапазона (`>, >=, <, <=`) |
| -------- | ---------------------- | ---------------------------------- |
| basic    | ✗                      | ✔ (только числовые столбцы)        |
| countmin | ✔                      | ✗                                  |
| minmax   | ✗                      | ✔ (только числовые столбцы)        |
| tdigest  | ✗                      | ✔ (только числовые столбцы)        |
| uniq     | ✔                      | ✗                                  |
| uniq\_v2 | ✔                      | ✗                                  |

Для `basic` в столбцах `String` / `FixedString` статистика учитывает только суммарную
длину в байтах значений, отличных от NULL (используется для оценки средней длины строк), и количество значений NULL;
фильтры диапазона и отсечение частей на её основе не применяются.

<div id="column-level-settings">
  ## Настройки на уровне столбцов
</div>

Некоторые настройки MergeTree можно переопределять на уровне столбцов:

* `max_compress_block_size` — Максимальный размер блоков несжатых данных перед сжатием при записи в таблицу.
* `min_compress_block_size` — Минимальный размер блоков несжатых данных, необходимый для сжатия перед записью следующей метки.

Пример:

```sql theme={null}
CREATE TABLE tab
(
    id Int64,
    document String SETTINGS (min_compress_block_size = 16777216, max_compress_block_size = 16777216)
)
ENGINE = MergeTree
ORDER BY id
```

Настройки на уровне столбца можно изменить или удалить с помощью [ALTER MODIFY COLUMN](/docs/ru/reference/statements/alter/column), например:

* Удалить `SETTINGS` из определения столбца:

```sql theme={null}
ALTER TABLE tab MODIFY COLUMN document REMOVE SETTINGS;
```

* Измените параметр:

```sql theme={null}
ALTER TABLE tab MODIFY COLUMN document MODIFY SETTING min_compress_block_size = 8192;
```

* Сбрасывает одну или несколько настроек, а также удаляет объявление настройки из выражения столбца в CREATE-запросе таблицы.

```sql theme={null}
ALTER TABLE tab MODIFY COLUMN document RESET SETTING min_compress_block_size;
```
