Go/Golang は私のお気に入りの言語の 1 つです。私はミニマリズムとそのクリーンさが大好きです。構文的には非常にコンパクトで、物事をシンプルに保つために非常に努力しています (私は KISS 原則の大ファンです)。
私が最近直面した大きな課題の 1 つは、高速な検索エンジンを構築することです。確かに、SOLR や ElasticSearch などのオプションはあります。どちらも非常にうまく機能し、スケーラビリティが非常に高いですが、依存関係をほとんどまたはまったく持たずにデプロイをより速く簡単に行うことで、検索を簡素化する必要がありました。
再ランク付けできるように、結果をすぐに返すには十分な最適化が必要でした。 C/Rust はこれに適しているかもしれませんが、私は開発スピードと生産性を重視しています。 Golang は両方の長所を備えたものだと思います。
この記事では、Go を使用して独自の検索エンジンを構築する方法の簡単な例を説明します。驚くでしょう。思っているほど複雑ではありません。
Golang: Python の強化
理由はわかりませんが、Golang はある意味 Python に似ているように感じます。構文は非常に理解しやすいです。おそらく、随所にセミコロンや括弧がないこと、または見苦しい try-catch ステートメントがないことが挙げられます。おそらくそれは素晴らしい Go フォーマッタかもしれませんが、わかりません。
とにかく、Golang は単一の自己完結型バイナリを生成するため、運用サーバーにデプロイするのは非常に簡単です。 「ビルドに行って」実行可能ファイルを交換するだけです。
まさに私が必要としていたものです。
ブルベですか?
いいえ、それはタイプミスではありませんか? Bleve は、強力で使いやすく、非常に柔軟な Golang 用の検索ライブラリです。
Go 開発者は通常、サードパーティのパッケージを疫病のように避けます。サードパーティのパッケージを使用することが合理的な場合もあります。 Bleve は高速で、適切に設計されており、使用を正当化するのに十分な価値を提供します。
さらに、私が「Bleve」した理由は次のとおりです:
Golang の大きな利点の 1 つは自己完結型で、バイナリが 1 つだけであることです。そのため、その感覚を維持し、ドキュメントの保存とクエリに外部 DB やサービスを必要としないようにしたいと考えました。 Bleve はメモリ内で実行され、Sqlite と同様にディスクに書き込みます。
簡単に拡張できます。これは単なる Go コードなので、必要に応じてライブラリを簡単に調整したり、コードベースで拡張したりできます。
高速: 1,000 万件のドキュメントにわたる検索結果は、フィルタリングを含めてわずか 50 ~ 100 ミリ秒しかかかりません。
ファセット: ある程度のレベルのファセット サポートがなければ、最新の検索エンジンを構築することはできません。 Bleve は、範囲や単純なカテゴリ数などの一般的なファセット タイプを完全にサポートしています。
高速インデックス作成: Bleve は SOLR よりも若干遅いです。 SOLR は 30 分で 1,000 万件のドキュメントのインデックスを作成できますが、Bleve では 1 時間以上かかりますが、1 時間程度でも私のニーズには十分であり、十分な速さです。
高品質の結果。 Bleve はキーワード結果でうまく機能しますが、一部のセマンティック タイプの検索も Bleve で非常にうまく機能します。
高速スタートアップ: 再起動またはアップデートの展開が必要な場合、Bleve を再起動するのにかかる時間はわずか数ミリ秒です。メモリ内でインデックスを再構築するための読み取りのブロックがないため、再起動後わずか数ミリ秒で問題なくインデックスを検索できます。
インデックスを設定しますか?
Bleve では、「インデックス」はデータベース テーブルまたはコレクション (NoSQL) と考えることができます。通常の SQL テーブルとは異なり、すべての単一列を指定する必要はありません。基本的に、ほとんどのユースケースではデフォルトのスキーマを使用できます。
Bleve インデックスを初期化するには、次の手順を実行できます:
mappings := bleve.NewIndexMapping() index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil) if err != nil { log.Fatal(err) }
Bleve はいくつかの異なるインデックス タイプをサポートしていますが、いろいろ試した結果、「scorch」インデックス タイプが最高のパフォーマンスを提供することがわかりました。最後の 3 つの引数を渡さない場合、Bleve はデフォルトで BoltDB を使用します。
ドキュメントの追加
Bleve へのドキュメントの追加は簡単です。基本的に、インデックスには任意のタイプの構造体を保存できます:
type Book struct { ID int `json:"id"` Name string `json:"name"` Genre string `json:"genre"` } b := Book{ ID: 1234, Name: "Some creative title", Genre: "Young Adult", } idStr := fmt.Sprintf("%d", b.ID) // index(string, interface{}) index.index(idStr, b)
大量のドキュメントのインデックスを作成する場合は、バッチ処理を使用することをお勧めします。
// You would also want to check if the batch exists already // - so that you don't recreate it. batch := index.NewBatch() if batch.Size() >= 1000 { err := index.Batch(batch) if err != nil { // failed, try again or log etc... } batch = index.NewBatch() } else { batch.index(idStr, b) }
お気づきのとおり、レコードをバッチ処理してインデックスに書き込むなどの複雑なタスクは、ドキュメントのインデックスを一時的に作成するコンテナを作成する「index.NewBatch」を使用して簡素化されます。
その後はループしながらサイズを確認し、バッチ サイズの制限に達したらインデックスをフラッシュします。
インデックスを検索する
Bleve は、検索ニーズに応じて選択できる複数の異なる検索クエリ パーサーを公開しています。この記事を短くわかりやすくするために、標準のクエリ文字列パーサーを使用することにします。
searchParser := bleve.NewQueryStringQuery("chicken reciepe books") maxPerPage := 50 ofsset := 0 searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false) // By default bleve returns just the ID, here we specify // - all the other fields we would like to return. searchRequest.Fields = []string{"id", "name", "genre"} searchResults, err := index.Search(searchResult)
これらの数行だけで、メモリとリソースの使用量を抑えながら良好な結果を提供する強力な検索エンジンが完成します。
これは検索結果の JSON 表現です。「ヒット」には一致するドキュメントが含まれます。
{ "status": { "total": 5, "failed": 0, "successful": 5 }, "request": {}, "hits": [], "total_hits": 19749, "max_score": 2.221337297308545, "took": 99039137, "facets": null }
ファセット加工
前述したように、Bleve は、スキーマで設定することなく、すぐに使える完全なファセット サポートを提供します。たとえば、書籍「ジャンル」をファセットするには、次の操作を行うことができます:
//... build searchRequest -- see previous section. // Add facets genreFacet := bleve.NewFacetRequest("genre", 50) searchRequest.AddFacet("genre", genreFacet) searchResults, err := index.Search(searchResult)
先ほどの searchRequest をわずか 2 行のコードで拡張します。 「NewFacetRequest」は 2 つの引数を受け取ります:
フィールド: ファセットのインデックス内のフィールド (文字列)。
サイズ: カウントするエントリの数 (整数)。したがって、この例では、最初の 50 ジャンルのみがカウントされます。
上記により、検索結果の「ファセット」が埋められます。
次に、ファセットを検索リクエストに追加するだけです。これは「ファセット名」と実際のファセットを受け取ります。 「ファセット名」は、検索結果でこの結果セットが表示される「キー」です。
高度なクエリとフィルタリング
「QueryStringQuery」パーサーを使用すると、かなりのメリットが得られます。場合によっては、「1 つが一致する必要がある」など、複数のフィールドに対して検索語を照合し、少なくとも 1 つのフィールドが一致する限り結果を返したい場合など、より複雑なクエリが必要になることがあります。
これを実現するには、「論理和」および「結合」クエリ タイプを使用できます。
結合クエリ: 基本的に、複数のクエリを連鎖させて 1 つの巨大なクエリを形成できます。すべての子クエリは少なくとも 1 つのドキュメントと一致する必要があります。
論理和クエリ: これにより、前述の「1 つが一致する必要がある」クエリを実行できるようになります。 x 個のクエリを渡し、少なくとも 1 つのドキュメントと一致する必要がある子クエリの数を設定できます。
論理和クエリの例:
mappings := bleve.NewIndexMapping() index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil) if err != nil { log.Fatal(err) }
前に「searchParser」を使用した方法と同様に、「disjunction Query」を「searchRequest」のコンストラクターに渡すことができます。
まったく同じではありませんが、これは次の SQL に似ています:
type Book struct { ID int `json:"id"` Name string `json:"name"` Genre string `json:"genre"` } b := Book{ ID: 1234, Name: "Some creative title", Genre: "Young Adult", } idStr := fmt.Sprintf("%d", b.ID) // index(string, interface{}) index.index(idStr, b)
「query.Fuzziness=[0 or 1 or 2]」を設定することで、検索のあいまいさを調整することもできます
接続クエリの例:
// You would also want to check if the batch exists already // - so that you don't recreate it. batch := index.NewBatch() if batch.Size() >= 1000 { err := index.Batch(batch) if err != nil { // failed, try again or log etc... } batch = index.NewBatch() } else { batch.index(idStr, b) }
構文が非常に似ていることがわかります。基本的に、「結合」クエリと「論理和」クエリを同じ意味で使用できます。
これは SQL では次のようになります:
searchParser := bleve.NewQueryStringQuery("chicken reciepe books") maxPerPage := 50 ofsset := 0 searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false) // By default bleve returns just the ID, here we specify // - all the other fields we would like to return. searchRequest.Fields = []string{"id", "name", "genre"} searchResults, err := index.Search(searchResult)
要約すると、すべての子クエリを少なくとも 1 つのドキュメントと一致させたい場合は「結合クエリ」を使用し、少なくとも 1 つの子クエリと一致させる必要があるが、必ずしもすべての子クエリを一致させる必要はない場合は「分離クエリ」を使用します。
シャーディング
速度の問題が発生した場合、Bleve を使用すると、複数のインデックス シャードにデータを分散し、1 つのリクエストでそれらのシャードをクエリすることもできます。次に例を示します。
{ "status": { "total": 5, "failed": 0, "successful": 5 }, "request": {}, "hits": [], "total_hits": 19749, "max_score": 2.221337297308545, "took": 99039137, "facets": null }
シャーディングは非常に複雑になる可能性がありますが、上で見たように、Bleve はすべてのインデックスを自動的に「マージ」し、インデックス全体を検索し、検索した場合と同じように 1 つの結果セットで結果を返すため、多くの手間が軽減されます。単一のインデックス。
私はシャーディングを使用して 100 個のシャードを検索しています。検索プロセス全体は平均してわずか 100 ~ 200 ミリ秒で完了します。
次のようにシャードを作成できます:
//... build searchRequest -- see previous section. // Add facets genreFacet := bleve.NewFacetRequest("genre", 50) searchRequest.AddFacet("genre", genreFacet) searchResults, err := index.Search(searchResult)
各ドキュメントに必ず一意の ID を作成するか、インデックスを台無しにすることなくドキュメントを追加および更新する予測可能な方法を用意してください。
これを行う簡単な方法は、シャード名を含むプレフィックスをソース DB、またはドキュメントの取得元に保存することです。そのため、挿入または更新しようとするたびに、「.index」を呼び出すシャードを示す「プレフィックス」を検索する必要があります。
更新について言えば、「index.index(idstr, struct)」を呼び出すだけで既存のドキュメントが更新されます。
結論
上記の基本的な検索テクニックを GIN または標準の Go HTTP サーバーの背後に配置するだけで、非常に強力な検索 API を構築し、複雑なインフラストラクチャを展開することなく何百万ものリクエストに対応できます。
ただし、注意点が 1 つあります。ただし、Bleve はレプリケーションを API でラップできるため、レプリケーションには対応していません。ソースから読み取り、ゴルーチンを使用してすべての Bleve サーバーに更新を「一斉送信」する cron ジョブを作成するだけです。
あるいは、ディスクへの書き込みを数秒間ロックしてから、データをスレーブ インデックスに「rsync」することもできます。ただし、毎回 go バイナリを再起動する必要があるため、そうすることはお勧めしません。 .
以上がBleve: ロケットのように高速な検索エンジンを構築するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

