Полнотекстовый поиск с использованием полнотекстовых индексов
Полнотекстовые индексы — это экспериментальный тип вторичных индексов, который обеспечивает быстрые возможности текстового поиска для колонок String или FixedString. Основная идея полнотекстового индекса заключается в том, чтобы хранить отображение от "терминов" к строкам, которые содержат эти термины. "Термины" — это токенизированные ячейки строковой колонки. Например, строковая ячейка "I will be a little late" по умолчанию токенизируется на шесть терминов: "I", "will", "be", "a", "little" и "late". Другой тип токенизатора — n-grams. Например, результат токенизации 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)
: Использовать максимальное количество строк по умолчанию, равное 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
, указанный при создании индекса, не имеет смысла (в будущем он может быть удален из синтаксиса).