ZOZOTOWN カート決済機能リプレイス Phase1 〜 キャパシティコントロールの実現

ogp

こんにちは。ECプラットフォーム部 カート決済ブロックの高橋です。

ZOZOTOWNでは、数年前よりClassic ASPからJavaへのリプレイスが実施されています。そのリプレイスの一環として、2021年4月からカート決済機能のマイクロサービス化を開始しました。

ZOZOTOWNの中長期目標である「商品取扱高5000億円」を達成するために、リプレイス後は以下の要件をシステムが満たしている必要があります。

  • セールなどの高負荷イベント時にスケール可能であること
  • キャパシティコントロールが可能であること
  • Datadog、SentryなどのSaaSを利用した運用監視の効率化できること
  • CI/CDなどを取り入れ、開発生産性を向上できること
  • レガシー技術をモダン化できること

そして、カート決済機能はZOZOTOWNの中でも最も大きな機能であり、最も重要な機能です。そのため、リプレイスは慎重に進めなければなりません。

本記事では、そのリプレイスのPhase1として先日リリースし、キャパシティコントロールを実現させた事例を紹介します。

カート投入機能のリプレイス

本章では、リプレイスしたカートの1つの機能である「カート投入機能」の概要を説明します。

カート投入機能の仕様

まず、ZOZOTOWNにおけるカート投入の仕様を説明します。

「カートに入れる」ボタンを押すことで、以下の処理が行われます。

  1. 在庫を引き当てる
    • 同時に、在庫数を減らす
  2. カートテーブルへ、その情報を登録する

ZOZOTOWNのカート機能の大きな特徴は、カート投入時に在庫の引き当てをしている点です。今回のリプレイスでは、それらのカート投入の仕様は変更せず、アーキテクチャの変更のみを行っています。

つまり、カート投入時に在庫の引き当てをする仕様を変更しないため、「FIFO(First-In First-Out)であること」が重要な要件となりました。

アーキテクチャの変更

これまでは、下図に示すアーキテクチャでした。リクエストをIISで受け、Classic ASPからストアドプロシージャを呼び出し、処理を実行していました。

image

そして、今回のリプレイスでは、下図のアーキテクチャに変更しています。IISとストアドプロシージャの間にCart Queuing Systemを新規で作成します。これにより、Cart Queuing Systemとストアドプロシージャ呼び出し用のIISを新たに追加しています。

image

リプレイスの目的

現在のシステムでは、ZOZOTOWNの成長に伴い、高負荷に耐えられなくなる可能性があります。そのため、今回のカート機能のリプレイスの目的は、キャパシティコントロールを可能にすることです。

それを実現するために、Cart Queuing Systemは、下図に示す構成にしました。

image

リクエストのステータス管理をAmazon DynamoDB(以下、DynamoDB)で管理します。そして、Amazon Kinesis Data Streams(以下、KDS)には、IISへのリクエスト情報やカート投入情報テーブルのキー情報を送信します。

全体の流れは、Cart APIの登録APIが呼び出されるところから始まります。その登録APIでは、以下の処理を行います。

  1. 商品情報などのカート投入に必要な情報をDynamoDBへ登録する
  2. 上記の 1. で登録した情報のキーと、Cart WorkerがAPIへリクエストするのに必要な情報をKDSへ送信する

次に、Cart Workerで以下の処理を行います。

  1. KDSに送信された情報を取得する
  2. DynamoDBの対象レコードに対し、ステータスを「処理中」に更新する
  3. 上記の 1. で取得した情報を元に、ストアドプロシージャ呼び出しAPIへリクエストする
  4. 上記の 3. のレスポンスを元に、DynamoDBの対象レコードに対し、ステータスを「処理完了」に更新する

そして、ステータス取得APIでは、以下の処理を行います。

  1. 登録APIで登録したDynamoDBのキーを元に、対象レコードのステータスを取得する
  2. ステータスがCart Workerによって「処理完了」に変更されている場合、DynamoDBに登録されている情報を返却する
  3. Cart Workerの処理ステータスが「処理中」の場合、一定間隔で指定期間までポーリングを行う
  4. 上記の 3. のポーリングの間にステータスが変更された場合、上記の 2. の処理を行う
  5. 上記の 3. で指定期間内にステータスが更新されなかった場合、タイムアウトとして返却する

