Amazon Web Services ブログ

マルチテナントソリューションでAmazon SQSを使う

AWS SaaS Factoryチームのシニアパートナーソリューションアーキテクト Raju Patel によって書かれた記事です。

AWS-SaaS-Factory-1

モダンアプリケーションは、サービス統合、バッチ処理、またはワークフローオーケストレーションの一部として、キューイングに依存することがよくあります。キューは、システムの環境に拡張性と耐障害性を追加するうえ重要です。

これは、キューイング戦略をマルチテナントソリューションのワークロードにどのように適用するかを考える必要がある、Software-as-a-Service(SaaS)環境では特に当てはまります。SaaS アプリケーションのキューイングモデルを設計する際には、データの分離、パフォーマンス、および運用を考慮する必要があります。

多くの組織がアマゾンウェブサービス (AWS) 上で SaaS ソリューションを構築および運用する過程において、筆者は AWS SaaS Factory のパートナーソリューションアーキテクトとして彼らと連携をしてきました。特に、テナントの分離とスケーラビリティを考慮しつつ、俊敏性を高めるマルチテナントソリューションの構築において組織を支援することにフォーカスしています。

この記事では、Amazon Simple Queue Service (SQS) を利用して SaaS ソリューションを構築する際の一般的なシナリオをいくつか紹介します。ここでは、データ分離、スケーラビリティ、コンプライアンス要件が、キューイングモデルの選択にどのように関係するかを説明します。

本投稿には、マルチテナントソリューションでの SQS の利用について示すサンプルコードが含まれています。

SaaS におけるキューイングの課題

SaaS 環境におけるキューの役割について理解を深めるために、サンプルユースケースを見てみましょう。図 1 に示す概念図は、キューを使用する注文管理システムの一例です。

SQS-Multi-Tenant-SaaS-1

図 1 – キューを用いた注文管理と在庫更新のフロー

最初のフローは、注文、作成、確定、出荷の各サービスの統合を示しています。注文キューと出荷キューは、複数のテナントからメッセージを受信します。注文確定や注文出荷などのサービスは、Amazon Elastic Kubernetes Service (Amazon EKS) のコンテナで実行することも、AWS Lambda でサーバーレスで実行することもできます。

図 1 の 2 番目のフローは在庫の更新についてです。このフローでは、テナントがファイルをアップロードし、メッセージがキューに配置されて処理されます。バッチによる在庫更新プロセスでメッセージが取得され、ファイルから在庫テーブルにデータがロードされます。

キューの構成は比較的単純ですが、マルチテナンシーでは追加の考慮事項がいくつかあり、それらはキュー設計に影響を与える可能性があります。

たとえば、あるテナントが (他のテナントと比較して) 多くの注文を生成してしまうと、メッセージの遅延の問題が発生する可能性があります。これらのメッセージにより、キュー内の他のテナントのメッセージに詰まりを発生させ、他のテナントのサービスレベルを低下させる可能性があります。図 2 に示すこのシナリオは「ノイジーネイバー」と呼ばれます。

SQS-Multi-Tenant-SaaS-2.1

図 2 – ノイジーネイバーを引き起こすテナント A

図中では、3 つの SaaS テナントからのメッセージをそれぞれ 3 つの異なる色で表しています。お気づきのとおり、キュー内にはテナントAからのメッセージが他のテナントに比べて多いため、テナント C のメッセージはキューの先にあるメッセージが処理されるまで待機します。この待ち時間によりレイテンシーが発生し、他のテナントのシステム使用における体験に影響を与える可能性があります。

在庫の更新フローでは、さらに別の種類の問題が発生する可能性があります。たとえば、テナント B が送信したファイル内に、処理する在庫項目が他のテナントのファイルよりも著しく多い場合、他のテナントの体験に影響を与える可能性もあります。

キューを使用するマルチテナントソリューションを設計する際の課題は、複数の目標を満たすキューイング戦略を見つけることです。セキュリティとデータ保護のために、他のテナントのメッセージと混在してはならないというテナントの分離要件を引き続き満たしながら、ノイジーネイバーの問題を乗り越える必要があります。

同時に、俊敏性を維持し、運用を簡素化し、コストを最適化できるアプローチが必要です。

マルチテナントソリューションでは、キューの設計に万能なアプローチはありません。クラウドネイティブなアーキテクチャーは、さまざまなサービスとアーキテクチャーコンポーネントに分解されることが多く、それぞれがシステム内で異なる役割を果たす可能性があります。その結果、システムのユースケースや要素ごとにキューイングの要件と戦略が異なる場合があります。

