Полнотекстовый поиск
Простой поиск
Для простого поиска в тексте можно использовать оператор LIKE.
Он ищет записи, в которых встречается слово, соответствующее заданному шаблону (pattern).
В pattern можно использовать _ — для обозначения любого одиночного символа и % — для обозначения любой последовательности символов.
Например, шаблон me_t будет соответствовать meet, meat, melt и так далее, а шаблон %tion будет соответствовать tion, condition, creation и так далее (вместо % может быть любая последовательность символов, в том числе и их отсутствие).
Примеры команд для простого поиска в Reindexer:
query := db.Query("items").
Where("field", reindexer.LIKE, "pattern")
SELECT * FROM items WHERE fields LIKE 'pattern'
Чтобы с помощью LIKE искать информацию в нескольких полях, нужно использовать несколько операторов, например:
SELECT * FROM items WHERE fields LIKE 'pattern' OR titles LIKE 'second_pattern'
Это ведет к увеличению нагрузки на сервер. Кроме того, LIKE использует метод полного сканирования поля. С учетом этого его рекомендуется использовать для отладки или внутри запросов с другими хорошими выборочными условиями.
Оператор
LIKEподдерживается в запросах к текстовым полям с типом значенияstringс типами индексовhashиtree. Для индексов типаtextон неприменим.
Для полнотекстового поиска с высокой скоростью рекомендуется использовать полнотекстовые индексы.
Полнотекстовый поиск
В Reindexer имеется встроенный движок fast для полнотекстового поиска на базе суффиксного массива (suffixarray) и алгоритма инвертированных индексов.
Он характеризуется минимальным потреблением памяти, а также поддержкой морфологии, разделителей слов, опечаток, транслита и поиска с неправильной раскладкой клавиатуры.
Размер индекса обычно равен 30-80% от исходного текста, но может выходить за пределы этого диапазона в некоторых случаях.
Полнотекстовый поиск реализован с поддержкой стеммеров для расширения поисковых запросов за счет естественного поиска слова по его основе. Например, запросу users также будет соответствовать значение user.
Стеммеры специфичны для каждого языка, поэтому при их использовании необходимо указывать язык. По умолчанию в Reindexer используются русский и английский языки (ru, en).
В полнотекстовом поиске также реализована специальная обработка составных слов.
Например, запросу человек-паук будут соответствовать документы, содержащие слова: человек-паук, человек, паук. При этом документы, содержащие слово ‘человек-паук’, будут иметь большую релевантность данному запросу, чем документы содержащие слова ‘человек’ или ‘паук’.
По запросу человек будет найдено как человек, так и человек-паук. Здесь в обоих случаях релевантность одинаковая.
Список символов, которые будут восприниматься как разделители частей составного слова, может быть задан в конфигурационном файле опцией WordPartDelimiters.
Полнотекстовый поиск возможен по text-индексам для одиночных полей типа string и по композитным (composite) text-индексам, объединяющим несколько string-полей.
Примеры объявления структур с индексами на Go:
type Item struct {
ID int64 `reindex:"id,,pk"`
Description string `reindex:"description,text"` // Индекс `description` с поддержкой полнотекстового поиска
}
type Item struct {
ID int64 `reindex:"id,,pk"`
Name string `reindex:"name,-"`
Description string `reindex:"description,-"`
_ struct{} `reindex:"name+description=text_search,text,composite` // Композитный (составной) индекс с псевдонимом`text_search` для полнотекстового поиска по полям `name` и `description`
}
Также в Reindexer имеется встроенный триграммный движок для полнотекстового поиска (fuzzy), работающий с индексами типа fuzzytext.
Он дает лучшее качество поиска, но при этом работает медленнее и требует больше памяти.
На данный момент он находится в экспериментальном статусе. В большинстве случаев для организации полнотекстового поиска лучше использовать движок fast
Особенности реализации полнотекстового поиска в Reindexer
- Полнотекстовый поиск в Reindexer нечувствителен к регистру.
- Исходный текст при поиске токенизируется до набора слов.
- Составные слова будут проиндексированы не только целиком, но и по частям.
- Под словом понимается последовательность любых букв в кодировке UTF-8, цифр или символов: “-/+_`’”.
- Первый символ в слове — это буква или цифра.
- Результаты выполнения запросов для полнотекстового поиска сортируются по релевантности, если не задан другой критерий сортировки.
Запросы к полнотекстовым индексам
Запросы к полнотекстовым индексам строятся через обычный интерфейс запросов по имени индекса. Примеры:
query := db.Query ("items").
Match ("name+description","text query") // Запрос для поиска по индексам name+description
SELECT * FROM items WHERE description = '*tor'
Или с использованием псевдонимов (пример на Go):
query := db.Query ("items").
Match ("text_search","text query") // Запрос для поиска по композитному (составному) индексу с псевдонимом`text_search` для полнотекстового поиска по индексам `name` и `description`
Здесь:
text query— запрос в DSL-формате, по которому будет происходить полнотекстовый поиск.
Кроме того, запросы к полнотекстовым индексам можно комбинировать с условиями выбора по другим полям неймспейса.
В таком случае все условия выбора по другим полям (where-фильтры) выполнятся после поиска по полнотекстовым индексам.
Примеры:
query := db.Query ("items").
Match ("description","text query"). // Запрос для поиска по индексу `description`
WhereInt("year",reindexer.GT,2010) // Условие выбора по полю `year`
SELECT * FROM items WHERE description = '*tor' AND id > 3
Результат каждого запроса при полнотекстовом поиске имеет степень (ранг) соответствия (совпадения).
Это — целое число, диапазон значений которого от 0 (минимальное соответствие запросу) до 255 (максимальное соответствие).
При взаимодействии с Reindexer через Go-коннектор для получения ранга полученного результата можно воспользоваться методом итератора запроса Rank(). Пример:
query := db.Query("items").
Match("description", "*text query*")
iterator := query.Exec()
defer iterator.Close()
if err := iterator.Error(); err != nil {
panic(err)
}
for iterator.Next() {
rank := iterator.Rank() // Получение степени соответствия соответствия результата запросу
fmt.Println(rank)
}
В SQL также есть функция rank(), которая позволяет получить ранг в ответ на запрос или может быть использована в сортировке. Пример:
SELECT rank(), * FROM items WHERE description = '*tor' ORDER BY 'rank()'
Формат запросов для полнотекстового поиска в Reindexer
В Reindexer используется следующий формат запросов для полнотекстового поиска:
query := [@[+]field[^boost][,field2[^boost]]] [=][*]term1[*][~][^boost] [+|-][*]term2[*][~][@][^boost] ...
Выбор полей при поиске по составным индексам
В фрагменте [@[+]field[^boost][,field2[^boost]]] указываются параметры для полнотекстового поиска по композитным (составным) индексам. Здесь используются следующие специальные символы:
| Символ | Значение | Пояснение, особенности использования |
|---|---|---|
@ |
Указывает на список индексов для поиска. | Индексы перечисляются через запятую. |
* |
Поиск будет происходить по всем индексам неймспейса. | |
^boost |
Буст (увеличение) значения ранга совпадения для индекса, участвующего в поиске (по умолчанию равно 1). |
При его указании (например field^x) совпадения, найденные для индекса field, будут вносить в x раз больший вклад в итоговый ранг документа в выдаче, чем совпадения для других индексов, без ^x. Параметр используется при полнотекстовом поиске по композитным текстовым индексам. |
+ |
Совпадения для индекса обязательно используются при вычислении итогового ранга совпадения слова. | Если индекс не помечен символом +, его использование в вычислении итогового ранга зависит от степени совпадения для других индексов: при наилучшем совпадении в этом индексном поле оно используется как наилучшее и при этом итоговый ранг совпадения слова вычисляется с его использованием и с использованием рангов совпадений для других индексов, помеченных +. Результаты для индексов, не помеченных + (если они не наилучшие) не используются при вычислении итогового ранга совпадения слова. Например, для паттерна text=‘@+field1,field2 word’, если наилучшее совпадение будет для field1, для вычисления итогового ранга будет использовано только оно (field2 не учитывается, т.к. индекс не помечен +). Если же наилучшее совпадение будет для field2, при вычислении общего ранга будут будут использованы совпадения слова из обоих индексных полей. Если оба индекса пометить +, совпадения для них будут учитываться при вычислении итогового ранга в любом случае. |
Вычисление итогового ранга слова при полнотекстовом поиске по нескольким индексам
Итоговый ранг совпадения слова при полнотекстовом поиске по нескольким индексным полям вычисляется следующим образом:
- Значения рангов совпадения слова из нескольких индексных полей сортируются от большего к меньшему.
- Каждое из этих значений умножается на коэффициент
SumRanksByFieldsRatio, возведенный в степеньномер значения минус 1(отсчет значений ведется от1). КоэффициентSumRanksByFieldsRatioзадается в конфигурации для каждого полнотекстового индекса. Таким образом, чем меньше ранг совпадения слова для индекса, тем меньше его вклад в итоговый ранг.
Пример расчета итогового ранга слова
Дано:
Коэффициент SumRanksByFieldsRatio для полнотекстовых индексов равен 0.5 (в примере обозначим его как K).
Совпадения для слова найдены в 3 индексных полях с рангами:
- R1=20,
- R2=90,
- R3=40.
Суммарный ранг будет следующим:
R=(K^0)*R2 + (K^1)*R3 + (K^2)*R1 = 1*90 + 0.5*40 + 0.5*0.5*20 = 115
Минимальное значение этого коэффициента SumRanksByFieldsRatio равно 0.0, что означает использовать только одно максимальное совпадение. Максимальное значение — 1 (простая сумма рангов совпадений из всех полей).
По умолчанию значение SumRanksByFieldsRatio для полнотекстового индекса равно 0.
Шаблоны (паттерны) для слов при полнотекстовом поиске
В фрагменте запроса [=][*]term1[*][~][^boost] [+|-][*]term2[*][~][@][^boost] указываются шаблоны (паттерны) слов для полнотекстового поиска.
Здесь используются следующие специальные символы:
| Символ | Значение | Пояснение, особенности использования |
|---|---|---|
* |
Любая последовательность символов. | Например шаблону termina* будут соответствовать значения terminator, terminal и так далее. Символ может использоваться только в начале или в конце слова (term*na, например, недопустимый вариант). |
~ |
Нечеткое слово с ошибкой согласно словарю опечаток. | Словарь опечаток включает слова с одной возможной ошибкой (опечаткой). Например, шаблону black~ будут соответствовать слова block, blck, blask и т.д. (одна ошибка или опечатка). |
^boost |
Буст (увеличение) значения ранга совпадения для слова при формировании результатов поиска. | Значение по умолчанию равно 1. |
= |
В результат включаются только точные совпадения для слова, следующего за этим символом. | При поиске не используется транслит, исправления при неверной раскладке, стеммеры. |
Бинарные операторы в запросах для полнотекстового поиска в Reindexer
С помощью бинарных операторов в запросах для полнотекстового поиска указываются паттерны, которые должны обязательно присутствовать или отсутствовать в результатах. В Reindexer используются следующие бинарные операторы:
+- следующий за оператором паттерн должен присутствовать в найденных документах для их включения в результат поиска,-- следующий за оператором паттерн не должен присутствовать в найденных документах для их включения в результат поиска.
Экранирование символов в запросах для полнотекстового поиска
Экранирование с помощью \ позволяет добавлять специальные символы +,-,@,*,^ и ~ в запросы для полнотекстового поиска. Например при наличии в запросе паттерна \*crisis будет искаться слово *crisis, а не все слова, заканчивающиеся на crisis.
Поиск фразы
Операндом может быть фраза. Фраза задается в форме
“word1 word2 …"~N word1, word2, … - слова фразы в заданной последовательности (последовательность “word2 word1” не найдется если ищется “word1 word2”) ~N - максимальное расстояние между словами соседними словами фразы (если не задано, то расстояние принимается за 1)
Синонимы из нескольких слов не поддерживаются во фразе.
Примеры паттернов для полнотекстового поиска в Reindexer
Ниже представлены примеры паттернов для полнотекстового поиска, построенных по рассмотренному формату:
termina* -genesis— поиск документов, включающих слово, начинающееся сtermina(terminator,terminalи т.д.). Документы содержащие словоgenesis, в результаты поиска включены не будут.black~— поиск документов со словами, соответствующими шаблонуblackс одной ошибкой или опечаткой. В результаты попадутblock,blck,blaskи т.д.tom jerry cruz^2— поиск документов, включающих хотя бы одно из словtomjerryилиcruz. При этом результаты, содержаниеcruz, будут иметь больший ранг:tom cruzбудет ранжироваться выше, чемtom jerry.fox +fast— в результаты поиска будут включены варианты, содержание оба слова (fastдолжно обязательно присутствовать в документе для включения в результат поиска)."one two"— поиск документов, содержащих фразу"one two"."one two"~5— поиск документов, содержащих словаoneиtwo, которые могут быть расположены на расстоянии до 5 (<5) слов друг от друга.@name rush— поиск документов со словомrushбудет вестись только по индексуname.@name^1.5,* rush— поиск документов, содержащих словоrushс бустом ранга совпадения для индексаname, равным 1.5.=windows— поиск документов, содержащих точное вхождение словаwindows.one -"phrase example"- поиск документов содержащих словоoneи не содержащих фразу “phrase example”.one "phrase example"- поиск документов содержащих словоoneили фразу “phrase example” или оба операнда.+one +"phrase example"- поиск документов содержащих словоoneи фразу “phrase example”.
Объединение результатов полнотекстового поиска
Reindexer поддерживает объединение результатов нескольких запросов и их сортировку.
Пример на Go:
query := db.Query ("items").
Match ("description","text query1")
q2 := db.Query ("another_items").
Match ("description","text query2")
query.Merge (q2)
iterator = query.Exec ()
// Проверка на наличие ошибки
if err := iterator.Error(); err != nil {
panic(err)
}
// Перебор результатов поиска
for iterator.Next() {
// Получение результатов, их добавление в Iterator, вывод результатов
switch elem := iterator.Object().(type) {
case Item:
fmt.Printf ("%v,rank=%d\n",*elem,iterator.Rank())
case AnotherItem:
fmt.Printf ("%v,rank=%d\n",*elem,iterator.Rank())
}
}
В Reindexer не поддерживается использование более одного метода
Match()в одном запросе. Для поиска по нескольким индексным полям используйте композитные индексы или объединяйте результаты нескольких запросов с помощьюMerge().
Использование функций для модификации результатов поиска
В Reindexer возможно использование функций выбора для обработки и модификации результатов поиска. С их помощью вы сможете добавлять теги для подсветки результатов и удалять из них лишние символы, адаптируя под введенный запрос.
На данный момент такая возможность доступна только для простых текстовых индексов. Функции не поддерживаются при полнотекстовом поиске по композитным индексам.
Функция highlight
Пример на Go:
highlight(first,second)
Используется для выделения текстовой области из состава поискового запроса.
Принимает 2 обязательных аргумента:
first— строка, которая будет вставлена перед найденной текстовой областью,second— строка, которая будет вставлена после найденной текстовой области.
Примеры использования:
query := db.Query("items").Match("field", "some").Limit(limit).Offset(offset).
Functions("field.highlight(<b>,</b>)")
При использовании dsl-query функции highlight должна передаваться в теле запроса в поле select_functions:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/fulltext/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "items",
"offset": 0,
"limit": 3,
"sort": {
"field": "",
"desc": true
},
"filters": [
{
"Field": "field",
"Cond": "EQ",
"Value": [
"some"
]
}
],
"select_functions": [
"field.highlight(<b>,</b>)"
]
}'
При выполнении запросов из приведенных выше примеров в результатах поиска по запросу “some” слово будет обрамлено html-тегами <b> </b>.
Результат выполнения запроса: “some”.
Функция snippet
Формат функции:
snippet(first,second,third,fourth,fifth,sixth)
Используется для выделения текстовой области из состава поискового запроса и удаления окружающего ее текста.
Функция принимает 6 аргументов:
first— строка, которая будет вставлена перед найденной текстовой областью;second— строка, которая будут вставлена после найденной текстовой области;third— количество символов окружающего текста, которые будут размещены в результатах поиска перед найденной текстовой областью из запроса;fourth— количество символов окружающего текста, которые будут размещены в результатах поиска после найденной текстовой области из запроса;fifth(необязательный, по умолчанию какой-либо символ отсутствует) — разделитель перед найденной текстовой областью;sixth(необязательный, значение по умолчанию — пробел) — разделитель после найденной текстовой области.
Пример использования:
query := db.Query("items").Match("field", "text").Limit(limit).Offset(offset).
Functions("field.snippet(<b>,</b>,2,0)")
При использовании dsl-query функции snippet должна передаваться в теле запроса в поле select_functions:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/fulltext/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "items",
"offset": 0,
"limit": 3,
"sort": {
"field": "",
"desc": true
},
"filters": [
{
"Field": "field",
"Cond": "EQ",
"Value": [
"text"
]
}
],
"select_functions": [
"field.snippet(<b>,</b>,2,0)"
]
}'
При выполнении запросов из приведенных выше примеров слово “text” будет обрамлено html-тегами <b> </b> и перед ним будет добавлено 2 символа из окружающего текста.
Например, если для индекса field будет найдено сочетание some text, результат выполнения запроса получится: “e text”.
Функция snippet_n
Формат функции:
snippet_n(first,second,third,fourth[,pre_delim='value1'][,post_delim='value2'][,with_area=0][,left_bound='value3'][,right_bound='value4'])
Используется для выделения текстовой области из состава поискового запроса и удаления окружающего ее текста. Отличается от snippet наличием именованных аргументов. Функция принимает 9 аргументов: 4 позиционных (обязательные) и 5 именованных (необязательные). Именованные аргументы следуют в любом порядке после позиционных:
first— строка, которая будет вставлена перед найденной текстовой областью;second— строка, которая будут вставлена после найденной текстовой области;third— количество символов окружающего текста, которые будут размещены в результатах поиска перед найденной текстовой областью из запроса;fourth— количество символов окружающего текста, которые будут размещены в результатах поиска после найденной текстовой области из запроса;pre_delim— (по умолчанию какой-либо символ отсутствует) — разделитель перед найденной текстовой областью;post_delim— (значение по умолчанию — пробел) — разделитель после найденной текстовой области;with_area— (значение по умолчанию — 0, не выводить) Выводить начало и конец фрагмент относительно начала документа в символах (UTF-8) в формате [B,E] послеpre_delim;left_bound— (значение по умолчанию — пустая строка, параметр не используется в вычислениях) Набор символов UTF-8. Началом фрагмента выступает символ из строкиleft_boundесли он встретится раньшеthird;right_bound— (значение по умолчанию — пустая строка, параметр не используется в вычислениях) Набор символов UTF-8. Концом фрагмента выступает символ из строкиright_boundесли он встретится раньшеfourth.
Строковые параметры задаются в одинарных кавычках. Имена параметров задаются либо без кавычек либо в двойных кавычках. Числовые параметры задаются либо без кавычек либо в одинарных кавычках.
Пример использования:
text: “some text string”
query := db.Query("items").Match("text", query).Limit(limit).Offset(offset).
Functions("text.snippet_n('<b>','</b>',2,2,pre_delim='{',post_delim='}',with_area=1)")
result: “{[3,11]e text s}”
query := db.Query("items").Match("text", query).Limit(limit).Offset(offset).
Functions("text.snippet_n('<b>','</b>',5,5,pre_delim='{',post_delim='}',left_bound='o',right_bound='i')")
result: “{me text str}”
При использовании dsl-query функции snippet_n должна передаваться в теле запроса в поле select_functions:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/fulltext/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "items",
"offset": 0,
"limit": 3,
"sort": {
"field": "",
"desc": true
},
"filters": [
{
"Field": "field",
"Cond": "EQ",
"Value": [
"text"
]
}
],
"select_functions": [
"field.snippet_n(<b>,</b>,2,0,pre_delim=!,post_delim=!)"
]
}'
При выполнении запросов из приведенных выше примеров слово “text” будет обрамлено html-тегами <b> </b> и перед ним будет добавлено 2 символа из окружающего текста.
Например, если для индекса field будет найден документ some text, результат выполнения запроса получится: “!e text!”.
Функция debug_rank
Формат функции:
debug_rank()
Используется для вывода дополнительной информации о найденном слове.
Go
text: “Маша ела кашу.”
b.Query("items").Match("text", "маша").Functions("text.debug_rank()")
result: {“text”:"{term_rank:97, term:маша, pattern:маша, bm25_norm:0.9798439468181269, term_len_boost:1, position_rank:1, norm_dist:0, proc:100, full_match_boost:0} Маша ела кашу.”}
Поиск с учетом ошибок и опечаток
В Reindexer реализован алгоритм поиска совпадений при сравнении поискового запроса и результатов поиска с удаленными из них символами.
Алгоритм является языконезависимым и работает со словами просто как с наборами символов.
Общее количество символов, которые могут быть удалены из обоих слов (запрос и результат поиска), настраивается с помощью параметра полнотекстового индекса MaxTypos.
Из каждого слова при сравнении может быть удалено до MaxTypos/2 символов с округлением до большего значения.
Не рекомендуется задавать значение
MaxTyposбольше 2, т.к. это существенно увеличивает потребление оперативной памяти и снижает скорость поиска.
Для более точной настройки работы алгоритма можно использовать TyposDetailedConfig.
Он содержит следующий набор параметров:
MaxMissingLetters. Максимальное количество символов, которое может быть удалено из первоначального поискового терма для получения результирующего слова. Например, задавMaxMissingLetters = 0иMaxTypos = 2можно получить поведение, при котором допустима либо только замена одного символа, либо только добавления одной буквы к исходному терму. Заданное значение будет учитывать ограничения, наложенныеMaxTypos. Диапазон значений: [-1,2].MaxExtraLetters. Максимальное количество символов, которое может быть добавлено к первоначальному поискового терма для получения результирующего слова. Например, задавMaxMissingLetters = 0,MaxExtraLetters = 0иMaxTypos = 2можно получить поведение, при котором допустима только замена одного символа в терме, но не разрешено добавление/удаление символов. Заданное значение будет учитывать ограничения, наложенныеMaxTypos. Диапазон значений: [-1,2].MaxTypoDistance. Максимальная дистанция между заменяемыми символами при обработке опечатки (актуально только приMaxTypos >= 2). Например, приMaxTypoDistance = -1(обозначает отсутствие ограничений по этому параметру) иMaxTypos = 2запросdword~сможет найти словаswordиwords(в первом случае символ ’d’ был заменён на ’s’, а во втором не только заменён, но и сдвинут в конец итогового слова). В то же время приMaxTypoDistance = 0(значение по умолчанию) иMaxTypos = 2позиции первоначального и итогового символа должны будут совпадать. В результате запросdword~вернёт только словоsword, но не вернёт словоwords. Диапазон возможных значений: [-1,100].MaxSymbolPermutationDistance. Максимальное расстояние, на которое может быть перемещён один и тот же символ при обработке опечатки. Этот параметр используется в паре сMaxTypoDistanceдля ослабления накладываемых им ограничений в случаях перестановки одного и того же символа. Например, сMaxSymbolPermutationDistance = 0,MaxTypoDistance = 0иMaxTypos = 2запросwsord~не найдёт словоsword, потому что для такого преобразования требуется заменить 2 буквы в исходном терме, либо же просто поменять их местами. ЗадавMaxSymbolPermutationDistance = 1(значение по умолчанию), можно как раз получить требуемую перестановку двух соседних букв, и запросwsord~вернёт словоsword. При этомMaxTypoDistance = 0всё ещё не позволит при такой перестановке заменить одну из этих букв на какую-то другую и, например, по тому же запросу~wsordне будет возвращено словоdword.
Принципы работы поиска с учетом ошибок и опечаток при различных значениях MaxTypos
Ниже рассмотрены принципы работы поиска с учетом ошибок и опечаток при различных параметра текстового индекса MaxTypos:
Значение MaxTypos |
Принципы сравнения | Примеры |
|---|---|---|
1 |
Один символ будет удален из поискового запроса или из результата поиска. | black будет соответствовать blaack, если удалить a во втором слове. А black уже не будет соответствовать block. |
2 |
В обоих словах (в поисковом запросе и в результате) может быть удалено по одному символу (2 символа в сумме). | black будет соответствовать blaack, если удалить a во втором слове;black будет соответствовать block, если удалить a в первом слове и o — во втором;black не будет соответствовать blok. |
3 |
Один символ будет удален в одном слове (в поисковом запросе или в результате поиска), и два символа — в другом (3 символа в сумме). | black будет соответствовать block, если удалить ac в первом слове и o — во втором. |
Управление параметрами индексов
В Reindexer реализована возможность управления параметрами индексов. Это возможно при подключении к БД различными способами. Задать конфигурацию индекса можно при его добавлении или изменении.
Ниже представлены примеры конфигурирования индексов для полнотекстового поиска разными способами:
При взаимодействии с БД через Go-коннектор настроить индекс можно при его добавлении (метод db.AddIndex) или с помощью метода db.UpdateIndex.
Пример:
...
ftconfig := reindexer.DefaultFtFastConfig()
// Настройка конфигурации
ftconfig.LogLevel = reindexer.TRACE
// Настройка других параметров конфигурации
// ...
// Создание определения индекса
indexDef := reindexer.IndexDef {
Name: "info",
JSONPaths: []string{"description"},
IndexType: "text",
FieldType: "string",
Config: ftconfig,
}
// Добавление индекса с заданной конфигурацией
return db.AddIndex ("items",indexDef)
Пример конфигурирования индекса для полнотекстового поиска по HTTP с помощью UPDATE-метода:
curl --location --request PUT 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/indexes' \
--header 'Content-Type: text/plain' \
--data-raw '{
"name": "info",
"field_type": "string",
"index_type": "text",
"is_pk": false,
"is_array": false,
"is_dense": false,
"is_sparse": false,
"collate_mode": "none",
"sort_order_letters": "",
"expire_after": 0,
"config": {
"bm25_boost": 1,
"bm25_weight": 0.5,
"position_boost": 1,
"position_weight": 0.1,
"distance_boost": 1,
"distance_weight": 0.5,
"enable_kb_layout": true,
"enable_preselect_before_ft": false,
"enable_numbers_search": false,
"enable_translit": true,
"enable_warmup_on_ns_copy": false,
"min_relevancy": 0.05,
"term_len_boost": 1,
"term_len_weight": 0.3,
"max_typos": 2,
"max_typo_len": 15,
"typos_detailed_config": {
"max_typo_distance": 0,
"max_symbol_permutation_distance": 1,
"max_missing_letters": 2,
"max_extra_letters": 2
},
"base_ranking": {
"full_match_proc": 100,
"prefix_min_proc": 50,
"suffix_min_proc": 10,
"base_typo_proc": 85,
"typo_proc_penalty": 15,
"stemmer_proc_penalty": 15,
"kblayout_proc": 90,
"translit_proc": 90,
"synonyms_proc": 95
},
"bm25_config": {
"bm25_k1": 2,
"bm25_b": 0.75,
"bm25_type": "rx_bm25"
},
"max_rebuild_steps": 50,
"max_step_size": 4000,
"merge_limit": 20000,
"stemmers": [
"en",
"ru"
],
"log_level": 0,
"extra_word_symbols": "-/+",
"full_match_boost": 1,
"partial_match_dercrease": 15,
"sum_ranks_by_fields_ratio": 0,
"fields": [],
"synonyms": [
{
"tokens": [],
"alternatives": []
}
]
},
"json_paths": [
"info"
]
}'
Список параметров индекса полнотекстового поиска:
| Параметр | Описание | Тип значения | Значение по умолчанию | Допустимые значения/диапазон значений | json-paths параметра |
|---|---|---|---|---|---|
Bm25Boost |
Ранг bm25. Определяет степень релевантности документа поисковому запросу | float | 1.0 | 0.0 - 10.0 | bm25_boost |
Bm25Weight |
Вес ранга bm25 индекса в суммарном ранге. При значении 0 bm25 ранг индекса не влияет на суммарный ранг, а при значении 1 — влияет на 100%. |
float | 0.1 | 0.0 - 1.0 | bm25_weight |
DistanceBoost |
Коэффициент влияния на ранг расстояния между словами запроса в найденном документе | float | 1 | 0.0 - 10.0 | distance_boost |
DistanceWeight |
Вес коэффициента DistanceBoost в суммарном ранге. При значении 0 DistanceWeight не влияет на суммарный ранг, при значении 1 — влияет на 100% |
float | 0.5 | 0.0 - 1.0 | distance_weight |
TermLenBoost |
Коэффициент влияния длины термов на их ранг. Чем выше значение, тем больший вклад будут давать совпадения по более длинным термы | float | 1 | 0.0 - 10.0 | term_len_boost |
TermLenWeight |
Вес коэффициента TermLenBoost в суммарном ранге. Диапазон значений: от 0 до 1. При значении 0 параметр не влияет на суммарный ранг, при значении 1 — влияет на 100% |
float | 0.3 | 0.0 - 1.0 | term_len_weight |
PositionBoost |
Коэффициент влияния на ранг близости расположения слов из поискового запроса к началу документа | float | 1.0 | 0.0 - 10.0 | position_boost |
PositionWeight |
Вес коэффициента PositionBoost в суммарном ранге. При значении 0 параметр не влияет на суммарный ранг, при значении 1 — влияет на 100% |
float | 0.1 | 0.0 - 1.0 | position_weight |
FullMatchBoost |
Коэффициент влияния на ранг полного совпадения поискового запроса с документом | float | 1.1 | 0.0 - 10.0 | full_match_boost |
PartialMatchDecrease |
Коэффициент влияния длины слова на релевантность. При расчете релевантности используется формула PartialMatchDecrease * (количество несовпадающих символов) / (количество совпадающих символов) |
int | 15 | 0 - 100 | partial_match_decrease |
MinRelevancy |
Минимальная релевантность, при которой документ попадет в поисковую выдачу. При значении параметра 0 в результат поиска будут добавлены все найденные документы, при значении 1 — только документы с релевантностью >=100% |
float | 0.05 | 0.0 - 1.0 | min_relevancy |
MaxTypos |
Максимальное количество ошибок (опечаток) в слове. Подробнее — в разделе «Поиск с учетом ошибок и опечаток» (#typos_search) | int | 2, если не задан параметр MaxTyposInWord, в противном случае — 2*MaxTyposInWord |
0 - 4 | max_typos |
MaxTyposInWord |
Устаревший параметр. Вместо него следует использовать MaxTypos. Максимальное количество опечаток в каждом слове: в поисковом запросе и в результате поиска. Не может использоваться совместно с параметром MaxTypos. При значении N из обоих слов (из запроса и результата) будет удалено по N символов. Не рекомендуется задавать значение параметра больше 2, т.к. это существенно увеличивает использование оперативной памяти и снижает скорость поиска |
int | 0 | 0 - 2 | max_typos_in_word |
FtTyposDetailedConfig |
Расширенные параметры настройки алгоритма поиска с опечатками | []Struct | typos_detailed_config Описание полей структуры |
||
MaxTypoLen |
Максимальная длина слова с опечаткой (ошибкой) в поисковом запросе, для которого будет выполняться поиск вариантов. | int | 15 | 0 - 100 | max_typo_len |
MaxRebuildSteps |
Максимальное количество шагов (операций вставки, модификации) без перестроения индекса. Чем меньше этот параметр, тем медленнее происходит фиксация изменений. Оптимальное значение параметра — 15 |
int | 50 | 1 - 500 | max_rebuild_steps |
MaxStepSize |
Максимальное количество уникальных слов в каждом шаге. Чем больше шагов, тем медленнее работает поиск, но быстрее происходит динамическое перестроение индекса и наоборот. | int | 4000 | max_step_size | |
MergeLimit |
Максимальное количество релевантных документов, которое будет учтено движком при выполнении запроса. Увеличение этого параметра позволяет улучшить результаты поиска по высокочастотным словам, но снижает скорость поиска. | int | 20000 | 1 - 0x1FFFFFFF | merge_limit |
Stemmers |
Список стеммеров, с использованием которых будет происходить полнотекстовый поиск. | []string | “en”, “ru” | “en”, “ru”, “nl”, “fin”, “de”, “da”, “fr”, “it”, “hu”, “no”, “pt”, “ro”, “es”, “sv”, “tr” | stemmers |
EnableTranslit |
Включение/отключение использования транслитерации русских слов при поиске. Например слово luntik будет соответствовать слову лунтик |
bool | true | true, false | enable_translit |
EnableKbLayout |
Включение/отключение поддержки неправильной раскладки клавиатуры. Например слово keynbr будет соответствовать слову лунтик |
bool | true | true, false | enable_kb_layout |
EnableNumbersSearch |
Включение/отключение поиска чисел по строке. Например, по слову «пятьсот» при включенном поиске должны находиться документы, содержащие «500». Работает только в одну сторону и только для русского языка: по запросу «500» слово «пятьсот» найдено не будет | bool | false | true, false | enable_numbers_search |
EnablePreselectBeforeFt |
Флаг изменения порядка выборки для планировщика. По умолчанию (enable_preselect_before_ft=false) в первую очередь происходит выборка из полнотекстового индекса, а дальше фильтрация по компараторам (если в запросе есть ещё какие-то условия). Если этот флаг включен, то в первую очередь выполняется поиск по всем неполнотекстовым индексам из запроса, а дальше полученная выборка будет использована в полнотекстовом индексе. Это может быть полезно в ситуациях, когда есть много документов, удовлетворяющих полнотекстовому DSL (больше, чем merge limit), но при этом среди всех этих документов нет ни одного, который бы удовлетворял остальным условиям запроса по другим полям. |
bool | false | true, false | enable_preselect_before_ft |
StopWords |
Список стоп-слов, которые будут игнорироваться в запросах и результатах при полнотекстовом поиске | []string | *Список по умолчанию для русского языка *Список по умолчанию для английского языка | stop_words | |
SumRanksByFieldsRatio |
Коэффициент для вычисления вклада индекса в итоговый ранг при полнотекстовом поиске по нескольким индексам. | float | 0.0 | sum_ranks_by_fields_ratio | |
LogLevel |
Уровень логирования для движка полнотекстового поиска. | int | 0 | 0 - 5 | log_level |
ExtraWordSymbols |
Список символов, которые будут восприниматься как часть слова. Все остальные символы будут оцениваться как пробельные. Данный список автоматически пополняется всеми символами из WordPartDelimiters. |
string | “-/+_`’” | extra_word_symbols | |
WordPartDelimiters |
Список символов, разделяющих составное слово на отдельные части. При этом данные символы считаются частью слова, то есть они автоматически будут добавлены в ExtraWordSymbols в том случае, если их там нет. |
string | “-/+_`’” | word_part_delimiters | |
FieldsCfg |
Конфигурация для отдельного поля. Перекрывает общие параметры конфигурации индекса. Содержит параметры: FieldName, Bm25Boost, Bm25Weight, TermLenBoost, TermLenWeight, PositionBoost, PositionWeight. |
[]struct | пустая структура | fields | |
EnableWarmupOnNsCopy |
Включить автоматическое прогревание (перестроение) индекса после транзакции, выполнившей копирование неймспейса. Может использоваться для ускорения транзакций и перестроения индекса. | bool | false | true, false | enable_warmup_on_ns_copy |
Synonyms |
Список синонимов, которые будут использоваться при полнотекстовом поиске. | []string | пустая структура | synonyms | |
MaxAreasInDoc |
Максимальное количество областей на документ, «подсвеченных» с помощью в функции snippet() или highlight(). |
int | 5 | -1 - 1000 Значение -1 означает отсутствие ограничений по количеству «подсвеченных» областей |
max_areas_in_doc |
MaxTotalAreasToCache |
Максимальное число «подсвеченных» с помощью snippet() или highlight() областей, при котором результаты запроса к полнотекстовому индексу могут быть закешированы. |
int | -1 | -1 - 1000 Значение -1 означает отсутствие ограничений |
max_total_areas_to_cache |
Optimization |
Способ оптимизации индекса: по памяти или CPU. | string | “Memory” | “Memory” “CPU” |
optimization |
FtBaseRanking |
Структура с настройками параметров релевантности слова в разных формах | []Struct | base_ranking Описание полей структуры |
||
Bm25Config |
Структура с настройками функции оценки релевантности документа | []Struct | bm25_config Описание полей структуры |
Структура с расширенными настройками алгоритма поиска с опечатками (FtTyposDetailedConfig)
| Параметр | Описание | Тип значения | Значение по умолчанию | Допустимые значения/диапазон значений | json-paths параметра |
|---|---|---|---|---|---|
MaxMissingLetters |
Максимальное количество символов, которое может быть удалено из первоначального поискового терма для получения результирующего слова. Подробнее о поиске с ошибками и опечатками. | int | 2 | -1 - 2 | typos_detailed_config.max_missing_letters |
MaxExtraLetters |
Максимальное количество символов, которое может быть добавлено к первоначальному поисковому терму для получения результирующего слова. Подробнее о поиске с ошибками и опечатками. | int | 2 | -1 - 2 | typos_detailed_config.max_extra_letters |
MaxTypoDistance |
Максимальная дистанция между заменяемыми символами при обработке опечатки (актуально только при MaxTypos >= 2). Подробнее о поиске с ошибками и опечатками. |
int | 0 | -1 - 100 | typos_detailed_config.max_typo_distance |
MaxSymbolPermutationDistance |
Максимальное расстояние, на которое может быть перемещён один и тот же символ при обработке опечатки. Подробнее о поиске с ошибками и опечатками. | int | 1 | -1 - 100 | typos_detailed_config.max_symbol_permutation_distance |
Структура с настройками параметров базовой релевантности слова в разных формах (FtBaseRanking)
| Параметр | Описание | Тип значения | Значение по умолчанию | Допустимые значения/диапазон значений | json-paths параметра |
|---|---|---|---|---|---|
FullMatch |
Релевантность полного совпадения | int | 100 | 0 - 500 | full_match_proc |
PrefixMin |
Минимальная релевантность при применении поиска по префиксам (см. PartialMatchDecrease) |
int | 50 | 0 - 500 | prefix_min_proc |
SuffixMin |
Минимальная релевантность при применении поиска по суффиксам (см. PartialMatchDecrease) |
int | 10 | 0 - 500 | suffix_min_proc |
Typo |
Базовая релевантность при поиске с опечатками | int | 85 | 0 - 500 | base_typo_proc |
TypoPenalty |
Дополнительный штраф за перестановку каждого слова в алгоритме опечаток. Минимальная релевантность после применения штрафов будет не меньше 1. | int | 15 | 0 - 500 | typo_proc_penalty |
StemmerPenalty |
Штраф за варианты, созданные стеммером. Минимальная релевантность после применения штрафов будет не меньше 1. | int | 15 | 0 - 500 | stemmer_proc_penalty |
Kblayout |
Релевантность слова в неправильной раскладке клавиатуры | int | 90 | 0 - 500 | kblayout_proc |
Translit |
Релевантность слова, написанного транслитом | int | 90 | 0 - 500 | translit_proc |
Synonyms |
Релевантность синонимов | int | 95 | 0 - 500 | synonyms_proc |
Delimited |
Релевантность совпадения по части составного слова | int | 80 | 0 - 500 | delimited_proc |
Базовая оценка релевантности документа
Для базовой оценки релевантности документа используются следующие алгоритмы:
bm25rx_bm25word_count
Формула для вычисления bm25:
R = (log(totalDocCount / (matchedDocCount + 1)) + 1) * termCountInDoc / wordsInDoc * (k1 + 1.0) / (termCountInDoc / wordsInDoc + k1 * (1.0 - b + b * wordsInDoc / avgDocLen))
Формула для вычисления rx_bm25:
R = (log(totalDocCount / (matchedDocCount + 1)) + 1) * termCountInDoc * (k1 + 1.0) / (termCountInDoc + k1 * (1.0 - b + b * wordsInDoc / avgDocLen))
Формула для вычисления word_count:
R = termCountInDoc
totalDocCount- общее количество документовmatchedDocCount- число документов в которых встретилась подформа исходного слова запросаtermCountInDoc- число слов в документе, которые соответствуют подформе исходного слова запросаwordsInDoc- число слов в документеavgDocLen— средняя длина документаk1- свободный коэффициент. Данный параметр задает порог насыщения по частоте терма. Чем больше коэффициент, тем выше порог и меньше скорость насыщения.b- свободный коэффициент. Данный параметр усиливает отношение длины документа к средней длине документа.
Структура с настройками функции оценки релевантности документа (Bm25Config)
| Параметр | Описание | Тип значения | Значение по умолчанию | Допустимые значения/диапазон значений | json-paths параметра |
|---|---|---|---|---|---|
Bm25k1 |
Коэффициент k1 в формуле для расчета bm25 (Используется только если выбран тип rx_bm25, bm25). | float | 2.0 | >= 0 | bm25_k1 |
Bm25b |
Коэффициент b в формуле для расчета bm25 (Используется только если выбран тип rx_bm25, bm25). | float | 0.75 | 0-1.0 | bm25_b |
Bm25Type |
Идентификатор формулы по которой происходит расчет релевантности Описание | string | “rx_bm25” | “rx_bm25” “bm25” “word_count” |
bm25_type |
Производительность и использование памяти при полнотекстовом поиске
В Reindexer по умолчанию реализована «ленивая» индексация: операция Upsert не перестраивает индекс.
Он перестраивается при первом запросе к text полю.
При «ленивой» индексации используется несколько потоков, что обеспечивает эффективное расходование ресурсов современного многоядерного процессора.
В результате удается добиться очень высокой скорости индексации. На современном оборудовании она равна ~50Мб/сек.
Но при слишком больших размерах текста «ленивая» индексация может серьезно замедлить первый запрос к текстовому индексу.
Избежать этого эффекта поможет прогревание индекса с помощью «фиктивного» запроса после последней операции Upsert.
Пример такого запроса:
query := db.Query("items").
Match("description","*text").
Limit(1) // Запрос для поиска по индексу `description` с минимальной выдачей
SELECT * FROM items WHERE descripton ='*text' LIMIT 1