Amazon Web Services ブログ

ZOZOTOWN のショッピングカート移行プロジェクトを支えた Amazon DynamoDB

このブログでは、リレーショナルデータベース管理システム (RDBMS) のパフォーマンスの問題があった e コマースサイトのケーススタディと、Amazon DynamoDB がソリューションにどのように貢献したかを解説します。ZOZOTOWNには大規模な販売イベントがあり、サービスに何か問題が起きた時に対応するため、エンジニアがリアルタイムで監視を行なっていました。DynamoDB によって ZOZO はエンジニアリングのオーバーヘッドを 85.8% 削減できました。また、DynamoDB のベストプラクティスもいくつか紹介します。

この投稿は、日本有数のファッションオンライン取引サイトの1つであるZOZOTOWNのDynamoDB利用に焦点を当てています。半澤詩織さんは、ZOZOTOWNのECの開発と運営を担当するエンジニアの一人です。この投稿では、彼女が ZOZOTOWN の DynamoDB への移行を決定した理由を説明します。

ZOZOTOWNについて

1,500以上のショップ 、8,400以上のブランド を 取り扱う日本最大級のファッション通 販サイト「ZOZOTOWN」。常時 83万点以上の商品アイテム数と毎日平均 2,900 点以上の新着商品を掲載しています ( 2021年12 月 末 時 点 )。株 式 会 社 ZOZO は 、 「 ZOZOTOWN 」 を は じ め 、 専門モール「ZOZOCOSME」や「ZOZOSHOES」、ブランド古着を取り扱う「ZOZOUSED」、ラグジュアリー&デザイナーズブランドを取り扱う「ZOZOVILLA」、ファッションコーディネートアプリ「WEAR」など、各種サービスの企画・展開をおこなうほか、「ZOZOSUIT 2」「ZOZOMAT」「ZOZOGLASS」などの計測テクノロジーの開発・活用にも取り組んでいます。

なぜZOZOTOWN が DynamoDBを利用したのか

ZOZOTOWN はシステムの成長に伴い、段階的に機能をマイクロサービスに切り出しながらリプレイスを実施しています。 そのリプレイスの中で、今回対象としたのがカート投入機能です。本機能の主な処理は、商品の在庫引き当てと、カートテーブルへの登録です。そして、人気商品の発売時などリクエスト数が急激に跳ね上がるイベントがあり、その際には特定の在庫データへのアクセスも集中します。その結果、DB が高負荷状態となり、エラーが多発するという課題を抱えていました。また、これに伴う運用コストも大きく、都度対応に追われる現場のエンジニアは疲弊していました。

この課題を解決するために、柔軟なキャパシティコントロールが可能なDynamoDBへのリプレイス案が浮上しました。最終的には現在オンプレミス上にあるSQL Serverの在庫テーブルとカートテーブルをDynamoDBへ置き換える想定です。それに向け、いくつかのフェーズに分けて段階的にリプレイスしていく予定です。

その一歩目として、カート投入処理にキューを追加し、オンプレミス上のDBへの負荷軽減を目的としたキャパシティコントロール可能な構成へ変更することにしました。
次の図 1 は、以降の段落で説明するリプレイス後のアーキテクチャを示しています。

図 1: リプレイス後の ZOZOTOWN データベースアーキテクチャ

リプレイス前は、Web サーバからオンプレミス上の DB にあるストアドプロシージャを呼び出し、そのストアドプロシージャ内で在庫引き当てとカートへの登録処理をしていました。リプレイスでは、これをストアドプロシージャ呼び出し前に、カート用のマイクロサービスを介して
Amazon Kinesis Data Streams(KDS)を挟む構成に変更します。また、人気商品の判定と、リクエストのキューイング状態を管理するために DynamoDB を使用します。

処理の流れは以下の通りです

  1. Web サーバからカート用マイクロサービスのリクエスト登録 API を呼び出し
  2. リクエスト登録 API は、以下の処理を行う
    1. DynamoDB の人気商品テーブルに問い合わせ
    2. DynamoDB のステータス管理用テーブルへ初期ステータスを登録
    3. KDS へステータス管理用テーブルのパーティションキーとリクエスト情報を送信。人気商品とそれ以外の商品でストリームを分離しているため、人気商品の場合は人気商品用ストリームへ送信
    4. ステータス管理用テーブルのパーティションキーを返却
  3. ワーカーは以下の処理を行う
    1. KDS からリクエスト情報を含むレコードを取得
    2. ステータス管理用テーブルのアイテムをリクエスト済みステータスへ変更
    3. バックエンドのストアドプロシージャ実行 API を呼び出し
  4. ストアドプロシージャ実行 API は在庫引き当てとカートへの登録処理を行い、実行結果を返却
  5. ワーカーが DynamoDB のステータス管理用テーブルに実行結果を保存
  6. Web サーバがステータス取得 API を呼び出し、ユーザへリクエスト結果を返却