アーキテクチャコンポーネントの違い以外に、テナントの使用パターンやデータ量も異なる場合があります。それだけでなく、一部の SaaS ソリューションでは、ユーザーエクスペリエンスを差別化するために使用できる、異なるサブスクリプション階層 (フリー、プロフェッショナル、エンタープライズなど) がティアとして提供されている場合があります。

また、これらのティアを使用して、キューの設計に影響する サービスレベルアグリーメント(SLA)の一部として、ノイジーネイバーの許容値を定義することもできます。たとえば、許容される要件が類似しているテナントを同じキューにグルーピングできます。

SaaS パーティショニングモデル

マルチテナントキューイングの概念を詳しく説明する前に、SaaS 環境でリソースを分割するために使用される一般的な戦略をいくつか見てみましょう。それらはサイロ、プール、ブリッジとして知られています。

これらのコアモデルは、マルチテナントアーキテクチャ戦略に対する考え方の基礎であり、結局のところ、キューイングモデルで取り得る選択肢を表しています。

ブリッジモデルはサイロモデルとプールモデルを組み合わせたもので、一部のキューは共有され、他のキューはテナントごとに分離されます。実際には、アーキテクチャコンポーネントやテナントごとの要件が異なるため、モダン SaaS アプリケーションのほとんどはブリッジモデルを使用しています。

これらのモデルの詳細は、SaaS ストレージ戦略ホワイトペーパーに記載されています。

サイロ、プール、ブリッジそれぞれのキューイングモデルが、マルチテナント SaaS ソリューションのスケーラビリティ、データ分離、コンプライアンス、階層機能にどのように影響するかを見てみましょう。

サイロモデル

サイロモデルでは、テナントごとに個別のキューが使用されます。これにより、最高レベルの分離とデータ保護が可能になる一方で、トレードオフとしてコストと運用の複雑さが増し、俊敏性が低下します。

テナントへのメッセージフローを完全に分離するために、テナントごとに個別の SQS キューを作成します。個別のキューを用いることで、1 つのテナントが他のテナントに影響を及ぼすボトルネックを生むことを防ぎます。

SQS-Multi-Tenant-SaaS-3

図 3 – 在庫読み取りのためのサイロSQSキュー

図 3 は、AWS Lambda、Amazon API Gateway 、SQS、Amazon Cognito、AWS Key Management Service (AWS KMS)、AWS Identity and Access Management (IAM) を使用するサイロモデルを示しています。これらは、サイロモデルの基本的な可動部です。

リクエストは Amazon API Gateway 経由で送信され、在庫の読み取りを行う Lambda 関数によって処理されます。これにより、SQS キューにメッセージが送信され、ダウンストリームで処理されます。テナント A とテナント B にはそれぞれ独自の SQS キューがあり、一方のテナントがもう一方のテナントに影響を与えないことが注目点です。

このアプローチは、特定のユーザーとそのテナントとの関係についての情報を伝達するために JSON Web Token (JWT) を使用したテナントコンテキストを必要とします。

サイロモデルを検討している場合は、分離を実行し、テナントリソースへのクロステナントアクセスを防止する方法についても検討する必要があります。図 3 のアーキテクチャでは、この追加の分離層を実現するいくつかの追加構造が導入されています。

たとえば、テナントのオンボーディング時に、そのテナントに対して IAM ポリシーを持つロールを設定します。このロールは、SQS に対するアクセス権限をそのテナントのみに制限し、Amazon Cognito ID プールに関連付けられます。Amazon Cognito はテナント ID を持つ JWT トークンを提供し、JWT トークンは Lambda 関数に渡されます。Lambda 関数はテナントのロールを引き受け、そのロールに基づきポリシーを適用できます。

この記事の後半にあるサンプルコードでは、Amazon Cognito、JWT、およびカスタムクレームを使用して、テナントコンテキストを管理し、ロールを引き受ける方法を示します。

テナント A の Amazon Cognito ID プールにアタッチされたロールの IAM ポリシーは、次のようになります。

“Effect”: “Allow”,
“Principal”: “*”,
“Action”: “sqs:SendMessage”,
“Resource”: “arn:aws:sqs:*:111122223333:TenantA-Inventory”

サイロモデルの欠点は、特にキューを持つテナントやアーキテクチャコンポーネントの数が増えた場合に、運用が複雑になり、俊敏性が低下することです。

