Итоги модуля: skip-индексы и словари
Модуль покрывает два механизма ускорения запросов: skip-индексы (вторичные индексы, пропускающие блоки данных) и словари (pre-loaded lookup-структуры, заменяющие JOIN). Оба дополняют primary key — skip-индексы фильтруют по столбцам вне ORDER BY, словари обогащают факты dimension-атрибутами без rebuild hash table.
Quick reference: skip-индексы
Синтаксис создания
-- При CREATE TABLE
CREATE TABLE logs (
timestamp DateTime,
user_id UInt64,
message String,
INDEX idx_user user_id TYPE set(100) GRANULARITY 4,
INDEX idx_msg message TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY timestamp;
-- ALTER TABLE (добавление к существующей таблице)
ALTER TABLE logs ADD INDEX idx_status status TYPE minmax GRANULARITY 2;
ALTER TABLE logs MATERIALIZE INDEX idx_status;
5 типов skip-индексов
| Тип | Что хранит | Для каких запросов | GRANULARITY |
|---|---|---|---|
| minmax | Min/max значения в блоке | Range: WHERE x > 100, BETWEEN | 2-4 |
| set(N) | N уникальных значений | Equality: WHERE status = 'ok', IN (...) | 2-4 |
| bloom_filter | Вероятностный фильтр | Equality, IN, has() для массивов | 1-2 |
| ngrambf_v1 | N-gram Bloom filter (legacy) | LIKE '%text%', hasToken() | 1 |
| tokenbf_v1 | Token Bloom filter (legacy) | hasToken(), equality по словам | 1 |
ngrambf_v1 и tokenbf_v1 — legacy. Для full-text поиска используйте text index (inverted index), GA с ClickHouse 26.2. Text index в 7-10x быстрее и не имеет ложных срабатываний Bloom filter.
Text index (inverted index)
-- Современная замена ngrambf_v1/tokenbf_v1
ALTER TABLE logs ADD INDEX idx_search message TYPE text GRANULARITY 1;
ALTER TABLE logs MATERIALIZE INDEX idx_search;
-- Использование: hasToken(), multiSearchAny(), LIKE
SELECT * FROM logs WHERE hasToken(message, 'error');
Quick reference: SAMPLE BY
-- Создание таблицы с SAMPLE BY
CREATE TABLE events (
user_id UInt64,
event_type String,
timestamp DateTime
) ENGINE = MergeTree()
ORDER BY (intHash32(user_id), timestamp)
SAMPLE BY intHash32(user_id);
-- Детерминированная 10% выборка
SELECT count(), uniq(user_id)
FROM events
SAMPLE 0.1;
-- _sample_factor: коэффициент экстраполяции
SELECT count() * _sample_factor AS estimated_total
FROM events
SAMPLE 0.01;
Quick reference: словари
CREATE DICTIONARY
CREATE DICTIONARY country_dict (
code String,
country_name String,
continent String
)
PRIMARY KEY code
SOURCE(CLICKHOUSE(TABLE 'countries' DB 'default'))
LAYOUT(HASHED())
LIFETIME(MIN 300 MAX 600);
dictGet()
-- Обогащение: dictGet(dict, attr, key)
SELECT
event_id,
dictGet('country_dict', 'country_name', country_code) AS country
FROM events;
-- С default-значением для missing keys
SELECT dictGetOrDefault('country_dict', 'country_name', code, 'Unknown')
FROM events;
-- Direct Join: JOIN через словарь (до 25x быстрее hash join)
SET join_algorithm = 'direct';
SELECT e.*, c.country_name
FROM events e LEFT JOIN country_dict c ON e.country_code = c.code;
Выбор layout-а
| Сценарий | Layout | Почему |
|---|---|---|
| UInt64 ключи, до 500K записей | flat | Массив, fastest O(1) |
| UInt64 ключи, 100K-10M записей | hashed | Hash table, fast O(1) |
| Составной ключ (country + city) | complex_key_hashed | Multi-column hash |
| IP-геолокация по CIDR | ip_trie | Longest prefix match |
| Огромный словарь, hot key pattern | cache | LRU, только hot keys в RAM |
| Date-range lookup (курсы валют) | range_hashed | Min/max range match |
Ключевые выводы по модулю
-
Skip-индексы пропускают блоки гранул по агрегированной информации. GRANULARITY skip-индекса — количество гранул на один блок (не строк). GRANULARITY=1 — самый точный.
-
Selectivity определяет эффективность skip-индекса. Низкая кардинальность (status, country) — хорошо. Высокая кардинальность (user_id, timestamp) — skip-индекс бесполезен (каждый блок содержит нужные значения).
-
Text index (inverted, GA 26.2) заменяет legacy ngrambf_v1/tokenbf_v1. Точный (нет false positives), в 7-10x быстрее. Используйте для full-text поиска.
-
SAMPLE BY — детерминированная выборка для approximate queries. intHash32() обеспечивает равномерное распределение. _sample_factor — коэффициент экстраполяции.
-
Словари загружают справочные данные в RAM. LAYOUT определяет структуру (flat/hashed/cache/ip_trie/…). LIFETIME рандомизирует обновление между репликами.
-
dictGet() заменяет JOIN для dimension enrichment. Pre-loaded lookup без rebuild hash table на каждый запрос. Direct Join (
join_algorithm='direct') — до 25x быстрее hash join при сохранении SQL JOIN синтаксиса.