Как искать длинные хеши и ID с dict='keywords_32k'
Как искать длинные хеши и ID с dict='keywords_32k'

Первоначально опубликовано на https://manticoresearch.com/ru/blog/dict_keywords_32k/ 31 августа 2026

Как искать длинные хеши и ID с dict=‘keywords_32k’

Практическое руководство по поиску длинных хешей, event ID, message ID и email в Manticore Search: лимиты, точное сравнение, wildcard-поиск, токенизация, миграция и ограничения.

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

В логах и технических данных всё иначе. SHA-256 занимает 64 символа, а идентификаторы сообщений, correlation ID, идентификаторы событий и некоторые email-адреса могут быть ещё длиннее. При этом такое значение часто имеет смысл только целиком: если потерять его хвост, один ID легко спутать с другим.

Для таких случаев в Manticore Search появился dict='keywords_32k'.

keywords_32k доступен начиная с Manticore Search 27.1.1. Для перевода существующей таблицы с keywords рекомендуется использовать версию 27.1.5 или новее.

В чём проблема обычного словаря

По умолчанию Manticore использует dict='keywords'. Максимальная длина токена при этом составляет 42 байта после нормализации.

Важно, что речь идёт именно о байтах, а не о символах. Для ASCII один символ занимает один байт, но в UTF-8 один символ может занимать несколько байт.

Если токен длиннее 42 байт, Manticore обрезает его:

  • при индексации документа;

  • при обработке поискового запроса.

Из-за этого запрос по полному значению не обязательно вернёт ноль результатов. Поскольку запрос тоже обрезается, документ может быть найден — но только по первым 42 байтам.

Проблема серьёзнее, чем может показаться: два разных ID с одинаковыми первыми 42 байтами становятся неразличимыми для полнотекстового поиска. Кроме того, найти значение по части, расположенной после 42-го байта, уже не получится.

Что меняет keywords_32k

dict='keywords_32k' увеличивает максимальную длину нормализованного токена до 32768 байт, то есть до 32 КБ.

Поведение

dict='keywords'

dict='keywords_32k'

Максимальная длина токена

42 байта

32768 байт

Токен длиннее лимита

Обрезается

Пропускается с предупреждением

Префиксный и инфиксный поиск

Поддерживается

Поддерживается

Морфология для токенов длиннее 42 байт

Токен уже обрезан

Не применяется

RT-таблицы

Поддерживаются

Поддерживаются

Plain-таблицы

Поддерживаются

Поддерживаются

Настройка задаётся для всей таблицы:

CREATE TABLE events (
  message text,
  event_id text
)
dict='keywords_32k';

Обычные короткие слова в такой таблице по-прежнему обрабатываются настроенной морфологией. Токены длиннее 42 байт сохраняются после нормализации, но без стемминга и лемматизации.

Для машинных идентификаторов это обычно именно то, что нужно: морфология для хешей и ID, как правило, просто не имеет смысла.

Когда нужен keywords_32k

Используйте его, если одновременно выполняются два условия:

  1. Значение после токенизации может быть длиннее 42 байт.

  2. Его нужно искать через MATCH(), по префиксу или по подстроке.

Типичные примеры:

  • SHA-256 и другие длинные хеши;

  • event ID и message ID;

  • request ID, trace ID и другие технические идентификаторы;

  • длинные ключи записей;

  • email-адреса с длинной локальной или доменной частью;

  • технические значения из логов;

  • длинные идентификаторы с разделителями.

Однако keywords_32k нужен не для любого поиска по ID.

Если требуется только полное равенство

Если приложение всегда получает полный ID и нужно проверить только его точное равенство, достаточно строкового атрибута:

CREATE TABLE events (
  message text,
  event_id string
);

Тогда поиск выполняется обычным фильтром:

SELECT id, message
FROM events
WHERE event_id =
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';

Если нужны и точное сравнение, и полнотекстовый поиск

Используйте string attribute indexed:

CREATE TABLE events (
  message text,
  event_id string attribute indexed
)
dict='keywords_32k';

В этом случае Manticore хранит исходное значение как строковый атрибут, позволяет фильтровать по нему через WHERE и одновременно индексирует его для MATCH() и wildcard-поиска.

Поиск по полному токену

Создадим таблицу и добавим 64-символьный SHA-256:

DROP TABLE IF EXISTS events;

CREATE TABLE events (
  message text,
  event_id string attribute indexed
)
dict='keywords_32k';

INSERT INTO events (id, message, event_id) VALUES
(
  1,
  'delivery accepted',
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);

Полнотекстовый поиск по полному нормализованному токену:

