Amazon Cognito の新機能「Provisioned Limit API」を実際に試してみた

Amazon Cognito の新機能「Provisioned Limit API」を実際に試してみた

Amazon Cognito の Provisioned Limit はサービスクォータ申請で引き上げ可能です。実際に UserCreation の上限を 50→75 RPS に引き上げ、Go による並列負荷テストでスロットリング解消とスループット改善を定量確認しました。申請プロセスの実態や費用面の考慮点も紹介します。
2026.07.15

はじめに

Amazon Cognito User Pools の API レート上限は、2 層のクォータで管理されています。1 つは AWS アカウントおよびリージョンごとに設定される上限(Account-level max limit)で、もう 1 つはその範囲内で調整できる Provisioned limit です。Provisioned limit は UpdateProvisionedLimit API で即時に変更できますが、上限自体を引き上げるにはサービスクォータの申請が必要です。

いわさの記事では Provisioned Limit を操作する API そのものを紹介しています。本記事ではその続きとして、実際にサービスクォータの引き上げを申請し、承認後に UserCreation の上限を 50→75 RPS へ引き上げました。Go による並列負荷テストでスループットの改善幅を測定しました。

https://dev.classmethod.jp/articles/cognito-provisioned-limits/

検証内容

サービスクォータ引き上げ申請

まず引き上げ前の状態を確認しました。GetProvisionedLimit で UserCreation カテゴリの現在値を取得します。

aws cognito-idp get-provisioned-limit \
  --region ap-northeast-1 \
  --limit-definition '{"LimitClass":"API_CATEGORY","Attributes":{"Category":"UserCreation"}}'

ProvisionedLimitValueFreeLimitValue がともに 50 でした。

{
    "Limit": {
        "LimitDefinition": {
            "LimitClass": "API_CATEGORY",
            "Attributes": {
                "Category": "UserCreation"
            }
        },
        "ProvisionedLimitValue": 50,
        "FreeLimitValue": 50
    }
}

この状態で Provisioned limit を 50 より大きい値に変更するには、先に Account-level max limit のサービスクォータ引き上げが必要です。Service Quotas で UserCreation の上限を 75 にリクエストしました。クォータコードは L-5987B8A0(Rate of UserCreation requests)です。

aws service-quotas request-service-quota-increase \
  --region ap-northeast-1 \
  --service-code cognito-idp \
  --quota-code L-5987B8A0 \
  --desired-value 75

リクエストは PENDING ステータスで受理されました。

{
    "RequestedQuota": {
        "ServiceName": "Amazon Cognito User Pools",
        "QuotaName": "Rate of UserCreation requests",
        "DesiredValue": 75.0,
        "Status": "PENDING",
        "Created": "2026-07-03T16:54:34+09:00"
    }
}
request-service-quota-increase の出力全文
{
    "RequestedQuota": {
        "Id": "9e1e8001-example-request-id",
        "ServiceCode": "cognito-idp",
        "ServiceName": "Amazon Cognito User Pools",
        "QuotaCode": "L-5987B8A0",
        "QuotaName": "Rate of UserCreation requests",
        "DesiredValue": 75.0,
        "Status": "PENDING",
        "Created": "2026-07-03T16:54:34+09:00",
        "Requester": "{\"accountId\":\"123456789012\",\"callerArn\":\"arn:aws:sts::123456789012:assumed-role/example-role/example-user\"}",
        "QuotaArn": "arn:aws:servicequotas:ap-northeast-1:123456789012:cognito-idp/L-5987B8A0",
        "GlobalQuota": false,
        "Unit": "None",
        "QuotaRequestedAtLevel": "ACCOUNT"
    }
}

このサービスクォータの引き上げはサポートケース経由でサービスチームとのやり取りが発生し、ユースケースの説明や希望する上限値・対象・利用期間の確認を求められ、週末を 2 回跨いだこともあり、10 日以上を要しました。

Limit 引き上げ確認

承認後、UpdateProvisionedLimit で Provisioned limit を 75 に設定しました。

aws cognito-idp update-provisioned-limit \
  --region ap-northeast-1 \
  --limit-definition '{"LimitClass":"API_CATEGORY","Attributes":{"Category":"UserCreation"}}' \
  --requested-limit-value 75

GetProvisionedLimit で変更後の状態を確認したところ、ProvisionedLimitValue が 75 に更新されていました。FreeLimitValue は 50 のままで、超過分の 25 RPS が課金対象になります。

{
    "Limit": {
        "LimitDefinition": {
            "LimitClass": "API_CATEGORY",
            "Attributes": {
                "Category": "UserCreation"
            }
        },
        "ProvisionedLimitValue": 75,
        "FreeLimitValue": 50
    }
}

負荷テスト

引き上げ効果を測定するため、Go で AdminCreateUser を goroutine 並列実行するツールを作成しました。1 回のテストで 1000 ユーザーの作成を試みます。スロットリングを直接カウントするため、SDK のリトライは無効化し、TooManyRequestsException の発生件数を記録する構成です。

テスト環境は次のとおりです。

項目 内容
インスタンス EC2 c8g.xlarge(Graviton4, 4 vCPU, 8 GiB)
OS Amazon Linux 2023 (ARM64)
言語 Go 1.22
SDK AWS SDK for Go v2
HTTP SDK デフォルト
並列制御 goroutine + channel ベースのセマフォ
リトライ 無効(retry.AddWithMaxAttempts(retry.NewStandard(), 1)
リージョン ap-northeast-1(東京)

テストコードは以下のとおりです。

負荷テストコード(Go)
package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/aws/retry"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
	"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types"
)

