Tech Blog

Facebook Icon Twitter Icon Linkedin Icon

AnyMind Group

Facebook Icon Twitter Icon Linkedin Icon

[Tech Blog] Elasticsearchを用いた、AnyTagのインフルエンサー検索機能の速度改善

AnyTagは、広告主とインフルエンサーをつなぐインフルエンサーマーケティングプラットフォームです。 あなたの商品を宣伝したいですか?お手伝いしましょう。 AnyTagはアジア全体に広がる300,000人以上のインフルエンサーやクリエイターの情報を保持しており、御社の広告キャンペーンに適切な方を探し出す検索機能を持っています。

例えば以下のようなフィルタを掛けて探し出すことができます。

  1. フォロワー数
  2. フォロワーの属性
  3. エンゲージメント率
  4. インフルエンサーの性別
  5. インフルエンサーの興味関心などなど

さて、本記事では、それらの検索が技術的にどのように改善されていったかをご説明します。

初期の構成

最初は、PostgreSQLを用いて検索していました。インフルエンサーの数も数千人程度で、検索を行うユーザーもあまり多くなかったため、最初はうまく機能しました。 ビジネスが成長し、より多くのインフルエンサーが登録され、利用者も増え続けるにつれて、データベースの負荷も増大しました。 やはりPostgreSQLでは用途に合わないのか、応答に時間が掛かったり、timeoutすることもありました。 300行にも及ぶSQLの最適化なども行ってみましたが、すぐに限界に達してしまい、最適化を頑張れどあまり改善されない状態になってしましました。 そのため、我々はElasticsearchに移行することに決めました。

Elasticsearchとは

Elasticsearchは、増え続けるデータ・ユースケースに対応でき、分析にも使えるRESTfulな分散型検索エンジンです。 Elastic Stackのコアとしてデータを一元的に保存し、超高速の検索、調整可能な類似度検索、拡張性の高い強力な分析機能を提供します。

使い方

Elasticsearchを使用したことがなくても、RDBMSの経験があれば次の対応表が理解に役立つでしょう。 ただしこの比較は単純化されていて、1対1の一致があることを意味するものではありません。

RDBMSElasticsearch
DBIndex
Record, rowDocument
SchemaMapping
To insert, updateTo index

Elasticsearchは、ドキュメントを転置インデックスと呼ばれるデータ構造でインデックスに保存します。 Elasticsearchはテキストを分析し、テキストをterms(単語、音節、あるいは任意の方法)で分割し、転置インデックスを使用してtermsとドキュメントの関係を保持します。 たとえば、ドキュメントA「Hello World」とドキュメントB「Hello Tony」は次のように保存されます。

TermDocument
helloDocument one, document two
worldDocument one
tonyDocument two

テキストを分割するために、Elasticsearchはさまざまなtext analyzerを提供しています。 たとえば、英語のアナライザーは「Hello World! This is Elasticsearch! I want a hamburger」を「hello」、「world」、「elasticsearch」、「i」、「want」、「hamburg」というtermsに変換します。 一部の単語は一般的すぎるため、除外されます。たとえば、「this」と「is」などです。 一部の単語はstemmingによって形が変わります。 たとえば、「hamburger」は「hamburg」に変更されました。

デフォルトでは、Elasticsearchはテキストの分析方法を推測しようとします。 ただし、開発者はデータの性質を知っているため、マッピングを定義してElastisearchに教えることもできます。 例えば:

"content": {
    "type": "text",
    "analyzer": "thai"
}

ここでは、thai語のanalyzerを使用してテキストを分割するように指定しています。

また、Elasticsearchの名前にある「Elastic」にも言及しましょう。 Elasticsearchはデータを複数のノードに分散されたシャードに格納するため、高可用性と負荷分散が大幅に改善され、システムが安定、安全、堅牢になります。

Shards are distributed among multiple nodes

もう1つの大きな特徴は、Elasticsearchがほぼリアルタイムでドキュメントにインデックスを付けることです。 つまり、登録されたばかりのドキュメントはまだインデックスされておらずすぐに検索に表示されないことがあります。 Elasticsearchはまだバッファに残っているドキュメントを定期的にインデックスに登録します。これは「リフレッシュ」と呼ばれます。デフォルトでは、更新間隔は1秒です。

なぜ高速に検索出来る?

