Полнотекстовый поиск с использованием полнотекстовых индексов
Полнотекстовые индексы — это экспериментальный тип вторичных индексов, которые обеспечивают быстрые возможности поиска по тексту для String или FixedString столбцов. Основная идея полнотекстового индекса заключается в том, чтобы хранить отображение от "терминов" к строкам, которые содержат эти термины. "Термины" — это токенизированные ячейки строкового столбца. Например, строковая ячейка "Я немного опоздаю" по умолчанию токенизируется на шесть терминов "Я", "немного", "опоздаю". Другой вид токенизатора — n-grams. Например, результат токенизации 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,7 млн строк комментариев на популярном сайте Hacker News. Вот таблица без полнотекстового индекса:
28,7 млн строк находятся в файле Parquet в S3 — давайте вставим их в таблицу hackernews
:
Рассмотрим следующий простой поиск термина ClickHouse
(и его различные варианты верхнего и нижнего регистра) в столбце comment
:
Обратите внимание, что выполнение запроса занимает 3 секунды:
Мы используем ALTER TABLE
и добавляем полнотекстовый индекс на строчные буквы столбца comment
, затем материализуем его (что может занять некоторое время — подождите, пока он не материализуется):
Мы выполняем тот же запрос...
...и замечаем, что запрос выполняется в 4 раза быстрее:
Мы также можем искать один или все несколько терминов, т.е. дизъюнкции или конъюнкции:
В отличие от других вторичных индексов, полнотекстовые индексы (пока) отображаются на номера строк (ID строк), а не на ID гранул. Причина этой разработки — производительность. На практике пользователи часто ищут несколько терминов одновременно. Например, предикат фильтрации WHERE s LIKE '%little%' OR s LIKE '%big%'
может быть оценен напрямую, используя полнотекстовый индекс, формируя объединение списков ID строк для терминов "little" и "big". Это также означает, что параметр GRANULARITY
, указанный при создании индекса, не имеет значения (он может быть удален из синтаксиса в будущем).