LINE MUSICが挑んだパフォーマンス改善。膨大な楽曲数を扱うためのElasticsearchの設計

2021年11月10日・11日の2日間にわたり、LINEのオンライン技術カンファレンス「LINE DEVELOPER DAY 2021」が開催されました。特別連載企画「DEVDAY21 +Interview」では、登壇者たちに発表内容をさらに深堀り、発表では触れられなかった関連の内容や裏話などについてインタビューします。今回の対象セッションは「膨大な楽曲数を扱う検索機能を支えるElasticsearchの構成と速度改善手法」です。 

サブスクリプション型の音楽配信サービス「LINE MUSIC」の楽曲数は8,600万曲を超える膨大なものです。音楽を聴くだけではなく、楽曲の中から好みに合う曲をレコメンドしてくれる機能やLINEアプリのプロフィールに設定する機能も備えています。  

LINE MUSICで扱っているこれらの楽曲は、音楽レーベル会社から提供されています。そしてその開発チームでは、音楽レーベル向けに楽曲の納品や情報の閲覧ができるシステムを開発・運用しています。 

今回は膨大な楽曲数を扱う検索機能を開発するうえで立ちはだかった課題とその解決策を、サーバーサイドエンジニア多田拓と山下将広に聞きました。 

左から山下さん、多田さん

音楽レーベル向けのAPI開放に伴い、明らかになったパフォーマンスの課題

――まずは自己紹介をお願いします。

多田:サーバーサイドエンジニアの多田拓です。先日に開催された「LINE DEVELOPER DAY 2021」では、私が登壇してLINE MUSICの事例についてお話ししました。これらの事例で同じチームのメンバーとして一緒に働いてくれたのが山下さんです。  

山下:同じくサーバーサイドエンジニアの山下将広です。この後のインタビューで負荷試験についての話が登場するのですが、試験の準備を私が主に担当し、アプリケーションの実装変更やElasticsearchのチューニングなどを多田さんが主に担当しています。 

――今回のインタビューでは、楽曲情報の検索機能を開発する過程で直面した課題についてお聞かせください。大きく分けて、Meta Search APIの改善と、楽曲情報を取り扱うためのCMSの検索機能の改善という2つの事例があると伺っています。 

山下:まずは前者の事例からご説明させてください。Meta Search APIとは、Elasticsearchによって楽曲情報を検索する機能を持つAPIです。もともとこのAPIは、社内のシステムからだけ呼び出されていました。 

LINE MUSICでは音楽レーベル各社から提供された楽曲情報をシステムに登録します。登録された楽曲情報を効率よく参照できるように、私たちはMeta Search APIを音楽レーベル向けにも提供することにしました。これに伴い、多数の企業が機能を利用した場合に、APIのパフォーマンスに問題が生じないかどうかを検証する目的から、負荷試験を実施しました。  

――負荷試験実施の流れについてご説明ください。 

山下:まず、APIへ大量のリクエストをするためのツールを準備する必要があります。LINE社内では、k6のソースコードをベースに独自拡張をしたStampedeというツールを負荷試験に用いています。Stampedeは他ツールとの連携が容易であり、Grafanaで負荷テストの結果を可視化しやすいなど利便性が高いツールです。さらに、負荷試験を行うためにはデータの準備も重要です。本番環境と同等量のデータを負荷試験時に準備することで、パフォーマンスの課題を発見しやすくなります。  

そうしたツールや環境を用いて試験を実施したところ、APIが負荷に耐えられないことがわかりました。ボトルネックになったのはElasticsearchです。楽曲情報を検索する際に、一定以上の負荷がかかるとElasticsearchのNodeのCPU使用率が100%に達してしまい、処理を捌ききれなくなることがわかりました。  

多田:パフォーマンス改善のため、まずはElasticsearchに対して発行しているクエリの最適化を試みることにしました。しかし、クエリを修正してもまだ思ったようにパフォーマンスが改善しなかったので、次にインフラの増設を試みました。  

