Текстовые индексы (inverted)
В предыдущем уроке мы видели, что ngrambf_v1 и tokenbf_v1 используют Bloom-фильтры для текстового поиска — и дают ложные срабатывания. ClickHouse предлагает современную замену: text index (inverted index). Это GA-фича, production-ready, которая обеспечивает детерминированный полнотекстовый поиск без ложных срабатываний.
Чем text index отличается от Bloom-фильтров
| Свойство | ngrambf_v1 / tokenbf_v1 | text index |
|---|---|---|
| Статус | Устаревшие (legacy) | GA (production-ready) |
| Внутренняя структура | Bloom-фильтр | Inverted index (token -> номера строк) |
| Ложные срабатывания | Да (вероятностный) | Нет (детерминированный) |
| Производительность (холодные запросы) | Базовая | 7-10x быстрее |
| Поддерживаемые типы | String | String, Array(String), Map ключи и значения |
Ключевое отличие: text index строит настоящий инвертированный индекс — маппинг “токен -> номера строк, содержащих этот токен”. Когда запрос ищет токен “error”, индекс точно знает, в каких строках он встречается. Bloom-фильтр же может только ответить “возможно есть” (с вероятностью ложного срабатывания).
Создание text index
CREATE TABLE logs (
timestamp DateTime,
service String,
message String,
INDEX idx_message message TYPE text GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY (service, timestamp);
TYPE text — тип индекса. GRANULARITY 1 — одна запись индекса на каждую гранулу (максимальная точность).
Добавление к существующей таблице:
ALTER TABLE logs ADD INDEX idx_message message TYPE text GRANULARITY 1;
ALTER TABLE logs MATERIALIZE INDEX idx_message;
Запросы с text index
Text index поддерживает несколько функций поиска:
hasToken — точное совпадение токена
-- Ищет строки, содержащие токен "error" (целое слово)
SELECT timestamp, service, message
FROM logs
WHERE hasToken(message, 'error');
hasToken разбивает строку на токены по не-алфавитно-цифровым разделителям. Строка “connection error: timeout” содержит токены: “connection”, “error”, “timeout”. Запрос hasToken(message, 'error') найдёт эту строку.
hasAny / hasAll — множественный поиск токенов
-- Строки, содержащие хотя бы один из токенов
SELECT * FROM logs
WHERE hasToken(message, 'error') OR hasToken(message, 'fatal');
-- Строки, содержащие все указанные токены
SELECT * FROM logs
WHERE hasToken(message, 'connection') AND hasToken(message, 'timeout');
Подстрочный поиск (LIKE)
-- Text index ускоряет и подстрочный поиск
SELECT * FROM logs WHERE message LIKE '%timeout%';
Поддержка сложных типов
Text index работает не только с обычными строками:
Array(String)
CREATE TABLE events (
timestamp DateTime,
tags Array(String),
INDEX idx_tags tags TYPE text GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY timestamp;
-- Поиск по массиву тегов
SELECT * FROM events WHERE has(tags, 'production');
Map ключи и значения
CREATE TABLE traces (
timestamp DateTime,
attributes Map(String, String),
INDEX idx_attrs_keys mapKeys(attributes) TYPE text GRANULARITY 1,
INDEX idx_attrs_values mapValues(attributes) TYPE text GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY timestamp;
-- Поиск по ключам и значениям Map
SELECT * FROM traces
WHERE attributes['service.name'] = 'payment-api';
Как text index работает внутри
Миграция с ngrambf_v1 / tokenbf_v1 на text index
Если у вас есть существующие таблицы с устаревшими Bloom-индексами:
-- 1. Удалить старый индекс
ALTER TABLE logs DROP INDEX idx_message_ngram;
-- 2. Добавить text index
ALTER TABLE logs ADD INDEX idx_message message TYPE text GRANULARITY 1;
-- 3. Материализовать для существующих данных
ALTER TABLE logs MATERIALIZE INDEX idx_message;
Миграция не требует пересоздания таблицы. Просто замените старый индекс новым. Новый text index будет работать для всех существующих данных после MATERIALIZE INDEX.
Ключевые выводы
- Text index — GA-фича ClickHouse, заменяющая ngrambf_v1 и tokenbf_v1 для полнотекстового поиска.
- В отличие от Bloom-фильтров, text index создаёт детерминированный inverted index (token -> номера строк) — ложных срабатываний нет.
- Производительность на холодных запросах в 7-10 раз выше, чем у Bloom-индексов.
- Поддерживает
String,Array(String), Map ключи и значения. - Основные функции:
hasToken()для токенного поиска,LIKEдля подстрочного.