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

Простой поиск

Для простого поиска в тексте можно использовать оператор 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).

Полнотекстовый поиск возможен по 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

  1. Полнотекстовый поиск в Reindexer нечувствителен к регистру.
  2. Исходный текст при поиске токенизируется до набора слов.
  3. Под словом понимается последовательность любых букв в кодировке UTF-8, цифр или символов: +, -, /.
  4. Первый символ в слове — буква или цифра.
  5. Результаты выполнения запросов для полнотекстового поиска сортируются по релевантности, если не задан другой критерий сортировки.

Запросы к полнотекстовым индексам

Запросы к полнотекстовым индексам строятся через обычный интерфейс запросов по имени индекса. Примеры:

query := db.Query ("items").
		Match ("name+description","text query","<stemmers>") // Запрос для поиска по индексам name+description
SELECT * FROM items WHERE description = '*tor'

Или с использованием псевдонимов (пример на Go):

query := db.Query ("items").
		Match ("text_search","text query","<stemmers>") // Запрос для поиска по композитному (составному) индексу с псевдонимом`text_search` для полнотекстового поиска по индексам `name` и `description`

Здесь:

  • text query — запрос, по которому будет происходить полнотекстовый поиск,
  • <stemmers> — список стеммеров, с использованием которых будет выполняться поиск по основе слова. Значение по умолчанию — "en","ru".

Кроме того, запросы к полнотекстовым индексам можно комбинировать с условиями выбора по другим полям неймспейса.

В таком случае все условия выбора по другим полям (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, при вычислении общего ранга будут будут использованы совпадения слова из обоих индексных полей. Если оба индекса пометить +, совпадения для них будут учитываться при вычислении итогового ранга в любом случае.
Вычисление итогового ранга слова при полнотекстовом поиске по нескольким индексам

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

  1. Значения рангов совпадения слова из нескольких индексных полей сортируются от большего к меньшему.
  2. Каждое из этих значений умножается на коэффициент 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, например, недопустимый вариант). Минимальная длина последовательности, соответствующей * — 2 символа.
~ Нечеткое слово с ошибкой согласно словарю опечаток. Словарь опечаток включает слова с одной возможной ошибкой (опечаткой). Например, шаблону black~ будут соответствовать слова block, blck, blask и т.д. (одна ошибка или опечатка).
^boost Буст (увеличение) значения ранга совпадения для слова при формировании результатов поиска. Значение по умолчанию равно 1.
= В результат включаются только точные совпадения для слова, следующего за этим символом. При поиске не используется транслит, исправления при неверной раскладке, стеммеры.

Бинарные операторы в запросах для полнотекстового поиска в Reindexer

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

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

Экранирование символов в запросах для полнотекстового поиска

Экранирование с помощью \ позволяет добавлять специальные символы +,-,@,*,^ и ~ в запросы для полнотекстового поиска. Например при наличии в запросе паттерна \*crisis будет искаться слово *crisis, а не все слова, заканчивающиеся на crisis.

Поиск фразы

Операндом может быть фраза. Фраза задается в форме

“word1 word2 …"~N word1, word2, … - слова фразы в заданной последовательности (последовательность “word2 word1” не найдется если ищется “word1 word2”) ~N - максимальное расстояние между словами соседними словами фразы (если не задано, то расстояние принимается за 1)

Синонимы из нескольких слов не поддерживаются во фразе.

Примеры паттернов для полнотекстового поиска в Reindexer

Ниже представлены примеры паттернов для полнотекстового поиска, построенных по рассмотренному формату:

  1. termina* -genesis — поиск документов, включающих слово, начинающееся с termina (terminator, terminal и т.д.). Документы содержащие слово genesis, в результаты поиска включены не будут.

  2. black~ — поиск документов со словами, соответствующими шаблону black с одной ошибкой или опечаткой. В результаты попадут block, blck, blask и т.д.

  3. tom jerry cruz^2 — поиск документов, включающих хотя бы одно из слов tom jerry или cruz. При этом результаты, содержание cruz, будут иметь больший ранг: tom cruz будет ранжироваться выше, чем tom jerry.

  4. fox +fast — в результаты поиска будут включены варианты, содержание оба слова (fast должно обязательно присутствовать в документе для включения в результат поиска).

  5. "one two" — поиск документов, содержащих фразу "one two".

  6. "one two"~5 — поиск документов, содержащих слова one и two, которые могут быть расположены на расстоянии до 5 (<5) слов друг от друга.

  7. @name rush — поиск документов со словом rush будет вестись только по индексу name.

  8. @name^1.5,* rush — поиск документов, содержащих слово rush с бустом ранга совпадения для индекса name, равным 1.5.

  9. =windows — поиск документов, содержащих точное вхождение слова windows.

  10. one -"phrase example" - поиск документов содержащих слово one и не содержащих фразу “phrase example”.

  11. one "phrase example" - поиск документов содержащих слово one или фразу “phrase example” или оба операнда.

  12. +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 — строка, которая будет вставлена после найденной текстовой области.

Примеры использования:

b.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 (необязательный, значение по умолчанию — пробел) — разделитель после найденной текстовой области.

Пример использования:

b.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”

b.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}”

b.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!”.

Поиск с учетом ошибок и опечаток

В 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 Список символов, которые будут восприниматься как часть слова. Все остальные символы будут оцениваться как разделители. string “-/+” extra_word_symbols
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

Базовая оценка релевантности документа

Для базовой оценки релевантности документа используются следующие алгоритмы:

  • bm25
  • rx_bm25
  • word_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 - число слов в документе
  • 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