Yahoo!ショッピングは1999年からサービスを開始しており、毎年、年末商戦の大規模なセールを実施してきました。特に11月11日を「いい買い物の日」と定めて、2015年~2019年はセールを開催していましたが、2018年にPayPayがサービスを開始したこともあり、2020年からは「超PayPay祭」と銘打っていくつかの季節で大規模セールを開催しています。
下図は、大型セールで「最も売れた日の取扱高」を棒グラフで示しています。年々記録は伸びており、2014年と2021年を比べると40~50倍もの差がありました。なお、2022年は現在もセール開催中のため、さらに伸びるかもしれません。
また、当日中の「1秒間当たりの最大注文数」を折れ線グラフでプロットしました。同じく年を追って成長していますが、ここ数年に関してはある程度平準化されているのが分かります。この平準化の取り組みを紹介します。
「1秒間当たりの注文数」が1日当たりでどう推移しているかを下図に示しました。0時のセール開始直後や9時~12時頃に山があり、夕方から増えて24時直前にピークを迎えるという流れになっています。
負荷対策の四つのアプローチ
Yahoo!ショッピングのセールは順調に成長を遂げているものの、その舞台裏では注文が集中することで通信障害や事故が発生し、ユーザー様やストア様にご迷惑をおかけした事実もあります。
こうした課題に対して行った負荷対策の中で効果のあったものを「ビジネス」「システム」「UI/UX」「組織」という4つの側面から、それぞれ紹介します。
ビジネス面での負荷対策
まず、ビジネス面ではセールのスケジュールを変更しました。もともと「いい買い物の日」は11月11日と定めて始めた経緯もあり、一日でいかに取扱高を伸ばせるかを最大の目標としていましたが、複数日に分けてトータルで売上を伸ばす方針に変更しました。
そこでのポイントは3つです。まず、セールの期間自体を伸ばしました。現在開催中の超PayPay祭は約1カ月間の開催となっています。2つ目は、最終日がよりお得になる「グランドフィナーレ」を単日ではなく、2日間にわたって開催するようにしました。3つ目は、グランドフィナーレの終了時刻を24時ではなく、そこから2時間延長して日付が変わっても買い物していただけるようにしました。
システム面での負荷対策
次にシステム面での負荷対策です。ここが全体の取り組みの中で特に大きなウェイトを占めています。下図は購入導線を簡易化したシステム構成図です。
一番左にあるのが、ユーザーが商品を選んで注文を確定させるUIを提供するカートシステムです。注文を確定させると、その情報が注文プラットフォームに流れます。ここで注文情報を保存し、ストア様の注文変更や配送処理を受け付けています。受注の際に、注文情報は決済システムにも流れています。そこでは各種決済手段を提供しており、最終的な決済が行われる流れです。
ヤフー内にはショッピングシステムのほかに、共通インフラとしてコマースに特化したチームがあります。そのシステムでウォレットやPayPayのゲートウェイ、アクワイアリングと連携しながらサービスを提供しています。この連携があることで、例えばPayPayの残高払いやPayPayカード払いなどにも対応できるようになっています。
また、共通して行った負荷対策を3つ紹介します。
1つ目はスケールアウトとスケールアップです。セールが決まると、負荷対策の指標が展開されます。各コンポーネントは、その指標に従いながらWebサーバーやAPIサーバーなどをスケールアウトします。データベースではスケールアウトが難しいケースもあるため、ここ数年でバージョンアップを行いました。具体的には、Oracle DBのバージョンを12Cから19Cに切り替えています。まずは注文と決済のデータベースを皮切りに、現在では全システムの切り替えを完了しました。
2つ目は、非同期化です。データベースに書き込みが殺到すると、処理しきれない部分もあるため、流量を調整する目的で設けました。注文システムと非同期の対応を行い、データベースへの書き込みを一定速度で行うように変更しました。これでユーザーから注文が殺到した場合にもデータベースを保護できます。また、決済とアクワイアリングの間にも非同期を設けて、アクワイアリングの先につながる各カード会社との連携を保護しています。
最後にRateLimitです。これは一定数のしきい値を設け、しきい値までのアクセスなら正常に処理しますが、しきい値を超えた場合はエラーにして返す機能です。カートとPayPayのゲートウェイとアクワイアリングのそれぞれに設けています。特にカートのRateLimitは、購入導線のシステム全体を保護する目的もあるため、セール最中でも設定値を変更できるようにしています。
データベースの待機イベントを削減した方法
ここまでは、システム面における共通の負荷対策として行ってきたものを紹介しましたが、次はデータベースに特化した内容を紹介します。
まず代表例として、注文データベースではキャッシュフュージョンによって待機イベントが発生するという課題がありました。
注文データベースは3ノード構成×2セットの6ノード構成をOracle DBで組み、ストアIDをプライマリーキーに持っているようなシステムです。注文情報だけでなく、キャンペーン情報や注文に付随する情報も保持しているため、非常に大きなレコード数を扱うという特徴があります。
注文を受け付けた際に追加され、ストアの変更処理を受け付けて更新がかかります。参照に関しては、簡易的な注文情報をリストで複数個引く「一覧参照」と、詳細を確認する「詳細参照」と2種類に分けられます。
データベースはストアとユーザー、双方からそれなりのアクセスを受けています。特にユーザーの一覧参照がリクエストとして処理が重くなっていることが判明しました。対策以前は、6ノードに対してアプリケーションからデータベースクライアントで均等にアクセスされていました。
この中で、例えばノード1へ注文が追加され、合わせてノード3に同じユーザーの注文一覧参照のリクエストが走った場合、ノード1に入った受注情報をノード3に反映しなければなりませんが、この間に待機イベントが発生しているという状況でした。
そこで、今回はこの部分に対する対策を行っています。具体的にはCQRSパターンを用いて参照と更新を分離する対策を行っています。ポイントは3つあります。
1つ目は、シャーディングです。先述したように、もともと均等にアクセスする形で処理を行っていました。そこで、プライマリーキーのストアIDごとにアクセス先のノードを変更するシャーディングを行いました。これによりリクエスト先を制限することで待機イベントの削減につなげました。下図のように、例えばAストアの注文や参照はノード1に、Cストアはノード3に行くような形を取っています。
リクエスト先は、直近数カ月間のアクセス状況や注文状況を確認し、ストアの偏りが出ないようストアごとに分散するロジックにしています。
2つ目のポイントとして、Read Onlyのレプリカを用意して対策を行っています。シャーディングを行った後に、こちらのノードと同じデータを保有しているものを、Oracle Data Guardの非同期処理で連携することで別ノードを立てました。こちらに対し参照を一部切り替えることで、もとのノードの負荷を下げる対策を実施しています。
ただし、Oracle Data Guardが非同期になるため、リアルタイムで参照するリクエストはこちらのレプリカに向けることができません。そのため、リアルタイム性を求めないリクエストのみを選定しています。
3つ目がデータソースの分離です。最も負荷が重かったユーザーの一覧参照をもとのノードにリクエストさせない対策を行いました。具体的には注文情報データベースに入っているもののうち、ごく一部に特化したものを切り出し、それをKVSに保存する形にしています。今回はApache Cassandraを用いました。
注文が入るとデータベースより先に、KVSへいったん書き込みを行ってからデータベースを更新するという処理の順番になっています。KVSに専用APIを立て、各フロントの向き先がKVSとなるよう変更しました。
こうした3つの対策で、もとのノードに対する負荷を下げられました。
UI/UX面での負荷対策
先ほどのKVSは、実は別の課題解決にもつながっています。KVSへのデータの書き込みとデータベースへの書き込みは非同期になっているため、「非同期の処理がどこまで進んでいるか」をユーザーに伝えられないという課題がありましたが、KVSを参照することで、この点も解消できました。それに応じたUIをデザインチームに用意してもらっています。
上図左側は、注文完了画面です。注文完了の表示を新たに「ご注文の手続きを進めております」へと変更し、「一度操作を受け付けているので、これから反映します」というステータスを表示する形にしています。
右側は注文履歴一覧の画面です。こちらも「注文受付中」と注文情報を表示させ、ユーザーに「注文を正しく受け付けてもらえた」と伝わる表示に変更しています。
また、「混雑予想」の情報も提供できるようにしました。過去のアクセス傾向をもとに混雑している時間帯を表示して、空いている時間帯をユーザーにお知らせします。セールなど複数の画面に表示して、注文の集中を緩和するようにしました。
このほかの点としては、これまでRateLimitに引っかかるとユーザーにはエラーという情報を返す流れとなっていましたが、これはユーザーにとって、「買おうとしたのに買えなかった」という好ましくない体験になってしまいます。そこで「お詫び画面」と呼ばれる画面へ誘導するように変更しました。さらに先ほどの「混雑予想」のグラフ表示もこの画面に合わせて用意することで、買いやすい時間帯を案内するような形としました。グラフの内容も、状況によって差し替えられるようにしています。
組織面での負荷対策
セール前の準備では、全システム横断的に負荷をかけ問題が起きないかの試験を行っています。Yahoo!ショッピングだけでなく全社的に関連する部署や、社外であればPayPayやPayPayカードにも協力いただき、試験を実施しています。
下図のような組織体制で、各部門の技術責任者にも参加してもらい、横断的なチームを組んで対策を行っています。全体方針の策定や他社との連携をスムーズに行うための組織体系です。
同じような考え方を下図のようにYahoo!ショッピングにも適用しており、SREの組織を新しく立ち上げました。SREは非機能面を担当する組織として、普段はトイルの削減・効率化などに取り組みますが、セールの負荷対策も担っています。これまで各コンポーネントで行っていた負荷対策を横断的に見たり、ノウハウの横展開を進めたりして、従来よりもスムーズな対応を実現できています。
負荷対策の成果と今後の展望
ここまでご紹介した大きく4つの対策の結果、2019年の「いい買い物の日」と2021年春の超PayPay祭を比較すると、以下の結果となりました。
まず、2021年は左端のように開始時の駆け込みが高くなっていました。この点は予想できていなかったものの、無事に処理できました。また24時のピークは、先述した2時間のロスタイムの確保で、ある程度緩和できたと思います。
最後に、今後の展開について紹介します。
まず、ユーザー向けUI/UXのブラッシュアップです。混雑予想や受付中の表示を伝えるという点はすでにお伝えしましたが、まだセール開始時の取り組みや、障害発生時の通知・コミュニケーションは改善の余地があると考えています。
また、これまで「購入後に非同期で待ってもらう」という仕組みに特化してきましたが、「購入前に待ってもらう」というアプローチも考えられるかもしれません。リアルタイムでレジ前に行列ができているイメージが近いのではないでしょうか。他者ECサイトで実際に取り組まれている施策であり、弊社としても検討するべきものと考えています。
続いて、ストア様向けに機能改善も目指しています。現在、ストア様向けにツールを複数提供しており、告知枠で案内をしていますが、障害の連絡も通常のお知らせと同じ温度感で連絡をしているため、見せ方や連携方法も改善したいと思います。そして、先ほど紹介した受付中の表示は、まだストア様には見せられない状態になっているため、対策を検討しております。
ほかにも、SPOF(単一障害点)もまだ残っている箇所があるため、取り組まなければならないと考えています。
組織体制については、準備をしていても障害や事故が起こってしまうため、現在の連携方法をさらにアップデートできないかと考えています。SREを立ち上げて中心に動いてもらっていますが、告知方法や役割、ユーザー様・ストア様とのコミュニケーションの対策を打っていきたいと考えています。
アーカイブ動画
Apache®, Apache Cassandra™, 及びCassandraのロゴは、米国および/またはその他の国におけるApache Software Foundationの商標または登録商標です。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました
- 常澤 邦幸
- ショッピング統括本部 部長
- 2007年からヤフーにてEコマースの開発に従事。2013年よりショッピング担当に。各種機能開発のほか、グループ会社でのCRM機能の開発を経験し、2019年より注文決済領域を担当として現職に至る。