ClickHouseMiddleTechnical

Что такое data skipping indexes в ClickHouse и когда их следует использовать?

Data skipping indexes — дополнительные индексы в ClickHouse, хранящие агрегированную информацию о блоках данных (minmax, bloom filter, set) для пропуска гранул без чтения колонки целиком.

Data Skipping Indexes в ClickHouse

Data skipping indexes (DSI) — дополнительные индексы поверх primary index. Они не ускоряют точечный поиск, а помогают пропускать (skip) блоки данных, которые заведомо не содержат нужных строк. Используются для колонок, не входящих в primary key.

Виды data skipping indexes

1. minmax

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

CREATE TABLE events
(
  user_id    UInt64,
  event_date Date,
  amount     Float64,
  INDEX amount_idx amount TYPE minmax GRANULARITY 4
  -- GRANULARITY 4: один блок индекса = 4 гранулы = 4*8192 строк
)
ENGINE = MergeTree()
ORDER BY (user_id, event_date);

-- Эффективно: пропускает блоки где max(amount) < 1000
SELECT count() FROM events WHERE amount > 1000;

2. set

Хранит множество уникальных значений для каждого блока. Хорош для колонок с малым числом уникальных значений в блоке (низкая кардинальность).

ALTER TABLE events
ADD INDEX status_idx status TYPE set(100) GRANULARITY 2;
-- set(100): хранит до 100 уникальных значений на блок
-- Если значений > 100, блок не пропускается (conservative)

ALTER TABLE events MATERIALIZE INDEX status_idx;
-- Принудительно построить индекс для существующих данных

-- Эффективно: пропускает блоки, не содержащие 'failed'
SELECT count() FROM events WHERE status = 'failed';

3. bloom_filter

Probabilistic структура данных — может дать false positive (скажет «блок содержит значение» когда не содержит), но никогда false negative. Хорош для String колонок и равенства.

ALTER TABLE events
ADD INDEX email_idx email TYPE bloom_filter(0.01) GRANULARITY 1;
-- 0.01: допустимый процент false positives (1%)

-- Эффективно для точечного поиска по email
SELECT * FROM events WHERE email = 'user@example.com';

-- Также работает с LIKE (prefix):
SELECT * FROM events WHERE email LIKE 'user@%';
-- НЕ работает с: LIKE '%suffix'

4. ngrambf_v1 и tokenbf_v1

Bloom filter по n-граммам или токенам строки. Используются для full-text поиска.

-- ngrambf: разбивает строку на n-граммы
ALTER TABLE articles
ADD INDEX content_ngram content
TYPE ngrambf_v1(3, 65536, 2, 0) GRANULARITY 1;
-- Параметры: (n=3, filter_size=65536, hash_functions=2, seed=0)

-- tokenbf: токенизирует по пробелам/знакам
ALTER TABLE logs
ADD INDEX message_token message TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 1;

-- Эффективно для LIKE с %substring%:
SELECT * FROM articles WHERE content LIKE '%ClickHouse%';

Когда использовать DSI

DSI имеет смысл когда:

  • Колонка не входит в primary key, но часто используется в WHERE.
  • Данные в колонке имеют хорошую локальность — строки с похожими значениями физически рядом.
  • Фильтр имеет высокую селективность (отсеивает много блоков).
-- Проверить эффективность индекса через system.query_log
SELECT
  query,
  ProfileEvents['SelectedMarks'] AS marks_read,
  ProfileEvents['SkippedUnreadGranules'] AS granules_skipped
FROM system.query_log
WHERE type = 'QueryFinish'
  AND query LIKE '%events%'
ORDER BY event_time DESC
LIMIT 5;

Управление индексами

-- Просмотр существующих индексов
SELECT name, type, expr, granularity
FROM system.data_skipping_indices
WHERE table = 'events';

-- Удаление индекса
ALTER TABLE events DROP INDEX amount_idx;

-- Материализация (построение для существующих данных)
ALTER TABLE events MATERIALIZE INDEX status_idx IN PARTITION '202401';

Подводные камни

  • DSI не помогает при случайном распределении данных: если значения колонки случайно перемешаны по блокам, каждый блок будет содержать искомое значение — skip rate = 0, только overhead.
  • GRANULARITY критична для bloom_filter: слишком маленькая гранулярность → много мелких фильтров → большой overhead памяти и I/O. Слишком большая → меньше пропускается блоков.
  • set(0) хранит все значения: параметр 0 означает «неограниченно» — может съесть огромное количество памяти для высококардинальных колонок.
  • bloom_filter даёт false positives: маленький false_positive_p (0.001) требует больше памяти; баланс между точностью и размером индекса.
  • Индекс не применяется без MATERIALIZE: добавление индекса через ALTER TABLE не применяется к существующим данным автоматически. Нужен явный MATERIALIZE INDEX.
  • ngrambf медленно строится: материализация ngrambf на большой таблице может занять часы — планировать в maintenance window.
  • DSI не заменяет правильный primary key: оптимизировать ORDER BY важнее, чем добавлять DSI. DSI — дополнение, а не замена основного дизайна схемы.
  • Несовместимость с некоторыми функциями: bloom_filter не работает с LIKE '%suffix' (trailing wildcard) и со многими функциями над колонкой — только прямое сравнение и некоторые LIKE.

Common mistakes

  • Объяснять data skipping indexes как OLTP-механику row-store базы вместо аналитической колоночной модели ClickHouse.
  • Путать primary key ClickHouse с уникальным constraint из PostgreSQL или MySQL.
  • Игнорировать parts, merges, ORDER BY, sparse index и стоимость маленьких вставок.
  • Предлагать синтаксис или транзакционное поведение, которого в ClickHouse нет.

What the interviewer is testing

  • Кандидат объясняет data skipping indexes через реальный механизм ClickHouse, а не общими словами.
  • Приводит корректный SQL или диагностический запрос для этой СУБД.
  • Называет ограничения, версионные отличия или эксплуатационные последствия.
  • Связывает ответ с проектированием приложения, производительностью, надежностью или безопасностью.

Sources

Related topics