> ## 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.

> Индексы пропуска данных позволяют ClickHouse не читать большие фрагменты данных, в которых заведомо нет подходящих значений.

# Что такое индексы пропуска данных в ClickHouse

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

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

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

Тем не менее, как бы тщательно ни был настроен первичный ключ, неизбежно найдутся сценарии, в которых его нельзя использовать эффективно. ClickHouse часто применяют для данных типа временных рядов, но при этом нередко хотят анализировать те же данные по другим бизнес-измерениям, таким как id клиента, URL веб-сайта или номер продукта. В таком случае производительность запроса может существенно снизиться, поскольку для применения условия из предложения WHERE может потребоваться полное сканирование всех значений столбца. Хотя ClickHouse и в таких условиях остается сравнительно быстрым, обработка миллионов или миллиардов отдельных значений приводит к тому, что запросы "без индекса" выполняются значительно медленнее, чем запросы, основанные на первичном ключе.

В традиционной реляционной базе данных один из подходов к решению этой проблемы — добавить к таблице один или несколько "вторичных" индексов. Это структура b-дерева, которая позволяет базе данных находить все совпадающие строки на диске за O(log(n)) вместо O(n) (то есть полного сканирования таблицы), где n — число строк. Однако такой тип вторичного индекса не подходит для ClickHouse (или других столбцовых баз данных), потому что на диске нет отдельных строк, которые можно было бы добавить в индекс.

Вместо этого ClickHouse предлагает другой тип индекса, который в определенных случаях может значительно повысить скорость выполнения запросов. Эти структуры называются индексами "Skip", потому что они позволяют ClickHouse пропускать чтение значительных фрагментов данных, в которых гарантированно нет совпадающих значений.

<div id="basic-operation">
  ## Основной принцип работы
</div>

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

* Имя индекса. Имя индекса используется для создания файла индекса в каждой партиции. Оно также требуется как параметр при удалении или материализации индекса.
* Выражение индекса. Выражение индекса используется для вычисления набора значений, хранящихся в индексе. Это может быть комбинация столбцов, простых операторов и/или подмножества функций, определяемого типом индекса.
* TYPE. Тип индекса определяет вычисление, по результатам которого решается, можно ли пропустить чтение и обработку каждого блока индекса.
* GRANULARITY. Каждый индексируемый блок состоит из GRANULARITY гранул. Например, если гранулярность первичного индекса таблицы составляет 8192 строки, а гранулярность индекса — 4, то каждый индексируемый "блок" будет содержать 32768 строк.

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

* `skp_idx_{index_name}.idx`, который содержит упорядоченные значения выражения
* `skp_idx_{index_name}.mrk2`, который содержит соответствующие смещения в связанных файлах столбцов.

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

```sql theme={null}
CREATE TABLE skip_table
(
  my_key UInt64,
  my_value UInt64
)
ENGINE MergeTree primary key my_key
SETTINGS index_granularity=8192;

INSERT INTO skip_table SELECT number, intDiv(number,4096) FROM numbers(100000000);
```

При выполнении простого запроса, не использующего первичный ключ, сканируются все 100 миллионов значений в столбце `my_value`:

```sql theme={null}
SELECT * FROM skip_table WHERE my_value IN (125, 700)
```

```response theme={null}
┌─my_key─┬─my_value─┐
│ 512000 │      125 │
│ 512001 │      125 │
│    ... |      ... |
└────────┴──────────┘

8192 rows in set. Elapsed: 0.079 sec. Processed 100.00 million rows, 800.10 MB (1.26 billion rows/s., 10.10 GB/s.
```

Теперь добавьте очень простой индекс пропуска данных:

```sql theme={null}
ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2;
```

Обычно индекс пропуска данных применяется только к вновь вставленным данным, поэтому простое добавление индекса не повлияет на приведённый выше запрос.

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

```sql theme={null}
ALTER TABLE skip_table MATERIALIZE INDEX vix;
```

Снова выполните запрос с только что созданным индексом:

```sql theme={null}
SELECT * FROM skip_table WHERE my_value IN (125, 700)
```

```response theme={null}
┌─my_key─┬─my_value─┐
│ 512000 │      125 │
│ 512001 │      125 │
│    ... |      ... |
└────────┴──────────┘

8192 rows in set. Elapsed: 0.051 sec. Processed 32.77 thousand rows, 360.45 KB (643.75 thousand rows/s., 7.08 MB/s.)
```

