Полнотекстовые инвертированные индексы

Начиная с версии 3.3.0, StarRocks поддерживает полнотекстовые инвертированные индексы, которые разбивают текст на слова и создают индексную запись для каждого слова с отображением этого слова на номер строки в файле данных. Для полнотекстовых поисков StarRocks опрашивает инвертированный индекс по ключевым словам и быстро находит строки, соответствующие этим словам.

Таблицы Primary Key поддерживают полнотекстовые инвертированные индексы с v4.0.

С v4.1, помимо реализации по умолчанию на базе CLucene, доступна встроенная (native) реализация инвертированного индекса. Встроенная реализация поддерживает кластеры shared-nothing и shared-data.

Обзор

Нижележащие данные StarRocks хранятся в колоночных файлах. Каждый файл может содержать полнотекстовый инвертированный индекс по индексируемым столбцам. Значения индексируемых столбцов токенизируются в отдельные слова. Каждое слово после токенизации становится записью индекса, указывающей на номер строки, где слово встречается. Поддерживаются режимы токенизации: английский, китайский, многоязычный и без токенизации.

Например, если строка данных содержит «hello world», а её номер — 123, инвертированный индекс создаёт записи: hello->123, world->123.

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

Базовые операции

Создание полнотекстового инвертированного индекса

Перед созданием включите параметр FE enable_experimental_gin.

ADMIN SET FRONTEND CONFIG ("enable_experimental_gin" = "true");

При создании полнотекстового инвертированного индекса необходимо отключить свойство таблицы replicated_storage.

  • Для v4.0 и новее оно отключается автоматически при создании индекса.

  • Для версий ниже v4.0 нужно вручную задать replicated_storage=false.

Создание при создании таблицы

Создание индекса по столбцу v с английской токенизацией:

CREATE TABLE `t` (
  `k` BIGINT NOT NULL COMMENT "",
  `v` STRING COMMENT "",
   INDEX idx (v) USING GIN("parser" = "english")
) ENGINE=OLAP 
DUPLICATE KEY(`k`)
DISTRIBUTED BY HASH(`k`) BUCKETS 1
PROPERTIES (
"replicated_storage" = "false"
);
  • Параметр parser задаёт метод токенизации. Значения:

    • none (по умолчанию): без токенизации. Вся строка считается одним элементом индекса.

    • english: английская токенизация (обычно по неалфавитным символам) и приведение к нижнему регистру. Поэтому ключевые слова в запросе должны быть в нижнем регистре.

    • chinese: китайская токенизация с использованием CJK Analyzer в CLucene.

    • standard: многоязычная токенизация (на базе Unicode Text Segmentation). Хорошо работает для большинства языков и смешанных случаев (например, китайский+английский). Английские слова приводятся к нижнему регистру.

  • Тип данных индексируемого столбца: CHAR, VARCHAR или STRING.

Добавление после создания таблицы

ALTER TABLE t ADD INDEX idx (v) USING GIN('parser' = 'english');
CREATE INDEX idx ON t (v) USING GIN('parser' = 'english');

Управление индексом

Просмотр индекса

MySQL [example_db]> SHOW CREATE TABLE t\G

Удаление индекса

DROP INDEX idx on t;
ALTER TABLE t DROP index idx;

Ускорение запросов с помощью инвертированного индекса

После создания индекса включите системную переменную enable_gin_filter, чтобы индекс ускорял запросы. Также учитывайте, применяется ли токенизация для индексируемого столбца: от этого зависят поддерживаемые предикаты.

Поддерживаемые запросы при включённой токенизации

Если у столбца инвертированного индекса включена токенизация (parser = standard | english | chinese), поддерживаются только предикаты:

  • <col_name> (NOT) MATCH '%keyword%'

  • <col_name> (NOT) MATCH_ANY 'keyword1, keyword2'

  • <col_name> (NOT) MATCH_ALL 'keyword1, keyword2'

Параметр Keyword должен быть строковым литералом (выражения не поддерживаются). Примеры создания таблицы, вставки данных и запросов оставлены в исходном виде в SQL‑блоках для точности.

Примечания:

  • Можно выполнять нестрогое совпадение %keyword%, но keyword должен содержать часть слова. Например, starrocks (с пробелом) не совпадает с starrocks.

  • При английской/многоязычной токенизации слова в индексе приводятся к нижнему регистру; в запросах используйте нижний регистр.

  • Предикаты MATCH/MATCH_ANY/MATCH_ALL должны быть pushdown‑предикатами: в WHERE и по индексируемому столбцу.

Поддерживаемые запросы без токенизации

Если 'parser' = 'none', для фильтрации по инвертированному индексу поддерживаются pushdown‑предикаты:

  • Выражения: (NOT) LIKE, (NOT) MATCH, (NOT) MATCH_ANY, (NOT) MATCH_ALL

    • В этом случае MATCH семантически эквивалентен LIKE.

    • MATCH и LIKE поддерживают только формат (NOT) <col_name> MATCH|LIKE '%keyword%'. Keyword — строковый литерал. Если формат иной, даже при корректном выполнении запрос не использует инвертированный индекс.

  • Регулярные предикаты: ==, !=, <=, >=, NOT IN, IN, IS NOT NULL, NOT NULL.