SELECT id, message
FROM events
WHERE MATCH(
  '@event_id 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);

Оператор @event_id ограничивает поиск нужным полем. Без него Manticore будет искать значение во всех полнотекстовых полях таблицы.

Кавычки вокруг одного простого буквенно-цифрового токена здесь не нужны. Они обозначают фразовый поиск, а не превращают MATCH() в побайтовое сравнение исходной строки.

Полнотекстовое совпадение и точное равенство — не одно и то же

MATCH() работает с результатом токенизации и нормализации. На результат могут влиять charset_table, приведение к нижнему регистру, blend_chars, ignore_chars, словоформы и другие настройки обработки текста.

Для строгого сравнения сохранённого значения используйте строковый атрибут:

SET collation_connection='binary';

SELECT id, message
FROM events
WHERE event_id =
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';

Практическое правило простое:

  • WHERE event_id = ... — строгое сравнение сохранённой строки;

  • MATCH('@event_id ...') — поиск нормализованного токена;

  • MATCH('@event_id prefix*') — префиксный поиск;

  • MATCH('@event_id *fragment*') — поиск по подстроке.

Как проверить токенизацию

Перед загрузкой большого объёма данных полезно убедиться, что Manticore действительно видит значение как один токен:

CALL KEYWORDS(
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
  'events'
);

В столбце normalized должен появиться полный 64-символьный хеш. CALL KEYWORDS особенно полезен для значений с точками, дефисами, @, двоеточиями, слешами и символами разных алфавитов.

Префиксный поиск и поиск по подстроке

Для поиска внутри токена включите min_infix_len:

DROP TABLE IF EXISTS events_infix;

CREATE TABLE events_infix (
  message text,
  event_id string attribute indexed
)
dict='keywords_32k'
min_infix_len='4';

Поиск по префиксу:

SELECT id, message
FROM events_infix
WHERE MATCH('@event_id 9f86d081*');

Поиск по подстроке:

SELECT id, message
FROM events_infix
WHERE MATCH('@event_id *b2b0b822*');

При положительном min_infix_len становится доступен и префиксный поиск. В production-системах не разрешайте слишком короткие фрагменты, выбирайте min_infix_len с учётом реальных данных, используйте expansion_limit, проверяйте производительность на словаре production-размера и ограничивайте поиск конкретным полем через @field.

Email и другие значения с разделителями

keywords_32k меняет только максимальную длину токена. Он не определяет, где токен начинается и заканчивается.

По умолчанию точка, @, дефис и другие символы могут разделять значение на несколько токенов. Если email-адрес или message ID нужно индексировать ещё и целиком, можно использовать blend_chars:

DROP TABLE IF EXISTS mail_events;

CREATE TABLE mail_events (
  sender string attribute indexed,
  subject text
)
dict='keywords_32k'
blend_chars='., @, -'
min_infix_len='4';

Символы из blend_chars позволяют индексировать значение двумя способами: как один цельный токен и как отдельные части значения. Благодаря этому можно искать как весь email-адрес, так и отдельные его части.

Как перевести существующую таблицу

Для RT-таблицы настройку можно изменить через ALTER TABLE:

ALTER TABLE events dict='keywords_32k';

Но изменение затронет только документы, добавленные или заменённые после этого. Уже существующие документы не будут автоматически токенизированы заново. Их длинные токены останутся в старом, обрезанном виде до переиндексации.

Порядок действий:

  1. Обновить dict.

  2. Проверить настройку через SHOW CREATE TABLE.

  3. Переиндексировать или повторно загрузить существующие документы.

  4. Проверить несколько длинных значений через CALL KEYWORDS и MATCH().

Для plain-таблицы нужно изменить конфигурацию на dict = keywords_32k, применить настройки через ALTER TABLE ... RECONFIGURE, если это подходит для вашего процесса, и полностью перестроить таблицу из источника данных.

Почему dict='crc' не решает эту задачу

dict='crc' хранит контрольные суммы ключевых слов вместо исходного текста, но не увеличивает допустимую длину токена. Исключение из обычного лимита в 42 байта реализовано именно в dict='keywords_32k'.

Текущие ограничения

На момент публикации у dict='keywords_32k' есть несколько ограничений:

  • CALL SUGGEST и CALL QSUGGEST не поддерживаются;

  • его нельзя использовать в percolate-таблицах;

  • для токенов длиннее 42 байт не работает подсветка в snippets или highlights;

  • indextool --dumpdict не умеет выгружать такой словарь;

  • полнотекстовый оператор REGEX работает с dict='keywords', но не с keywords_32k.

Не индексируйте секреты

Возможность искать длинное значение не означает, что его стоит сохранять в поисковом индексе. Без крайней необходимости не индексируйте API-ключи, bearer-токены, сессионные cookie, приватные ключи, пароли, токены сброса пароля и другие данные, предоставляющие доступ к системе.

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

Краткий чек-лист

Перед включением keywords_32k проверьте:

  1. Действительно ли нормализованный токен длиннее 42 байт?

  2. Нужен ли полнотекстовый или wildcard-поиск, а не только WHERE value = ...?

  3. Видит ли CALL KEYWORDS всё значение как один токен?

  4. Нужны ли blend_chars для точек, дефисов, @ и других разделителей?

  5. Не слишком ли мал min_infix_len?

  6. Ограничено ли число термов, в которые может развернуться wildcard-запрос?

  7. Переиндексированы ли старые документы?

  8. Не содержит ли поле секретных данных?

  9. Не зависит ли приложение от подсветки, SUGGEST, percolate или полнотекстового REGEX?

Итог

dict='keywords_32k' решает конкретную проблему: позволяет полнотекстовому индексу хранить нормализованные токены длиной до 32768 байт вместо обычных 42 байт.

Он хорошо подходит для длинных хешей, event ID, message ID, email-адресов и других машинных идентификаторов. При этом важно помнить три вещи:

  • keywords_32k увеличивает максимальную длину токена, но не меняет правила токенизации;

  • MATCH() по полному токену не равен строгому сравнению исходной строки;

  • после изменения настройки существующие документы необходимо переиндексировать.

Если нужно только точное равенство, используйте строковый атрибут. Если нужны и точное сравнение, и поиск по частям значения, используйте string attribute indexed вместе с dict='keywords_32k'.

Документация