Полнотекстовый поиск с использованием полнотекстовых индексов
Полнотекстовые индексы являются экспериментальным типом вторичных индексов, которые предоставляют быстрые возможности текстового поиска для String или FixedString колонок. Основная идея полнотекстового индекса заключается в том, чтобы хранить сопоставление от "терминов" к строкам, которые содержат эти термины. "Термины" — это токенизированные ячейки строковой колонки. Например, ячейка строки "I will be a little late" по умолчанию токенизируется на шесть терминов: "I", "will", "be", "a", "little" и "late". Другой вид токенизатора — это n-граммы. Например, результат токенизации 3-граммами будет 21 термин: "I w", " wi", "wil", "ill", "ll ", "l b", " be" и т. д. Чем более мелко токенизируются входные строки, тем больше и полезнее будет получившийся полнотекстовый индекс.
Полнотекстовые индексы являются экспериментальными и не должны использоваться в производственных средах. Они могут измениться в будущем несовместимым образом, например, в отношении их 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)
: Использовать максимальное количество строк по умолчанию, равное 64К.
Будучи типом индекса пропуска, полнотекстовые индексы могут быть удалены или добавлены к колонке после создания таблицы:
Чтобы использовать индекс, не требуются специальные функции или синтаксис. Обычные предикаты строкового поиска автоматически используют индекс. В качестве примеров рассмотрим:
Полнотекстовый индекс также работает с колонками типа 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
, предоставленный при создании индекса, не имеет значения (в будущем он может быть убран из синтаксиса).