Learning Platform
Глоссарий Troubleshooting
Урок 06.01 · 25 мин
Средний
Skip Indexminmaxsetbloom_filterngrambf_v1tokenbf_v1GRANULARITYData Skipping

Типы skip-индексов

Primary key в ClickHouse (sparse index) позволяет эффективно pruneить гранулы по префиксу ORDER BY ключа. Но что делать, когда фильтр по столбцу, который не входит в ORDER BY? Для этого существуют skip-индексы (data skipping indexes) — вторичные индексы, которые хранят агрегированную информацию о блоках гранул и позволяют пропускать заведомо не подходящие блоки.

Skip-индекс НЕ указывает на конкретную строку. Он говорит: “в этих N гранулах точно нет значения X” или “в этих N гранулах значения лежат в диапазоне [A, B]”. Если skip-индекс определяет, что блок не содержит нужных данных, ClickHouse пропускает его целиком.


GRANULARITY таблицы vs GRANULARITY skip-индекса

Прежде чем создавать skip-индексы, нужно чётко понять два разных значения слова GRANULARITY:

GRANULARITY таблицы (index_granularity = 8192 строк на гранулу) — это количество строк в одной грануле. Это свойство таблицы, определяющее размер минимального блока чтения. Каждая гранула — 8192 строки (по умолчанию).

GRANULARITY skip-индекса (например, GRANULARITY 4) — это количество гранул в одном блоке индекса. Одна запись skip-индекса агрегирует информацию о N гранулах.

WARNING

GRANULARITY таблицы (index_granularity = 8192 строк на гранулу) — это НЕ то же самое, что GRANULARITY skip-индекса (количество гранул на один блок индекса). GRANULARITY=1 означает запись индекса на каждую гранулу (самый точный). GRANULARITY=4 — одна запись на 4 гранулы (грубее, компактнее).

При index_granularity=8192 и skip-индексе с GRANULARITY=4:

  • 1 блок skip-индекса = 4 гранулы = 4 * 8192 = 32 768 строк
  • Если индекс определяет, что блок не содержит нужных данных, пропускаются все 32 768 строк

При GRANULARITY=1:

  • 1 блок skip-индекса = 1 гранула = 8192 строк
  • Максимальная точность, но больший размер индекса

Синтаксис создания skip-индекса

CREATE TABLE events (
    event_date Date,
    user_id UInt64,
    status String,
    INDEX idx_status status TYPE set(100) GRANULARITY 2
) ENGINE = MergeTree()
ORDER BY (event_date, user_id);

Также можно добавить skip-индекс к существующей таблице:

ALTER TABLE events ADD INDEX idx_status status TYPE set(100) GRANULARITY 2;
-- Индекс создаётся только для новых данных.
-- Для существующих данных:
ALTER TABLE events MATERIALIZE INDEX idx_status;

Пять типов skip-индексов

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

5 типов skip-индексов ClickHouse
minmax: min/max значенияminmax -- хранит минимальное и максимальное значения столбца в каждом блоке гранул. Если запрос ищет значение вне диапазона [min, max], блок пропускается. Ложных срабатываний нет -- результат всегда точный.
set(N): множество значенийset(N) -- хранит до N уникальных значений столбца в блоке. Если искомое значение не входит в множество, блок пропускается. Ложных срабатываний нет (если N достаточно). Если уникальных значений больше N, индекс для этого блока отключается.
bloom_filter: Bloom-фильтрbloom_filter(fp_rate) -- хранит Bloom-фильтр для блока. Проверяет, может ли значение присутствовать в блоке. Ложные срабатывания возможны (вероятность задаётся fp_rate, по умолчанию 0.025). Подходит для высококардинальных столбцов.
ngrambf_v1: n-граммы (legacy)ngrambf_v1(n, size, hashes, seed) -- Bloom-фильтр n-грамм для подстрочного поиска (LIKE). Разбивает строку на подстроки длиной n символов и добавляет в Bloom-фильтр. Устаревший тип -- используйте text index вместо него.
tokenbf_v1: токены (legacy)tokenbf_v1(size, hashes, seed) -- Bloom-фильтр токенов для поиска по словам (hasToken). Разбивает строку по не-алфавитно-цифровым разделителям и добавляет токены в Bloom-фильтр. Устаревший тип -- используйте text index вместо него.

1. minmax

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

CREATE TABLE metrics (
    timestamp DateTime,
    host String,
    cpu_usage Float32,
    INDEX idx_cpu cpu_usage TYPE minmax GRANULARITY 4
) ENGINE = MergeTree()
ORDER BY (host, timestamp);

-- Запрос: ClickHouse пропустит блоки, где max(cpu_usage) < 90
SELECT * FROM metrics WHERE cpu_usage > 90;