デフォルト設定でも、Elasticsearchはドキュメントの検索は非常に高速です。どう実現しているのでしょうか? 少なくとも2つの大きな技術的特徴があります: 1)すべてのドキュメントは単体で検索に必要なデータを保持していると仮定されます。検索に必要なすべてのデータが一箇所にあるため、Joinなどの処理は必要ありません。 2)全文検索において、転置インデックスを使うことでドキュメントを全てスキャンする必要はなくなります。RDBMSで言えばテキストのカラムにもindexが張られているイメージです。

あなたがITに精通していれば、いわゆる「銀の弾丸」が無いことはご存知かと思います。何事にもトレードオフがあるということです。 Elasticsearchの場合でいうと、欠点はインデックス作成です。 トランザクション、リアルタイムの更新、およびデータの一貫性を捨てることになります。言い換えるなら、"writes"を捨てて"reads"に特化したDBということになります。

Deployment and monitoring

AnyTagでは、cloud.elastic.co によって提供されるマネージドElasticsearchクラスターを使用し、2つのdata nodeと1つのtiebreaker nodeを運用しています。 tiebreaker nodeはデータを保存しませんが、スプリットブレイン状況を回避するためにどのノードがマスターノードになるかを決定します。

Elasticsearch deployment in AnyTag

cloud.elastic.co を用いてクラスターの状態を監視することが可能です。

Deployment monitoring from cloud.elastic.co

また、シャード、インデックス、CPUロードなどのストレージ内部を視覚化するために Elasticvue Google Plugin も活用しています。

Elasticvue monitoring

Testing and debugging

Elasticsearchは簡単にテストできます。 AnyTagでは、KotlinとPythonでコードを記述していますが、特に問題はありません。 ほとんどのAnyMindプロジェクトではCircleCiを用いてCI/CDを組んでいるのですが、そこで以下のようにテスト用にElasticsearchコンテナーを実行するだけです。

  - image: docker.elastic.co/elasticsearch/elasticsearch:7.15.2
        environment:
          - transport.host: localhost
          - network.host: 127.0.0.1
          - http.port: 9200
          - cluster.name: es-test-cluster
          - discovery.type: single-node
          - xpack.security.enabled: false
          - ES_JAVA_OPTS: "-Xms256m -Xmx256m"

覚えておくべきことが1つだけあります。 前述のように、インデックス作成は多少の時間差があり、デフォルトではデータの登録と検索の間に1秒の遅延があります。

ちなみにElasticsearchは、テキストの分析方法、ドキュメントが結果に含まれた、または含まれなかった理由などを教えてくれるRESTfulAPIを提供するのでデバッグに便利です。

価格

AnyTagには、2つのデータノードがあります。それぞれ8GBのRAMと180GBのストレージです。 現時点でのコストは1時間あたり0.60ドルで、1か月あたり432ドルです。

AnyTagに導入してみての結果

PostgreSQLからElasticsearchへの移行後、検索はより速く、より便利になりました。 ページのロードに2秒もかからず、インフルエンサーは関連度でソートされます。 ユーザーは、8000万件のソーシャルメディア投稿の検索がとても高速になり満足してくれているようです。 さらにElasticsearchは、オートコンプリートなど将来的に他の検索関連機能を実装する際にも便利でしょう。

遭遇した課題

Elasticsearchは他のテクノロジーと同様に欠点もあります。私たちの場合は次の問題が発生しました。

  1. インデックス作成が遅い
  2. 日本語の対応

インデックス作成が遅い

デフォルトの構成ではElasticsearchは大量の受信データの処理にそれほど優れていません。 AnyTagでは非常に頻繁にデータが更新されますが、これがElasticsearchをデプロイする際に問題になりました。 インデックス作成の応答時間は遅く、CPU負荷はすべてのノードで100%でした。 Elasticsearchが30秒のタイムアウト後にリクエストを拒否してしまうケースもありました。

そこで次のことを考慮しました。

  1. フィルタに使用されない値にインデックスを付けない
  2. Refresh intervalを変更する

一つずつみていきましょう。

フィルタに使用されない値にインデックスを付けない

Elasticsearchでは、すべての値がデフォルトで転置インデックスに入れられ検索可能になります。 ただし、フィルタせずにストレージから読み取ることだけを目的としているデータもあります。 たとえば、インフルエンサーのプロフィール写真のURLなどはフィルタに用いたいわけではなくUIで表示したいだけです。 このような特定のケースで、以下のようにElasticsearchに値を転置インデックスに入れないように指示できます。

 "profile_pic_url": {
    "type": "keyword",
    "index": false
 }

