Avertissement
Le gemme n'est pas actuellement maintenu et le développement est mis en attente. Si vous êtes intéressé à prendre le relais, n'hésitez pas à me contacter.
Besoin d'une recherche rapide en texte intégral pour votre script Ruby, mais Solr et Elasticsearch sont un exagéré? ?
Vous êtes au bon endroit. Tantiny est une bibliothèque de recherche en texte intégral minimaliste pour Ruby basée sur Tanti V y (une alternative impressionnante à Apache lucene écrite en rouille). C'est idéal pour les cas où votre tâche à accomplir nécessite une recherche en texte intégral, mais la configuration d'un moteur de recherche distribué à part entière prendrait plus de temps que la tâche elle-même. Et même si vous utilisez déjà un tel moteur dans votre projet (ce qui est très probable, en fait), il peut toujours être plus facile d'utiliser Tantiny à la place car contrairement à SOLR et ELASTICSEARCH, il n'a rien besoin de fonctionner (pas de serveur ou de processus séparé ou autre), il est purement intégré. Ainsi, lorsque vous vous trouvez dans une situation lorsque vous utilisez votre moteur de recherche de choix serait délicat / gênant ou nécessiterait une configuration supplémentaire, vous pouvez toujours revenir à une solution rapide et sale non flexible et rapide.
Le tintin n'est pas exactement des liaisons rubis à tantivy, mais il essaie d'être proche. La philosophie principale est de fournir un accès de bas niveau à l'indice inversé de Tantivy, mais avec une belle API Ruby-esque, des paramètres sensibles et des fonctionnalités supplémentaires saupoudrées sur le dessus.
Jetez un œil à l'exemple le plus élémentaire:
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 Ajoutez cette ligne à Gemfile de votre application:
gem "tantiny"Puis exécuter:
$ bundle install
Ou installez-le vous-même comme:
$ gem install tantiny
Vous n'avez pas besoin de faire installer de la rouille sur votre système, car Tantiny essaiera de télécharger les binaires pré-compilés hébergés sur les versions GitHub pendant l'installation. Cependant, si aucun binaire précompilé n'a été trouvé pour votre système (qui est une combinaison de plate-forme, d'architecture et de version Ruby), vous devrez d'abord installer Rust.
Avertissement
Seules les versions de rouille jusqu'à 1.77 sont prises en charge. Voir ce problème pour plus de détails.
Important
S'il vous plaît, assurez-vous de spécifier la version mineure lors de la déclaration de dépendance à tantiny . L'API est un sujet à changer, et jusqu'à ce qu'il atteigne 1.0.0 , une bosse dans la version mineure signifie très probablement un changement de rupture.
Vous devez spécifier un chemin vers l'endroit où l'index serait stocké et un bloc qui définit le schéma:
Tantiny :: Index . new "/tmp/index" do
id :imdb_id
facet :category
string :title
text :description
integer :duration
double :rating
date :release_date
endVoici les descriptions de chaque type de champ:
| Taper | Description |
|---|---|
| identifiant | Spécifie où les ID de documents sont stockés (par défaut :id ). |
| facette | Champs avec des valeurs comme /animals/birds (c.-à-d. Catégories hiérarchies). |
| chaîne | Champs avec texte qui ne sont pas tokenisés. |
| texte | Champs avec texte qui sont tokenisés par le tokenizer spécifié. |
| entier | Champs avec des valeurs entières. |
| double | Champs avec des valeurs de flotteur. |
| date | Champs avec type DateTime ou quelque chose qui s'y convertit. |
Vous pouvez nourrir l'index tout type d'objet qui a des méthodes spécifiées dans votre schéma, mais les hachages simples fonctionnent également:
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 << brotherAfin de mettre à jour le document, ajoutez-le à nouveau (tant que l'ID est le même):
rio_bravo . rating = 10.0
index << rio_bravoVous pouvez également le supprimer si vous le souhaitez:
index . delete ( rio_bravo . imdb_id ) Si vous avez besoin d'effectuer plusieurs opérations d'écriture (c'est-à-dire plusieurs), vous devez toujours utiliser transaction :
index . transaction do
index << rio_bravo
index << hanabi
index << brother
end Le groupe de transactions change et les engage à l'index en une seule fois. Ceci est considérablement plus efficace que d'effectuer ces changements un par un. En fait, toutes les opérations d'écriture (c'est-à-dire << et delete ) sont enveloppées dans une transaction implicitement lorsque vous les appelez en dehors d'une transaction, donc appeler << 10 fois en dehors d'une transaction est la même chose que l'exécution de 10 transactions distinctes.
Tantiny est un sens de filetage signifiant que vous pouvez partager en toute sécurité une seule instance de l'index entre les threads. Vous pouvez également engendrer des processus séparés qui pourraient écrire et lire à partir du même index. Cependant, bien que la lecture de l'indice soit parallèle, l'écriture n'est pas . Chaque fois que vous appelez transaction ou toute autre opération qui modifie l'index (c'est-à-dire << et delete ), il verrouille l'index pour la durée de l'opération ou attendra un autre processus ou thread pour libérer le verrou. La seule exception à cela est lorsqu'il y a un autre processus avec un index avec un écrivain exclusif exécutant quelque part auquel cas les méthodes qui modifient l'index échoueront immédiatement.
Ainsi, il est préférable d'avoir un seul processus d'écrivain et de nombreux processus de lecture si vous souhaitez éviter de bloquer les appels. La bonne façon de le faire est de définir exclusive_writer sur true lors de l'initialisation de l'index:
index = Tantiny :: Index . new ( "/path/to/index" , exclusive_writer : true ) { }De cette façon, l'écrivain index ne sera acquis qu'une seule fois, ce qui signifie que la mémoire pour elle et les threads d'indexation ne seront alloués qu'une seule fois. Sinon, un nouvel écrivain d'index est acquis à chaque fois que vous effectuez une opération d'écriture.
Assurez-vous que votre index est à jour en le rechargeant d'abord:
index . reloadEt le rechercher (enfin!):
index . search ( "a drunk, a kid, and an old man" )Par défaut, il renverra les ID de 10 meilleurs documents correspondants, mais vous pouvez le personnaliser:
index . search ( "a drunk, a kid, and an old man" , limit : 100 ) Vous vous demandez peut-être, comment cela mène-t-il exactement la recherche? Eh bien, le comportement par défaut consiste à utiliser la recherche smart_query (voir ci-dessous pour plus de détails) sur tous les champs text définis dans votre schéma. Ainsi, vous pouvez transmettre les paramètres que le smart_query accepte ici:
index . search ( "a dlunk, a kib, and an olt mab" , fuzzy_distance : 1 )Cependant, vous pouvez le personnaliser en composant votre propre requête à partir de blocs de construction de base:
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 )Je sais, un goût bizarre! Mais plutôt cool, hein? Jetez un œil à toutes les requêtes disponibles ci-dessous.
| Requête | Comportement |
|---|---|
| all_query | Renvoie tous les documents indexés. |
| vide_query | Renvoie exactement rien (utilisé en interne). |
| term_query | Documents qui contiennent le terme spécifié. |
| Fuzzy_term_query | Documents qui contiennent le terme spécifié à une distance de Levenshtein. |
| phrase_query | Documents qui contiennent la séquence spécifiée de termes. |
| regex_query | Documents qui contiennent un terme qui correspond à l'expansion spécifiée. |
| prefix_query | Documents qui contiennent un terme avec le préfixe spécifié. |
| range_query | Documente qui avec un champ integer , double ou date dans la plage spécifiée. |
| FACET_QUERY | Documents appartenant à la catégorie spécifiée. |
| smart_query | Une combinaison de term_query , fuzzy_term_query et prefix_query . |
Jetez un œil au fichier de signatures pour voir quels paramètres les requêtes acceptent.
Toutes les requêtes peuvent rechercher sur des champs multiples (sauf pour facet_query car il n'a pas de sens là-bas).
Ainsi, la requête suivante:
index . term_query ( %i[ title description ] , "hello" )Équivaut à:
index . term_query ( :title , "hello" ) | index . term_query ( :description , "hello" ) Toutes les requêtes prennent en charge le paramètre boost qui permet de bosser la position des documents dans la recherche:
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 La recherche smart_query extraire les termes de votre chaîne de requête en utilisant les tokenisers de champ respectifs et recherchera l'index des documents contenant ces termes via le term_query . Si le paramètre fuzzy_distance est spécifié, il utilisera le fuzzy_term_query . En outre, il permet au dernier terme d'être inachevé en utilisant le prefix_query .
Ainsi, la requête suivante:
index . smart_query ( %i[ en_text ru_text ] , "dollars рубли eur" , fuzzy_distance : 1 )Équivaut à:
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 ) ) Remarquez comment les mots "dollars" et "рубли" sont tirés différemment selon le terrain que nous recherchons. Cela suppose que nous avons des champs en_text et ru_text dans notre schéma qui utilisent respectivement l'anglais et le russe STEMMER.
regex_query Le regex_query accepte le motif regex, mais il doit être un regex rouille, pas un Regexp rubis. Ainsi, au lieu index.regex_query(:description, /hel[lp]/) vous devez utiliser index.regex_query(:description, "hel[lp]") . En tant que note latérale, le regex_query est assez rapide car il utilise la caisse FST en interne.
Donc, nous avons déjà mentionné les tokenisers plus d'une fois. Quels sont-ils?
Les tokenisers sont ce que Tantivy utilise pour hacher votre texte en termes pour créer un index inversé. Ensuite, vous pouvez rechercher l'index par ces termes. C'est un concept important à comprendre afin que vous ne soyez pas confus lorsque index.term_query(:description, "Hello") ne renvoie rien parce que Hello n'est pas un terme, mais hello l'est. Vous devez extraire les termes de la requête avant de rechercher l'index. Actuellement, seul smart_query le fait pour vous. De plus, le seul type de champ qui est tokenisé est text , donc pour les champs string , vous devez utiliser la correspondance exacte (c'est-à-dire index.term_query(:title, "Hello") ).
Par défaut, le tokenzer simple est utilisé, mais vous pouvez spécifier le jeton souhaité globalement via des options d'index ou localement via des options spécifiques au champ:
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
endLe jetons simples coupe le texte sur la ponctuation et les espaces blancs, supprime les jetons longs et les cas inférieurs du texte.
tokenizer = Tantiny :: Tokenizer . new ( :simple )
tokenizer . terms ( "Hello World!" ) # ["hello", "world"]Les tokeniseurs STEMMER sont exactement comme un simple jetons, mais avec des tiges supplémentaires selon la langue spécifiée (par défaut en anglais).
tokenizer = Tantiny :: Tokenizer . new ( :stemmer , language : :ru )
tokenizer . terms ( "Привет миру сему!" ) # ["привет", "мир", "сем"]Jetez un œil à la source pour voir quelles langues sont prises en charge.
Le tokenzer NGRAM coupe votre texte sur des ngrams de taille spécifiée.
tokenizer = Tantiny :: Tokenizer . new ( :ngram , min : 5 , max : 10 , prefix_only : true )
tokenizer . terms ( "Morrowind" ) # ["Morro", "Morrow", "Morrowi", "Morrowin", "Morrowind"] Vous avez peut-être remarqué que la méthode search ne renvoie que les ID de documents. C'est par conception. Les documents eux-mêmes ne sont pas stockés dans l'index. Tantiny est une bibliothèque minimaliste, il essaie donc de garder les choses simples. Si vous avez besoin de récupérer un document complet, utilisez un magasin de valeurs clés comme Redis à côté.
Après avoir vérifié le dépôt, exécutez bin/setup pour installer des dépendances. Ensuite, exécutez rake build pour construire des extensions natives, puis rake spec pour exécuter les tests. Vous pouvez également exécuter bin/console pour une invite interactive qui vous permettra d'expérimenter.
Nous utilisons des validations conventionnelles pour générer automatiquement le Changelog, baisser la version sémantique et publier et publier le GEM. Tout ce que vous avez à faire est de vous en tenir à la convention et CI s'occupera de tout le reste pour vous.
Les rapports de bogues et les demandes de traction sont les bienvenus sur GitHub sur https://github.com/baygeldin/tantiny.
Le GEM est disponible en open source en vertu des termes de la licence du MIT.