この移行の詳細については、 ZOZOTOWN のブログ記事 を参照してください。

DynamoDB のベストプラクティス

DynamoDB の読み取り/書き込みスループットの課金方法と容量の管理方法のうち、オンデマンドとプロビジョンドのどちらを選択するかは早い段階で決定する事が出来ました。

オンデマンドモードは、キャパシティプランニングなしで実質的に無制限のスループットにスケーリングできる柔軟な課金オプションです。DynamoDB オンデマンドでは、読み取りリクエストと書き込みリクエストに対してリクエストごとの料金が提供されるため、使用した分だけ料金が発生します。

プロビジョンドモードでは、アプリケーションに必要な読み取りと書き込みのスループットを指定できます。また、プロビジョニングされた容量で Auto Scaling を使用して、トラフィックの変化に基づいてテーブルのスループット容量を自動的に調整することもできます。これは、コストの予測可能性を得るために、定義されたリクエストレート以下にとどまるように DynamoDB の使用を管理するのに役立ちます。

以下の条件のいずれかに該当する場合、オンデマンドモードは適切なオプションです。

  • 不明なワークロードを含む新しいテーブルを作成
  • アプリケーションのトラフィックが予測不可能
  • わかりやすい従量課金制の支払いを希望

以下の条件のいずれかに該当する場合、プロビジョンドモードは適切なオプションです。

  • アプリケーションのトラフィックが予測可能
  • トラフィックが一定した、または徐々に増加するアプリケーションを実行
  • キャパシティーの要件を予測してコストを管理

まずオンデマンドモードで一定期間使用し、次にプロビジョンドモードで同じ期間使用し比較を行い自らのワークロードに最適なオプションを決定するのがよい方法だと思います。

カート投入リクエストがどの程度来るのかはイベントや時間帯によって大きく異なるため、キャパシティを予測することは困難でした。その結果、カート投入機能にオンデマンドモードを選択しました。オンデマンドモードの柔軟な課金とスケーリングの容易さにより、キャパシティを計画する必要がなくなります。DynamoDB に移行した後、いくつかの人気製品が発売されました。オンデマンドモードにより、エンジニアリングのオーバーヘッドなしにこれらの高負荷イベントをサポートすることができました。

また、キャッシュを使用して人気のあるアイテムからの読み取り負荷を減らすという改善を行い、その結果、コスト削減とパフォーマンスの向上につなげる事も実施しました。Amazon DynamoDB Accelerator (DAX) は、Amazon DynamoDB 用のフルマネージド型の高可用性インメモリキャッシュです。DAX は、読み取り集約型のワークロード、特に特定の項目に対する要求率が高いワークロードにフォーカスして実装されました。アプリケーションが DAX からこれらの項目を取得したときに、読み込みキャパシティーユニット (RCU) の消費量を削減し、課金コストを下げました。DAX はTime to Live (TTL) eviction policyを使用するため、 TTL の有効期限が切れると、データはキャッシュから自動的に消去されます。DAX は書き込みスルーキャッシュであるため、クライアントは DynamoDB テーブルとキャッシュを 1 回のオペレーションで更新します。DAX クライアントは DynamoDB と同じオペレーションをサポートし、DAXクライアントライブラリを既存のAWS SDKから変更しDynamoDB クライアントエンドポイントをDAXクラスタに変更するだけで使用できます。これにより、複雑なキャッシュ管理が不要になります。

ZOZOTOWNのカートリプレイスで活用しているテクニック

このセクションでは、ZOZOTOWNが現在活用している機能のいくつかを紹介します。

条件付き書き込みによるキューの制御

ワーカーは Kinesis Data Streams から同じレコードを複数回取得する可能性があります。したがって、何も制御していない場合、次の図 2 に示すように、1 つのリクエストを複数回実行して、ユーザーがリクエストしたよりも多くの数量をカートに追加する可能性があります。


図2: カートテーブルに送信された重複リクエスト

