毎時間に約1億人分のレコメンドをインポート

稲葉大樹氏:先ほどのアーキテクチャを見てみて「なんだ、ぜんぜん普通じゃないか」と思った方もいらっしゃるかと思います。どちらかというと課題はレコメンドのインポートにありました。

ここからはそこに関する話に移りたいと思います。インポートの課題と言ってもひとえにこれにつきますね。1億人を超えるユーザ分のレコメンドを毎時間インポートする必要がある点です。続いてはこれに対してどのように対処していったのかという事例を紹介していきたいと思います。

こちらも始めに全容をお見せしておきます。またイベントの流れに沿って紹介していきたいと思います。

マシンラーニングによるレコメンドの説明で、レコメンドはData Labsというチームによって生成されていると言いましたが、Data Labsはレコメンドファイルの生成が完了すると、まずそれをDatahubと呼ばれるストレージサーバにアップロードします。

そしてDatahubへのアップロードが完了するとLINE NEWS側で用意している通知用のAPIをコールする仕組みになっています。

Data Labsから通知を受けたAPIサーバは、現状3台のImporterに分散しています。

具体的にどのように分散を行っているのかと言うと、ここも各Importerに実装されているAPIをコールするような感じになっています。その際に各Importerに自分が複数に分割されたレコメンドファイルのどこからどこまでをインポートするべきなのかという情報をLIMIT・OFFSET方式で教えてあげます。例えばImporter1は0から100件、Importer2は100件目から100件といった感じですね。

各Importerは受け取ったLIMIT・OFFSETに基づいてDatahubからレコメンドをインポートします。インポートの処理をもう少し詳しく見ていきたいと思います。

インポートの処理が走ると、まずは自分が担当するファイルを1つダウンロードします。ファイルはTSV形式になっていて、こんな感じのデータが入っています。

ユーザID、記事IDのリスト、記事IDに対するスコアのリストが、それぞれタブ区切りになっています。記事IDのリストと記事IDに対するスコアのリストはカンマで区切られている状態です。記事IDとスコアはインデックスでそれぞれ対応しています。

このようなデータを1行ずつパースします。

こんな感じのユーザIDと記事IDとスコアの組み合わせのリストを持ったオブジェクトに変換します。

そして変換した各データをユーザIDをキーにしてRedis上に格納しています。

このファイルダウンロードからRedisへ格納するまでの一連の処理は20のスレッドで非同期かつ並列に実行されています。それが合計3台のサーバで実行されているので、合計60スレッドで実行されている感じになっています。

こうすることで、毎時間に約1億人分のレコメンドをインポートすることを実現しています。現状のインポートの処理はだいたい10分程度で完了しています。現在はだいぶ余裕がある感じになっています。マシンラーニングによるレコメンドの話はこれで終わりなんですが、最後にリクエストとインポートの処理の全体を振り返っていきたいと思います。

リクエストはこんな感じです。

インポートはこんな感じになっていました。マシンラーニングによるレコメンドは、現状このアーキテクチャでとくに問題もなく動いています。

手動レコメンド導入時のアーキテクチャ

続いて、そこに手動レコメンドが導入されたアーキテクチャを紹介していきたいと思います。手動レコメンドについては初期の実装に問題があったと言いましたが、まずは初期の実装とそれによる問題についてお話して、それをどのように解決していったのかという流れで説明していきたいと思います。

手動レコメンドについて軽くおさらいをしておくと、運用チームがCMS上で設定し、対象のユーザは複数の属性の組み合わせによって行われるといったものでした。表示期間を設定できたり、マシンラーニングのレコメンドのどの位置に混ぜて表示するのかなどの情報も設定することができました。

では、まずは初期の実装からお話していきたいと思います。初期の実装では属性からユーザIDの抽出を行う部分をシステム化するために十分な時間がなかったため、この部分に関しては手運用でカバーする方針を取りました。システム的には事前に抽出してもらった対象ユーザのIDのリストをCSV形式でアップロードしてもらうようになっています。

先にアーキテクチャの全容をお見せすると、こんな感じになっています。これがインポート側。

これがリクエスト側です。データの流れに沿って、また一つひとつ説明していきたいと思います。

