Перейти к содержимому
Последнее обновление

Полнотекстовый поиск

Кратко Полнотекстовый поиск в Apache Doris преобразует текстовые столбцы в доступные для поиска термы с помощью инвертированного индекса и предоставляет шесть операторов MATCH_* в SQL: MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_PHRASE_EDGE и MATCH_REGEXP. Вы выбираете токенизатор при создании индекса и пишете обычный SQL-запрос. Планировщик Apache Doris проталкивает предикат в индекс, поэтому отдельный поисковый кластер не требуется.

Полнотекстовый поиск в Apache Doris: выполнение полнотекстовых запросов непосредственно в SQL с помощью операторов, учитывающих токенизацию и предлагающих гораздо больше возможностей, чем поиск подстрок с LIKE.

Зачем использовать полнотекстовый поиск в Apache Doris?​

Полнотекстовый поиск в Apache Doris объединяет полноценный текстовый поиск и аналитику хранилища данных в одном движке. Его операторы учитывают токены и предлагают гораздо больше возможностей, чем поиск подстрок с LIKE. Большинство хранилищ данных предоставляют LIKE '%foo%' и называют это текстовым поиском. Такой подход работает для одной строки из десяти тысяч. Он перестаёт справляться, как только нужно найти все комментарии к заказам с упоминанием «battery» или «batteries», все строки журнала с фразой «connection refused» или все товары, описание которых начинается с «wireless».

LIKE читает каждое значение целиком, байт за байтом. Оператор не знает, что «Running» и «running» — одно и то же слово, а «search engine optimization» — три терма в определённом порядке, а не просто подстрока. Обычный обходной путь — подключить Elasticsearch, оплачивать второй кластер и создавать ненадёжные конвейеры синхронизации.

Apache Doris объединяет всё это в одном движке. Та же таблица, которая обслуживает агрегации, обслуживает и текстовые запросы, а имена операторов выглядят привычно для SQL.

  • Поиск подстрок с LIKE приводит к полному сканированию. Инвертированный индекс превращает текстовые предикаты в поиск по спискам вхождений.
  • Поиск по мере ввода, поиск фраз и регулярные выражения по токенам требуют семантики сопоставления, которую LIKE не может выразить.
  • Смешанные предикаты по структурированным данным и тексту (category = 'A' AND content MATCH_ANY 'doris') планируются и выполняются в рамках одного запроса.

Что такое полнотекстовый поиск в Apache Doris?​

Полнотекстовый поиск в Apache Doris — это возможность выполнения запросов на основе инвертированного индекса. Вы объявляете индекс INVERTED для столбца типа STRING, TEXT, VARCHAR или ARRAY<STRING>, выбираете анализатор, после чего операторы MATCH_* в предложениях WHERE проталкиваются в индекс. Базовая таблица остаётся обычной таблицей Apache Doris, к которой уже обращаются ваши дашборды.

Основные термины

  • Анализатор (парсер): конвейер, который преобразует исходный текст в доступные для поиска термы. Встроенные варианты: none (точное совпадение), english, chinese, unicode, standard и icu. Пользовательские анализаторы могут последовательно объединять char_filter, tokenizer и один или несколько этапов token_filter.
  • Запрос по термам: сопоставляет отдельные токены. MATCH_ANY объединяет термы по OR, а MATCH_ALL — по AND.
  • Запрос по фразе: сопоставляет токены, расположенные рядом и в заданном порядке. Для парсеров без токенизации требует настройки индекса "support_phrase" = "true".
  • score(): оценка релевантности BM25, доступная при наличии индекса с токенизацией, предиката MATCH_* и ORDER BY score() LIMIT N.

Как работает полнотекстовый поиск в Apache Doris?​

Полнотекстовый поиск в Apache Doris токенизирует текст при записи, распознаёт каждый предикат MATCH_* в оптимизаторе Nereids и проталкивает его в инвертированный индекс, благодаря чему строки, не удовлетворяющие условию, вообще не декодируются. Описанный ниже жизненный цикл из пяти этапов охватывает разбор запроса, проталкивание предикатов, оценку релевантности и объединение с предикатами по структурированным данным.

  1. Токенизация при записи. Когда строка попадает в tablet, анализатор разбивает каждое текстовое значение на термы. Инвертированный индекс фиксирует для каждого терма, какие строки его содержат и в каких позициях.
  2. Разбор запроса. Оператор MATCH_* распознаётся в Nereids как Match, MatchAny, MatchAll, MatchPhrase, MatchPhrasePrefix, MatchPhraseEdge или MatchRegexp. Каждый из них содержит подсказку анализатора, которая определяет, как токенизируется сама строка запроса.
  3. Проталкивание в индекс. BE находит соответствующие списки вхождений, пересекает или объединяет их в зависимости от оператора и возвращает битовую карту строк. Строки, не удовлетворяющие условию, вообще не декодируются.
  4. Оценка релевантности по запросу. Если запрос содержит SELECT score() ... ORDER BY score() DESC LIMIT N, Doris вычисляет BM25 (частота терма, обратная частота документа, нормализация по длине) и возвращает N лучших результатов. Без этих трёх составляющих предикат работает как обычный фильтр.
  5. Свободное комбинирование. В одном запросе можно сочетать MATCH_* с =, IN, фильтрами по диапазону, соединениями и агрегациями. Оптимизатор обрабатывает текстовые предикаты как ещё один вид проталкиваемых предикатов.

Быстрый старт​

CREATE TABLE articles (
id INT,
title STRING,
body STRING,
INDEX idx_body (body) USING INVERTED
PROPERTIES("parser" = "english", "support_phrase" = "true")
) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1;

