ホームページ  >  記事  >  バックエンド開発  >  Go で高性能の全文検索エンジンを構築する

Go で高性能の全文検索エンジンを構築する

Linda Hamilton
Linda Hamiltonオリジナル
2024-11-02 09:44:31906ブラウズ

1. はじめに

今日の世界では、膨大な量の情報が絶えず生成されており、関連データに効率的にアクセスすることが不可欠です。全文検索エンジンは、テキスト コンテンツにインデックスを付けることで高速なデータ取得を可能にし、検索エンジンからデータ分析ツールに至るまでのアプリケーションのバックボーンを形成します。膨大なデータセットが関係するため、検索エンジンには、最適なパフォーマンスを得るためにインデックス作成とクエリに対する洗練されたアプローチが必要です。

このブログでは、データ ストリーミング、マルチスレッド、効率的なインデックス構造などの高度な概念に焦点を当てて、Go を使用して全文検索エンジンを構築する手順を説明します。大規模なデータセット (特に Wikipedia の要約) をメモリ効率の高い方法で処理および検索する方法を説明します。このガイドに従うことで、Go の同時実行モデルの活用と、高パフォーマンス アプリケーションへの適合性についての洞察が得られます。


2. テクノロジースタック

このプロジェクトのテクノロジー スタックには、主要なプログラミング言語として Go が含まれており、その単純な構文、堅牢な標準ライブラリ、およびネイティブ同時実行サポートにより選択されました。重要なツールとライブラリの内訳は次のとおりです:

  • プログラミング言語: Go (Golang)

    • Go は、パフォーマンスを犠牲にすることなく複数のタスクを管理するツールを備えた、同時アプリケーションのための効率的な環境を提供します。
  • ライブラリ:

    • Gzip および XML 解析: これらは、Wikipedia の圧縮 XML データを処理するために不可欠です。標準のencoding/xmlライブラリとcompress/gzipライブラリにより、簡単な解析と解凍が可能になり、Goのエコシステムにうまく適合します。
    • Sync パッケージ: このコア Go パッケージは、ゴルーチンを調整するための sync.WaitGroup やデータ アクセスを処理するための sync.Mutex などの構造を使用して同時プロセスを管理するために使用されます。
    • kljensen/snowball: このライブラリはトークンのステミングを提供し、単語を基本形式に減らすことで検索の最適化を向上させます。
  • データ ソース:

    • このプロジェクトは、Wikipedia 記事の要約を含む圧縮 XML ファイルである Wikipedia 要約を利用します。このデータセットは多様であり、検索エンジンの機能の実用的なテストとして機能するのに十分な大きさです。ここからダウンロード

3. アイデアの根源

問題提起

データ量が増え続ける中、意味のある情報を効率的に取得することは大きな課題です。検索エンジンは、膨大なテキスト データセットを迅速に管理し、アクセスする必要があります。この問題は、転置インデックス、トークン化、データ正規化などの革新につながりました。

インスピレーションと研究

Elasticsearch のような人気のあるツールは、堅牢なインデックス作成および検索技術に基づいて構築された全文検索エンジンの能力を実証しています。このプロジェクトは、これらの業界標準エンジンに触発されて、同様のソリューションを Go に実装することを目指しています。 Go のシンプルさ、パフォーマンス、同時実行機能はこのタスクに適しており、主要な検索エンジンで使用されている概念を調査し、カスタム実装に合わせて調整する機能を提供します。

対象ユーザー

このプロジェクトは、検索エンジンが内部でどのように機能するかを理解することに興味がある人だけでなく、Go の同時実行モデルを探索したい開発者や愛好家向けに設計されています。実践的な経験を提供することで、特にバックエンドやフルスタックの開発に興味がある人にとって、リアルタイムのインデックス作成や検索などの集中的なタスクを Go がどのように処理できるかを理解する機会となります。


4. このプロジェクトを構築する理由

実践的な学習

このプロジェクトでは、Go でのストリーミングとマルチスレッドをマスターするための実践的なアプローチを提供するとともに、全文検索エンジンがどのように機能するかを深く掘り下げます。これにより、インデックス作成、トークン化、ドキュメント処理の実験が可能になり、検索エンジンの内部構造を包括的に理解できるようになります。

囲碁の効率化

Go を使用すると、その高い同時実行効率を探索できます。 Go は、複数のタスクを並行して実行する必要があるアプリケーションの構築に適しており、このプロジェクトのパフォーマンス重視の目標にとって理想的な言語となっています。

囲碁のスキルを向上させる