その前に、ここにCentral Dogmaというコンポーネントがありますね。Central Dogmaは弊社が独自で開発しているシステムですが、アーキテクチャの中でもかなり重要なファクターなので、まずはこれについて説明をさせてください。

Central Dogmaとは端的に言うと設定ファイルを中央集権的に管理するためのリポジトリシステムです。「設定ファイル」とは言っていますが、要するにJSONやYAMLなどのファイルを管理できるシステムです。ここが今回のアーキテクチャの上でかなり重要なポイントなんですが、Central DogmaのクライアントはCentral Dogma上のファイルをウォッチすることができて、ファイルの変更があると最新のデータをクライアント側に保持するようになっています。

LINE NEWS初期の実装

それを踏まえた上でアーキテクチャの説明に戻りたいと思います。

Datalakeというのは社内の大規模なHadoopクラスタです。その中にユーザの推定情報なども保存されていて、LINE NEWSではこれを活用して手動レコメンドを実現しています。手動レコメンドを登録したい場合は、まずはデータ分析チームにレコメンドの対象にしたいユーザの属性を渡します。

ユーザの属性を受け取ったデータ分析チームは、それを基にSQLを組み立ててDatalakeから対象ユーザのIDのリストを取得します。

そして運用チームが分析チームに、取得してもらったユーザIDの一覧をCSVファイルとしてCMSにアップロードします。このときにレコメンドの表示期間、レコメンドする記事、マシンラーニングのレコメンドのどこに差し込むかなどの情報も同時に入力します。

CMSサーバは、アップロードされたCSVファイルを基に作成した、記事とユーザのマッピングデータをRedisに保存します。

マッピングデータはこのような感じで入力された記事のIDがキー、CSVから抽出したユーザIDのリストをSETとして、そのままバリューに設定しています。ユーザIDはだいたい30バイトぐらいの文字列になっています。なので、例えば1,000万人のユーザに対してレコメンドを設定した場合、1キーあたりの容量が30×1,000万で300メガバイトになっています。

マッピングデータの保存に成功したら、次に、入力された情報をMySQLに保存します。

そしてバッチサーバがMySQLを確認して、表示期間中のレコメンドの記事が存在すれば、その記事情報をCentral Dogmaにアップロードします。

Central Dogma上には、こんな感じのJSONがアップロードされています。タイトルや画像、マシンラーニングのレコメンドのどこに差し込むかなど、実際にはもっといろんな情報が入っているのですが、こんな感じでユーザに記事を表示するための情報が配列で入っているイメージです。

そして、先ほど説明したようにCentral DogmaのクライアントはCentral Dogma上の変更を検知してクライアント上にデータを保持するため、Webサーバはこの時点で記事の情報をすでに保持していることになります。

実際にユーザからリクエストが来ると、まずRedisからユーザに対するレコメンドの記事IDを取得します。

Redisから記事IDを取得する具体的な方法としては、例えばこのようなマッピングデータがあったとすると、ここからuser_id_1に対するレコメンド記事を取得したい場合は、このようにRedis上のすべてのキーに対してSISMEMBERのオペレーションを投げます。SISMEMBERは バリューのSETの中に特定の値が含まれているかどうかを判別するオペレーションです。

この場合user_id_1が含まれていればSETをバリューに持つキーはarticle_id_1、3、4となるので、これがuser_id_1に対するレコメンドIDとなります。

記事の詳細な情報は、Central Dogmaクライアントによってすでにサーバ内に保持されているので、ネットワークを介さずに取得することができます。

その情報を基に、マシンラーニングのレコメンドの中に混ぜてユーザにレスポンスを返します。初期の実装はこんな感じになっていました。

データ分析チームに大きな負担がかかってしまった

先述のとおり、この実装には問題があったわけですが、その問題について紹介をしていきたいと思います。まずはマッピングデータの形式のせいでスケールできないという問題がありました。クラスタ上に分散をしていたとしても、先ほど説明したように1キーあたりの容量が300メガバイトにもなってしまっているとクラスタのメリットは最大限に活かせません。

ちなみに2,000万人のユーザに対してレコメンドが設定されていた場合は600メガバイトなので、こういったレコメンドの設定が頻発するとクラスタ上でもなかなか……といった感じですね。