可動部が増えると、デプロイと管理が難しくなります。開発環境、テスト環境、本番環境用に SQS キューを設定することになり、管理するキューの数がさらに増えます。

テナントごとに個別のキューがあるということは、コンシューマーがどのように設定されるかを考える必要があることも意味します。コンシューマーが Lambda 関数の場合、複数のキューからのイベントに対してトリガーするように Lambda 関数を設定できます。Lambda 関数は、予約済み同時実行数と SQS キュー内のメッセージ数に基づいて、最大 1,000 件の同時実行までスケールできます。

コンテナを使用している場合、コンテナサービスが適切な SQS キューをポーリングし、キュー内のメッセージ数に基づいてスケーリングできるように、設定情報を管理する必要があります。

プールモデル

効率性と俊敏性を最大化することは、SaaS ソリューションにおいて重要です。これは、リソースを共有することで実現できる場合があります。テナントごとにプロビジョニングするリソースが少なくなると、テナントのオンボーディングが容易になります。これは、プールモデルでテナントが SQS キューを共有することで実現できます。

プールモデルの設計目標は、メッセージ処理がその SaaS 環境の規模とスループットの要件に対応できることを前提として、すべてのテナントがキューリソースを共有することです。

プールモデルでの課題は、ノイジーネイバーのシナリオで、個々のテナントがキューを飽和させる可能性があることです。この問題に対処するために使用できる戦略は複数あります。

1 つのアプローチは、キュー内のメッセージの数に基づいて共有されたキューコンシューマーをスケーリングすることです。たとえば、SQS キューのメッセージ数に基づいて Amazon Elastic Compute Cloud (Amazon EC2) インスタンスをスケーリングするには、SQS キューのメッセージ数に基づくスケーリングが利用可能です。AWS Lambda を使用している場合は、プロビジョニング済み同時実行数を設定できます。

他の方法として、複数テナントがティアに基づいてキューを共有することです。ティアには許可されるメッセージレートを定義でき、アプリケーションはリクエストをスロットルします。これは、各メッセージの処理コストが同じであるか、少なくともメッセージがエンキューされるとき (消費するスロットリングユニットを検出するために) このコストを決定できることを前提としています。

必要に応じて、1 つのティアに複数のキューを用意し、テナントを小さなキューの集合にハッシュ化し、保持するメッセージ数が最も少ないキューにメッセージを送るようにすることができます。このアプローチの詳細については、Amazon Builder’s Library にある「乗り越えられないキューバックログの回避」を参照してください。

次の表は、サービスのティアに基づいて関数ごとに制限を設定する例です。

関数 ティア 分あたりのレート
Create Order Free 100
Create Order Professional 1,000
Create Order Enterprise 100,000
PO integration Free 10
PO integration Professional 100
PO integration Enterprise 1,000
Ship order Free 100
Ship order Professional 1,000
Ship order Enterprise 10,000

キュー内にとどまるメッセージが増えすぎないようにし、他のテナントに属する未処理メッセージがたまることを防ぐために、SaaS アプリケーションでスロットリングを使用できます。スロットル制限は、テナントのサービスティアに基づいている場合があります。

スロットリングはアプリケーションのメッセージパブリッシャサービスに追加する必要があります。Amazon API Gateway などのサービスを使用する場合は、使用量プランを設定してリクエストを調整できます。使用量プランは、テナントのサービスティアに関連付けることができます。図 4 に、スロットルの概念的なフローを示します。

SQS-Multi-Tenant-SaaS-4

図 4 – リクエストのスロットリング

プールモデルでは、データ分離も考慮する必要があります。共有 SQS キューでは、IAM ポリシーはアクセスのスコープと制御にあまり役に立ちません。つまり、アプリケーションのサービスにて認可モデルを使用する必要があります。

プールモデルでデータを分離する 1 つの方法は、SQS メッセージでメッセージ属性を使用し、テナント ID を渡すことです。サービスでは、JWT トークンを使用して、テナントメッセージのインジェクションと処理を制御します。

ユーザーが認証されると、テナント ID を使用してテナントコンテキストが確立され、JWT トークンで渡されます。サービスでは、JWT トークンのクレームから tenant_id が読み取られます。つまり、送信者はただ自分のテナント ID を導入するだけではいけないということになります。メッセージのコンシューマーは tenant_id 属性を使用してテナントコンテキストを構築します。

プールモデルの欠点は、ノイジーネイバーを防ぐように設計する必要があることに加えて、テナントが分離を必要とするコンプライアンス要件または規制要件を持っている場合、個別のキューが使用できないことです。

