2022/05/27
優れたアーキテクチャを設計するためのプラクティスを AWS のソリューションアーキテクトと一緒に学びましょう。 このセッションでは、 お題として提示されたシステム要件や技術的な課題に対して AWS のソリューションアーキテクトが実際に設計したアーキテクチャ例を題材に、 アーキテクチャを設計する際の着眼点、良いアーキテクチャが備えている性質、モデリングの手法やデザインパターンなどを議論・解説します。
内海 英⼀郎 さん
奥野 友哉 さん
⼭﨑 翔太 さん
オンラインセール アパレル EC サイトを運営する X 社のオンラインセールは⼈気ブランド のスニーカーが数量限定で出品されることで有名です。EC サイトへの アクセスは⽬⽟商品の販売開始と同時にスパイクすることが分かってお り、在庫管理サービスのスケーラビリティが課題になっています。 そこで X 社は新たに⽬⽟商品専⽤の在庫管理サービスを構築して⼤量の アクセスを捌こうと考えました。あなたはこのサービスをどのように設 計しますか︖
上記のお題に対して、奥野さんが考えたアーキテクチャの解説へと移ります。
また、上記だと設計の範囲や方針が定まりずらいということで、ある程度前提となるアーキテクチャがシェアされました。
処理の流れ ①: クライアントから、商品購入リクエストがBFFに送られる ②: BFFは目玉商品用の在庫管理サービスを呼び、在庫の有無を確認 ③: 在庫がある場合は、再度、目玉商品用の在庫管理サービスを呼び在庫の引当を行う ④: 在庫が確保できた場合、カートに追加する
今回のトランザクションは、商品がカートに入ったら完了
販売のやり方を変えて抽選にする
確保した在庫数を超えて購入させる
責任範囲としては、このサービス群の⽬⽟商品⽤在庫管理サービスを考えるというお題
NLB + ECS Fargate(コンテナ上にNginx) + NLB + ECS Fargate(コンテナ上に在庫マネージャアプリ) + DynamoDB(在庫数を管理)
在庫確認処理 : BFFより別のサービスより取得したProduct Idをもとにこのサービスで作成したAPIを使用して、Getのリクエストを投げる。DynamoDBではProduct IdをPartition keyにしたレコードを保持っている。Getのリクエストの処理としては、Product IdをもとにDynamoDBにGetItemをして、在庫の数字があれば在庫がありを返す。
在庫引当処理 : BFFより商品のIDと希望している購入数を作成したAPIのPOSTでリクエストする。
在庫を減らすときの処理 : UpdateExpressionやConditionExpressionを使用する。在庫を指定の個数減らすが、在庫が指定の個数を下回る場合はエラーを返却する。
カートへの商品追加処理: 在庫が確保できたら商品追加のリクエストを送る。
問題点1. 特定の Item に Read/Update が集中するため、Partition ごとに存在する 3,000 RCUおよび 1,000 WCUの上限に当たる可能性がある
なので書き込み/読み込みをどのようにスケールさせるかを検討する必要がある。
いやぁ色々考えられていてすごいっす。。
NLB + ECS Fargate(コンテナ上にNginx) + NLB + ECS Fargate(コンテナ上に在庫マネージャアプリ)
ここまでは、最初の設計と同じだが、DynamoDBへの書き込み箇所に工夫点がある。 ・在庫を管理するDynamoDBの後続処理として、DynamoDB Streamを用意しLambdaへ流す。 ・Lambdaは分割したDynamoDBのテーブルに対して、Updateをするのだが、Daxに対しデータを流し、在庫マネージャからの読み取り時は、Daxから読み取り流れとした。
問題点への解決策 問題点1
=> ただ、シャーディングさせると読み込み時Scanを使ってしまうとコスト効率が悪くなる。 なので、テーブル分割させる(在庫がある場合にそのPartition Idのlistを保持するテーブル)ことでScanを回避する作りにした。
普通ここくらいまで考慮していれば、よく考えられているなと思うのですが、さすがAWSのSA。ここからさらに分割したテーブルへの読み込みへの集中が発生することを問題視。
=> じゃあどうするのかということで、 DynamoDB Accelerator (DAX)を使っているようです。 使い方としては、分割したテーブルのデータを複製させスケールさせる。なので、最大10個のリードレプリカが作成できるDAXのクラスタを使う。
新たな問題発生(問題点2): 在庫が0になった時にどのようにして分割したテーブルに反映していくか 問題点2
なので、DnyamoDBからStreamでLambdaに流し、在庫状態の Update に失敗した場合でも、リトライが実⾏でき、それでもダメな場合はDLQに対応すべきイベントを残せる。 Lambdaの障害時でも DynamoDB Streamsに対応すべきイベント履歴が残る
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-ddb.html
SaaS メータリング 経費精算 SaaS プロダクトのローンチに向けて準備を進めている Y 社は 課⾦システムの開発に着⼿することにしました。 テナント別に集計した使⽤量や課⾦額はテナント向けダッシュボードに 表⽰する予定です。テナント管理者がリアルタイムに利⽤状況を確認で きるよう、テナント向けダッシュボードにはできるだけ最新のデータを 反映しなければなりません。あなたはこのシステムをどのように設計し ますか︖
上記のお題に対して、⼭﨑さんが考えたアーキテクチャの解説へと移ります。
SaaSのアプリがあり、そこから今回検討する課金システムに使用量データが送られてくる
課金システムは、テナント別の使⽤量・課⾦額、APIをダッシュボードに公開する
SaaSアプリは機能ごとの異なる料金プランを持っている。(機能Aと機能Bがある)
機能A: トランザクションの実⾏数に応じて費⽤が発⽣する料⾦プラン
機能B: 利⽤開始から停⽌までの時間に応じて費⽤が発⽣する料⾦プラン
課金額の計算は、機能を利用した時点での単価で計算するのではない。ダッシュボードに表示する時点の最新単価を用いる。
上記のシンプルな設計での課題。 課題①: トランザクション数の増加に伴い、DBへの書き込み/読み込み負荷が増加してボトルネックになったり、時間課⾦機能の使⽤時間を計算するためにクエリが複雑化する
問題点①への解決策
課題②: SaaS アプリケーションや課⾦システムの障害時にメータリングができない/トランザクションイベントが 遅延なく到着するとは限らない
問題点②への解決策
課題③: 二重計上
問題点③への解決策
こちらも色々考えられていて、かなりやばいです。
トランザクション課金機能側: Kinesis Data Streamにトランザクション課金のイベントを流し、lambdaが処理を行い、データストアであるDynamoDBに対し、トランザクション課金のデータを入れていくのだが、ここでDynamoDBテーブルのパーティションキーをトランザクションIDとし、条件付き書き込みにより重複を排除する。
トランザクション課金の事前集計には、Kinesis Data Streams + Kinesis Data Analyticsを組み合わせることで実現する。
使用時間課金の機能側: こちらもSaasアプリ側に、エージェントを配置し、Kinesis Data Streamsでデータを送る。後続は、Lambda + RDS Proxy + Auroraを使用する。
トランザクション課金と使用量の集計データについては、最終的にデータベースに書き込みを行うのだが、書き込み処理でボトルネックにならないように、前段にRDS Proxyを置きコネクション過多に対応する。
最終的な、課⾦額の計算とAPIの公開については、Amazon ECS + ALBで対応しダッシュボード向けに公開する。