ただ、このマッピングデータ形式は不要になったデータをマッピングデータから削除するというのが非常に簡単だというメリットがありました。記事のIDがキーになっているので、Redisから該当の記事IDを削除するだけで済んでいました。

しかし、そうは言っても設定される手動レコメンドの数が当初想定した量よりもかなり多かったため、この「スケールできない」という問題が浮き彫りになってきました。

そしてもう1つ。運用コストが高すぎるという大きなデメリットがありました。ターゲットの抽出が手運用なので、抽出すること自体はもちろん、ターゲットの属性の変更などがあった場合は、まあまあなコストが発生してしまっていました。

ユーザIDの抽出はSQLによって行われているので、ターゲットの属性の変更があるとSQLまで変更しなければならず、データ分析チームにかなり大きな負担が掛かっていました。

ここからはこの問題に対してどのように対処していったのかという事例を紹介していきたいと思います。まず、マッピングデータの形式によってスケーラビリティが低くなってしまっている問題ですが、これは当然ですがマッピングデータの形式を変更することで対処しました。どのように変更したのかという点も後ほど紹介します。

運用コストが高すぎる問題をどう解決したか

次に運用コストが高すぎる問題ですが、これはターゲットのユーザIDの抽出をCMS上にシステムとして組み込むことで解決しました。こちらも先に全容をお見せしておきます。

こちらがインポート側。

こちらがリクエスト側です。

先ほどのアーキテクチャと変わったところで言うと、今ハイライトされている部分ですね。

リクエスト側はマッピングデータの形式が変わったこと以外はとくに変更点はありません。こちらもイベントの流れに沿って説明していきたいと思います。

まず先ほどのアーキテクチャでは運営チームが分析チームに依頼してSQLを組み立ててと、手運用でがんばっていたところですが、ここは運用チームがCMS上からターゲット属性の組み合わせを入力するように変更しました。

入力を受けたCMSサーバは、とりあえず先ほどと同様に記事の情報をMySQLに保存します。

それと同時に新たに登場したManual Importerと呼ばれるサーバに、入力されたターゲット属性などの情報を受け渡します。

Manual Importerは受け取ったターゲット属性を基にSQLを組み立てて、DatalakeからターゲットのユーザIDを抽出します。先ほどはデータ分析チームが行っていた処理ですね。このユーザIDの抽出の処理をCMSサーバから直接行わない理由としては、このユーザIDの抽出がSQL次第では最大1時間ぐらい掛かるかなり重い処理なので、専用のサーバに処理を切り分けました。

ユーザIDの抽出が完了すると、ユーザIDと記事IDのマッピングデータをRedisクラスタ上に保存します。

マッピングデータの形式は先ほどとは逆で、ユーザIDがキー、記事IDのSETをバリューにしています。こうすることで1キーあたりのデータサイズが小さくなり、Redisクラスタのメリットを潰すことなくマッピングデータを保存することができます。

ユーザIDの抽出からRedisへの保存までが完了すると、MySQL上にフラグを立てます。正常終了した場合はVALID、異常終了した場合はINVALIDなフラグを立てます。そこからの流れはとくに先ほどのものから変更されていません。

バッチで表示可能な記事情報をCentral Dogma上にアップロード。

アップロードした記事情報はCentral Dogmaクライアントによってサーバに保持され、ユーザからリクエストが来た場合はRedisからレコメンド対象の記事IDを取得して、Central Dogmaクライアントによって保持されている記事情報を基にマシンラーニングのレコメンドの中の適切な位置に差し込んでユーザに表示するといったような感じです。

1つ変わっている点としましては、先ほどはこんな感じでがんばってレコメンド対象の記事IDを取得していました。

マッピングデータの形式を変えたことで、単純にGETのオペレーションを投げるだけでよくなって非常に簡略化されています。最終的にはこのアーキテクチャによって、スケーラビリティの向上と運用コストの大幅な削減に成功しました。

アーキテクチャ変更による代償

一方で、このアーキテクチャに変更したことによる軽い代償みたいなものがあるので、最後にそこについて軽く触れたあと、LINE NEWSのレコメンドの今後の展望についてお話して、本セッションを締めたいと思います。

まず、このアーキテクチャに変更したことによる代償ですが、マッピングデータの生成、及び削除が難しくなってしまっていたという代償があります。

