Advertencia
La gema no se mantiene actualmente y el desarrollo se suspende. Si está interesado en hacerse cargo, no dude en comunicarse conmigo.
¿Necesita una búsqueda rápida de texto completo para su script Ruby, pero Solr y Elasticsearch son una exageración? ?
Estás en el lugar correcto. Tantiny es una biblioteca minimalista de búsqueda de texto completo para Ruby basada en Tanti V y (una alternativa increíble a Apache Lucene escrita en Rust). Es excelente para los casos cuando su tarea en cuestión requiere una búsqueda de texto completo, pero configurar un motor de búsqueda distribuido en toda regla tomaría más tiempo que la tarea en sí. E incluso si ya usa un motor de este tipo en su proyecto (lo cual es muy probable, en realidad), aún podría ser más fácil usar Tantiny porque, a diferencia de Solr y Elasticsearch, no necesita nada para funcionar (sin servidor o proceso separado o lo que sea), es puramente integrable. Por lo tanto, cuando se encuentre en una situación, cuando use su motor de búsqueda de elección, sería complicado/inconvincente o requeriría una configuración adicional, siempre puede volver a una solución rápida y sucia que no sea flexible y rápida.
Tantiny no es exactamente las ataduras de Ruby a Tantivy, pero trata de estar cerca. La filosofía principal es proporcionar acceso de bajo nivel al índice invertido de Tantivy, pero con una bonita API al estilo rubí, valores predeterminados sensibles y funcionalidad adicional rociada en la parte superior.
Eche un vistazo al ejemplo más básico:
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 Agregue esta línea al archivo gem de su aplicación:
gem "tantiny"Y luego ejecutar:
$ bundle install
O instálelo usted mismo como:
$ gem install tantiny
No es necesario que estén instalados con óxido en su sistema, ya que Tantiny intentará descargar los binarios precompilados alojados en las versiones de GitHub durante la instalación. Sin embargo, si no se encontraron binarios precompilados para su sistema (que es una combinación de plataforma, arquitectura y versión de Ruby), primero deberá instalar Rust.
Advertencia
Solo son compatibles con las versiones de óxido de hasta 1.77 . Vea este tema para más detalles.
Importante
Por favor, asegúrese de especificar la versión menor al declarar la dependencia de tantiny . La API está sujeta a cambios, y hasta que alcance 1.0.0 un aumento en la versión menor probablemente significará un cambio de ruptura.
Debe especificar una ruta hacia donde se almacenará el índice y un bloque que define el esquema:
Tantiny :: Index . new "/tmp/index" do
id :imdb_id
facet :category
string :title
text :description
integer :duration
double :rating
date :release_date
endAquí están las descripciones para cada tipo de campo:
| Tipo | Descripción |
|---|---|
| identificación | Especifica dónde se almacenan los ID de documentos (predeterminados a :id ). |
| faceta | Campos con valores como /animals/birds (es decir, categorías jerárquicas). |
| cadena | Campos con texto que no se tokenizan. |
| texto | Campos con texto que son tokenizados por el tokenizador especificado. |
| entero | Campos con valores enteros. |
| doble | Campos con valores flotantes. |
| fecha | Campos con tipo de DateTime o algo que se convierta en él. |
Puede alimentar el índice de cualquier tipo de objeto que tenga métodos especificados en su esquema, pero los hashes simples también funcionan:
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 << brotherPara actualizar el documento, simplemente agréguelo nuevamente (siempre que la ID sea la misma):
rio_bravo . rating = 10.0
index << rio_bravoTambién puedes eliminarlo si quieres:
index . delete ( rio_bravo . imdb_id ) Si necesita realizar múltiples operaciones de escritura (es decir, más de una), siempre debe usar transaction :
index . transaction do
index << rio_bravo
index << hanabi
index << brother
end El grupo de transacciones cambia y comprométalos con el índice de una vez. Esto es dramáticamente más eficiente que realizar estos cambios uno por uno. De hecho, todas las operaciones de escritura (es decir << y delete ) están envueltas en una transacción implícitamente cuando las llamas fuera de una transacción, por lo que llamar a << 10 veces fuera de una transacción es lo mismo que realizar 10 transacciones separadas.
Tantiny es a prueba de subprocesos, lo que significa que puede compartir de manera segura una sola instancia del índice entre hilos. También puede generar procesos separados que podrían escribir y leer desde el mismo índice. Sin embargo, si bien leer del índice debe ser paralelo, escribirlo no lo es. Siempre que llame a transaction o cualquier otra operación que modifique el índice (es decir << y delete ) bloqueará el índice durante la operación o espere que otro proceso o subproceso libere el bloqueo. La única excepción a esto es cuando hay otro proceso con un índice con un escritor exclusivo que se ejecuta en algún lugar en cuyo caso los métodos que modifican el índice fallarán de inmediato.
Por lo tanto, es mejor tener un proceso de escritor único y muchos procesos de lectores si desea evitar bloquear las llamadas. La forma adecuada de hacerlo es establecer exclusive_writer en true al inicializar el índice:
index = Tantiny :: Index . new ( "/path/to/index" , exclusive_writer : true ) { }De esta manera, el escritor índice solo se adquirirá una vez, lo que significa que la memoria para él y los hilos de indexación solo se asignarán una vez también. De lo contrario, se adquiere un nuevo escritor índice cada vez que realiza una operación de escritura.
Asegúrese de que su índice esté actualizado al recargarlo primero:
index . reloadY búscalo (¡finalmente!):
index . search ( "a drunk, a kid, and an old man" )Por defecto, devolverá ID de 10 mejores documentos coincidentes, pero puede personalizarlo:
index . search ( "a drunk, a kid, and an old man" , limit : 100 ) Puede preguntarse, ¿cómo realiza exactamente la búsqueda? Bueno, el comportamiento predeterminado es usar smart_query Search (ver a continuación para más detalles) sobre todos los campos text definidos en su esquema. Por lo tanto, puede pasar los parámetros que la smart_query acepta aquí:
index . search ( "a dlunk, a kib, and an olt mab" , fuzzy_distance : 1 )Sin embargo, puede personalizarlo componiendo su propia consulta fuera de los bloques de construcción básicos:
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 )Lo sé, sabor extraño! Pero bastante genial, ¿eh? Eche un vistazo a todas las consultas disponibles a continuación.
| Consulta | Comportamiento |
|---|---|
| All_Query | Devuelve todos los documentos indexados. |
| vacía_query | No devuelve exactamente nada (utilizado internamente). |
| Term_query | Documentos que contienen el término especificado. |
| fuzzy_term_query | Documentos que contienen el término especificado dentro de una distancia de Levenshtein. |
| frase_query | Documentos que contienen la secuencia de términos especificada. |
| regex_query | Documentos que contienen un término que coincida con el Regex especificado. |
| prefijo_query | Documentos que contienen un término con el prefijo especificado. |
| range_query | Documenta que con un campo integer , double o de date dentro del rango especificado. |
| facet_query | Documentos que pertenecen a la categoría especificada. |
| smart_query | Una combinación de term_query , fuzzy_term_query y prefix_query . |
Eche un vistazo al archivo de firmas para ver qué parámetros aceptan consultas.
Todas las consultas pueden buscar en los campos múltiples (a excepción de facet_query porque no tiene sentido allí).
Entonces, la siguiente consulta:
index . term_query ( %i[ title description ] , "hello" )Es equivalente a:
index . term_query ( :title , "hello" ) | index . term_query ( :description , "hello" ) Todas las consultas admiten el parámetro boost que permite aumentar la posición de los documentos en la búsqueda:
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 búsqueda smart_query extraerá los términos de su cadena de consulta utilizando los tokenizadores de campo respectivos y buscará en el índice los documentos que contienen esos términos a través de term_query . Si se especifica el parámetro fuzzy_distance , usará el fuzzy_term_query . Además, permite que el último término no esté terminado utilizando el prefix_query .
Entonces, la siguiente consulta:
index . smart_query ( %i[ en_text ru_text ] , "dollars рубли eur" , fuzzy_distance : 1 )Es equivalente a:
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 ) ) Observe cómo las palabras "dólares" y "рубли" se detienen de manera diferente dependiendo del campo que estemos buscando. Esto supone que tenemos campos en_text y ru_text en nuestro esquema que usan tokenizadores de Stemmer en inglés y ruso respectivamente.
regex_query El regex_query acepta el patrón Regex, pero tiene que ser un Rust Regex, no un Ruby Regexp . Entonces, en lugar de index.regex_query(:description, /hel[lp]/) debe usar index.regex_query(:description, "hel[lp]") . Como nota al margen, el regex_query es bastante rápido porque usa la caja FST internamente.
Entonces, hemos mencionado tokenizers más de una vez. ¿Qué son?
Tokenizers es lo que Tantivy usa para cortar su texto en términos para construir un índice invertido. Luego puede buscar el índice por estos términos. Es un concepto importante para entender para que no se confunda cuando index.term_query(:description, "Hello") no devuelve nada porque Hello no es un término, pero hello lo es. Debe extraer los términos de la consulta antes de buscar el índice. Actualmente, solo smart_query hace eso por usted. Además, el único tipo de campo que se tokenizado es text , por lo que para los campos string debe usar la coincidencia exacta (es decir index.term_query(:title, "Hello") ).
Por defecto, se usa el tokenizador simple , pero puede especificar el tokenizador deseado a nivel mundial a través de opciones de índice o localmente a través de opciones específicas de campo:
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
endEl tokenizador simple corta el texto sobre puntuación y espacios en blanco, elimina las tokens largos y baja el texto.
tokenizer = Tantiny :: Tokenizer . new ( :simple )
tokenizer . terms ( "Hello World!" ) # ["hello", "world"]Stemmer Tokenizers es exactamente como Tokenizer simple, pero con una votación adicional de acuerdo con el idioma especificado (predeterminado al inglés).
tokenizer = Tantiny :: Tokenizer . new ( :stemmer , language : :ru )
tokenizer . terms ( "Привет миру сему!" ) # ["привет", "мир", "сем"]Eche un vistazo a la fuente para ver qué idiomas son compatibles.
Tokenizer NGRAM corta su texto en NGRAM de tamaño especificado.
tokenizer = Tantiny :: Tokenizer . new ( :ngram , min : 5 , max : 10 , prefix_only : true )
tokenizer . terms ( "Morrowind" ) # ["Morro", "Morrow", "Morrowi", "Morrowin", "Morrowind"] Es posible que haya notado que el método search devuelve solo los ID de documentos. Esto es por diseño. Los documentos en sí no se almacenan en el índice. Tantiny es una biblioteca minimalista, por lo que trata de mantener las cosas simples. Si necesita recuperar un documento completo, use una tienda de valor clave como Redis junto.
Después de revisar el repositorio, ejecute bin/setup para instalar dependencias. Luego, ejecute rake build para construir extensiones nativas y luego rake spec para ejecutar las pruebas. También puede ejecutar bin/console para un mensaje interactivo que le permitirá experimentar.
Utilizamos confirmaciones convencionales para generar automáticamente el cambio de cambios, aumentar la versión semántica y publicar y lanzar la gema. Todo lo que necesita hacer es seguir la convención y CI se encargará de todo lo demás por usted.
Los informes de errores y las solicitudes de extracción son bienvenidas en GitHub en https://github.com/baygeldin/tantiny.
La gema está disponible como código abierto bajo los términos de la licencia MIT.