AWSではマルチテナントの公平性をどう実現しているのか?

# 概要

* 今回は、AWSではマルチテナントの公平性をどう実現しているのかについて記事にしていこうと思います。
* なおこちらの記事については、[Amazon Builders' LibraryにあるFairness in multi-tenant systems](https://aws.amazon.com/jp/builders-library/fairness-in-multi-tenant-systems/)を基に個人の解釈でまとめていったものになります。

# AWSにおいてのマルチテナントの公平性

* AWSの各種サービスはものすごい数生まれ、提供されている中でAWS自体の普及も拡大し、その分利用に関してAWSのアカウントが作成されていると思います。
* 今回お話していくのはそんなAWSがどうやってAWSを利用するユーザに対しサービス利用の公平性を実現しているのかについてを深ぼっていくという内容になります。
* またここで述べている公平性というのは、**AWSにおいてはすべてのクライアントにシングルテナントと同等の体験が提供できることを公平性**としています。

## 公平性を実現するために

* 上記で定義した公平性を実現するために重要なのが、AWSが提供しているサービスの**APIのレート制限**にあります。
* このレート制限を設けることで、処理できる規模を超えるトラフィックがシステムに流れ込んだ際にシステムが過負荷に陥ってしまうことを防ぐことができます。

---

## AWSとサービス指向アーキテクチャ (SOA)

* AWSにおいて、[サービス指向アーキテクチャ (SOA)](https://www.redhat.com/ja/topics/cloud-native-apps/what-is-service-oriented-architecture)はAWSのカルチャーの中心にあります。
* そもそもサービス指向アーキテクチャ (SOA)　というのは、システムを「**1つの大きなシステムと捉えるのではなく、それぞれの機能を提供する部品（サービス）の集まりと見なす考え方**です。
* カルチャーといったのは、AWSではチームとサービスが強固なオーナーシップを持ちかつ疎結合になっているためです。
* またサービス指向アーキテクチャ (SOA)において、それぞれのサービスで利用するリソースは、共有することによりハードウェア効率の向上を図っています。

### マルチテナントとシングルテナントの違い

* ここで改めて、マルチテナントなシステムとシングルテナントなシステムの違いについて確認します。
  * **マルチテナントなシステム**では、複数のクライアントが行なっている複数のワークロードを一度に処理します。また、優先順位の低いワークロードと、優先順位の高いワークロードを同時に処理することができます。
  * **シングルテナントなシステム**では、1人のクライアントが行なっているワークロードを処理します。

### マルチテナントにおけるリソースの共有の重要性

* マルチテナントにおけるリソースの共有は重要です。
* リソースの共有により、システムの利用率が高まればサーバーの台数が減り、インフラへの電力供給や冷却に必要なエネルギーが減るため、環境コストの削減にもつながります。


## マルチテナントの公平性

* マルチテナントなシステムにおいての公平性を確保するサービスでは、[bin-packing アルゴリズム](https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%B3%E3%83%91%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%E5%95%8F%E9%A1%8C)のような動きをします。

### どういうアルゴリズムで動作するのか

* 新しいワークロードのためにフリート内にあるリソースを見つけるアルゴリズムを実行します。(実行するためのリソースを探すと思ってください。)
* フリート内の各処理の負荷と各サーバーの利用率を継続的に監視し、処理にかかる負荷を分散させます。
* フリート全体の使用率を監視し、必要に応じてリソースの容量を追加または削除します。
* もし、他の処理が行われておらず、ある処理だけがフロートのリソースを使用している場合は、その処理の負荷のためにハードウェア的に割り当てられた性能の限界を超えることを許可します。逆に他にも処理を行うタスクがフリート内であれば、それぞれの処理に割り当てられる性能は、あらかじめ設定された限界点以下で処理されます。

#### 特徴

* 上記の動作を見たときに、なるべく公平にだけどシングルテナントと思わせる体験を届けられるために内部的にはかなり複雑なアルゴリズムが動いていることがわかります。
* まとめると、各処理の利用状況を監視し、複数の処理が同一のフリートで動作しても問題なさそうであれば同じフリート内で処理を行います。
* また、ある処理を行うためプロビジョニングした結果、リソースを完全に利用していないと分かれば、他の処理負荷を軽減するために、空いているリソースを借りるような動きになります。

=> そしてすごいのが、この処理負荷を軽減するために空いているリソースを貸し借りする動きがワークロードに気付かれないようになっているようです。

## 負荷の制限と公平性

* 一般的にシステムで負荷が増加すると、自動的にスケールさせる必要があります。
* その方法としては、垂直方向のスケールと水平方向のスケールが最もシンプルな方法でしょう。
* AWS Lambdaのようなサーバーレスを採用したサービスの場合だと、オンデマンドでキャパシティをスピンアップ(起動からアプリケーションの読み込みを行い、リクエストを受けることができる状態にするまでの処理)して処理するため、水平方向のスケーリングは瞬時に行われます。
* サーバーレスでないサービスでは、自動スケーリングに時間がかかります。

### マルチテナントにおける負荷分散

* AWSでは、先に述べたようにスケールさせる機能を持つサービスがあるものの、処理の負荷が偏りユーザ体験を損なわないよう上手く負荷分散しています。
* しかし、マルチテナントのサービスでは負荷分散という方法だけでは、各テナントにシングルテナントのような体験を届けることができません。

なぜか？
=> それは、通常、複数のテナントからの負荷に相関がないためです。
当たり前ですが、各テナントは独自のユースケースを持っていますし、そのユースケースごとの要求を裁かなければいけません。

* なので、あるサービスで全体の負荷が増加した場合は、その増加の原因があるテナントによるものである可能性が高いということです。なので、公平性を考慮するならば、1つのテナントからの予期しない負荷の増加に対して、全てのテナントからのリクエストが失敗することは避けたいです。

#### じゃあどうしているのか？

* マルチテナントの公平性を保つために、AWSではレート制限を使用して計画外のトラフィック増加を抑制しかつ、テナントごと/ワークロードごとの粒度でクォータによる制限を適用しています。
* これにより、マルチテナントに提供しているサービスが予定外の負荷へと増加した場合、そのワークロードのうち予定外の処理は拒否され、他のワークロードは予測可能なパフォーマンスで動作を継続することができるのです。

=> ただ、クォータの制限を行うということは逆にサービスの可用性を高めるのと同時に低下させていることにもなります。
なぜかというと、。あるテナントのワークロードがクォータを超過すると、超過したリクエストが失敗するようになってしまうためです。

## クォータのvisibilityとflexibility

* AWSのそれぞれのサービスでは、クライアント(AWSアカウント)ごとにクォータを設定する場合が多いです。
* ただ、クライアントという粒度だけではなく、サービスのリソースに対して設定するパターンもあります(例えば、DynamoDBテーブルなど)
* そして、デフォルトのクォータのルールはあるものの、クォータの限界に近づいている場合や、クライアントが今後の負荷上昇を予測した場合は、クライアントがサービスにクォータを引き上げるよう依頼することができます。


また、クォータにはいくつか種類があります。
* 例えば、「クライアントが同時に実行できる数」などです。
* EC2が該当吸うのですが、EC2では特定のAWSアカウントで起動できるインスタンスの数に対するクォータがあります。

また別のクォータだとレートベースのクォータもあったりします。
* これは「1秒あたりのリクエスト数」のような単位で測定されるものですね。
* 今回は、このレートベースのクォータに着目し話を進めていきます。

以下のグラフは、このクォータの使い方を表しています。
* このグラフではある容量が決められているサービスの時を想定したものになっています。
* Y軸はプロビジョニングされた総容量です。
* このサービスには3つのクライアントがあったとしましょう。(青、オレンジ、グレーがそれぞれのクライアントを示しています。)
* また、このサービスでは各クライアントが使用できる容量が全体の容量に対し1/3と決められているため、そこがクォータだと思ってください。

グラフを見ると、青のクライアントが割り当てられたスループットを超えようとしていますが、超えられないことを示しています。

![スクリーンショット 2022-06-25 9.53.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/31be2088-0a2d-b1f7-4385-cb0fc86288df.png)

[画像引用元](https://aws.amazon.com/jp/builders-library/fairness-in-multi-tenant-systems/)

* この場合、クライアントがクォータを超過すると、それに応じてクライアントにエラーが返される可能性が高いです。
* なので、**各サービスでは、クライアントが確認できるサービスのメトリクスを提供し、利用率がクォータの最大値に近づいたときにアラームを出せるようにしているのです。**
* 例を挙げるのであれば、DynamoDBだと、テーブルに対してプロビジョニングされたスループットと、そのスループットが時間とともにどれだけ消費されたかを示すCloudWatchのメトリクスを公開しています。
* 他には、長期的なキャパシティプランニングができるようなツールの提供もあったりします。
* あとは、ビジネスの成長に伴うクライアントのトラフィックの増加に合わせて、時間経過とともにクォータを柔軟に調整できるような方法もあったりします。
* 例えば、一部のサービスでは、クライアントの成長に合わせて自動的にクォータが増加するようになっています。

## クライアント側でレート制限に対応するには

* あるサービスを利用するクライアントがレート超過のエラーを受け取ると[再試行](https://aws.amazon.com/jp/builders-library/timeouts-retries-and-backoff-with-jitter/)するかエラーを返すのかどちらかになると思います。

* あとはクライアント側のワークロードによるのですが、システムが非同期なシステムか、同期的に動作するシステムかでがレート超過のエラーを受け取った時の振る舞いを変えることなんかもできます。

* 同期的に動作するシステムでは、応答を待っている処理がいるのでそれを考慮しないといけません。
* なので、例えばレート超過のエラーを受け取った場合、AWSのサービスを扱うためのAWS SDKを使っていれば頻繁にサービスからエラーが返ってくるのであれば[自動的に再試行を停止する設定](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/RetryMode.java#L33)があったりします。
* なぜ再試行を停止するのかというと、レート超過のエラーを頻繁に返している場合、再試行はすべてのレスポンスを遅くするだけで、すでに負荷の高いシステムでより多くのリソースを縛ることになってしまうためです。

* 非同期に動作するシステムでは、。レート超過のエラー応答を受け取った場合、すべてのリクエストが再び成功するまでの間、処理をスローダウンさせるだけでよいです。

# まとめ

* 普段AWSを使用している場合、あまりマルチテナントの公平性などは意識しませんが、AWSではマルチテナントになるべくサービスの価値としてシングルテナントの体験と思わせるような考慮をし、サービスを作っているんだなということがよく分かりました。
* そして各サービスのクォータが、公平性を実現するための方法であったことがよく分かりました。
* より詳細を知りたい場合は、参考に載せさせていただきました原文をお読みいただけますと幸いです。
* 最後までありがとうございました！


# 参考

https://aws.amazon.com/jp/builders-library/fairness-in-multi-tenant-systems/