プールモデルのサンプルコード

JWT のクレームから tenant_id を取得し、メッセージ属性に設定するサンプルキューイングアプリケーション (この記事の最後にあるリンクを参照) のコードを次に示します。

# tenant_id を取得するためにクレームを使用できます
    if not claims['custom:tenant_id']:
       print('No tenant_id attribute found in claims')
       return {
            "statusCode": 500,
            "body": json.dumps({
                "message": "No tenant_id attribute found in JWT claims"
            })
        }
     else:
    tenant_id=claims['custom:tenant_id']
    # tenant_id をメッセージの属性として使用し、メッセージを送信します
    response = queue.send_message( 
                    MessageBody= message_body,
                    MessageAttributes=
                        {
                        'tenant_id': {
                            'StringValue': tenant_id,
                            'DataType': 'String'
                            },
                        'message_version': {
                            'StringValue': 'Version 1.0',
                            'DataType': 'String'
                            }
                        })

ブリッジモデル

SaaS ソリューションでは、一部のテナントに固有の規制要件がある場合があります。または、アプリケーションの一部がテナントごとの分離に適しており、テナントごとに個別の SQS キューを設定する必要があります (サイロモデル)。

同時に、俊敏性と効率性を高めるために、アプリケーションの他の部分がプールモデルに適している場合もあります。

サイロモデルとプールモデルの両方を混在させることをブリッジモデルといいます。

たとえば、外部アプリケーションへの統合を考えた時に、メッセージを SQS キューに配信し、テナントがそのメッセージにアクセスできるようにしたいとします。このシナリオでは、そのテナントに対してのみメッセージへのアクセスを制限する必要があるため、プールモデルは適用できません。これは、アプリケーションの一部にサイロモデルを使用することが理にかなっているシナリオです。

図 5 は、注文メッセージのキューイングにプールモデルを使用し、テナント統合のために発注書メッセージのキューイングにサイロモデルを使用するブリッジモデルの概念図を示しています。プールモデルには、ノイジーネイバーの状況を防ぐために複数のキューがあります。

SQS-Multi-Tenant-SaaS-5

図 5 – ブリッジモデルの概念図

SaaS キューのモニタリング

マルチテナントソリューションにおける SQS のモデルに関係なく、一貫したテナント体験を確保するには、モニタリングとメトリクスが必要です。Amazon CloudWatch には SQS メトリクスがあり、キューをモニタリングできます。これはキュー全体のメトリクスには適していますが、キューが共有されているプール内のテナントレベルではあまり可視性が得られません。

プールモデルでは、イベントをログに記録してメトリクスを分析するコードをインストルメンテーションして、テナント固有のメトリクスを収集する必要があります。SQS は、各メッセージがキューに到達したときのタイムスタンプを提供します。タイムスタンプ情報を使用して、メッセージをキューから取り出すたびに、レイテンシーなどのメトリクスをログに記録して分析できます。

キューのメッセージ数、最も古いメッセージの経過時間 (ポイズンピルなどのエッジケースをキャッチするためなど)、テナントごとにメッセージがキューに存在した平均時間などのメトリクスは、ソリューションの状態を把握するうえで役立ちます。これらのメトリクスを使用してキューイングモデルを改良し、すべてのテナントに一貫した体験を提供できます。

また、CloudWatch でカスタムアラームを設定して、テナントの 1 つが影響を受ける可能性がある場合にアラートを受け取ることもできます。

キューのパフォーマンスに関するメトリクスを用意しておくと、テナントのニーズに合わせてソリューションを拡張できます。これは、キューを追加したり、テナントを独自のキューに分離したりするといったことです。

暗号化

コンプライアンスまたは規制上の理由から、SQS キューを通過するデータの暗号化が必要になる場合があります。SQS では、キュー全体に対して AWS KMS のキーを使用した暗号化がサポートされています。つまり、SQS キュー内のすべてのメッセージは 1 つの暗号化キーを使用して暗号化されます。

テナントが管理するキーを使用してデータを暗号化する必要がある場合、暗号化戦略はサイロモデルとプールモデルのどちらを使用するかによって異なります。サイロモデルでは、各テナントが別々の SQS キューを既に使用しているため、AWS KMS の暗号化キーを使用することで要件を満たすことができます。

