こんにちは! クロスユースプラットフォーム(以下 クロスユースPF)開発の大島です。
Yahoo! JAPAN Tech Conference 2019 in Shibuyaで大量のトラフィックを高速に捌くヤフーのコンテンツを紹介するプラットフォーム、「クロスユースPF」のシステムについて話をしました。
本記事では発表を行った内容の一部を紹介したいと思います。
YJTC クロスユースプラットフォーム ~ 秒間10万リクエスト・レスポンスタイム100ms以下を実現するシステムについて ~
クロスユースPFとは
クロスユースPFとはユーザーにYahoo! JAPANのコンテンツを紹介するためのプラットフォームです。
Yahoo! JAPAN内のさまざまなページでYahoo!ショッピングの商品情報やお得なクーポン情報、その他のお得情報などいろいろなコンテンツをユーザーごとに表示しています。これらのコンテンツはユーザーにヤフーをもっと知っていただき、より便利に使ってもらうことを目的として表示しています。
クロスユースPFを利用しているページは、Yahoo! JAPANのトップページやYahoo!ニュース、Yahoo!ショッピングなどがあります。
どのページもアクセス数の多いページとなっており、クロスユースPFを利用している全ページの最大リクエスト数を合計すると秒間10万リクエストほどです。
また、クロスユースPFはユーザーがYahoo! JAPANのページにアクセスした後に呼ばれるため、少しでも早くコンテンツが表示できるよう100ms以内でレスポンスを返すことが求められています。
まとめるとクロスユースPFは以下の機能・要件を満たすPFとなっています。
- ユーザーごとにニーズにあったコンテンツを表示
- 秒間10万リクエストを捌く
- レスポンスタイム100ms以下でコンテンツを返す
システム構成
システムは大きく分けてPivotal Cloud Foundryを用いてPaaS基盤上で稼働させているアプリインスタンスと、データストアとして各種データを格納しているCassandraの構成となっています。
スループットとレイテンシー
現在では大量のリクエストを受けているクロスユースPFですが、サービス開始当初はクロスユースPFを利用しているページも少なく現在ほど厳しい要件は求められていませんでした。
サービス開始から2年間で多くのページにクロスユースPFが導入されたことによって、スループット要件は2年間で約33倍、レイテンシー要件はサービス開始当初と比べると50%の速度でコンテンツを返すことが求められるようになりました。
サービス開始時 | 現在 | |
---|---|---|
スループット要件 | 3,000rps | 100,000rps |
レイテンシー要件 | 200ms | 100ms |
要件を満たすためにクロスユースPFでは、要件が厳しくなるとともにスケーリングを行っています。
PaaS基盤上で稼働しているクロスユースPFのサーバーアプリは、要件の変化によって利用するクラスタ数、インスタンス数をスケールさせており、サービス開始から2年間で40倍にスケールさせています。Cassandraも同様にスケーリングが容易なことを活かし、クラスタ、ノード数を2年間で約17倍にスケールさせています。
サービス開始時 | 現在 | |
---|---|---|
サーバーアプリインスタンス | 2インスタンス | 80インスタンス |
Cassandraノード | 12ノード | 200ノード |
このようにスケーリングを行いスループット要件、レイテンシー要件を満たそうとしましたが、これだけでは要件を満たすことができませんでした。
課題とその対策
四つの課題
クロスユースPFでは過去に4度ほどレイテンシー周りで問題が起こり、以下のような課題が発生しました。
第一の課題
サービス開始に時のレイテンシー要件は200msが求められていた。
開発時はCassandraからのデータ取得に時間がかり、レイテンシー要件を満たせておらず課題となった。
第二の課題
利用されるページの拡大に伴い、より高い品質が要求されるようになり、レイテンシー要件が200ms→100msへと厳しくなった。
Cassandraから取得していたコンテンツは、データサイズが大きく取得に時間がかかり、要件を満たせておらず課題となった。
第三の課題
適切なユーザーにコンテンツを表示させるために、出し分けをより詳細に行うようになっていった。
ルール結合データが肥大化しリクエスト数は変わらないが、Cassandraからのデータ取得に時間がかり、要件を満たすのが厳しくなり課題となった。
第四の課題
クロスユースPFが多くのページで利用されるようになり、データ数、データサイズが増えた。
データの増加によってCassandraの特定ノードでデータの偏りが発生、多くのデータが入っているノードのレイテンシーが悪化してしまい課題となった。
本ブログでは四つの課題の中から「第二の課題」、「第四の課題」二つをピックアップし、課題と対策について紹介します。「第一の課題」、「第三の課題」についてはYJTCのスライドをご覧ください。
YJTC クロスユースプラットフォーム ~ 秒間10万リクエスト・レスポンスタイム100ms以下を実現するシステムについて ~
利用データ
課題と対策の説明をする前に説明の中で出てくるクロスユースPFデータの説明をします。
上記の図は各データの関わりを表した図です。
ページID、ルール、コンテンツ、ユーザー属性のデータはクロスユースPFで利用されているデータとなっています。
ページID
Yahoo! JAPANのトップページや、Yahoo!ショッピングページなどページを示すIDです。
ルール
アクセスしてきたユーザーの属性に応じて、適切なコンテンツを選ぶためのデータで各属性に対してYes, Noの分岐の組み合わせでデータを保持しており、分岐の数だけコンテンツが紐づいています。
コンテンツ
商品の情報やクーポン情報などユーザーにお得なデータをHTMLやJSONの形式で保持しています。
ユーザー属性
ユーザー情報とヤフーが持っているビッグデータを組み合わせたデータとなりルールの分岐をたどる際に利用します。
第二の課題
第二の課題はクロスユースPFを運用していくにつれて、より高い品質が求められるようになり、レイテンシー要件が200msから100msへと厳しくなった際に起こった課題です。
レスポンスに100ms以上かかる要因として、ルールとコンテンツを組み合わせたルール結合データのデータサイズが大きく、Cassandraからのデータ取得に時間がかかることが原因です。
リクエストの度にぺージIDに紐づくルール結合データを、都度取得しているためレスポンスに時間がかかっていました。
この課題を解決するために、ページIDとルール結合データをキャッシュとして保持するようにしましたが、考慮する点が2つあります。
- ページIDには複数のルールが紐づいている
- ルールには開始、終了という概念があり有効期限が存在する
課題を改善するために試行錯誤を行ったため、「初期キャッシュ仕様」、「現行キャッシュ仕様」の2つを紹介します。
初期キャッシュ仕様
キャッシュはページIDとページIDに紐づくルール結合データの組み合わせで保持しています。
上記図のようにページ1に対して複数のルール結合データを紐付け、1つのキャッシュとして保持しています。
保持するデータは有効期限内のルールデータに加え、キャッシュ定期更新時間内に開始となる未来のルールデータも含んでいます。
キャッシュしている間に未来のルールが有効になった場合でも、正しいコンテンツが返せるようにするためです。
キャッシュの有効期限はルールの中で最も期限が短い値が設定されており、期限が切れた場合は再度Cassandraにアクセスしキャッシュの生成を行います。
しかし、このキャッシュの仕組みでは以下の課題を抱えていました。
- キャッシュにはキャッシュ更新時間内に始まる未来のルールを含むため、キャッシュデータを利用する時は現在有効なルールだけにフィルタリングすることが都度必要
- 日付が変わる際に多くのルールが有効期限を迎え、キャッシュ更新処理が一斉に行われるためCassandraにスパイク的な負荷が発生
現行キャッシュ仕様
初期キャッシュ仕様の課題を解決したものが、現行キャッシュ仕様です。
現行キャッシュ仕様は実際のコンテンツ表示に使われるL1キャッシュと、Cassandraからデータを取得しキャッシュとして持つL2キャッシュの2つから構成されています。
* L1、L2キャッシュはクロスユースPF内の定義
L1キャッシュ
L1キャッシュは現在有効なルールのみ保持しており、未来のルールは保持していません。
そのため初期キャッシュ仕様で課題となった、キャッシュを利用する際に未来のルールを考慮する必要がなく、フィルタリングが不要となり処理時間が短くなっています。
またキャッシュの有効期限が切れた場合は、L2キャッシュから必要なデータを取得し、アプリ内でキャッシュ更新を行えるようにしています。
L2キャッシュ
L2キャッシュが保持しているデータは、初期キャッシュ仕様と同様に未来のルールを含めた全てのデータです。
初期キャッシュ仕様と違う点は、有効期限が切れた際のキャッシュの取り扱いとなっており、期限切れのルールデータのみキャッシュから削除する仕様です。
この対策を行ったことで、日付が変わる際に発生する大量のキャッシュ更新によるCassandraの負荷上昇を回避しています。
第四の課題
第四の課題はクロスユースPFを利用するページが増え、リクエスト数が増加しレイテンシーが悪化した課題です。
この課題はCassandraのレスポンス悪化が原因です。
Cassandoraのデータは上記図の右上のオレンジで囲んであるPartition Keyの設定によって分散されるため、適切に分散されるよう考慮して設計するのが一般的となっています。
Primary Keyに設定されているデータがPartition Keyですが、クロスユースPFはページIDがPartition Keyとなっており、うまく分散が行われませんでした。
* Cassandraのレプリケーション数は3
ページIDごとに紐づくルール結合データ数、データサイズが違うため、上記図のようなデータ数が多くデータサイズが大きいページAは、Cassandraからのデータ取得に時間がかかり、同時に取得が行われると負荷がかかります。
一方、データ数、データサイズともに少ないページCはCassandraからのデータ取得に時間はかからず、同時に取得が行われても大きな負荷とはなりません。
ページIDごとにPartition Keyを切っていたことから、3つのノードにしかデータが分散されず、ページAのようなページのデータ取得が大量に行われると、Cassandraの対象ノードが高負荷となり、レイテンシーが悪化してしまいました。
ページID単位でデータを格納していたことから、負荷分散が十分に行われずCassandraノードのスケーリングが活かせていませんでした。
課題を解決するためにはデータをより多くのノードに分散させる必要があり、Partition Keyの設定を見直しました。
新たにランダムIDを定義し、ページIDとランダムIDの複合Partition Keyとして設定しました。
ランダムIDは[0-19]までの数値データとなっており、ページIDごとにランダムID[0-19]がついた同一のデータが生成され、分散して各ノードに保存されるようになります。
この対策を行ったことで最大で60ノードにデータが分散されるようになりました。
データを読み出す際はページIDと、都度ランダムで生成されるランダムIDを用いてデータの取得を行っています。
キャッシュ更新を行うアプリインスタンスは現在80インスタンスあるため、対策前は3つのノードに対して80インスタンスがキャッシュ更新を行っていましたが、対策の結果最大60ノードに対して80インスタンスがキャッシュ更新を行うようになり、1ノードあたりの負荷が大幅に軽減されました。
まとめ
本記事では実際にクロスユースPFで行っているシステムのスケーリングや、データの持ち方について説明しましたが、クロスユースPFで発生した課題は世の中で運用される他のシステムでも発生し得るものです。
そのため、クロスユースPFのようなスループットとレイテンシーを両立したシステムを運用していくには、以下のような点を考慮する必要があります。
- インフラからデータストアまで全体がスケーリングする仕組み
- データストアとシステムの特性を十分に理解した設計
- 環境の変化、システム劣化の継続的な監視
システムを長く運用していると環境の変化によって、当時有効な対応を行ったシステムも陳腐化してしまい、再度対応が必要となることがあります。
そのため継続的にシステムをウォッチし、常に新しい対策を考え実施していく必要があります。
今後もクロスユースPFは厳しい要件が求められるPFです。どのような要件でもヤフーとしての品質にこだわり続け、大量のリクエストを高速かつ安定的に捌けるよう、改善を続けていきたいと思います。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました
- 大島 圭貴
- クロスユースプラットフォームエンジニア
- クロスユースプラットフォームの開発、ディレクション業務を行なっています。