func main() {
	concurrency := flag.Int("concurrency", 10, "Number of concurrent goroutines")
	totalUsers := flag.Int("users", 1000, "Total number of users to create")
	userPoolID := flag.String("user-pool-id", "", "Cognito User Pool ID")
	region := flag.String("region", "ap-northeast-1", "AWS Region")
	flag.Parse()

	prefix := fmt.Sprintf("loadtest-%d", time.Now().Unix())

	// Load AWS config with NO retries (we want to measure throttling directly)
	cfg, err := config.LoadDefaultConfig(context.Background(),
		config.WithRegion(*region),
		config.WithRetryer(func() aws.Retryer {
			return retry.AddWithMaxAttempts(retry.NewStandard(), 1)
		}),
	)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to load AWS config: %v\n", err)
		os.Exit(1)
	}

	client := cognitoidentityprovider.NewFromConfig(cfg)

	var (
		successCount  int64
		throttleCount int64
		latencies     []time.Duration
		latencyMu     sync.Mutex
	)

	sem := make(chan struct{}, *concurrency)
	var wg sync.WaitGroup

	startTime := time.Now()

	for i := 0; i < *totalUsers; i++ {
		wg.Add(1)
		sem <- struct{}{}

		go func(seq int) {
			defer wg.Done()
			defer func() { <-sem }()

			username := fmt.Sprintf("%s-%04d", prefix, seq)
			reqStart := time.Now()

			_, createErr := client.AdminCreateUser(context.Background(),
				&cognitoidentityprovider.AdminCreateUserInput{
					UserPoolId:    userPoolID,
					Username:      aws.String(username),
					MessageAction: types.MessageActionTypeSuppress,
				})

			elapsed := time.Since(reqStart)

			if createErr != nil {
				var tooMany *types.TooManyRequestsException
				if errors.As(createErr, &tooMany) {
					atomic.AddInt64(&throttleCount, 1)
				}
			} else {
				atomic.AddInt64(&successCount, 1)
				latencyMu.Lock()
				latencies = append(latencies, elapsed)
				latencyMu.Unlock()
			}
		}(i)
	}

	wg.Wait()
	totalDuration := time.Since(startTime)

	// Calculate percentiles and output results...
}

Limit=50 と Limit=75 それぞれについて、並列数を変えて実行しました。

結果

負荷テストの結果は以下のとおりです。計測回数 1 回の簡易検証ですが、引き上げ効果は実効 RPS がデフォルト Limit 付近に達する負荷帯で最も顕著でした。

並列数 10(実効 RPS 約 33)

Limit 成功 スロットル 実効RPS Duration p50 (ms) p95 (ms) p99 (ms)
50 1000 0 32.52 30.75s 293.01 400.52 702.82
75 996 4 34.30 29.04s 287.32 337.10 361.56

Limit=50・75 のどちらもスロットルはほぼ発生しませんでした。実効 RPS がデフォルト Limit に達しない負荷帯です。ただし Limit=75 の試行で 4 件のスロットルが発生しており、短時間のリクエスト集中により瞬間的に Limit 付近に達した可能性があります。

並列数 20(実効 RPS 約 65〜70)

Limit 成功 スロットル 実効RPS Duration p50 (ms) p95 (ms) p99 (ms)
50 689 311 64.43 10.69s 287.50 393.02 473.02
75 1000 0 69.73 14.34s 277.38 336.68 413.25

明確な差が出ました。Limit=50 では 311 件がスロットルされましたが、Limit=75 に引き上げると成功数は 1000 件、スロットルは 0 件と完全に解消しました。実効 RPS がデフォルト Limit の 50 を超えて上限に張り付く負荷帯です。

並列数 50(実効 RPS 約 137〜158)

Limit 成功 スロットル 実効RPS Duration p50 (ms) p95 (ms) p99 (ms)
50 386 614 137.13 2.82s 303.49 455.73 492.47
75 513 487 157.95 3.25s 278.83 354.73 384.73

Limit=75 でもスロットルが発生しました。スロットルを含む総試行レートは 1000 / 3.25s ≒ 308 req/s であり、引き上げ後の Limit 75 を大きく上回っているためです。それでも実効 RPS は約 137 から約 158 へ向上し、成功数も 386 件から 513 件へ増加しました。Limit の引き上げ分が成功スループットの差として表れています。

まとめ

今回の検証では、Amazon Cognito の UserCreation に対する Provisioned Limit を 50→75 RPS に引き上げることで、Cognito 起因のスロットリングが発生していた負荷帯において、成功スループットの改善を確認できました。Provisioned Limit とサービスクォータ引き上げは、Cognito の API レート上限がボトルネックになっている場合の有効な解決手段です。

ただし、実際のシステムでは Cognito 以外にも、アプリケーション側の並列制御、SDK のリトライ、HTTP クライアント、ネットワーク、下流処理などがボトルネックになる可能性があります。まずはピーク負荷を再現したテストで、どこで詰まっているのかを切り分けることが重要です。

また、Account-level max limit の引き上げにはサービスクォータ申請が必要で、承認までに時間がかかる場合があります。大規模なユーザー登録やサインインが見込まれる場合は、Cognito がボトルネックだと分かってから慌てて申請するのではなく、余裕を持ったスケジュールで検証と申請を進めることをおすすめします。

参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事