Aviso
A jóia não é mantida no momento e o desenvolvimento é suspenso. Se você estiver interessado em assumir o controle, fique à vontade para entrar em contato comigo.
Precisa de uma pesquisa rápida de texto completo pelo seu script Ruby, mas o Solr e o Elasticsearch são um exagero? ?
Você está no lugar certo. Tantiny é uma biblioteca de pesquisa de texto completo minimalista para Ruby baseado em Tanti V Y (uma alternativa incrível ao Apache Lucene escrito em Rust). É ótimo para casos em que sua tarefa em mãos exige uma pesquisa de texto completo, mas a configuração de um mecanismo de pesquisa distribuído completo levaria mais tempo do que a própria tarefa. E mesmo que você já use esse mecanismo em seu projeto (o que é altamente provável, na verdade), ainda pode ser mais fácil usar o Tantiny, porque, diferentemente do Solr e do Elasticsearch, ele não precisa de nada para funcionar (nenhum servidor ou processo separado ou o que for), ele é puramente incorporável. Portanto, quando você se encontrar em uma situação ao usar seu mecanismo de pesquisa de escolha, seria complicado/inconveniente ou exigiria uma configuração adicional, você sempre pode voltar a uma solução rápida e suja que não é flexível e rápida.
Tantiny não é exatamente as ligações rubies a Tantivy, mas tenta estar perto. A filosofia principal é fornecer acesso de baixo nível ao índice invertido de Tantivy, mas com uma boa API do estilo Ruby, padrões sensíveis e funcionalidade adicional polvilhada no topo.
Dê uma olhada no exemplo mais 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 Adicione esta linha ao GemFile do seu aplicativo:
gem "tantiny"E depois execute:
$ bundle install
Ou instale você mesmo como:
$ gem install tantiny
Você não precisa instalar a ferrugem no seu sistema, pois a Tantiny tentará baixar os binários pré-compilados hospedados no GitHub Lankes durante a instalação. No entanto, se nenhum binário pré-compilado foi encontrado para o seu sistema (que é uma combinação de plataforma, arquitetura e versão Ruby), você precisará instalar a ferrugem primeiro.
Aviso
Somente versões de ferrugem de até 1.77 são suportadas. Veja este problema para obter mais detalhes.
Importante
Por favor, especifique a versão menor ao declarar dependência de tantiny . A API é um sujeito para alterações e, até atingir 1.0.0 , uma colisão na versão menor provavelmente significará uma mudança de quebra.
Você deve especificar um caminho para onde o índice seria armazenado e um bloco que define o esquema:
Tantiny :: Index . new "/tmp/index" do
id :imdb_id
facet :category
string :title
text :description
integer :duration
double :rating
date :release_date
endAqui estão as descrições para cada tipo de campo:
| Tipo | Descrição |
|---|---|
| eu ia | Especifica onde os IDs dos documentos são armazenados (padrão para :id ). |
| faceta | Campos com valores como /animals/birds (ou seja, categorias hierarquiais). |
| corda | Campos com texto que não são tokenizados. |
| texto | Campos com texto que são tokenizados pelo tokenizer especificado. |
| Inteiro | Campos com valores inteiros. |
| dobro | Campos com valores de flutuação. |
| data | Campos com tipo DateTime ou algo que se converte a ele. |
Você pode alimentar o índice qualquer tipo de objeto que tenha métodos especificados em seu esquema, mas os hashes simples também funcionam:
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 atualizar o documento, basta adicioná -lo novamente (desde que o ID seja o mesmo):
rio_bravo . rating = 10.0
index << rio_bravoVocê também pode excluí -lo se quiser:
index . delete ( rio_bravo . imdb_id ) Se você precisar executar várias operações de escrita (ou seja, mais de uma), você sempre deve usar transaction :
index . transaction do
index << rio_bravo
index << hanabi
index << brother
end O grupo de transações muda e as compromete com o índice de uma só vez. Isso é dramaticamente mais eficiente do que executar essas alterações uma por uma. De fato, todas as operações de escrita (ou seja << e delete ) são embrulhadas em uma transação implicitamente quando você as chama fora de uma transação; portanto, ligar << 10 vezes fora de uma transação é a mesma coisa que executar 10 transações separadas.
Tantiny é seguro para thread, o que significa que você pode compartilhar com segurança uma única instância do índice entre os threads. Você também pode gerar processos separados que podem escrever e ler no mesmo índice. No entanto, enquanto a leitura do índice deve ser paralela, a gravação de ele não é. Sempre que você ligar para transaction ou qualquer outra operação que modifique o índice (ou seja, << e delete ), ele bloqueará o índice durante a duração da operação ou aguarde que outro processo ou encadeamento libere o bloqueio. A única exceção a isso é quando há outro processo com um índice com um escritor exclusivo em algum lugar em algum lugar, os métodos que modificam o índice falharão imediatamente.
Portanto, é melhor ter um processo de escritor único e muitos processos de leitores, se você quiser evitar o bloqueio de chamadas. A maneira correta de fazer isso é definir exclusive_writer como true ao inicializar o índice:
index = Tantiny :: Index . new ( "/path/to/index" , exclusive_writer : true ) { }Dessa forma, o escritor de índices será adquirido apenas uma vez, o que significa que a memória e os threads de indexação também serão alocados apenas uma vez. Caso contrário, um novo escritor de índices é adquirido toda vez que você executa uma operação de escrita.
Certifique-se de que seu índice esteja atualizado recarregando-o primeiro:
index . reloadE pesquise (finalmente!):
index . search ( "a drunk, a kid, and an old man" )Por padrão, ele retornará IDs de 10 melhores documentos correspondentes, mas você pode personalizá -lo:
index . search ( "a drunk, a kid, and an old man" , limit : 100 ) Você pode se perguntar, como exatamente isso conduz a pesquisa? Bem, o comportamento padrão é usar a pesquisa smart_query (veja abaixo os detalhes) em todos os campos text definidos em seu esquema. Então, você pode passar os parâmetros que o smart_query aceita aqui:
index . search ( "a dlunk, a kib, and an olt mab" , fuzzy_distance : 1 )No entanto, você pode personalizá -lo compondo sua própria consulta a partir de blocos básicos de construção:
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 )Eu sei, sabor estranho! Mas muito legal, hein? Dê uma olhada em todas as consultas disponíveis abaixo.
| Consulta | Comportamento |
|---|---|
| All_Query | Retorna todos os documentos indexados. |
| vazio_query | Retorna exatamente nada (usado internamente). |
| Term_Query | Documentos que contêm o termo especificado. |
| Fuzzy_term_Query | Documentos que contêm o termo especificado a uma distância de Levenshtein. |
| phrase_query | Documentos que contêm a sequência especificada de termos. |
| regex_query | Documentos que contêm um termo que corresponde ao regex especificado. |
| prefix_query | Documentos que contêm um termo com o prefixo especificado. |
| range_query | Documenta que com um campo integer , double ou date dentro do intervalo especificado. |
| FACET_QUERY | Documentos que pertencem à categoria especificada. |
| Smart_Query | Uma combinação de term_query , fuzzy_term_query e prefix_query . |
Dê uma olhada no arquivo de assinaturas para ver quais parâmetros fazem consultas aceitam.
Todas as consultas podem pesquisar em campos multuple (exceto facet_query porque não faz sentido lá).
Então, a seguinte consulta:
index . term_query ( %i[ title description ] , "hello" )É equivalente a:
index . term_query ( :title , "hello" ) | index . term_query ( :description , "hello" ) Todas as consultas suportam o parâmetro boost que permite a posição de documentos na pesquisa:
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 A pesquisa smart_query extrairá termos da sua sequência de consulta usando os respectivos tokenizadores de campo e pesquisará o índice por documentos que contêm esses termos através do term_query . Se o parâmetro fuzzy_distance for especificado, ele usará o fuzzy_term_query . Além disso, ele permite que o último termo seja inacabado usando o prefix_query .
Então, a seguinte consulta:
index . smart_query ( %i[ en_text ru_text ] , "dollars рубли eur" , fuzzy_distance : 1 )É 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 como as palavras "dólares" e "рбли" são decorrentes de maneira diferente, dependendo do campo que estamos pesquisando. Isso supõe que temos campos en_text e ru_text em nosso esquema que usam tokenizadores de stemmer inglês e russo, respectivamente.
regex_query O regex_query aceita o padrão Regex, mas deve ser um regex de ferrugem, não um rubi Regexp . Portanto, em vez de index.regex_query(:description, /hel[lp]/) você precisa usar index.regex_query(:description, "hel[lp]") . Como nota lateral, o regex_query é muito rápido porque usa a caixa FST internamente.
Então, mencionamos os tokenizadores mais de uma vez. O que eles são?
Tokenizers é o que Tantivy usa para cortar seu texto em termos para criar um índice invertido. Então você pode pesquisar o índice por estes Termos. É um conceito importante para entender para que você não fique confuso quando index.term_query(:description, "Hello") não retorna nada porque Hello não é um termo, mas hello é. Você precisa extrair os termos da consulta antes de pesquisar no índice. Atualmente, apenas smart_query faz isso por você. Além disso, o único tipo de campo que é tokenizado é text ; portanto, para campos string , você deve usar a correspondência exata (ou seja index.term_query(:title, "Hello") ).
Por padrão, o tokenizador simple é usado, mas você pode especificar o tokenizador desejado globalmente por meio de opções de índice ou localmente por meio de opções 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
endO tokenizador simples corra o texto sobre pontuação e espaços em branco, remove os tokens longos e abaixa o texto.
tokenizer = Tantiny :: Tokenizer . new ( :simple )
tokenizer . terms ( "Hello World!" ) # ["hello", "world"]Os tokenizadores Stemmer é exatamente como o simples tokenizador, mas com um caule adicional de acordo com o idioma especificado (padrão para o inglês).
tokenizer = Tantiny :: Tokenizer . new ( :stemmer , language : :ru )
tokenizer . terms ( "Привет миру сему!" ) # ["привет", "мир", "сем"]Dê uma olhada na fonte para ver quais idiomas são suportados.
O Ngram Tokenizer choca seu texto em ngrams de tamanho especificado.
tokenizer = Tantiny :: Tokenizer . new ( :ngram , min : 5 , max : 10 , prefix_only : true )
tokenizer . terms ( "Morrowind" ) # ["Morro", "Morrow", "Morrowi", "Morrowin", "Morrowind"] Você deve ter notado que o método search retorna apenas documenta os IDs. Isso é por design. Os próprios documentos não são armazenados no índice. Tantiny é uma biblioteca minimalista, por isso tenta simplificar as coisas. Se você precisar recuperar um documento completo, use uma loja de valores-chave como o Redis ao lado.
Depois de verificar o repositório, execute bin/setup para instalar dependências. Em seguida, execute rake build para criar extensões nativas e, em seguida, rake spec para executar os testes. Você também pode executar bin/console para um prompt interativo que permitirá experimentar.
Utilizamos compromissos convencionais para gerar automaticamente o Changelog, aumentar a versão semântica e publicar e lançar o GEM. Tudo o que você precisa fazer é seguir a convenção e o CI cuidará de todo o resto para você.
Relatórios de bug e solicitações de puxar são bem -vindos no github em https://github.com/baygeldin/tantiny.
A GEM está disponível como código aberto nos termos da licença do MIT.