Проверка ускорения

После выполнения запроса проверьте метрики GinFilterRows и GinFilter в узле сканирования профиля запроса.

Встроенные инвертированные индексы

С v4.1 StarRocks предоставляет встроенную реализацию инвертированного индекса поверх bitmap‑индекса; поддерживает shared-nothing и shared-data.

CLucene‑реализация не поддерживается в shared-data. Для shared-data используйте встроенную реализацию.

Сравнение реализаций

Реализация

Поддержка с

Shared-nothing

Shared-data

Описание

CLucene (default)

v3.3.0

Да

Нет

Основана на библиотеке CLucene; реализация по умолчанию для shared-nothing.

built-in

v4.1.0

Да

Да

Нативная реализация StarRocks; поддерживает оба режима кластеров.

Вы можете явно задать тип реализации параметром imp_lib при создании индекса. Если не задан, система выберет автоматически по режиму кластера:

  • В shared-nothing — по умолчанию CLucene.

  • В shared-data — по умолчанию built-in (CLucene не поддерживается).

Создание встроенного индекса

При создании таблицы

-- Create table with built-in inverted index
CREATE TABLE `t_builtin` (
    `id1` bigint(20) NOT NULL COMMENT "",
    `value` varchar(255) NOT NULL COMMENT "",
    INDEX gin_english (`value`) USING GIN ("parser" = "english", "imp_lib" = "builtin") COMMENT 'builtin english index'
)
DUPLICATE KEY(`id1`)
DISTRIBUTED BY HASH(`id1`)
PROPERTIES (
"replicated_storage" = "false"
);

После создания таблицы

ALTER TABLE t ADD INDEX idx_builtin (v) USING GIN('parser' = 'english', 'imp_lib' = 'builtin');
-- Or
CREATE INDEX idx_builtin ON t (v) USING GIN('parser' = 'english', 'imp_lib' = 'builtin');

Встроенный индекс в shared-data кластерах

В shared-data встроенная реализация выбирается автоматически даже без imp_lib. Можно использовать тот же синтаксис:

CREATE TABLE `t_shared_data` (
    `id1` bigint(20) NOT NULL COMMENT "",
    `value` varchar(255) NOT NULL COMMENT "",
    INDEX gin_english (`value`) USING GIN ("parser" = "english") COMMENT 'english index'
)
DUPLICATE KEY(`id1`)
DISTRIBUTED BY HASH(`id1`);

Либо указать явно:

CREATE TABLE `t_shared_data_explicit` (
    `id1` bigint(20) NOT NULL COMMENT "",
    `value` varchar(255) NOT NULL COMMENT "",
    INDEX gin_english (`value`) USING GIN ("parser" = "english", "imp_lib" = "builtin") COMMENT 'builtin english index'
)
DUPLICATE KEY(`id1`)
DISTRIBUTED BY HASH(`id1`);

dict_gram_num

Параметр dict_gram_num доступен только для встроенной реализации. Он управляет размером n‑грамм, используемых для построения n‑gram‑словаря поверх словаря инвертированного индекса, что значительно ускоряет wildcard/substring‑запросы.

Принцип работы

При dict_gram_num = 2 строка "starrocks" разбивается на "st", "ta", "ar", "rr", "ro", "oc", "ck", "ks". При запросе MATCH '%rock%' строка "rock" также разбивается на "ro", "oc", "ck", и n‑gram‑индекс быстро сужает кандидатов без полного прохода по словарю.

Детали параметра

Параметр

Значение по умолчанию

Диапазон

Описание

dict_gram_num

-1 (disabled)

Положительные целые

Размер n‑gram; работает только с imp_lib = builtin.

Рекомендации

  • Меньшее значение (например, 2) генерирует больше n‑грамм на запись (больше индекс), но лучше фильтрует короткие шаблоны.

  • Большее значение (например, 4) уменьшает индекс, но хуже фильтрует короткие шаблоны.

  • Если преобладают короткие wildcard‑паттерны (%ab%), используйте меньшее значение (например, 2).

  • Если длина шаблона короче dict_gram_num, n‑gram‑индекс не применяется и выполняется полный просмотр словаря.

Примеры

Создание со значением dict_gram_num:

CREATE TABLE `t_gram` (
    `id1` bigint(20) NOT NULL COMMENT "",
    `text_val` varchar(255) NOT NULL COMMENT "",
    INDEX idx_gram (`text_val`) USING GIN (
        "parser" = "english",
        "imp_lib" = "builtin",
        "dict_gram_num" = "2"
    ) COMMENT 'builtin index with ngram'
)
DUPLICATE KEY(`id1`)
DISTRIBUTED BY HASH(`id1`)
PROPERTIES (
"replicated_storage" = "false"
);

Добавление dict_gram_num после создания таблицы:

CREATE INDEX idx_gram ON t (v) USING GIN('parser' = 'english', 'imp_lib' = 'builtin', 'dict_gram_num' = '3');