この問題を解決するために、条件付き書き込み機能を使用します。処理の流れは次のとおりです。

  1. ステータス管理テーブルの項目を確認します。
  2. ステータスに応じて、次のプロセスに分岐します。
    1. ステータスが is_queuing (キューイング状態) の場合は、次の手順に進む
    2. ステータスが is_requested (リクエスト済み) の場合は、次の手順をスキップ
  3. ステータス管理テーブルのステータスを is_requested に条件付きで更新
  4. ストアドプロシージャ実行 APIにリクエスト
    以下の図 3 は、ワーカーが既に処理済みの a1 というリクエストと、a2 と、a3 という新規のリクエストを取得した例です。

図3: リクエスト処理フロー

リクエストがユーザーの操作と一致することを確認する処理フローは次のとおりです。

  1. まず、a1 レコードが処理されます。ワーカーは、a1 レコードをパーティションキーとして使用して GetItem を発行し、ステータス is_requested を返します。その後、ワーカーはその後の処理をスキップします。
  2. プロセスは a2 に移行します。GetItem の結果は is_requested ではないため、後続の UpdateItem が実行されます。条件が満たされれば更新は成功し、そうでなければエラーが発生します。
  3. カート投入リクエストには、全体のタイムアウト値が設定されることに注意してください。何らかの理由でタイムアウトが発生した場合、ステータス管理テーブルに timed_out_at が追加され、ユーザーにエラーが返されます。すでにエラーで停止したリクエストの処理を続行することはできないため、UpdateItem オペレーションを使用し、timed_out_at が存在してはならない条件式を含めます。
  4. また、Kinesis Client Libraryの仕様により、複数のワーカーが同じシャード内のレコードを処理する可能性があるため、ステータスが is_queuing という条件も追加します。
  5. a2はUpdateItem の時点で timed_out_at が存在するため更新に失敗します。そのため、後続の処理は行われず、a3 処理に移行します。
  6. a3 は、a2 と同様に条件付き UpdateItem を実行します。その結果、timed_out_at がないため、更新は成功します。このような場合にのみ、後続のリクエストを処理することが許可されます。

ホットキーとBotアクセスの検出

DynamoDB のデータアクセスが不均衡になると、ホットパーティションが発生し他のパーティションに比べて大量の読み書きトラフィックを処理しなければいけない可能性があります。トラフィックが極端に偏った場合は、スロットルが発生する可能性もあります。ZOZO は、CloudWatch contributor insightsを利用したホットキー検出を可能にする強化されたモニタリング機能を構築しました。追加料金が発生しますが、次の情報は、DynamoDB テーブルまたはグローバルセカンダリインデックス (GSI) で最もアクセスまたはスロットリングされたアイテムのグラフが提供されます。負荷テストでは、最もアクセス数の多いアイテムのトラフィックの確認に焦点を当てました。これは、テーブル設計が負荷を効率的に分散していることを確認するのに役立ちました。リクエストが一般ユーザーまたはボットからのものかを識別できるように、アクセス元の特定が可能な要素をパーティションキーとして使用して GSI を構築しました。次の図 4 は、1 分間に最もアクセスされるパーティションキーを示しています。

図4: 1 分あたりのパーティションキーアクセス

図4のグラフの下部にある「ユーザー」と示している点は、数個の商品を一分間のうちにカートへ投入されたことを示します。これは通常の動作です。ただし、グラフで「Bot」と表示される 毎分数百件の連続するリクエストは、一般ユーザーの挙動とは考えにくく1 つ以上のボットからのものと思われます。ZOZOは、この情報を使用してボットを特定し、適切に対応することができます。
まとめ
DynamoDBを使用したカート投入機能のリプレイスプロジェクトは大成功を収めました。頻繁なイベント時のエラーやアクセス集中時のレイテンシが減少し、パフォーマンスが向上しました。

このプロジェクトの詳細は、ZOZOTOWN カート決済機能リプレイス Phase1 〜 キャパシティコントロールの実現(日本語)をご覧ください。


著者について


半澤詩織は、ZOZO, Inc. のカート決済部カート決済基盤ブロックのマネージャーであり、2013年1月にスタートトゥデイ(現在のZOZO)に入社しました。現在、彼女はファッションショッピングサイトZOZOTOWNのカート決済機能のリプレイスプロジェクトを推進しています。彼女はハードコアパンクが大好きで、お酒を楽しむ事が大好きです。

 


成田 俊は、シニアNoSQLスペシャリストソリューションアーキテクトです。彼は、DynamoDB を使用する多くの AWS のお客様にアーキテクチャのレビューや技術支援を行っています。

ブログ原文

How Amazon DynamoDB supported ZOZOTOWN’s shopping cart migration project