Обзор библиотеки bleve в Golang

от автора

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

Чтобы начать работу с Bleve, нужно установить саму библиотеку в рабочее пространство Go. Процесс установки выполняется с помощью команды go get:

go get -u github.com/blevesearch/bleve

Bleve

Создание индекса начинается с определения маппинга. Маппинг — это описание структуры данных, которое указывает Bleve, как индексировать и хранить различные поля документов:

import "github.com/blevesearch/bleve"  func createIndex() {     // определение базового маппинга     mapping := bleve.NewIndexMapping()      // создание индекса с базовым маппингом     index, err := bleve.New("example.bleve", mapping)     if err != nil {         panic(err)     } }

После создания индекса можно добавить в него данные. Это делается с помощью метода Index, принимающего идентификатор документа и сам документ:

type BlogPost struct {     Title   string     Content string     Tags    []string     Author  string }  func indexData(index bleve.Index) {     post := BlogPost{         Title:   "Bleve: индексация в Go",         Content: "Bleve предоставляет разработчикам...",         Tags:    []string{"поиск", "индексация", "go"},         Author:  "Кот",     }      err := index.Index("post_1", post)     if err != nil {         log.Fatal(err)     } }

Для выполнения поиска в индексе Bleve используется метод Search. Пример поиска документов по тексту:

func searchIndex(index bleve.Index) {     query := bleve.NewMatchQuery("индексация")     search := bleve.NewSearchRequest(query)     searchResults, err := index.Search(search)     if err != nil {         log.Fatal(err)     }      log.Printf("Найдено документов: %d", searchResults.Total) }

Можно настроить маппинг для текстового поля с использованием конкретного анализатора для обработки текста на английском языке:

textFieldMapping := bleve.NewTextFieldMapping() // текстовый тип поля textFieldMapping.Analyzer = "en"  docMapping := bleve.NewDocumentMapping() docMapping.AddFieldMappingsAt("description", textFieldMapping)  mapping := bleve.NewIndexMapping() mapping.AddDocumentMapping("myDoc", docMapping)  index, err := bleve.New("path/to/index.bleve", mapping) if err != nil {     log.Fatal(err) }

Создаём маппинг для документа с типом «myDoc«, в котором есть текстовое поле «description«, обрабатываемое английским анализатором

В первой строчке здесь юзаем текстовый тип поля. Можно объявить числовое поле с помощью bleve.NewNumericFieldMapping() или к примеру поле даты bleve.NewDateTimeFieldMapping() .

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

dynamicMapping := bleve.NewDocumentMapping()  // настройка динамического маппинга для обработки всех текстовых полей dynamicMapping.DefaultTextFieldMapping = bleve.NewTextFieldMapping() dynamicMapping.DefaultTextFieldMapping.Analyzer = "en"  mapping := bleve.NewIndexMapping() mapping.DefaultMapping = dynamicMapping  index, err := bleve.New("example.bleve", mapping) if err != nil {     panic(err) }

Запросы

TermQuery ищет документы, содержащие точное значение термина в указанном поле. Это самый базовый тип запроса:

query := bleve.NewTermQuery("specific term") query.SetField("fieldName") searchRequest := bleve.NewSearchRequest(query) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents matching 'specific term'\n", searchResult.Total)

MatchQuery предназначен для поиска документов, содержащих термины, соответствующие запросу, с учётом анализатора поля, звучит намного круче чем termquery:

query := bleve.NewMatchQuery("text to match") query.SetField("content") searchRequest := bleve.NewSearchRequest(query) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents matching 'text to match'\n", searchResult.Total)

PrefixQuery ищет документы, в которых поля содержат термины, начинающиеся с указанного префикса:

query := bleve.NewPrefixQuery("pre") query.SetField("fieldName") searchRequest := bleve.NewSearchRequest(query) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents with terms starting with 'pre'\n", searchResult.Total)

FuzzyQuery поддерживает поиск по схожим терминам, опираясь на заданное query := bleve.NewFuzzyQuery("fuzze term") query.SetFuzziness(2) query.SetField("fieldName") searchRequest := bleve.NewSearchRequest(query) searchResult, err := index.Search(searchRequest) if err != nil { log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents with terms similar to 'fuzze term'\n", searchResult.Total)

BoolQuery позволяет комбинировать другие запросы с использованием логических операторов:

query1 := bleve.NewMatchQuery("first term") query2 := bleve.NewMatchQuery("second term") boolQuery := bleve.NewBooleanQuery() boolQuery.AddMust(query1) boolQuery.AddShould(query2) searchRequest := bleve.NewSearchRequest(boolQuery) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents matching conditions\n", searchResult.Total) 

PhraseQuery используется для поиска точной последовательности слов в документе:

phraseQuery := bleve.NewPhraseQuery([]string{"search", "phrase"}, "fieldName") searchRequest := bleve.NewSearchRequest(phraseQuery) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents matching the exact phrase\n", searchResult.Total) 

DateRangeQuery позволяет искать документы с датами в определённом диапазоне. Этот тип запроса оч хорошо подходит для фильтрации событий, записей или любых данных, связанных с временными интервалами:

startDate := "2000-01-01" endDate := "2024-12-31" dateRangeQuery := bleve.NewDateRangeQuery(startDate, endDate) dateRangeQuery.SetField("dateField") searchRequest := bleve.NewSearchRequest(dateRangeQuery) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents within the date range\n", searchResult.Total)

GeoDistanceQuery предназначен для поиска документов, находящихся в определённом радиусе от заданной географической точки:

location := bleve.NewGeoPoint(37.7749, -122.4194) // Пример для Сан-Франциско geoQuery := bleve.NewGeoDistanceQuery(location.Lat, location.Lon, "100km") geoQuery.SetField("locationField") searchRequest := bleve.NewSearchRequest(geoQuery) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents within 100km of the location\n", searchResult.Total)

WildcardQuery поддерживает поиск с использованием подстановочных знаков, что позволяет находить документы с частичным совпадением:

wildcardQuery := bleve.NewWildcardQuery("te*t") wildcardQuery.SetField("fieldName") searchRequest := bleve.NewSearchRequest(wildcardQuery) searchResult, err := index.Search(searchRequest) if err != nil {     log.Fatalf("error executing search: %v", err) } fmt.Printf("Found %d documents matching the wildcard pattern\n", searchResult.Total)

Анализаторы

Анализаторы в Bleve — это комбинации токенайзеров и фильтров (токенов и символов), предназначенные для обработки текста определенным образом. Bleve предлагает несколько встроенных анализаторов, например, en для английского языка, которые настраиваются для выполнения стемминга, удаления стоп-слов и т.д

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

mapping := bleve.NewIndexMapping() textFieldMapping := bleve.NewTextFieldMapping() textFieldMapping.Analyzer = "en" mapping.DefaultMapping.AddFieldMappingsAt("description", textFieldMapping)

Токенайзеры разбивают текст на токены (обычно слова или фразы), которые затем индексируются. В Bleve доступны различные встроенные токенайзеры, к примеруunicode, который разбивает текст по пробелам и пунктуации:

customAnalyzer := bleve.NewAnalyzer() customAnalyzer.SetTokenizer(bleve.NewUnicodeTokenizer())

Фильтры токенов применяются к каждому токену отдельно, позволяя модифицировать, добавлять или удалять токены. Например, фильтр ToLower преобразует все токены к нижнему регистру:

customAnalyzer := bleve.NewAnalyzer() customAnalyzer.SetTokenizer(bleve.NewUnicodeTokenizer()) customAnalyzer.AddTokenFilter(bleve.NewLowerCaseTokenFilter())

Можно создать кастомный анализатор:

indexMapping := bleve.NewIndexMapping() customAnalyzer := bleve.NewAnalyzerNamed("custom_analyzer") customAnalyzer.SetTokenizer(bleve.NewUnicodeTokenizer()) customAnalyzer.AddTokenFilter(bleve.NewLowerCaseTokenFilter()) customAnalyzer.AddTokenFilter(bleve.NewStopTokenFilter()) indexMapping.AddCustomAnalyzer("custom_analyzer", customAnalyzer)  textFieldMapping := bleve.NewTextFieldMapping() textFieldMapping.Analyzer = "custom_analyzer" indexMapping.DefaultMapping.AddFieldMappingsAt("custom_text", textFieldMapping) 

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

В Bleve напрямую символьные фильтры не выделяются как отдельная категория для настройки анализаторов, в отличие от токенайзеров и фильтров токенов. Но все эт можно реализовать на уровне кастомных анализаторов или в процессе предварительной обработки данных перед индексацией.

Предположим, мы хотим создать символьный фильтр, который удаляет все знаки препинания из текста перед токенизацией. Это можно сделать, определив функцию предварительной обработки текста:

import (     "github.com/blevesearch/bleve"     "strings"     "unicode" )  // функция для удаления знаков препинания из строки func removePunctuation(text string) string {     return strings.Map(func(r rune) rune {         if unicode.IsPunct(r) {             return -1 // Удаление символа         }         return r     }, text) }  // пример использования в кастомном анализаторе func createCustomAnalyzerWithCharFilter(indexMapping *bleve.IndexMapping) {     textMapping := bleve.NewTextFieldMapping()          // допустм что customAnalyzer уже определён     customAnalyzer := &bleve.CustomAnalyzer{         Tokenizer: bleve.NewUnicodeTokenizer(),         TokenFilters: []string{"lower_case", "stop_en"},         CharFilters: []bleve.CharFilter{             // используем функцию для обработки текста здесь             removePunctuation,         },     }          indexMapping.AddCustomAnalyzer("custom_with_char_filter", customAnalyzer)     textMapping.Analyzer = "custom_with_char_filter"     indexMapping.DefaultMapping.AddFieldMappingsAt("myTextField", textMapping) }

Bleve позволяет строить высокопроизводительные и функционально богатые приложения.

В завершение хочу порекомендовать вам бесплатный вебинар курса Highload Architect про асинхронную обработку данных и ее использование в высоконагруженных проектах.


ссылка на оригинал статьи https://habr.com/ru/articles/793526/