警告
宝石は現在維持されておらず、開発は保留されています。あなたが引き継ぐことに興味があるなら、私に連絡してください。
Rubyスクリプトの高速なテキスト検索が必要ですが、SolrとElasticsearchは過剰ですか? ?
あなたは正しい場所にいます。 Tantinyは、Tanti V Y(Rustで書かれたApache Luceneの素晴らしい代替品)に基づいたRubyの最小限のフルテキスト検索ライブラリです。手元のタスクがフルテキスト検索を必要とする場合に最適ですが、本格的な分散検索エンジンを構成するには、タスク自体よりも時間がかかります。また、プロジェクトでこのようなエンジンを既に使用している場合でも(実際には非常に可能性が高い)、SolrやElasticsearchとは異なり、機能する必要がない(個別のサーバーやプロセスなど)、純粋に組み込み可能であるため、Tantinyを使用する方が簡単かもしれません。したがって、選択した検索エンジンを使用することは難しい/不便な場合、または追加のセットアップが必要になる場合、状況に陥る場合、それでも柔軟で速い迅速で汚れたソリューションに戻ることができます。
TantinyはTantivyへのRuby Bindingsではありませんが、近くになろうとします。主な哲学は、Tantivyの倒立インデックスへの低レベルのアクセスを提供することですが、Ruby風の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バージョンの組み合わせです)、最初にRustをインストールする必要があります。
警告
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 | ドキュメントのIDが保存される場所を指定します(デフォルトは次のとおりです: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ドキュメントを更新するために、それをもう一度追加するだけです(IDが同じである限り):
rio_bravo . rating = 10.0
index << rio_bravo必要に応じて削除することもできます。
index . delete ( rio_bravo . imdb_id )複数のライティング操作を実行する必要がある場合(つまり、1つ以上)、常にtransactionを使用する必要があります。
index . transaction do
index << rio_bravo
index << hanabi
index << brother
endトランザクショングループの変更とそれらを一度にインデックスにコミットします。これは、これらの変更を1つずつ実行するよりも劇的に効率的です。実際、すべてのライティング操作(IE <<およびdelete )は、トランザクションの外部でそれらを呼び出すと暗黙的にトランザクションにラップされているため、トランザクション以外で<< 10を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の最高のマッチングドキュメントのIDを返しますが、カスタマイズできます。
index . search ( "a drunk, a kid, and an old man" , limit : 100 )あなたは疑問に思うかもしれません、それはどのように正確に検索を行うのですか?さて、デフォルトの動作は、スキーマで定義されているすべてのtextフィールドでsmart_query検索(詳細については以下を参照)を使用することです。したがって、 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 | Levenshtein距離内に指定された用語を含むドキュメント。 |
| phrase_query | 指定された一連の用語を含むドキュメント。 |
| regex_query | 指定されたregexと一致する用語を含むドキュメント。 |
| prefix_query | 指定されたプレフィックスを使用した用語を含むドキュメント。 |
| range_query | 指定された範囲内のinteger 、 double 、またはdateフィールドを使用した文書。 |
| FACET_QUERY | 指定されたカテゴリに属するドキュメント。 |
| Smart_Query | term_query 、 fuzzy_term_query 、 prefix_queryの組み合わせ。 |
署名ファイルを見て、クエリが受け入れるパラメーターを確認してください。
すべてのクエリは、マルチアップフィールドで検索できます(そこには意味がないため、 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動作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 ) )単語「ドル」と「 "豚の"は、検索しているフィールドに応じて異なる方法で抑制されていることに注意してください。これは、それぞれ英語とロシアのステムマートークナザーを使用するスキーマにen_textとru_textフィールドがあることを前提としています。
regex_queryについてregex_query Regexパターンを受け入れますが、Ruby Regexpではなく、錆のregexでなければなりません。したがって、 index.regex_query(:description, /hel[lp]/)の代わりに、 index.regex_query(:description, "hel[lp]")を使用する必要があります。サイドノートとして、 regex_query FSTクレートを内部で使用するため、かなり高速です。
そのため、トークンザーについてはすでに複数回言及しています。彼らは何ですか?
トークンザーは、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トークネザーは、指定されたサイズのngramsにテキストをチョップします。
tokenizer = Tantiny :: Tokenizer . new ( :ngram , min : 5 , max : 10 , prefix_only : true )
tokenizer . terms ( "Morrowind" ) # ["Morro", "Morrow", "Morrowi", "Morrowin", "Morrowind"] search方法はドキュメントIDのみを返すことに気付いたかもしれません。これは設計によるものです。ドキュメント自体はインデックスに保存されません。 Tantinyはミニマルなライブラリなので、物事をシンプルに保とうとします。完全なドキュメントを取得する必要がある場合は、Redisのようなキー価値ストアを使用してください。
リポジトリをチェックアウトした後、 bin/setupを実行して依存関係をインストールします。次に、 rake buildを実行してネイティブエクステンションを構築し、テストを実行するためにrake spec実行します。また、実験を可能にするインタラクティブなプロンプトのbin/consoleを実行することもできます。
従来のコミットを使用して、チェンジログを自動的に生成し、セマンティックバージョンをバンプし、GEMを公開およびリリースします。あなたがする必要があるのはコンベンションに固執することであり、CIはあなたのために他のすべての世話をします。
バグレポートとプルリクエストは、https://github.com/baygeldin/tantinyのGithubで大歓迎です。
GEMは、MITライセンスの条件の下でオープンソースとして利用できます。