I actually tried out the new Amazon Cognito feature "Provisioned Limit API"

I actually tried out the new Amazon Cognito feature "Provisioned Limit API"

Amazon Cognito Provisioned Limits can be raised through a service quota request. We actually increased the UserCreation limit from 50 to 75 RPS, and quantitatively confirmed the elimination of throttling and improvement in throughput through parallel load testing in Go. We will also cover the realities of the application process and cost considerations.
2026.07.15

This page has been translated by machine translation. View original

Introduction

The API rate limits for Amazon Cognito User Pools are managed with a two-tier quota system. One is the limit set per AWS account and region (Account-level max limit), and the other is the Provisioned limit that can be adjusted within that range. The Provisioned limit can be changed immediately via the UpdateProvisionedLimit API, but raising the limit itself requires a service quota request.

Iwasa's article introduces the API that operates the Provisioned Limit itself. This article continues from there — we actually submitted a service quota increase request, and after approval, raised the UserCreation limit from 50→75 RPS. We measured the throughput improvement using a parallel load test written in Go.

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

Verification Details

Service Quota Increase Request

First, we checked the state before the increase. We use GetProvisionedLimit to retrieve the current value for the UserCreation category.

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

Both ProvisionedLimitValue and FreeLimitValue were 50.

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

To change the Provisioned limit to a value greater than 50 in this state, a service quota increase for the Account-level max limit is required first. We submitted a request via Service Quotas to raise the UserCreation limit to 75. The quota code is 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

The request was accepted with a PENDING status.

{
    "RequestedQuota": {
        "ServiceName": "Amazon Cognito User Pools",
        "QuotaName": "Rate of UserCreation requests",
        "DesiredValue": 75.0,
        "Status": "PENDING",
        "Created": "2026-07-03T16:54:34+09:00"
    }
}
Full output of 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"
    }
}

This service quota increase involved back-and-forth communication with the service team via a support case, requiring explanations of the use case, the desired limit value, the target, and the intended usage period. Combined with spanning two weekends, the process took more than 10 days.

Confirming the Limit Increase

After approval, we set the Provisioned limit to 75 using UpdateProvisionedLimit.

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

When we confirmed the updated state with GetProvisionedLimit, ProvisionedLimitValue had been updated to 75. FreeLimitValue remained at 50, meaning the excess 25 RPS is subject to billing.

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

Load Test

To measure the effect of the increase, we created a tool in Go that runs AdminCreateUser in parallel using goroutines. Each test attempts to create 1,000 users. To directly count throttling, we disabled SDK retries and recorded the number of TooManyRequestsException occurrences.

The test environment is as follows.

Item Details
Instance EC2 c8g.xlarge (Graviton4, 4 vCPU, 8 GiB)
OS Amazon Linux 2023 (ARM64)
Language Go 1.22
SDK AWS SDK for Go v2
HTTP SDK default
Concurrency control goroutine + channel-based semaphore
Retry Disabled (retry.AddWithMaxAttempts(retry.NewStandard(), 1))
Region ap-northeast-1 (Tokyo)

The test code is as follows.

Load test code (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...
}

We ran tests with varying concurrency levels for both Limit=50 and Limit=75.

Results

The load test results are as follows. While this is a simplified verification with a single measurement per configuration, the effect of the increase was most pronounced in the load range where the effective RPS approaches the default Limit.

Concurrency 10 (effective RPS ~33)

Limit Success Throttle Effective 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

Throttling barely occurred with either Limit=50 or 75. This is a load range where the effective RPS does not reach the default Limit. However, 4 throttles occurred in the Limit=75 trial, possibly due to a momentary spike approaching the Limit during a brief burst of requests.

Concurrency 20 (effective RPS ~65–70)

Limit Success Throttle Effective 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

A clear difference emerged. With Limit=50, 311 requests were throttled, but after raising to Limit=75, the success count reached 1,000 and throttling was completely eliminated. This is the load range where the effective RPS exceeds the default Limit of 50 and hits the ceiling.

Concurrency 50 (effective RPS ~137–158)

Limit Success Throttle Effective 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

Throttling occurred even with Limit=75. The total attempted request rate including throttles was 1000 / 3.25s ≈ 308 req/s, far exceeding the raised Limit of 75. Even so, the effective RPS improved from approximately 137 to approximately 158, and the success count increased from 386 to 513. The difference in successful throughput reflects the amount by which the Limit was raised.

Pricing

Setting the Provisioned limit beyond the default free tier (50 RPS) incurs monthly charges for the excess. According to the Cognito pricing page, a permanent increase costs $20/RPS/month, and a temporary increase (minimum 1 day) costs $45/RPS/month (prorated daily). Increasing by 25 RPS from 50→75 and keeping it permanently raised would result in 25 × $20 = $500/month. Rather than leaving the limit permanently raised, a practical approach is to raise and lower it as needed using the UpdateProvisionedLimit API.

The costs incurred during this test are as follows.

Item Details
Increase amount 25 RPS (50→75)
Duration of increase Approximately 3 minutes 32 seconds (confirmed via UpdateProvisionedLimit event in CloudTrail)
RPSAdjustment (temporary increase $45/RPS/month) $0.05
RPSFull (base $20/RPS/month) $0.04
Total $0.09

In this measurement, the usage recorded in Cost Explorer (0.00199 RPS-Month) matched the value back-calculated from the CloudTrail timestamps, confirming that billing was prorated to the second.

Note that the users created during testing (Cognito Essentials MAU: 5,339) fell within the free tier and cost $0.00.

Summary

In this verification, we confirmed that raising the Provisioned Limit for Amazon Cognito UserCreation from 50→75 RPS improved successful throughput in the load range where Cognito-induced throttling was occurring. The Provisioned Limit and service quota increase are effective solutions when the Cognito API rate limit is a bottleneck.

However, in real-world systems, bottlenecks may also arise from factors other than Cognito, such as application-side concurrency control, SDK retries, the HTTP client, the network, and downstream processing. The important first step is to reproduce the peak load in a test and identify exactly where the bottleneck lies.

Also, raising the Account-level max limit requires a service quota request, which can take time to be approved. If large-scale user registration or sign-ins are anticipated, it is advisable to proceed with testing and applications on a schedule with ample lead time, rather than rushing to submit a request after discovering that Cognito is the bottleneck.

Share this article

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