Полнотекстовый поиск с использованием полнотекстовых индексов
Полнотекстовые индексы - это экспериментальный тип вторичных индексов, который предоставляет быстрые возможности текстового поиска для колонок String или FixedString. Основная идея полнотекстового индекса заключается в хранении соответствия от "терминов" к строкам, которые содержат эти термины. "Термины" представляют собой токенизированные ячейки строковой колонки. Например, ячейка строки "Я немного опоздаю" по умолчанию токенизируется на шесть терминов: "Я", "немного", "опоздаю". Другой вид токенизатора - это n-граммы. Например, результат токенизации 3-граммами будет 21 термин "Я н", " нем", "нем", "ем", "м ", " о", " оп", " оп", "поз", "оза", "заю" и т.д. Чем более детализированно токенизируются входные строки, тем больше, но и более полезным будет полученный полнотекстовый индекс.
Полнотекстовые индексы являются экспериментальными и пока не должны использоваться в производственных средах. В будущем они могут изменяться обратно несовместимыми способами, например, в отношении их синтаксиса DDL/DQL или характеристик производительности/сжатия.
Использование
Чтобы использовать полнотекстовые индексы, сначала активируйте их в конфигурации:
Полнотекстовый индекс можно определить на строковой колонке, используя следующий синтаксис:
В предыдущих версиях ClickHouse соответствующее название типа индекса было inverted
.
где N
указывает токенизатор:
full_text(0)
(или сокращенно:full_text()
) устанавливает токенизатор на "токены", т.е. разбивает строки по пробелам,full_text(N)
сN
от 2 до 8 устанавливает токенизатор на "ngrams(N)".
Максимальное количество строк в списке публикаций может быть указано в качестве второго параметра. Этот параметр может быть использован для контроля размеров списков публикаций, чтобы избежать генерации огромных файлов списков публикаций. Существуют следующие варианты:
full_text(ngrams, max_rows_per_postings_list)
: использовать указанный max_rows_per_postings_list (при условии, что он не равен 0)full_text(ngrams, 0)
: без ограничения максимального количества строк в списке публикацийfull_text(ngrams)
: использовать максимальное количество строк по умолчанию, которое составляет 64K.
Будучи типом индекса, который пропускает данные, полнотекстовые индексы могут быть удалены или добавлены в колонку после создания таблицы:
Для использования индекса не требуется никаких специальных функций или синтаксиса. Типичные предикаты строкового поиска автоматически используют индекс. В качестве примеров рассмотрим:
Полнотекстовый индекс также работает с колонками типа Array(String)
, Array(FixedString)
, Map(String)
и Map(String)
.
Как и для других вторичных индексов, каждая часть колонки имеет свой собственный полнотекстовый индекс. Более того, каждый полнотекстовый индекс внутренне делится на "сегменты". Наличие и размер сегментов, как правило, являются прозрачными для пользователей, но размер сегмента определяет потребление памяти во время создания индекса (например, когда два блока сливаются). Параметр конфигурации "max_digestion_size_per_segment" (по умолчанию: 256 МБ) контролирует объем данных, считываемых из подлежащей колонки перед созданием нового сегмента. Увеличение параметра увеличивает промежуточное потребление памяти для создания индекса, но также улучшает производительность поиска, поскольку в среднем нужно проверять меньше сегментов для оценки запроса.
Полнотекстовый поиск в наборе данных Hacker News
Давайте рассмотрим улучшения производительности полнотекстовых индексов на большом наборе данных с большим объемом текста. Мы будем использовать 28.7M строк комментариев на популярном сайте Hacker News. Вот таблица без полнотекстового индекса:
28.7M строк находятся в файле Parquet в S3 - давайте вставим их в таблицу hackernews
:
Рассмотрим следующий простой поиск термина ClickHouse
(и его различные варианты с заглавными и строчными буквами) в колонке comment
:
Обратите внимание, что выполнение запроса занимает 3 секунды:
Мы используем ALTER TABLE
и добавляем полнотекстовый индекс на строчном варианте колонки comment
, затем материализуем его (это может занять некоторое время - подождите, пока он будет материализован):
Мы выполняем тот же запрос...
...и замечаем, что запрос выполняется в 4 раза быстрее:
Мы также можем искать один или все из нескольких терминов, т.е. дизъюнкции или конъюнкции:
В отличие от других вторичных индексов, полнотекстовые индексы (пока) отображаются на номера строк (идентификаторы строк), а не на идентификаторы гранул. Причина такого дизайна заключается в производительности. На практике пользователи часто ищут несколько терминов одновременно. Например, предикат фильтра WHERE s LIKE '%little%' OR s LIKE '%big%'
может быть оценен напрямую с использованием полнотекстового индекса путем формирования объединения списков идентификаторов строк для терминов "little" и "big". Это также означает, что параметр GRANULARITY
, предоставляемый для создания индекса, не имеет значения (в будущем он может быть исключен из синтаксиса).