このプロジェクトでは、クラウドネイティブでスケーラブルなアプリケーションで広く使用されている言語である Go の高度なスキルを構築します。マルチスレッドおよび同時実行ソリューションの実装について説明すると同時に、需要の高いアプリケーションでのメモリとパフォーマンスの管理に対する Go の独自のアプローチを強調します。


5. 作業プロセスと主要な概念

ワークフローの概要

エンジンは、複数のステージを含む構造化されたワークフローに従います。

  1. ドキュメントのロード: ドキュメントはストリーミング形式で XML ファイルからロードおよび解凍され、メモリ使用量が最小限に抑えられます。
  2. トークン化とテキスト処理: 各ドキュメントはトークン化され、テキストは小文字への変換、ストップワードの削除、ステミングの適用によって正規化されます。
  3. インデックスの構築: 処理されたトークンは逆インデックスに保存され、各トークンをそれを含むドキュメント ID にマッピングします。
  4. インデックスの保存/読み込み: 最終的なインデックスを保存してディスクから読み込み、将来のセッションのインデックス作成作業を保存し、検索エンジンの初期化を高速化します。

Building a High-Performance Full-Text Search Engine in Go

データのストリーミングと処理

ストリーミングにより、データセット全体をメモリにロードせずに、ドキュメントを一度に 1 つずつ処理できます。 LoadDocuments 関数は、解凍と解析をリアルタイムで処理し、各ドキュメントをチャネルにフィードします。この設定により、システムはデータを順次処理することで大規模なデータセットを処理できるようになり、メモリの負担が軽減されます。

ドキュメント処理の同時実行性

ドキュメントの処理は、ドキュメントの解析、分析、インデックス作成を担当する複数のゴルーチンによって並行して行われます。この同時実行により、インデックス作成プロセスが大幅に高速化され、リアルタイムの検索更新が可能になります。


6. ストリーミングとマルチスレッドの簡単な紹介

Go でのストリーミング

定義と目的

ストリーミングは、データを一度にすべてロードするのではなく、利用可能になったときにデータを分割して処理する技術です。これは、メモリ制限によりデータセット全体を読み込むことが現実的でない大規模なデータセットの場合に特に役立ちます。

大規模なデータセットの利点

ストリーミングは、常にデータの一部のみを処理することでメモリを効率的に管理するのに役立ちます。これは、この検索エンジンにとって理想的です。システムは、すべての Wikipedia 要約を一度にロードする必要はありません。代わりに、各ドキュメントを定常的なフローで個別に処理します。

実装例

LoadDocuments 関数は、Go の encoding/xml ライブラリと compress/gzip ライブラリを使用して、ストリーミング方式でドキュメントをロードおよび解凍し、各ドキュメントを解析して処理チャネルに送信します。

Go でのマルチスレッド化

定義と中心概念

マルチスレッドによりコード セグメントの同時実行が可能になり、複数の操作を同時に実行することでアプリケーションのパフォーマンスが向上します。 Go のネイティブ同時実行モデルは、ゴルーチンとチャネルを使用して、マルチスレッドを実現する簡単な方法を提供します。

Go の同時実行性

Go の同時実行性は、複数の関数を同時に実行できる軽量のスレッドであるゴルーチンを使用して実現されます。チャネルによりゴルーチン間の通信が可能になり、複雑な同期を必要とせずにデータを安全に受け渡すことができます。

ここでの使用方法

この検索エンジンでは、複数のゴルーチンがドキュメントの処理とインデックス作成を同時に処理します。たとえば、AddStreamed 関数はドキュメントのチャネルから読み取り、各ドキュメントに同時にインデックスを作成するため、大規模なデータセット全体のインデックス作成を高速化できます。

課題と最適化

複数のスレッドを管理すると、複数のスレッドが共有リソースに同時にアクセスする競合状態などの問題が発生する可能性があります。 Go の同期パッケージと Mutex および WaitGroup は、データ アクセスを同期し、次のステップに進む前にタスクが確実に完了するようにすることで、これらの問題を回避します。


全文検索エンジンの機能と特徴

この全文検索エンジンは、Go の同時実行機能を利用して、パフォーマンスの高いインデックス作成および検索メカニズムを構築します。データ ストリーミングとマルチスレッドを使用することにより、アプリケーションはメモリに過負荷をかけることなく、Wikipedia の要約などの大規模なデータセットを効率的に処理します。このセクションでは、コードで使用される主な関数、特徴、主要なメソッドについて説明します。