この記事では、プロファイリングの有効化、データの収集、CPUやメモリの問題などの一般的なボトルネックの識別など、GOパフォーマンスを分析するためにPPROFツールを使用する方法について説明します。

この記事では、GOでユニットテストを書くことで、ベストプラクティス、モッキングテクニック、効率的なテスト管理のためのツールについて説明します。

この記事では、ユニットテストのためにGOのモックとスタブを作成することを示しています。 インターフェイスの使用を強調し、模擬実装の例を提供し、模擬フォーカスを維持し、アサーションライブラリを使用するなどのベストプラクティスについて説明します。 articl

この記事では、GENICSのGOのカスタムタイプの制約について説明します。 インターフェイスがジェネリック関数の最小タイプ要件をどのように定義するかを詳しく説明し、タイプの安全性とコードの再利用性を改善します。 この記事では、制限とベストプラクティスについても説明しています

この記事では、コードのランタイム操作に使用されるGoの反射パッケージについて説明します。シリアル化、一般的なプログラミングなどに有益です。実行やメモリの使用量の増加、賢明な使用と最高のアドバイスなどのパフォーマンスコストについて警告します

この記事では、トレースツールを使用してGOアプリケーションの実行フローを分析します。 手動および自動計装技術について説明し、Jaeger、Zipkin、Opentelemetryなどのツールを比較し、効果的なデータの視覚化を強調しています

この記事では、GOでテーブル駆動型のテストを使用して説明します。これは、テストのテーブルを使用して複数の入力と結果を持つ関数をテストする方法です。読みやすさの向上、重複の減少、スケーラビリティ、一貫性、および

この記事では、go.modを介してGOモジュールの依存関係の管理、仕様、更新、競合解決をカバーすることについて説明します。セマンティックバージョンや定期的な更新などのベストプラクティスを強調しています。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

ホットトピック









