全文検索

ほとんどのアプリでは、ユーザーがアプリのコンテンツを検索できるようになっています。たとえば、特定の単語を含む投稿や特定のトピックについて書いたメモを検索するケースなどが考えられます。

Cloud Firestore では、ネイティブ インデックスの作成やドキュメント内のテキスト フィールドの検索をサポートしていません。さらに、コレクション全体をダウンロードして、クライアントサイドでフィールドを検索することは現実的ではありません。

Cloud Firestore データの全文検索を有効にするには、専用のサードパーティの検索サービスを使用します。これらのサービスは、単純なデータベース クエリで利用できる機能をはるかに上回る、高度なインデックス作成と検索の機能を提供します。

続行する前に、以下の検索プロバイダを調査し、いずれかを選択してください。

ソリューション: 外部の検索サービス

各メモがドキュメントとして記録されているメモ作成アプリを見てみましょう。

// /notes/${ID}
{
  owner: "{UID}", // Firebase Authentication's User ID of note owner
  text: "This is my first note!"
}

Elastic と Cloud Functions を使用して、各メモのコンテンツのインデックスを登録し、検索を有効にすることができます。まず、Elastic インスタンス ID、ユーザー名、パスワードを使用して Elastic クライアントを構成します。

Node.js

const { Client } = require("@elastic/elasticsearch");

// Initialize Elastic, requires installing Elastic dependencies:
// https://github.com/elastic/elasticsearch-js
//
// ID, username, and password are stored in functions config variables
const ELASTIC_ID = functions.config().elastic.id;
const ELASTIC_USERNAME = functions.config().elastic.username;
const ELASTIC_PASSWORD = functions.config().elastic.password;

const client = new Client({
  cloud: {
    id: ELASTIC_ID,
    username: ELASTIC_USERNAME,
    password: ELASTIC_PASSWORD,
  }
});

次に、メモが書き込まれるたびにインデックスを更新する関数を追加します。

Node.js

// Update the search index every time a blog post is written.
exports.onNoteCreated = functions.firestore.document('notes/{noteId}').onCreate(async (snap, context) => {
  // Get the note document
  const note = snap.data();

  // Use the 'nodeId' path segment as the identifier for Elastic
  const id = context.params.noteId;

  // Write to the Elastic index
  client.index({
    index: "notes",
    id,
    body: note,
  });
});

データのインデックスが作成されたら、呼び出し可能な Cloud Functions の関数を使用してインデックスを検索します。

Node.js

exports.searchNotes = functions.https.onCall(async (data, context) => {
  const query = data.query;

  // Search for any notes where the text field contains the query text.
  // For more search examples see:
  // https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/search_examples.html
  const searchRes = await client.search({
    index: "notes",
    body: {
      query: {
        query_string: {
          query: `*${query}*`,
          fields: [
            "text"
          ]
        }
      }
    }
  });

  // Each entry will have the following properties:
  //   _score: a score for how well the item matches the search
  //   _source: the original item data
  const hits = searchRes.body.hits.hits;

  const notes = hits.map(h => h["_source"]);
  return {
    notes: notes
  };
});

これで、Firebase SDK を使用して検索機能を呼び出すことができます。

ウェブ

const searchNotes = firebase.functions().httpsCallable('searchNotes');
searchNotes({ query: query })
  .then((result) => {
    const notes = result.data.notes;
    // ...
  });

セキュリティの追加

元のソリューションは、すべてのメモをすべてのユーザーが検索しても問題ない場合にのみ有効です。しかし、多くのユースケースでは出力する結果を制限し、セキュリティを確保する必要があります。

この場合、最も簡単なセキュリティ対策としては、Cloud Functions の関数を呼び出すユーザーの uid を確認し、owner フィールドがそのユーザー ID に一致するメモのみが出力されるように検索を制限します。

Elastic には、検索インデックスにロールベースのアクセスを提供するために使用できる、さまざまな認証方式と認可方式も用意されています。詳細については、Elasticsearch の認証と認可を徹底解説をご覧ください。

制限事項

  • 料金: Elastic Cloud には多くのサービス階層が用意されています。必ず料金ページにアクセスして、お客様のニーズに最適なオプションを選択してください。