1. 検索エンジンの主要機能

  • 効率的なインデックス作成: 逆索引を使用して、クエリ用語に一致するドキュメントを迅速に取得できます。
  • 同時処理: ドキュメントのインデックス作成と検索操作をマルチスレッド化し、高速でノンブロッキングな操作を可能にします。
  • メタデータ付きドキュメント ストレージ: インデックス付きコンテンツと一緒にメタデータ (タイトルや URL など) を保存し、ドキュメントの完全な詳細を取得できるようにします。
  • インデックスの永続性: インデックスはディスクに保存したりディスクからロードしたりできるため、セッション間で検索インデックスを再利用できます。
  • データのフィルタリングと正規化: ストップワードの削除、大文字と小文字の正規化、および検索トークンを標準化するためのステミングが含まれます。

2. 主要コンポーネントと機能

a.ドキュメントのロードとストリーミング

LoadDocuments 関数は、圧縮された XML ファイルからのドキュメントのロードを処理し、ストリームとして解凍して解析します。このアプローチはメモリ効率が高く、特に大規模なデータセットに役立ちます。

コード スニペット: LoadDocuments

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

こちら:

  • XML ファイルは外出先で解凍および解析されます。つまり、ファイル全体が一度にロードされるわけではありません。
  • ドキュメントはチャネル docChan にストリーミングされ、ロードされるとすぐに処理できるため、同時インデックス作成に最適です。

b.トークン化とテキスト分析

tokenizer.go ファイルには、トークン化、大文字と小文字の正規化、ストップワードの削除、ステミングを通じてテキストを正規化および標準化する関数が含まれています。

コードスニペット: 分析

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

この機能:

  • テキストを個々の単語またはトークンにトークン化します
  • トークンを小文字に変換して、大文字と小文字を区別しないようにします。
  • ストップワードを削除し、インデックス内の不要なデータを削減します。
  • トークンをルート形式にステムして、検索の一貫性を確保します (例: 「running」が「run」になります)。
c.転置インデックスの構築と管理

Index 構造体はコア データ構造であり、逆インデックスとドキュメント ストアを保持します。転置インデックスは、各トークン (単語) がその単語を含むドキュメント ID のリストにマッピングされるマップであり、効率的な検索を可能にします。

コード スニペット: インデックスへのドキュメントの追加

// analyze analyzes the text and returns a slice of tokens.
func analyze(text string) []string {
    tokens := tokenize(text)
    tokens = lowercaseFilter(tokens)
    tokens = stopwordFilter(tokens)
    tokens = stemmerFilter(tokens)
    return tokens
}
AddDocument 関数:

    同時書き込み中の競合状態を防ぐために、インデックスを
  • ロックします。
  • ドキュメントを ID ごとに docStore に保存し、ID による全文検索を可能にします。
  • 文書内の各トークンを処理し、その ID をトークンのリストに追加することで逆インデックスを構築し、高速な検索を保証します。
インデックスの保存と取得

インデックスの永続的な使用を可能にするために、index.go の Save メソッドと Load メソッドは、シリアル化と逆シリアル化に Go のエンコーディング/gob パッケージを使用します。


// AddDocument adds a single document to the index.
func (idx *Index) AddDocument(doc Document) {
    idx.mu.Lock()
    defer idx.mu.Unlock()

    idx.docStore[doc.ID] = doc
    for _, token := range analyze(doc.Text) {
        ids := idx.index[token]
        if ids != nil && ids[len(ids)-1] == doc.ID {
            continue
        }
        idx.index[token] = append(ids, doc.ID)
    }
}
d.ストリーミングによる同時ドキュメントインデックス作成

AddStreamed メソッドを使用すると、docChan からのドキュメントに同時にインデックスが付けられます。複数のゴルーチンがドキュメント追加プロセスを処理し、大規模なデータセットのインデックス作成を大幅に高速化します。

コード スニペット: AddStreamed

// Save serializes both the index and docStore to a file.
func (idx *Index) Save(filePath string) error {
    idx.mu.RLock()
    defer idx.mu.RUnlock()

    file, err := os.Create(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := gob.NewEncoder(file)
    if err := encoder.Encode(idx.index); err != nil {
        return err
    }
    if err := encoder.Encode(idx.docStore); err != nil {
        return err
    }

    return nil
}
この方法:

  • 複数の goroutines をスピンアップして、ドキュメントを並行して処理します。
  • WaitGroup を使用して、すべてのゴルーチンが完了するまで待機し、先に進む前にすべてのドキュメントが処理されていることを確認します。
e.文書を検索する

index.go の検索機能を使用すると、すべてのクエリ トークンを含むドキュメントを検索することで、検索クエリに一致するドキュメント ID を効率的に取得できます。

コード スニペット: 検索

// AddStreamed adds documents from a channel to the index concurrently.
func (idx *Index) AddStreamed(docChan <-chan Document) {
    var wg sync.WaitGroup
    numWorkers := 4 // Number of concurrent workers

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for doc := range docChan {
                idx.AddDocument(doc)
            }
        }()
    }
    wg.Wait()
}
検索機能:

  • クエリ テキストを分析して トークンにし、各トークンがインデックスに存在するかどうかを確認します。
  • 各トークンの ID の交差部分を検索し、クエリ内のすべての用語を含むドキュメントのみを返します。