Вместо обработки 100 миллионов строк общим объемом 800 мегабайт ClickHouse прочитал и проанализировал всего 32768 строк объемом 360 килобайт
— четыре гранулы по 8192 строки каждая.

Более наглядно это выглядит так: были прочитаны и отобраны 4096 строк, у которых `my_value` равно 125, а следующие строки
были пропущены без чтения с диска:

<Image img="https://mintcdn.com/private-7c7dfe99/NvnCM4vX9aZ07JxK/images/guides/best-practices/simple_skip.webp?fit=max&auto=format&n=NvnCM4vX9aZ07JxK&q=85&s=f4aa2703d9e4e94cae9d4007b0e033f6" size="md" alt="Простой пропуск" width="1859" height="1618" data-path="images/guides/best-practices/simple_skip.webp" />

Подробную информацию об использовании индекса пропуска данных можно получить, включив trace при выполнении запросов.  В
clickhouse-client задайте `send_logs_level`:

```sql theme={null}
SET send_logs_level='trace';
```

Это даст полезную отладочную информацию при попытке настроить SQL-запрос и индексы таблицы.  Из приведённого выше
примера видно, что отладочный журнал показывает, что индекс пропуска данных отбросил все гранулы, кроме двух:

```sql theme={null}
<Debug> default.skip_table (933d4b2c-8cea-4bf9-8c93-c56e900eefd1) (SelectExecutor): Index `vix` has dropped 6102/6104 granules.
```

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

<div id="minmax">
  ### minmax
</div>

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

Этот тип индекса корректно работает только со скалярным выражением или кортежем -- индекс никогда не будет применяться к выражениям, которые возвращают тип данных Array или Map.

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

Этот облегчённый тип индекса принимает один параметр — max\_size набора значений на блок (0 допускает
неограниченное количество отдельных значений). Этот набор содержит все значения в блоке (или остаётся пустым, если число значений превышает max\_size). Этот тип индекса хорошо подходит для столбцов с низкой мощностью в пределах каждого набора гранул (то есть когда значения, по сути, "сгруппированы"), но с более высокой общей мощностью.

Накладные расходы, производительность и эффективность этого индекса зависят от мощности внутри блоков. Если каждый блок содержит большое количество уникальных значений, то либо проверка условия запроса по большому набору индекса будет очень затратной, либо индекс не будет применён, поскольку он пуст из-за превышения max\_size.

<div id="text">
  ### text
</div>

Для рабочих нагрузок, связанных с поиском на естественном языке или по произвольному тексту (например, поиском слов или фраз в больших текстовых столбцах), ClickHouse предоставляет **текстовый индекс** (настоящий инвертированный индекс).
Текстовый индекс поддерживает эффективный полнотекстовый поиск и поиск по токенам. Это рекомендуемый вариант для запросов полнотекстового поиска, поскольку он обеспечивает детерминированное индексирование токенов и более высокую производительность для поисковых функций, таких как `hasAnyToken` и `hasAllTokens`, а также оптимизирует все распространённые функции текстового поиска.

Подробности см. в документации по текстовому индексу [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/textindexes).

<div id="bloom-filter-types">
  ### Типы bloom-фильтров
</div>

*bloom-фильтр* — это структура данных, позволяющая с минимальными затратами памяти проверять принадлежность множеству ценой небольшой вероятности ложноположительных срабатываний. Для индексов пропуска данных ложноположительные срабатывания не представляют серьёзной проблемы, поскольку их единственный недостаток — чтение нескольких лишних блоков. Однако сама возможность ложноположительных срабатываний означает, что индексируемое выражение должно с высокой вероятностью быть истинным, иначе релевантные данные могут быть пропущены.

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

Существует три типа индексов пропуска данных на основе bloom-фильтра:

* Базовый **bloom\_filter**, который принимает один необязательный параметр — допустимую вероятность ложноположительных срабатываний в диапазоне от 0 до 1 (если параметр не указан, используется .025).