テナントが管理するキーをプールモデルで使用するには、クライアント側の暗号化が必要です。つまり、プロデューサとコンシューマはクライアントライブラリを使用してメッセージを暗号化および復号する必要があります。クライアント側の暗号化と復号によって分離が可能ですが、アプリケーションが複雑になり、パフォーマンスのオーバーヘッドが発生する可能性があります。メッセージを暗号化および復号するクライアントライブラリがあっても、AWS KMS を使用してキーを管理できます。

AWS KMS では、管理するキーの自動ローテーションがサポートされています。また、キーを顧客管理することもできます。つまり、テナントは独自のキーを管理できます。AWS KMS でカスタマー管理型のキーを使用するには、SaaS ソリューションでテナントが自身のキーを管理するための機能を公開する必要があります。これには、キーを更新したり、キーをローテーションしたりする機能が必要です。

コスト

SaaS キューイング戦略の一環として、各アプローチがソリューションのコストに与える影響についても考慮する必要があります。

SQS の料金は、キューの数ではなく API 呼び出しに基づきます。最初の 100 万件の月間リクエスト以降の分について、API 呼び出しごとに課金されます。サイロとプールのどちらに決めるか、またソリューションを設計する際には、API 呼び出しを最小限に抑える方法を探してください。

サイロモデルでは、多数の SQS キューをポーリングしたり、メッセージの配信頻度が低く、バッチサイズの平均が 10 未満になったりすると、コストが増加する可能性があります。

共有キューを使用するプールモデルでは、管理する SQS キューの数が減り、リソースの使用が最適化されます。ポーリングする SQS キューが少なくなると、サイロモデルと比較して API 呼び出しと CPU サイクルが削減されます。メッセージのバッチ処理では、複数のメッセージを受け取る可能性が高くなり、コストの最小化にも役立ちます。

消費モデルに基づいてテナントに課金する必要がある場合は、SQS キューの消費量を測定する必要があります。サイロモデルでは、各 SQS キューにコスト配分タグを適用し、そのタグを使用してコストを報告することでこれを実現します。タグはテナントの名前で、値はテナント ID になります。

コスト配分タグを使用すれば、サイロモデルで運用する場合に、追加のメトリクスや使用量を収集する必要がなくなります。

プールモデルでは、SQS キューが共有されるため、テナントごとの SQS キューの計算はより困難になります。コスト配分タグは、共有キューではあまり役に立ちません。代わりに、テナントごとの呼び出しに関するメトリクスをキャプチャする必要があります。パブリッシュイベントとサブスクライブイベントをテナント ID でログに記録するようにアプリケーションを変更し、そのデータを使用してテナントごとにコストを配分します。

キューイングのサンプルコード

先ほど説明した概念を示すために、Python でサンプルコードをいくつか書きました。このソリューションは、お客様の AWS アカウントの AWS サーバーレスアプリケーションモデル (SAM) を使用してデプロイできます。このサンプルでデプロイされたアーキテクチャを次に示します。

SQS-Multi-Tenant-SaaS-6

図 6 – サンプルアプリケーションアーキテクチャ

サンプルコードは AWS SaaS Factory Github で入手できます。

まとめ

マルチテナント SaaS アプリケーションのサービス統合、バッチ処理、ワークフローオーケストレーションに拡張性と耐障害性をもたらすには、慎重なキューイング戦略が不可欠です。サイロ、プール、ブリッジキューイングの各モデルは、ビジネスの俊敏性、効率性、コストにそれぞれ異なる影響を与えます。

適切なキューイングモデルを選択するには、アプリケーションのデータ分離、スケーラビリティ、コンプライアンス要件を理解し、暗号化の要件とコストを考慮する必要があります。

ここでは、開始に役立つ追加リソースをいくつか紹介します。

AWS-SaaS-Factory-Banner-1

AWS SaaS Factory について

AWS SaaS Factory は、SaaS ジャーニーのあらゆる段階で組織を支援します。AWS での新製品の構築、既存のアプリケーションの移行、SaaS ソリューションの最適化など、あらゆるニーズにお応えします。AWS SaaS Factory Insights Hub にアクセスして、技術やビジネスに関するコンテンツ、ベストプラクティスをご覧ください。

SaaS ビルダーは、アカウント担当者に連絡してエンゲージメントモデルについて問い合わせたり、AWS SaaS Factory チームと協力したりすることをお勧めします。

サインアップして、SaaS on AWS のニュース、リソース、イベントに関する最新情報を入手してください。

翻訳はソリューションアーキテクト 福本 健亮 が担当しました。原文はこちらです。