例えばarticle_id_5をuser_id_3、4、5のレコメンドとして設定する場合、初期のマッピングデータの形式ではこのようなマッピングデータがすでに存在していたとします。

このように設定したい内容通りにRedisにセットするだけでした。

対して現在のマッピングデータの形式の場合は、新しく追加されるユーザIDに関しては今までと同じように単純にSETするだけでいいです。

しかし、すでに何らかのレコメンドが設定されてしまっているユーザに対しては、既存のバリューのSETの中にその記事IDを追加する必要があります。

処理自体はSADDのオペレーションを投げる感じでかなり単純なものなんですが、設定するレコメンドの対象のユーザの数だけこの処理を行わなければならないため、初期のマッピングデータの形式に比べてだいぶ複雑になってしまっています。

マッピングデータを削除する場合も同様で、削除対象の記事IDが含まれているキーの数だけ、その削除の処理を行わなければなりません。このような代償はありますが、パフォーマンス、スケーラビリティの向上を優先して現在のマッピングデータの形式を採用しています。

レコメンドの今後の展望

そして最後にLINE NEWSのレコメンドの今後の展望について2つほど紹介をしていきたいと思います。

まず1つ目は当然なんですが、レコメンドのクオリティを向上させるということです。レコメンドのクオリティというのはインプレッション等のユーザからのレスポンスによって改善されたレコメンドに、またユーザがレスポンスを返してといったように双方向のレスポンスによって向上していくものです。

本日紹介したLINE NEWSのレコメンドのアーキテクチャでは、ユーザによるレスポンスはいつでも返ってきているわけですが、それに対して改善されたレコメンドが反映されるのは最大で1時間後になってしまっています。これはレコメンドの取り込みがHourlyのバッチで行われているためですね。

せっかくユーザからのレスポンスが常に来ているのに、これは非常にもったいないことです。

「Future Plans」と言っておいてあれなんですが、そこでLINE NEWSでは現在「AiRS」と呼ばれる新たなレコメンドエンジンを導入しています。

このレコメンドエンジンでは、LINE NEWSでインポートの処理を行う必要がないため、ユーザには常にリアルタイムでユーザからのレスポンスに基づいて改善されたレコメンドを返すことができます。

現在はA/Bテストで一部のユーザにのみ、これが適用されています。LINE NEWSではこのようにレコメンドのクオリティを向上するために、いろいろな施策をとっていく予定です。

より一層ユーザの興味に沿ったニュースを

2つ目はレコメンドを導入している箇所を拡大していくということです。こちらも具体例を1つ紹介したいと思います。LINE NEWSではLINE公式アカウントからニュースのダイジェストを配信しています。ここにレコメンドを導入しようという話も薄らと出てきていたりします。ダイジェストは現在すべてのユーザに一律で同じ内容を配信しています。

ここに掲載されているのはLINE NEWS編集チームによる選りすぐりのニュースなのですが、レコメンドと組み合わせることで、より一層ユーザの興味に沿った内容をお届けすることができるだろうと考えています。

これを実現するためにはLINE NEWSのLINE公式アカウントと友だちになっているユーザ一人ひとりに対して異なる内容をプッシュする必要があって、実現できた場合にはおもしろい話ができるんじゃないかなと思います。

さて、本日はLINE NEWSの中でもとくにレコメンド記事配信に焦点を当ててお話をさせていただきました。マシンラーニングによるレコメンドと手動レコメンドを組み合わせている話、それぞれどのようにリクエストとインポートを実現しているのかなどをご紹介させていただきました。楽しんでいただけましたでしょうか。

ですが、冒頭でも紹介をさせていただいたように、LINE NEWSではその他にもさまざまなコンテンツを配信していますし、これからさらにコンテンツを増やしていく予定です。なので、ぜひみなさんLINE NEWSを使ってください! ということで、セッションを締めたいと思います。

セッションはこれで終わりとなりますが、本日の話の中でもっと詳しく聞きたいことや本日の話と関係ない部分でも、LINE NEWSについて技術的でも何でも何か聞きたいことがある方は、ぜひこのあとAsk the Speakerでお話をさせていただければと思います。また、採用ブースのほうにもLINE NEWSのエンジニアが立っているので興味のある方はぜひ足を運んでみてください。

では、本日はご清聴どうもありがとうございました。

(会場拍手)