Ложных срабатываний нет: если искомое значение вне диапазона [min, max] блока, блок гарантированно не содержит нужных строк.

2. set(N)

Хранит до N уникальных значений столбца в блоке. Подходит для равенственных фильтров на столбцах с ограниченной кардинальностью.

CREATE TABLE logs (
    timestamp DateTime,
    level String,
    message String,
    INDEX idx_level level TYPE set(10) GRANULARITY 2
) ENGINE = MergeTree()
ORDER BY timestamp;

-- Запрос: пропустит блоки, где level='ERROR' не встречается
SELECT * FROM logs WHERE level = 'ERROR';

Если в блоке больше N уникальных значений, skip-индекс для этого блока просто не работает (не пропускает его). Ложных срабатываний нет.

3. bloom_filter

Хранит Bloom-фильтр для блока. Подходит для равенственных фильтров и IN на высококардинальных столбцах (user_id, request_id, session_id).

CREATE TABLE events (
    event_date Date,
    user_id UInt64,
    event_type String,
    INDEX idx_user user_id TYPE bloom_filter(0.01) GRANULARITY 3
) ENGINE = MergeTree()
ORDER BY event_date;

-- Запрос: Bloom-фильтр проверяет, может ли user_id=12345 быть в блоке
SELECT * FROM events WHERE user_id = 12345;

Ложные срабатывания возможны: Bloom-фильтр может ответить “возможно есть”, когда на самом деле значения нет (false positive). Параметр fp_rate (по умолчанию 0.025) определяет вероятность ложного срабатывания. Чем ниже — тем больше памяти занимает фильтр.

4. ngrambf_v1 (устаревший)

Bloom-фильтр n-грамм для подстрочного поиска. Разбивает текст на подстроки длиной n символов.

CREATE TABLE logs (
    timestamp DateTime,
    message String,
    INDEX idx_message message TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY timestamp;

-- Подстрочный поиск
SELECT * FROM logs WHERE message LIKE '%timeout%';
WARNING

ngrambf_v1 считается устаревшим. Для текстового поиска рекомендуется использовать text index (inverted index), который является GA-фичей и работает в 7-10 раз быстрее на холодных запросах.

5. tokenbf_v1 (устаревший)

Bloom-фильтр токенов для поиска по словам. Разбивает текст по не-алфавитно-цифровым разделителям.

CREATE TABLE logs (
    timestamp DateTime,
    message String,
    INDEX idx_tokens message TYPE tokenbf_v1(256, 2, 0) GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY timestamp;

-- Токенный поиск
SELECT * FROM logs WHERE hasToken(message, 'error');
WARNING

tokenbf_v1 считается устаревшим. Используйте text index вместо него — он обеспечивает детерминированный результат без ложных срабатываний.


Матрица поддерживаемых функций

Не каждый тип skip-индекса поддерживает каждую операцию:

Функцияminmaxsetbloom_filter
=ДаДаДа
!=ДаДаДа
< / > / <= / >=ДаНетНет
INДаДаДа
LIKEНетДаНет
hasTokenНетНетНет
TIP

Для полнотекстового поиска (LIKE '%pattern%' и hasToken()) используйте text index (урок 02) — он заменяет ngrambf_v1 и tokenbf_v1 с лучшей производительностью и без ложных срабатываний.


Проверка эффективности skip-индекса

Чтобы убедиться, что skip-индекс действительно пропускает блоки:

EXPLAIN indexes = 1
SELECT count()
FROM events
WHERE user_id = 12345;

Вывод покажет строку вида:

  Skip
    Name: idx_user
    Description: bloom_filter GRANULARITY 3
    Parts: 4/8
    Granules: 12/100

Granules: 12/100 означает: из 100 гранул skip-индекс пропустил 88, читаются только 12. Чем меньше отношение — тем эффективнее индекс.


Ключевые выводы

  1. Skip-индексы — вторичные индексы для столбцов вне ORDER BY. Они не указывают на строку, а говорят “в этом блоке точно нет нужных данных”.
  2. GRANULARITY таблицы (8192 строки) — не путать с GRANULARITY skip-индекса (количество гранул на блок индекса).
  3. minmax и set(N) — детерминированные (нет ложных срабатываний). bloom_filter — вероятностный.
  4. ngrambf_v1 и tokenbf_v1 устарели — используйте text index (урок 02).
  5. Всегда проверяйте эффективность через EXPLAIN indexes = 1.
B-tree: структура страницы и точечный поиск Parquet Metadata и Statistics: row group pruning

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Столбец user_id (UInt64) имеет высокую кардинальность — миллионы уникальных значений. Запросы часто фильтруют по user_id через равенство и IN. Какой тип skip-индекса наиболее подходит?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7