"index": false を付けることで、値はフィルタには使われませんが通常どおり読み取ることができます。 これによりインデックス作成の時間とストレージを削減できます。

Refresh intervalを変更する

Refresh intervalは、Elasticsearchが最近書き込まれたデータを検索できるようにするまでの間隔です。 デフォルトでは、値は1秒です。これは、頻繁な更新ではうまく機能しません。 そこで値を30sに変更しました。これによりデータを登録して30秒待たないとElasticsearchが実際にデータを探してこれないことになります。 驚いたことに、この小さな変更はインデックス作成時間に最大の影響を及ぼしました。 大量のデータのインデックス作成に問題がある場合は、最初にこれを検討することをオススメします。

その他のインデックス作成の最適化の手法は以下に記載されています。 official documentation.

日本語の対応

私たちが直面したもう1つの非常に予想外の問題は日本語の対応でした。 AnyTagは日本に多くのユーザーが居ることもあり、 AnyCreator アプリと AnyTag アプリの両方に日本語版があります。 ご想像のとおり、日本人のユーザーは日本人のインフルエンサーを日本語で検索します。 ただここに問題があります。AnyTag/AnyCreatorのエンジニアチームは誰も日本語を話せません。 日本人のユーザーが日本語で関連データを見つけられない場合に、それをいったいどうやって英語話者のエンジニアに説明出来るでしょうか? 信じられないかもしれませんが、私たちは日本語を話せなくてもこの問題を適切に解決することができました。

幸い、ElasticsearchはCJK(Chinese, Japanese, Korean)を含む多くのtext analyzerをサポートしています。 我々もこれらのtext analyzerを使用しましたが、無関係なドキュメントが多く検索に出てきてしまいました。 例えば次の文を見てみましょう。

"世界中のボランティアの共同作業によって執筆及び作成されるフリーの多言語[インターネット百科事典]である"

英語にすると以下です。

"A free multilingual [Internet encyclopedia] written and created by the collaboration of volunteers from around the world"

日本語を話さない人にとっては、日本語の文が完全に連続していて単語の間にスペースがないのは大きな驚きかもしれません。 さらに、テキストにはカタカナ、ひらがな、漢字の記号が1つの文に含まれています。 これは日本人にとっては大きな問題ではないかもしれませんが、私たちにとっては問題でした。 しかし簡単な調査の結果、CJK analyzerがテキストをbigramsに分割していることがわかりました。

たとえば、「ソウル」は「ソウ」と「ウル」になります。 英語で 過度に単純化された例 を上げると、テキストが「Hello World」であるとしましょう。 CJKは、それを「he」、「el」、「lo」、「ow」、「wo」、「or」、「rl」、および「ld」に分割します。 デフォルトでは、Elasticsearchはこれらのいずれかを含むドキュメントを返します。 もちろん、多くのテキスト文書には「he」と「el」が含まれています。これらのbigramsは英語では非常に一般的だからです。

その結果、関連性のないドキュメントが大量に引っかかりました。 「Hello World」、「Hey, how are you doing?」、「It was a hell of a day」というドキュメントがある場合、「Hello World」で検索するとこれら全てのドキュメントが検索結果として出てくるでしょう。 しかし繰り返しになりますが、これは過度に単純化された例です。英語のテキストは実際にはCJKではないanalyzerが用いられています。

幸い、上述した問題の解決策はありました。termsの75%以上を含むドキュメントを検索結果とするように検索クエリを構成しました。 上記の例では、クエリ結果のドキュメントには「he」、「el」、「lo」、「ow」、「wo」、「or」、「rl」、「ld」というtermsの少なくとも75%が含まれている必要があります。 こうすることで、タイプミス(25%のしきい値)を許容しつつ、ほとんどの場合に関連するドキュメントを返すことができます。

まとめ

Elasticsearchはあなたの課題を解決しうる優れたツールかもしれません。高速で可用性は高いですが、学習曲線は高いと思います。 productionで使用する場合は、Elasticsearchの内部を知る必要があります。Elasticsearchをインストールしただけで高速に動作することを期待すべきではないでしょう。

(翻訳:柴田)

Latest News