Elasticsearchのクラスタには、処理の制御をするMaster Nodeと、データを持ち分散的な処理するData Nodeの2種類があります。Data Nodeを増やすことで検索性能を向上させられますが、その分サーバーを稼働させるための金銭的コストがかかります。また、私たちは今回の事例において、社内のプライベートクラウドであるVerdaのElasticsearchを利用しているのですが、Verdaでは申請なしで使用できるNodeの数に一定の制限があります。この制限の範囲では、負荷試験の結果が目標の性能を達成することができませんでした。そこで、Verdaの運用を行っているインフラ担当者と相談することでNodeの増設をしつつ、限られた数のNodeを効率的に動作させるために、ShardとReplicaの設定を最適化していきました。設定した数値の詳細やその理由はLINE DEVELOPER DAY 2021のセッションにて詳しく解説しています。  

――Nodeの増設とそのパフォーマンス検証を行うにあたり、印象に残っている作業があれば教えてください。 

山下:この事例ではNodeの台数を調整しつつ負荷試験を実施していったのですが、負荷試験用の環境には8,000万曲ほどの楽曲データが存在しているため、Elasticsearchのインデックス構築に7時間近くもかかります。 

そこで作業を効率良く進めるために、負荷試験の前日の夕方ごろにチームメンバーと「明日はElasticsearchをこういう設定にして負荷試験をしましょう」と相談し、その日の夜にElasticsearchのインデックスを構築する方針にしました。そして、翌日にオンラインミーティングツールのZoomを用いてエンジニア同士で連携をとりながら負荷試験を実施し、設定の調整をしていったという経緯があります。  

Slack上で負荷テスト結果を確認している様子

――今後さらにシステムを改良したい部分はありますか? 

山下:現在は、検索結果に大量の楽曲データがヒットしてページングが必要な場合には、LIMIT-OFFSETを用いています。しかし実は、この方式はパフォーマンスに悪影響を及ぼします。歴史的な経緯から私たちのシステムではLIMIT-OFFSETを用いているのですが、将来的にはこの方式をやめて、Continuation Tokensを使ったページングに変更したいと考えています。 

CMSの検索機能を高速化するため、Elasticsearchのデータ型を変更 

――もうひとつの事例であるCMSの検索機能の改善事例もご説明ください。 

多田:私たちは音楽レーベル向けに楽曲情報を取り扱うCMSを提供しており、その画面上には楽曲の検索フォームがあります。フォームに文字列を入力して検索を行うと、条件に部分一致した楽曲が一覧表示されます。この検索機能は、もともと楽曲名検索しかサポートしていませんでした。アルバム名やアーティスト名で絞り込むことができなかったのです。 

利便性を向上させるため、ひとつの検索フォームで楽曲名だけでなくアルバム名やアーティスト名などの項目も同時に検索できるように機能拡張しました。しかし、従来の検索手法のまま項目数を増やしてしまうと、検索にかかる時間が1秒程度だったのが5秒程度まで増えてしまいました。そこで、検索を高速化するために検索手法を見直しました。 

――なぜ従来の手法はパフォーマンスが悪かったのでしょうか? 

多田:従来は、楽曲情報を検索項目に格納する際に、Elasticsearchのキーワード型というデータ型を用いていました。キーワード型はシンプルなデータ型であり、データを格納する際に特に何も加工することなく、そのままの文字列を用います。 

そのデータに対して、タームレベルクエリの一種であるワイルドカードクエリを使って検索をしていました。ワイルドカードクエリは、その名のとおりワイルドカードを使ってパターンを記述するクエリです。「*bc*」というクエリの場合、「bc」「abc」「bcd」「abcd」などの文字列がマッチします。しかしワイルドカードクエリは、後方一致の場合に検索パフォーマンスが悪化することが欠点です。 

Elasticsearchには、高速検索を可能にしてくれるテキスト型というデータ型もあります。しかしテキスト型を使うには、文字列をあるルールに沿って分解するというトークナイズの処理が必要です。そして、楽曲データのように文字列が短く固有名詞が含まれる場合には、一般的によく用いられる辞書ベースのトークナイズがあまり効果的ではありません。アーティスト名や楽曲名は、辞書に載っていない新語や造語が使われているケースが多いためです。 