INSERT INTO articles VALUES
(1, 'Doris 4.0 release', 'Apache Doris adds BM25 scoring for full-text search'),
(2, 'Storage notes', 'Inverted indexes accelerate text and structured filters'),
(3, 'Vector primer', 'Approximate nearest neighbor search complements text search');

SELECT id, title, score() AS rel
FROM articles
WHERE body MATCH_ANY 'text search'
ORDER BY rel DESC LIMIT 3;

Ожидаемый результат

+------+-------------------+-----------+
| id | title | rel |
+------+-------------------+-----------+
| 3 | Vector primer | 0.8009131 |
| 1 | Doris 4.0 release | 0.5634479 |
| 2 | Storage notes | 0.1384567 |
+------+-------------------+-----------+

MATCH_ANY 'text search' разбивает запрос на токены text и search, находит оба списка вхождений и возвращает строки, содержащие хотя бы один из термов. score() ранжирует их по BM25, когда ORDER BY rel LIMIT 3 заставляет оптимизатор обрабатывать запрос как Top-N: строка 3 содержит оба терма (search — дважды), строка 1 содержит оба по одному разу, а строка 2 содержит только text.

Краткий обзор операторов​

ОператорДля чего используется
MATCH / MATCH_ANYПоиск по любому из указанных ключевых слов (OR)
MATCH_ALLПоиск с обязательным присутствием каждого ключевого слова (AND)
MATCH_PHRASE 'a b'Соседние термы в заданном порядке; поддерживает ~slop и ~slop+ со строгим порядком
MATCH_PHRASE_PREFIX 'a b'Фраза, в которой последний терм является префиксом; используется для подсказок при вводе
MATCH_PHRASE_EDGE 'a b c'Суффикс первого терма, точное совпадение средних термов, префикс последнего терма
MATCH_REGEXP '^abc.*'Регулярное выражение применяется к токенам, а не к исходным байтам

Если для одного столбца существует несколько инвертированных индексов с разными анализаторами, добавьте USING ANALYZER <name>, чтобы выбрать анализатор при выполнении запроса.

Когда стоит использовать полнотекстовый поиск в Apache Doris?​

Используйте полнотекстовый поиск в Apache Doris для поиска по журналам и трассировкам, поиска по каталогам (Catalog) интернет-магазинов, работы со смешанным содержимым CJK и латиницей, поиска по мере ввода и конвейеров RAG, сочетающих текстовые совпадения с векторным сходством. Используйте LIKE или n-граммные индексы, когда нужен поиск подстрок внутри токенов, нечёткое сопоставление по расстоянию редактирования или подсветка результатов.

Подходящие сценарии

  • Поиск по журналам и трассировкам, при котором пользователи сочетают произвольный текст с фильтрами по структурированным данным (level = 'ERROR' AND message MATCH_ANY 'timeout disconnect').
  • Поиск в электронной торговле и каталогах, где одна строка содержит цену, категорию и описание.
  • Смешанное содержимое CJK и латиницей; парсеры chinese, unicode и icu обрабатывают оба вида текста в одном столбце.
  • Поиск по мере ввода, при котором MATCH_PHRASE_PREFIX возвращает варианты завершения без отдельного сервиса автодополнения.
  • Конвейеры RAG, сочетающие текстовые совпадения со сходством эмбеддингов. См. Гибридный поиск, где описано объединение MATCH_* с векторным расстоянием в одном SQL-запросе.

Неподходящие сценарии

  • Поиск подстроки внутри отдельного токена (например, всех строк, где столбец содержит «ear» внутри «year»). Токенизация не сохраняет такое представление данных. Используйте LIKE, индекс N-Gram BloomFilter или инвертированный индекс без токенизации.
  • Нечёткое сопоставление по расстоянию редактирования («возможно, вы имели в виду...»). Сейчас в Doris нет встроенного оператора нечёткого поиска. Выполняйте предварительную обработку при записи с помощью фонетического или n-граммного анализатора либо формируйте варианты запроса в приложении.
  • Подсветка результатов (возврат совпавшего фрагмента с разметкой). Сейчас Doris вычисляет оценки релевантности, но не возвращает фрагменты с подсветкой. Формируйте подсветку в приложении на основе совпавших термов.
  • Ранжирование по релевантности между полями, подобное dis_max в Elasticsearch. score() вычисляется для каждого предиката отдельно. Если нужны более сложные стратегии оценки релевантности, используйте SEARCH('title:foo OR body:foo') и настраивайте их на уровне приложения.

Выбор токенизатора​

Выбор анализатора имеет решающее значение. Он определяет, какие запросы смогут находить совпадения, а индексируемые термы фиксируются при записи.

  • none: без токенизации. Всё значение считается одним термом. Подходит для идентификаторов, тегов, перечислений статусов и фильтров по точному совпадению.
  • english: разбиение на слова по правилам ASCII с лёгкой нормализацией. Подходит для текстов на английском языке.
  • chinese: разбиение китайского текста на слова со значением parser_mode, равным fine_grained или coarse_grained. Подходит для содержимого CJK.
  • unicode: не зависящее от языка разбиение на слова с учётом CJK и пунктуации. Разумный вариант по умолчанию для смешанного содержимого.
  • standard: токенизатор на основе сегментации текста Unicode; работает для большинства языков.
  • icu: токенизатор на основе ICU для языков, которым нужны полные правила сегментации Unicode (тайский, кхмерский, смешанные системы письма).
  • Пользовательские анализаторы (3.1+): последовательно объединяйте edge_ngram для автодополнения по префиксу, word_delimiter для camelCase и термов с дефисами, lowercase и asciifolding для поиска без учёта регистра и диакритики и другие компоненты.

Изменение анализатора требует перестроения индекса с помощью BUILD INDEX.

Дополнительные материалы​