検索結果の表示

PrintResultsTable メソッドは、読みやすいように、一致したドキュメント ID をタイトルとテキスト スニペットとともに書式設定して表示します。

// LoadDocuments loads documents from a gzip-compressed XML file and sends them through a channel.
func LoadDocuments(path string, docChan chan<- Document) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    gz, err := gzip.NewReader(f)
    if err != nil {
        return err
    }
    defer gz.Close()

    dec := xml.NewDecoder(gz)
    dump := struct {
        Documents []Document `xml:"doc"`
    }{}

    if err := dec.Decode(&dump); err != nil {
        return err
    }

    for i, doc := range dump.Documents {
        doc.ID = i
        docChan <- doc
    }
    return nil
}

このテーブル ビューには、一致する各ドキュメントのテキストのスニペットが含まれているため、結果を迅速に確認し、読みやすくするのに役立ちます。


7. 将来の範囲

この全文検索エンジンは、包括的な検索システムを構築するための強固な基盤ですが、さらに強力で機能豊富にするための機能強化がいくつかあります。

1.分散処理

  • 目標: ワークロードを複数のマシンに分散することで、さらに大量のデータを処理できるように検索エンジンを拡張します。
  • 実装: ドキュメントのインデックス作成とクエリをサーバー間で分散することで、検索エンジンはより多くのクエリとより大きなデータセットを処理できるようになります。 gRPC や HTTP/2 などのテクノロジーにより、分散ノード間の効率的な通信が促進される可能性があります。

2. 高度なクエリのサポート

  • 目標: ユーザーが演算子 (AND、OR、NOT など) と近接クエリを使用して、より高度な検索を実行できるようにします。
  • 実装: インデックス作成アルゴリズムを拡張して、完全一致フレーズやワイルドカード検索などの複雑なクエリをサポートし、検索の柔軟性を高めます。

3. リアルタイムのインデックス更新

  • 目標: 新しいドキュメントが追加されるとエンジンがインデックスを動的に更新できるようにします。
  • 実装: リアルタイムのインデックス作成機能により、完全なインデックスの再作成を必要とせずに新しいドキュメントを追加できるため、頻繁に更新されるコンテンツを処理するアプリケーションに最適です。

4. ランキングのための機械学習の統合

  • 目標: 機械学習モデルを組み込んで、ユーザーの行動と関連性に基づいてドキュメントをランク付けすることで、結果の関連性を向上させます。
  • 実装: 過去の検索データとユーザーの好みを分析することで、エンジンはより関連性の高いドキュメントを優先し、検索結果をより正確かつパーソナライズすることができます。

5. 自然言語処理 (NLP) の改善

  • 目標: NLP を使用してトークン化、ステミング、同義語のサポートを改善し、エンジンがユーザー クエリをより直感的に処理できるようにします。
  • 実装: NLP 技術を活用すると、同義語、複数形、コンテキストを考慮してクエリの一致が強化され、ユーザーの意図を解釈するエンジンの能力が向上します。

8. 結果のスクリーンショット

Building a High-Performance Full-Text Search Engine in Go


9. 結論

Go を使用した全文検索エンジンの構築は、同時実行性、マルチスレッド、データ ストリーミングなどの複雑なプログラミング概念を理解するための実践的なプロジェクトです。このプロジェクトは、高いパフォーマンスを維持しながら大規模なデータセットを効率的に処理する Go の能力を実証します。効率的なインデックス作成とマルチスレッド処理に重点を置くことで、この検索エンジンは驚異的な速度とメモリ効率を実現します。

このプロセスを通じて、検索エンジンの重要なコンポーネントであるストリーミング、トークン化、逆インデックス付け、マルチスレッドを調査し、これらの要素がどのように連携して応答性の高いリソース重視の検索ソリューションを作成するかを確認しました。分散処理や NLP 統合などの機能強化の可能性により、この検索エンジンはさらに進化し、より優れた機能を提供できる可能性があります。

ここで得た経験は、Go のパフォーマンスを示すだけでなく、データ量の多い環境の要求を満たす、スケーラブルな現実世界のアプリケーションを構築するための基盤としても機能します。

以上がGo で高性能の全文検索エンジンを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。