なお、Cart APIにあるステータス取得APIのレスポンスがタイムアウトである場合、IISからステータス更新APIを呼び、DynamoDBのレコードのステータスを「タイムアウト」に更新します。

技術スタック

Cart APIとCart Workerの技術スタックをご紹介します。

Cart API Cart Worker
言語 Java Java
フレームワーク Spring Boot -
データベース DynamoDB DynamoDB

なお、Cart WorkerはKinesis Client Libraryを使用したアプリケーションであり、フレームワークを使用していません。

過熱商品への対応

ZOZOTOWNでは、福袋のように限定で発売されるような商品があり、このような商品を「過熱商品」と呼んでいます。過熱商品は、発売開始のタイミングで一時的にアクセスが急増する特徴があります。これまでのシステム構成では、そのアクセス急増により、過熱商品以外の通常の商品をカートに投入しようとしているユーザーにも影響が出ていました。そのため、今回のリプレイスでは、過熱商品への対応も行っています。

その対応として、前述した登録APIの処理の中には、過熱商品かどうかを判定する処理が追加されています。そのため、過熱商品の情報のみを持つテーブルが新たに必要になります。そして、すべてのリクエストが、新しく用意した過熱商品用テーブルを参照しているため、パフォーマンスを考慮してAmazon DynamoDB Accelerator(以下、DAX)を使用しています。

過熱商品の判定を含めた、登録APIからCart Workerへの処理の流れは以下の通りです。

image

  1. リクエスト時の商品情報を元に、過熱商品用テーブルへ問い合わせをする
  2. 商品情報などの情報をDynamoDBのカート投入情報テーブルに登録する
  3. 上記の 2. で登録した情報のキーと、後続のCart Workerが次のIISのAPIへリクエストする際に必要な情報を、KDSへ送信する
    • 上記の 1. でデータが存在した場合は、過熱商品用のストリームを使用する
    • 上記の 1. でデータが存在しない場合は、通常用のストリームを使用する
  4. KDSの情報を取得する
  5. 上記の 4. で取得した情報を元に、カート投入情報テーブルのステータスを更新する
  6. 上記の 4. で取得した情報を元に、IISへリクエストする
  7. カート投入ストアドプロシージャを呼び出す

通常商品と過熱商品でストリームを分けたことで、過熱商品が発売するタイミングであっても、通常商品をカート投入するユーザーに大きな影響を与えることがなくなりました。また、商品単位でKDSの同一のシャードを使用しており、FIFOのカート投入順も担保できるようになっています。

問題点とその対処法

本章では、リプレイスを進める中で見つかった問題点と、その対処法の一部を紹介します。

DAXへのアクセスをローカルで試行できない

AWSを利用したシステムを開発するため、ローカル環境でLocalStackを使用していました。DynamoDBやKDSは、LocalStackを使用してモック環境を構築し、開発を進めていました。

しかし、DAXはLocalStackでは構築できないため、動作確認ができない状態で実装を進める必要がありました。さらに、ローカル環境で実行していたものを、クラウド上で実行させる場合には、SpringのDIコンテナに管理するクラスを変更するという対応も必要でした。

最終的には、ローカル環境で開発する際にはDAXへの通信ではなく、LocalStackで構築したDynamoDBを参照する形式で開発を進めました。

モニタリングが1画面でできない

ZOZOTOWNのモニタリングには、Datadogを導入しています。Datadogは、トレースをリクエスト単位で収集します。そのため、Cart APIからCart Workerまでのトレース情報が別々に表示されてしまい、エラー調査時にそれらの関連付けに苦労していました。

そこで、必要な情報をAPIのリクエストに渡すことで、すべてのトレース情報を1つの画面で表示できるようにしました。その結果、下図のようにDatadogのトレース情報を集約できました。

image

登録APIからCart Worker、ステータス取得APIまで1つのトレース情報として表示されていることがわかります。これにより、Cart Queuing Systemのエラー発生時には、情報が1つの画面に集約して表示されるため、エラー調査もスムーズになりました。

おわりに

冒頭で説明した通り、カート決済機能はZOZOTOWNの中で最も大きい機能です。今回のカート機能のリプレイスは、キャパシティコントロールを目的にした「カート決済機能リプレイス Phase1」です。今後も、マイクロサービス化を半年から1年にフェーズを分けて、段階的に進めていきます。

ECプラットフォーム部 カート決済ブロックでは、仲間を募集しています。ご興味のある方は、こちらからご応募ください。

hrmos.co

カテゴリー