適切なトークナイズの手法を検討するには、開発にそれなりの時間がかかります。検索機能の初期リリース時はユーザーに早くサービスを提供することを優先したため、データの事前加工の必要がないキーワード型を用いたという経緯があります。 

――その後、ユースケースの変化に伴って高速化が必要になったわけですね。 

多田:そうです。検索機能の高速化を実現するため、時間をかけて設計と開発を行い、データ型をテキスト型へと変更してフルテキストクエリの一種であるマッチクエリを採用しました。これにより、Elasticsearchの転置インデックスを使用でき、大量のデータを高速で検索できるようになります。 

――具体的にはどのような方法でトークナイズを実現したのでしょうか? 

多田:辞書ベースのトークナイズではなく、n-gramによるトークナイズを使っています。n-gramは文字列をn文字の連続したパーツに分割する手法です。「abcde」という文字列は、1-gramであれば、「a」「b」「c」「d」「e」に、2-gramであれば「ab」「bc」「cd」「de」に、3-gramであれば「abc」「bcd」「cde」に分割されます。 

n-gramでトークナイズされたデータに関してマッチクエリを使用した場合、Elasticsearchは転置インデックスを作成して、どの文字列がどのドキュメントに含まれているかを表現します。これにより、検索の際には転置インデックスからすべての文字が含まれるドキュメントを探すだけでよいため、余計な情報を探索する必要がなくなり処理が高速化します。 

しかし、この方法にも課題があります。「kyoto」のように文字の順序が異なるドキュメントも検索結果に含まれることです。フルテキストクエリでは文字列の順序を保証してくれません。この検索方法では、ユーザーにとって意図しない結果が含まれてしまいます。 

そこで、検索文字列の大きさによってn-gramのnを動的に変更することで、私たちはこの問題を実用上解消しました。より詳細な情報は、こちらも「LINE DEVELOPER DAY 2021」のセッションにて発表しています。 

大量のデータを扱うからこそ身につくスキルがある 

――この事例と同じように、Elasticsearchのパフォーマンス最適化に取り組む人たちに対して、アドバイスはありますか? 

多田:LINE MUSICと同じような性質の検索機能ならば、私たちの事例がきっと参考になると思います。例えば書籍のタイトルや料理名のように、文字列が短くて固有名詞や造語が含まれるデータが多数ある場合には「テキスト型+n-gram」の手法がマッチするはずです。 

また、大量のデータを扱う場合には、Elasticsearchのインデックスの作り直しにかなりの時間がかかります。インデックス作成を効率よく実現できるように、環境を整備することを大切にしてください。 

山下:私たちの事例において、「テキスト型+マッチクエリ」の構造へと変更してElasticsearchの検索処理最適化を行う際に大切だと感じたことがあります。それは普段の業務で検索機能を利用する方々に開発途中のプロトタイプを提供し、検索結果が適切であるかを確認してもらいながら作業を進めることです。 

仮に、システムが示しているマッチ度合いのスコアが高かったとしても、必ずしもその結果が人間の直感と一致しているわけではありません。システム的には高いマッチ度を示しているけれど、人間の直感に反しているケースもあり得ます。だからこそ、利用者と密にコミュニケーションをとりながら、検索機能のチューニングを行うことが開発では重要になります。 

また、負荷試験の準備や実行を円滑に行うために、チーム内でどのような方法で作業を実施して結果を確認するかという合意形成も大切だと感じました。特に今回の事例においては、新型コロナウイルス感染症の影響でフルリモートでの作業だったため、対面での作業のように密な連携をとることが難しい環境でした。だからこそ、余計に適切に認識合わせをすることの重要性を感じました。 

多田:かなり多くの学びがありましたよね。今回、LINE DEVELOPER DAY 2021で発表した内容は、楽曲のデータが膨大でなければ起こり得ないものです。大量のデータを扱うLINEだからこそ経験できることですし、エンジニアとしてスキルの向上に結びついたと、私は感じました。 

 

採用情報 

LINE株式会社では一緒に働くエンジニアを募集しています!
今回のインタビューと関連する募集ポジションはこちらです。