Throttling Simulation
Probabilistically inject the throttle error code that each AWS service
actually returns: DynamoDB
ProvisionedThroughputExceededException,
S3 SlowDown,
SQS OverLimit,
Lambda TooManyRequestsException,
and so on. Tune the rate globally or per service, then drive load and
watch your retry / backoff logic earn its keep.
SIMULATE_THROTTLING=1 THROTTLE_RATE=0.3,
fire 20 parallel requests at each of DynamoDB, S3 and SQS, and observe
the per-service real error code at roughly the configured rate. Then
switch to the Python SDK with
retries={max_attempts: 5, mode: adaptive}
and watch every call still land. For the full retry-budget drill
(50 invocations, 10-way concurrent, zero-loss assertion at the end),
see the Chaos Resilience walkthrough.
Real Error Codes
Each service returns the exact same throttling error code that real AWS uses. Not a generic 429.
Configurable Rate
Set global or per-service throttle rates. Test at 5%, 30%, or 80% to stress-test your retry logic.
Test Retries
Verify that SDK adaptive retry, exponential backoff, and custom retry logic all work before production.
Step-by-Step Walkthrough
Step 1: Enable throttling simulation
$ SIMULATE_THROTTLING=1 THROTTLE_RATE=0.3 localemu start THROTTLE_RATE=0.3 means 30% of requests get throttled. Default is 0.05 (5%). Set SIMULATE_THROTTLING=1 to enable the feature.
Step 2: DynamoDB throttling
$ for i in $(seq 1 20); do
awsemu dynamodb put-item \
--table-name test-table \
--item "{\"id\": {\"S\": \"item-$i\"}}" &
done; wait
# Server log (illustrative: roughly 6 of 20 requests throttled at rate=0.3).
# Throttling is probabilistic, the exact mix varies run to run.
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 400 (ProvisionedThroughputExceededException)
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 400 (ProvisionedThroughputExceededException)
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 400 (ProvisionedThroughputExceededException)
...
AWS dynamodb.PutItem => 200
AWS dynamodb.PutItem => 200 Send 20 rapid PutItem requests. The server log shows ProvisionedThroughputExceededException (400) at approximately the configured rate (binomial variance around n * rate). The AWS CLI has built-in retry, so most requests eventually succeed despite throttling.
Step 3: S3 throttling
$ for i in $(seq 1 20); do
awsemu s3api put-object \
--bucket test-bucket \
--key "file-$i.txt" \
--body /dev/null &
done; wait
# Server log:
AWS s3.PutObject => 200
AWS s3.PutObject => 503 (SlowDown)
AWS s3.PutObject => 200
AWS s3.PutObject => 200
AWS s3.PutObject => 503 (SlowDown)
AWS s3.PutObject => 200
AWS s3.PutObject => 503 (SlowDown)
AWS s3.PutObject => 200
AWS s3.PutObject => 200
AWS s3.PutObject => 200 S3 returns SlowDown (503), the exact same error code real AWS uses when you exceed S3 request rate limits.
Step 4: SQS throttling
$ for i in $(seq 1 20); do
awsemu sqs send-message \
--queue-url http://localhost:4566/000000000000/test-queue \
--message-body "msg-$i" &
done; wait
# Server log (illustrative at rate=0.3):
AWS sqs.SendMessage => 200
AWS sqs.SendMessage => 403 (OverLimit)
AWS sqs.SendMessage => 200
AWS sqs.SendMessage => 403 (OverLimit)
AWS sqs.SendMessage => 403 (OverLimit)
AWS sqs.SendMessage => 200
...
AWS sqs.SendMessage => 200 SQS returns OverLimit (403). Unlike DynamoDB, the SQS CLI does not retry on OverLimit by default, so throttled requests fail visibly. Expect around THROTTLE_RATE * n failures; the exact count is a probabilistic draw.
Step 5: SDK retry handles it automatically
$ python3 -c "
import boto3
from botocore.config import Config
client = boto3.client(
'dynamodb',
endpoint_url='http://localhost:4566',
region_name='us-east-1',
config=Config(retries={'max_attempts': 5, 'mode': 'adaptive'})
)
succeeded = 0
for i in range(10):
try:
client.put_item(
TableName='test-table',
Item={'id': {'S': f'sdk-item-{i}'}}
)
succeeded += 1
except Exception as e:
print(f'Failed: {e}')
print(f'Result: {succeeded}/10 succeeded')
"
# With rate=0.3 and max_attempts=5, per-request success probability
# is 1 - 0.3^5 = 99.76%. Expect 10/10 almost every run.
Result: 10/10 succeeded Python boto3 with Config(retries={'max_attempts': 5, 'mode': 'adaptive'}): all 10/10 succeeded. The SDK retried throttled requests automatically. This is the whole point: test that your retry logic works BEFORE production.
Step 6: Per-service rate overrides
$ SIMULATE_THROTTLING=1 \
DYNAMODB_THROTTLE_RATE=0.8 \
S3_THROTTLE_RATE=0.5 \
THROTTLE_RATE=0.1 \
localemu start
# DynamoDB: 80% of requests throttled
# S3: 50% of requests throttled
# All other services: 10% (global default) DYNAMODB_THROTTLE_RATE=0.8 throttles 80% of DynamoDB while others keep the global rate. S3_THROTTLE_RATE=0.5 for S3-specific testing. Mix and match per service.
How It Works
ThrottlingHandler in request chain
Sits after request parsing, before service dispatch. Probabilistic: random.random() < rate triggers throttling.
Per-service error codes
Each service returns its real AWS error code. DynamoDB: 400, S3: 503, SQS: 403, Lambda: 429.
Skips internal calls
Cross-service calls (e.g. Lambda invoking S3 internally) are never throttled. Only external client requests pay the dice roll.
Off by default
The handler short-circuits when SIMULATE_THROTTLING is unset. Calm-mode runs see no rejected requests at all.