Предупреждение
Драгоценный камень в настоящее время не поддерживается, и разработка приостановлена. Если вы заинтересованы в захвате, не стесняйтесь обратиться ко мне.
Нужен быстрый полнотекстовый поиск для вашего сценария Ruby, но Solr и Elasticsearch-это избыточно? ?
Ты в нужном месте. Tantiny -это минималистичная библиотека полного текста для Ruby, основанная на Tanti V Y (потрясающая альтернатива Apache Lucene, написанную в Rust). Это отлично подходит для случаев, когда ваша задача требует полнотекстового поиска, но настройка полномасштабной распределенной поисковой системы займет больше времени, чем сама задача. И даже если вы уже используете такой двигатель в своем проекте (что, на самом деле, весьма вероятно,), вместо этого может быть проще использовать Tantiny, потому что в отличие от Solr и Elasticsearch, ему не нужно ничего работать (без отдельного сервера, процесса или чего -то еще), он является чисто внедренным. Таким образом, когда вы окажетесь в ситуации при использовании выбранной поисковой системы, будет сложным/неудобным или потребует дополнительной настройки, вы всегда можете вернуться к быстрому и грязному решению, которое, несомненно, является гибким и быстрым.
Tantiny - это не совсем рубиновые привязки к Tantivy, но он пытается быть близким. Основная философия состоит в том, чтобы обеспечить низкоуровневый доступ к перевернутому индексу Tantivy, но с хорошим API в стиле рубинового эска, разумных дефолтов и дополнительной функциональности, посыпанных сверху.
Взгляните на самый основной пример:
index = Tantiny :: Index . new ( "/path/to/index" ) { text :description }
index << { id : 1 , description : "Hello World!" }
index << { id : 2 , description : "What's up?" }
index << { id : 3 , description : "Goodbye World!" }
index . reload
index . search ( "world" ) # 1, 3 Добавьте эту строку в Gemfile вашего приложения:
gem "tantiny"А затем выполнить:
$ bundle install
Или установите его самостоятельно как:
$ gem install tantiny
Вам не нужно устанавливать ржавчину в вашу систему, так как Tantiny попытается загрузить предварительно скомпилированные двоичные файлы, размещенные в выпусках GitHub во время установки. Однако, если для вашей системы не было найдено некомпилированных двоичных файлов (которая представляет собой комбинацию платформы, архитектуры и версии Ruby), вам нужно будет сначала установить ржавчину.
Предупреждение
Поддерживаются только версии ржавчины до 1.77 . Смотрите эту проблему для более подробной информации.
Важный
Пожалуйста, убедитесь, что укажите второстепенную версию при объявлении зависимости от tantiny . API является субъектом изменения, и до тех пор, пока он не достигнет 1.0.0 , шишка в незначительной версии, скорее всего, означает нарушение изменения.
Вы должны указать путь к тому, где будет храниться индекс, и блок, который определяет схему:
Tantiny :: Index . new "/tmp/index" do
id :imdb_id
facet :category
string :title
text :description
integer :duration
double :rating
date :release_date
endВот описания для каждого типа поля:
| Тип | Описание |
|---|---|
| идентификатор | Указывает, где хранятся идентификаторы документов (по умолчанию :id ). |
| аспект | Поля со значениями, такими как /animals/birds (т.е. иерархические категории). |
| нить | Поля с текстом, которые не являются токенизированными. |
| текст | Поля с текстом, которые токенизируются указанным токенизатором. |
| целое число | Поля с целочисленными значениями. |
| двойной | Поля со значениями плавания. |
| дата | Поля с типом DateTime или чем -то, что к нему преобразуется. |
Вы можете накормить индекс любого вида объекта с методами, указанными в вашей схеме, но простые хэши также работают:
rio_bravo = OpenStruct . new (
imdb_id : "tt0053221" ,
type : '/western/US' ,
title : "Rio Bravo" ,
description : "A small-town sheriff enlists a drunk, a kid and an old man to help him fight off a ruthless cattle baron." ,
duration : 141 ,
rating : 8.0 ,
release_date : Date . parse ( "March 18, 1959" )
)
index << rio_bravo
hanabi = {
imdb_id : "tt0119250" ,
type : "/crime/Japan" ,
title : "Hana-bi" ,
description : "Nishi leaves the police in the face of harrowing personal and professional difficulties. Spiraling into depression, he makes questionable decisions." ,
duration : 103 ,
rating : 7.7 ,
release_date : Date . parse ( "December 1, 1998" )
}
index << hanabi
brother = {
imdb_id : "tt0118767" ,
type : "/crime/Russia" ,
title : "Brother" ,
description : "An ex-soldier with a personal honor code enters the family crime business in St. Petersburg, Russia." ,
duration : 99 ,
rating : 7.9 ,
release_date : Date . parse ( "December 12, 1997" )
}
index << brotherЧтобы обновить документ, просто добавьте его снова (если идентификатор одинаково):
rio_bravo . rating = 10.0
index << rio_bravoВы также можете удалить его, если хотите:
index . delete ( rio_bravo . imdb_id ) Если вам нужно выполнить несколько операций по написанию (то есть более одного), вы должны всегда использовать transaction :
index . transaction do
index << rio_bravo
index << hanabi
index << brother
end Группа транзакций изменяется и совершает их в индекс за один раз. Это значительно более эффективно, чем выполнение этих изменений один за другим. Фактически, все операции по написанию (то есть << и delete ) оказаны в транзакции, неявно, когда вы называете их вне транзакции, поэтому вызов << 10 раз за пределами транзакции - это то же самое, что выполнение 10 отдельных транзакций.
Tantiny-это безопасно для потока, что означает, что вы можете безопасно разделить один экземпляр индекса между потоками. Вы также можете создавать отдельные процессы, которые могут писать и читать из того же индекса. Однако, хотя чтение из индекса должно быть параллельным, писать к нему нет . Всякий раз, когда вы называете transaction или любую другую операцию, которая изменяет индекс (то есть << и delete ), она будет заблокировать индекс для продолжительности операции или ждать другого процесса или потока, чтобы отпустить блокировку. Единственное исключение из этого - это когда есть еще один процесс с индексом с эксклюзивным писателем, работающим где -то, и в этом случае методы, которые изменяют индекс, не будут сразу не следовать.
Таким образом, лучше всего иметь единый процесс писателя, и многие процессы читателей, если вы хотите избежать блокировки вызовов. Правильный способ сделать это - установить exclusive_writer на true при инициализации индекса:
index = Tantiny :: Index . new ( "/path/to/index" , exclusive_writer : true ) { }Таким образом, писатель индекса будет приобретен только один раз, что означает, что память для него и индексационные потоки будут также распределены только один раз. В противном случае новый индексный писатель приобретается каждый раз, когда вы выполняете операцию по написанию.
Убедитесь, что ваш индекс совпадает, перезаряжая его сначала:
index . reloadИ поиск (наконец -то!):
index . search ( "a drunk, a kid, and an old man" )По умолчанию он вернет идентификаторы из 10 лучших соответствующих документов, но вы можете настроить его:
index . search ( "a drunk, a kid, and an old man" , limit : 100 ) Вы можете задаться вопросом, как именно он проводит поиск? Что ж, поведение по умолчанию состоит в том, чтобы использовать поиск smart_query (для получения подробной информации см. Ниже) по всем text полям, определенным в вашей схеме. Итак, вы можете передать параметры, которые smart_query принимает прямо здесь:
index . search ( "a dlunk, a kib, and an olt mab" , fuzzy_distance : 1 )Тем не менее, вы можете настроить его, составив свой собственный запрос из основных строительных блоков:
popular_movies = index . range_query ( :rating , 8.0 .. 10.0 )
about_sheriffs = index . term_query ( :description , "sheriff" )
crime_movies = index . facet_query ( :cetegory , "/crime" )
long_ass_movies = index . range_query ( :duration , 180 .. 9999 )
something_flashy = index . smart_query ( :description , "bourgeoisie" )
index . search ( ( popular_movies & about_sheriffs ) | ( crime_movies & ! long_ass_movies ) | something_flashy )Я знаю, странный вкус! Но довольно круто, а? Взгляните на все доступные запросы ниже.
| Запрос | Поведение |
|---|---|
| all_query | Возвращает все индексированные документы. |
| empty_query | Возвращает точно ничего (используется внутри). |
| term_query | Документы, которые содержат указанный термин. |
| fuzzy_term_query | Документы, которые содержат указанный термин на расстоянии Левенштейна. |
| phrase_query | Документы, которые содержат указанную последовательность терминов. |
| regex_query | Документы, которые содержат термин, который соответствует указанному корпорации. |
| prefix_query | Документы, которые содержат термин с указанным префиксом. |
| RANGE_QUERY | Документы, которые с integer полем, double или date в пределах указанного диапазона. |
| facet_query | Документы, которые принадлежат указанной категории. |
| Smart_query | Комбинация term_query , fuzzy_term_query и prefix_query . |
Взгляните на файл подписей, чтобы увидеть, какие параметры принимают запросы.
Все запросы могут искать поля Multuple (за исключением facet_query , потому что это не имеет смысла там).
Итак, следующий запрос:
index . term_query ( %i[ title description ] , "hello" )Эквивалентно:
index . term_query ( :title , "hello" ) | index . term_query ( :description , "hello" ) Все запросы поддерживают параметр boost , который позволяет установить позицию документов в поиске:
about_cowboys = index . term_query ( :description , "cowboy" , boost : 2.0 )
about_samurai = index . term_query ( :description , "samurai" ) # sorry, Musashi...
index . search ( about_cowboys | about_samurai )smart_query Bework Поиск smart_query будет извлекать термины из вашей строки запроса, используя соответствующие токенизаторы поля и поиск индекса для документов, которые содержат эти термины через term_query . Если указан параметр fuzzy_distance , он будет использовать fuzzy_term_query . Кроме того, он позволяет последнему члену быть незаконченным с помощью prefix_query .
Итак, следующий запрос:
index . smart_query ( %i[ en_text ru_text ] , "dollars рубли eur" , fuzzy_distance : 1 )Эквивалентно:
t1_en = index . fuzzy_term_query ( :en_text , "dollar" )
t2_en = index . fuzzy_term_query ( :en_text , "рубли" )
t3_en = index . fuzzy_term_query ( :en_text , "eur" )
t3_prefix_en = index . prefix_query ( :en_text , "eur" )
t1_ru = index . fuzzy_term_query ( :ru_text , "dollars" )
t2_ru = index . fuzzy_term_query ( :ru_text , "рубл" )
t3_ru = index . fuzzy_term_query ( :ru_text , "eur" )
t3_prefix_ru = index . prefix_query ( :ru_text , "eur" )
( t1_en & t2_en & ( t3_en | t3_prefix_en ) ) | ( t1_ru & t2_ru & ( t3_ru | t3_prefix_ru ) ) Обратите внимание, как слова «доллары» и «ruebli» по -разному возникают в зависимости от области, которую мы ищем. Это предполагает, что у нас есть поля en_text и ru_text в нашей схеме, которые используют английские и русские токенизаторы ствола соответственно.
regex_query regex_query принимает рисунок регуляции, но это должно быть ржавчине, а не рубиновая Regexp . Таким образом, вместо index.regex_query(:description, /hel[lp]/) вам нужно использовать index.regex_query(:description, "hel[lp]") . В качестве примечания, regex_query довольно быстро, потому что он использует FST Crate внутренне.
Итак, мы уже раз упоминали токенизаторы уже раз. Кто они такие?
Tokenizers - это то, что Tantivy использует, чтобы нарезать ваш текст на термины, чтобы построить перевернутый индекс. Затем вы можете искать индекс этими терминами. Это важная концепция, чтобы понять, чтобы вы не запутались, когда index.term_query(:description, "Hello") ничего не возвращает, потому что Hello не термин, но hello . Вы должны извлечь термины из запроса перед поиском индекса. В настоящее время только smart_query делает это для вас. Кроме того, единственный тип поля, который является токенизированным, - это text , поэтому для string полей вы должны использовать точное совпадение (т.е. index.term_query(:title, "Hello") ).
По умолчанию используется simple токенизатор, но вы можете указать нужный токенизатор по всему миру с помощью параметров индекса или локально через специфические для поля опции:
en_stemmer = Tantiny :: Tokenizer . new ( :stemmer )
ru_stemmer = Tantiny :: Tokenizer . new ( :stemmer , language : :ru )
Tantiny :: Index . new "/tmp/index" , tokenizer : en_stemmer do
text :description_en
text :description_ru , tokenizer : ru_stemmer
endПростой токенизатор сбрасывает текст на пунктуацию и пробелы, удаляет длинные жетоны и снижает текст.
tokenizer = Tantiny :: Tokenizer . new ( :simple )
tokenizer . terms ( "Hello World!" ) # ["hello", "world"]Токенизаторы Stemmer точно такие же, как простой токенизатор, но с дополнительным стволом в соответствии с указанным языком (по умолчанию на английский).
tokenizer = Tantiny :: Tokenizer . new ( :stemmer , language : :ru )
tokenizer . terms ( "Привет миру сему!" ) # ["привет", "мир", "сем"]Взгляните на источник, чтобы увидеть, какие языки поддерживаются.
NGRAM Tokenizer поднимает ваш текст на NGRAM определенного размера.
tokenizer = Tantiny :: Tokenizer . new ( :ngram , min : 5 , max : 10 , prefix_only : true )
tokenizer . terms ( "Morrowind" ) # ["Morro", "Morrow", "Morrowi", "Morrowin", "Morrowind"] Возможно, вы заметили, что метод search возвращает только идентификаторы документов. Это по дизайну. Сами документы не хранятся в индексе. Tantiny - это минималистичная библиотека, поэтому она пытается сделать вещи простыми. Если вам нужно получить полный документ, используйте магазин ключей, такой как Redis вместе.
После проверки репо, запустите bin/setup для установки зависимостей. Затем запустите rake build , чтобы построить собственные расширения, а затем rake spec для запуска тестов. Вы также можете запустить bin/console для интерактивной подсказки, которая позволит вам экспериментировать.
Мы используем традиционные коммиты для автоматического генерирования изменчивости, увеличить семантическую версию, а также публиковать и выпустить драгоценность. Все, что вам нужно сделать, это придерживаться конвенции, и CI позаботится обо всем остальном для вас.
Отчеты об ошибках и запросы на привлечение приветствуются на GitHub по адресу https://github.com/baygeldin/tantiny.
Драгоценный камень доступен в качестве открытого исходного кода в соответствии с условиями лицензии MIT.