* Специализированный **tokenbf\_v1** *(Устарело)*). Он принимает три параметра, все они относятся к настройке используемого bloom-фильтра: (1) размер фильтра в байтах (чем больше фильтр, тем меньше ложноположительных срабатываний, но тем больше затраты на хранение), (2) количество применяемых хеш-функций (чем их больше, тем ниже вероятность ложноположительных срабатываний) и (3) seed для хеш-функций bloom-фильтра. Подробнее о том, как эти параметры влияют на работу bloom-фильтра, см. в калькуляторе [здесь](https://hur.st/bloomfilter/).
  Этот индекс работает только с типами данных String, FixedString и Map. Входное выражение разбивается на последовательности символов, разделённые неалфавитно-цифровыми символами. Например, значение столбца `This is a candidate for a \"full text\" search` будет содержать токены `This` `is` `a` `candidate` `for` `full` `text` `search`. Он предназначен для использования в LIKE, EQUALS, IN, hasToken() и аналогичных поисковых запросах по словам и другим значениям внутри более длинных строк. Например, его можно использовать для поиска небольшого числа имён классов или номеров строк в столбце с произвольными строками журналов приложения.

* Специализированный **ngrambf\_v1** *(Устарело)*. Этот индекс работает так же, как token-индекс. Он принимает ещё один параметр перед настройками bloom-фильтра — размер n-грамм для индексации. N-грамма — это строка длиной `n` из любых символов, поэтому строка `A short string` при размере n-граммы 4 будет индексироваться как:
  ```text theme={null}
  'A sh', ' sho', 'shor', 'hort', 'ort ', 'rt s', 't st', ' str', 'stri', 'trin', 'ring'
  ```

Этот индекс также может быть полезен для текстового поиска, особенно в языках без пробелов между словами, например в китайском.

> Для рабочих нагрузок полнотекстового поиска рекомендуется использовать специализированный **текстовый индекс** (см. [Text index for full-text search](/docs/ru/reference/engines/table-engines/mergetree-family/textindexes)) вместо устаревших индексов *tokenbf\_v1* или *ngrambf\_v1*.
> Текстовый индекс предоставляет полноценный инвертированный индекс с более высокой производительностью поиска, более предсказуемым поведением, а также большей гибкостью и производительностью по сравнению с индексами bloom-фильтра на основе токенов.

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

Основная задача индексов пропуска данных — сократить объем данных, анализируемых типичными запросами. С учетом аналитического характера данных в ClickHouse такие запросы в большинстве случаев содержат функциональные выражения. Поэтому для эффективной работы индексы пропуска данных должны корректно взаимодействовать с распространенными функциями. Это происходит в одном из двух случаев:

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

Каждый тип индекса пропуска данных работает с подмножеством доступных функций ClickHouse, подходящих для соответствующей реализации индекса, перечисленных
[здесь](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#functions-support). В целом set-индексы и индексы на основе bloom-фильтра (еще один тип set-индекса) неупорядочены и поэтому не работают с диапазонами. Напротив, индексы minmax особенно хорошо подходят для диапазонов, поскольку определение пересечения диапазонов выполняется очень быстро. Эффективность функций частичного совпадения LIKE, startsWith, endsWith и hasToken зависит от используемого типа индекса, выражения индекса и конкретной структуры данных.

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

Доступны две настройки, применимые к индексам пропуска данных.

* **use\_skip\_indexes**  (0 или 1, по умолчанию 1). Не все запросы могут эффективно использовать индексы пропуска данных. Если определённое условие фильтрации,
  скорее всего, охватывает большинство гранул, применение индекса пропуска данных влечёт за собой лишние, а иногда и существенные, затраты. Установите значение
  0 для запросов, которым индексы пропуска данных, скорее всего, не дадут выигрыша.
* **force\_data\_skipping\_indices** (список имён индексов, разделённых запятыми). Эту настройку можно использовать, чтобы предотвратить некоторые виды неэффективных
  запросов. Если запрос к таблице становится слишком затратным без использования индекса пропуска данных, то при использовании этой настройки с одним или несколькими именами
  индексов для любого запроса, который не использует указанный индекс, будет возвращено исключение. Это не позволит неудачно написанным запросам
  расходовать ресурсы сервера.

<div id="skip-best-practices">
  ## Рекомендации по использованию индекса пропуска данных
</div>

Индексы пропуска данных не всегда интуитивно понятны, особенно тем, кто привык к вторичным построчным индексам из мира RDMS или к инвертированным индексам в документных хранилищах. Чтобы от них был хоть какой-то эффект, индекс пропуска данных в ClickHouse должен позволять избежать чтения достаточного количества гранул, чтобы компенсировать затраты на вычисление самого индекса. Важно, что если значение встречается в индексируемом блоке хотя бы один раз, весь блок всё равно придётся загрузить в память и проверить, а значит, затраты на индекс будут понесены напрасно.

Рассмотрим следующее распределение данных:

<Image img="https://mintcdn.com/private-7c7dfe99/NvnCM4vX9aZ07JxK/images/guides/best-practices/bad_skip.webp?fit=max&auto=format&n=NvnCM4vX9aZ07JxK&q=85&s=de9cae80a4056483dc3c1e07d56f7dbc" size="md" alt="Плохой пропуск" width="2269" height="1616" data-path="images/guides/best-practices/bad_skip.webp" />

Предположим, что primary/ключ ORDER BY — `timestamp`, и есть индекс по `visitor_id`. Рассмотрим следующий запрос:

```sql theme={null}
SELECT timestamp, url FROM table WHERE visitor_id = 1001`
```

Традиционный вторичный индекс был бы очень полезен при таком распределении данных.  Вместо чтения всех 32768 строк, чтобы найти
5 строк с нужным visitor\_id, вторичный индекс содержал бы лишь пять указателей на строки, и с диска были бы
прочитаны только эти пять строки.  В случае индекса пропуска данных ClickHouse верно ровно обратное.  Все 32768 значений в столбце `visitor_id` будут проверены
независимо от типа индекса пропуска данных.

Соответственно, естественное стремление ускорить запросы ClickHouse, просто добавив индекс к ключевым
столбцам, часто оказывается ошибочным.  Эту продвинутую возможность следует использовать только после рассмотрения других альтернатив, таких как изменение первичного ключа (см. [Как выбрать первичный ключ](/docs/ru/guides/clickhouse/data-modelling/sparse-primary-indexes)), использование проекций или materialized view. Даже когда индекс пропуска данных уместен, часто
требуется тщательная настройка как самого индекса, так и таблицы.

В большинстве случаев полезный индекс пропуска данных требует сильной корреляции между первичным ключом и целевым неключевым столбцом/выражением.
Если корреляции нет (как на диаграмме выше), высока вероятность того, что условию фильтрации будет соответствовать хотя бы одна из строк в
блоке из нескольких тысяч значений, и тогда удастся пропустить лишь немного блоков.  Напротив, если диапазон значений первичного ключа (например, время
суток) тесно связан со значениями в потенциально индексируемом столбце (например, возрастом телезрителей), то индекс
типа minmax, скорее всего, окажется полезным.  Обратите внимание, что эту корреляцию можно усилить при вставке данных: либо включив дополнительные
столбцы в ключ сортировки/ORDER BY, либо организовав батчинг вставок так, чтобы значения, связанные с первичным ключом, группировались при вставке.  Например,
все события для определённого site\_id можно сгруппировать и вставить вместе в процессе приёма, даже если первичный ключ
— это временная метка, содержащая события с большого числа сайтов.  Это приведёт к появлению множества гранул, содержащих лишь несколько site ids, поэтому многие
блоки можно будет пропустить при поиске по конкретному значению site\_id.

Ещё один хороший кандидат для индекса пропуска данных — выражения с высокой мощностью, где каждое отдельное значение встречается в данных относительно редко.  Например,
это может быть платформа обсервабилити, которая отслеживает коды ошибок в API-запросах.  Некоторые коды ошибок, хотя и редко встречаются в данных, могут быть особенно
важны для поиска.  Индекс пропуска данных типа set для столбца error\_code позволит обходить подавляющее большинство блоков, которые не содержат
ошибок, и тем самым значительно ускорит запросы, ориентированные на ошибки.

Наконец, главная рекомендация — тестировать, тестировать и ещё раз тестировать. Опять же, в отличие от вторичных b-tree индексов или инвертированных индексов для поиска по документам,
поведение индекса пропуска данных нелегко предсказать. Их добавление в таблицу влечёт заметные издержки как для приёма данных, так и для запросов,
которые по тем или иным причинам не получают выгоды от индекса. Их всегда следует тестировать на данных, близких к реальным, а тестирование должно
включать вариации типа, размера гранулярности и других параметров. Тестирование часто выявляет закономерности и подводные камни, которые неочевидны при
одних лишь мысленных экспериментах.

<div id="related-docs">
  ## Связанная документация
</div>

* [Руководство по лучшим практикам](/docs/ru/concepts/best-practices/using-data-skipping-indices)
* [Примеры индексов пропуска данных](/docs/ru/concepts/features/performance/skip-indexes/skipping-indexes-examples)
* [Управление индексами пропуска данных](/docs/ru/reference/statements/alter/skipping-index)
* [Информация о системной таблице](/docs/ru/reference/system-tables/data_skipping_indices)
