I tried the new feature "DryRun" supported by Kinesis Data Streams

I tried the new feature "DryRun" supported by Kinesis Data Streams

I tested Amazon Kinesis Data Streams DryRun with three IAM roles having different permissions. I created a single-shard stream with CloudFormation and verified permission and request parameter validation, responses during throttling, and whether the stream remained empty after execution.
2026.09.03

This page has been translated by machine translation. View original

Introduction

On 2026-09-01, Amazon Kinesis Data Streams added support for DryRun. This parameter lets you verify whether a request would succeed without performing any actual data reads or writes.

https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-kinesis-data-streams-api/

https://docs.aws.amazon.com/streams/latest/dev/kds-dryrun-validation.html

Item Before Now
Permission validation method Required sending intentionally failing requests (e.g., exceeding the payload limit) Can be validated simply by specifying DryRun
Risk of unintended writes If service limits changed, a "designed-to-fail request" could unexpectedly succeed and write records to the production stream No risk of unintended writes since dry run performs no data reads or writes whatsoever
Response on success The DryRun parameter itself did not exist DryRunOperationException (HTTP 400) is returned, indicating the request would have succeeded

In this article, we verified permission and request parameter validity, throttling behavior, and the state of the stream after execution.

What We Verified

According to the developer guide, DryRun validates three things: IAM permissions, request parameter validity, and the existence of the target resource. The five supported APIs are PutRecord, PutRecords, GetRecords, GetShardIterator, and SubscribeToShard.

Verification Environment

Whether the DryRun flag can be specified depends on the version of the AWS CLI. In aws-cli/2.36.38 used for verification, the aws kinesis put-record help shows [--dry-run | --no-dry-run]. The region is ap-northeast-1.

Using CloudFormation, we created one provisioned-mode stream with 1 shard and three IAM roles. kds-dryrun-allow-role was granted permission to perform data plane operations. kds-dryrun-deny-role was granted only DescribeStreamSummary and ListShards. kds-dryrun-partial-role was granted only read operations. Since named IAM roles are created, CAPABILITY_NAMED_IAM is specified during deployment.

CloudFormation template for the verification environment
AWSTemplateFormatVersion: '2010-09-09'
Description: Verification environment for Kinesis Data Streams DryRun parameter.

Parameters:
  TrustedPrincipalArn:
    Type: String
    AllowedPattern: '^arn:aws:iam::\d{12}:(role|user)/.+$'
  StreamName:
    Type: String
    Default: kds-dryrun-test
    AllowedPattern: '^[a-zA-Z0-9_.-]+$'

Resources:
  TestStream:
    Type: AWS::Kinesis::Stream
    Properties:
      Name: !Ref StreamName
      StreamModeDetails:
        StreamMode: PROVISIONED
      ShardCount: 1
      RetentionPeriodHours: 24

  AllowRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: kds-dryrun-allow-role
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref TrustedPrincipalArn
            Action: sts:AssumeRole
      Policies:
        - PolicyName: kds-dataplane-allow
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - kinesis:PutRecord
                  - kinesis:PutRecords
                  - kinesis:GetRecords
                  - kinesis:GetShardIterator
                  - kinesis:DescribeStream
                  - kinesis:DescribeStreamSummary
                  - kinesis:ListShards
                Resource: !GetAtt TestStream.Arn

  DenyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: kds-dryrun-deny-role
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref TrustedPrincipalArn
            Action: sts:AssumeRole
      Policies:
        - PolicyName: kds-describe-only
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - kinesis:DescribeStreamSummary
                  - kinesis:ListShards
                Resource: !GetAtt TestStream.Arn

  PartialRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: kds-dryrun-partial-role
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Ref TrustedPrincipalArn
            Action: sts:AssumeRole
      Policies:
        - PolicyName: kds-reader-only
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - kinesis:GetShardIterator
                  - kinesis:GetRecords
                  - kinesis:DescribeStreamSummary
                  - kinesis:ListShards
                Resource: !GetAtt TestStream.Arn

Outputs:
  StreamArn:
    Value: !GetAtt TestStream.Arn
  AllowRoleArn:
    Value: !GetAtt AllowRole.Arn
  DenyRoleArn:
    Value: !GetAtt DenyRole.Arn
  PartialRoleArn:
    Value: !GetAtt PartialRole.Arn
aws cloudformation deploy \
    --template-file template.yaml \
    --stack-name kds-dryrun-verify \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameter-overrides TrustedPrincipalArn=arn:aws:iam::123456789012:role/example-role \
    --region ap-northeast-1

The command was run at 2026-09-03T01:30:57Z, and the CLI returned after stack creation completed at 01:32:04Z, taking 67 seconds. With 5-second interval polling, the first confirmation that StreamStatus returned ACTIVE was at 01:32:13Z, 76 seconds after the command was run.

In this verification, a DryRun executed via AssumeRole at 01:32:36Z, 32 seconds after stack creation completed, succeeded without waiting for IAM role propagation.

Permission-specific verification was performed by assuming each respective role. Temporary credentials were passed as environment variables and not written to files. Since the trust policy in the template allows only the one specified principal, environment variables are unset before switching to a different role.

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

CREDS=$(aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/kds-dryrun-allow-role \
    --role-session-name dryrun-test \
    --query Credentials --output json)

export AWS_ACCESS_KEY_ID=$(jq -r .AccessKeyId <<<"$CREDS")
export AWS_SECRET_ACCESS_KEY=$(jq -r .SecretAccessKey <<<"$CREDS")
export AWS_SESSION_TOKEN=$(jq -r .SessionToken <<<"$CREDS")

After verification is complete, delete the stack. Shard hours are billed until deletion.

aws cloudformation delete-stack --stack-name kds-dryrun-verify --region ap-northeast-1
aws cloudformation wait stack-delete-complete --stack-name kds-dryrun-verify --region ap-northeast-1

Deletion started at 01:50:13Z and finished at 01:50:44Z including waiting for completion (31 seconds).

Permission Verification

We ran PutRecord with DryRun across three roles, and also tried GetShardIterator with the read-only role. First, PutRecord with the role that allows data plane operations.

$ aws kinesis put-record \
    --stream-name kds-dryrun-test \
    --data aGVsbG8= --cli-binary-format base64 \
    --partition-key fixed-key \
    --dry-run --region ap-northeast-1

aws: [ERROR]: An error occurred (DryRunOperationException) when calling the PutRecord operation: DryRunOperation validation succeeded while calling PutRecord operation.: Request would have succeeded, but DryRun flag is set.

Even when validation passed, the CLI output it as an error, and the exit code was 254.

Next, the same command was run with the role that allows only DescribeStreamSummary and ListShards.

aws: [ERROR]: An error occurred (AccessDeniedException) when calling the PutRecord operation: User: arn:aws:sts::123456789012:assumed-role/kds-dryrun-deny-role/dryrun-test is not authorized to perform: kinesis:PutRecord on resource: arn:aws:kinesis:ap-northeast-1:123456789012:stream/kds-dryrun-test because no identity-based policy allows the kinesis:PutRecord action

The exit code when denied was also 254. Success and failure cannot be determined by exit code alone; the exception type must be used to distinguish them.

DescribeStreamSummary was run with the same role.

{
    "StreamDescriptionSummary": {
        "StreamName": "kds-dryrun-test",
        "StreamARN": "arn:aws:kinesis:ap-northeast-1:123456789012:stream/kds-dryrun-test",
        "StreamStatus": "ACTIVE",
        "StreamModeDetails": {
            "StreamMode": "PROVISIONED"
        },
        "RetentionPeriodHours": 24,
        "EncryptionType": "NONE",
        "OpenShardCount": 1,
        "ConsumerCount": 0,
        "MaxRecordSizeInKiB": 1024
    }
}

Since DescribeStreamSummary succeeded, the previous denial was due to insufficient PutRecord permissions, not because the role itself was unavailable.

When GetShardIterator was run with DryRun using the read-only role, DryRunOperationException was returned and validation succeeded.

aws kinesis get-shard-iterator \
    --stream-name kds-dryrun-test \
    --shard-id shardId-000000000000 \
    --shard-iterator-type TRIM_HORIZON \
    --dry-run --region ap-northeast-1

PutRecord with the same role returned AccessDeniedException. Since the results are for the same stream, the determination is per API action, not per stream.

Note that the developer guide mentions permissions that DryRun does not validate: kms:GenerateDataKey and kms:Decrypt for streams encrypted with a customer-managed KMS key.

Parameter Validation

We verified how request parameter validity is handled using the record size limit. Two payloads were prepared: exactly 1 MiB and 1 MiB + 1 byte.

head -c 1048576 /dev/zero | tr '\0' 'a' > payload-1mib.bin
head -c 1048577 /dev/zero | tr '\0' 'a' > payload-over1mib.bin

Running DryRun with a 1,048,577-byte payload returned the following error.

aws kinesis put-record \
    --stream-name kds-dryrun-test \
    --data fileb://payload-over1mib.bin \
    --partition-key fixed-key \
    --dry-run --region ap-northeast-1
{"__type":"ValidationException","message":"1 validation error detected: Value at 'data' failed to satisfy constraint: Member must have length less than or equal to 1048576"}

With 1,048,576 bytes, DryRunOperationException was returned and validation succeeded. The constraint value shown in the error message is 1048576, and the MaxRecordSizeInKiB for the same stream was 1024. The response headers captured with --debug included x-amzn-RequestId, confirming the determination was made service-side.

The PutRecord API reference lists the Length Constraints for Data. The value is Minimum length of 0. Maximum length of 10485760. The introduction states a maximum record size of 10 MiB (referenced 2026-09-03). The value returned for this stream was 1048576.

Throttling

The developer guide states that DryRun has a dedicated throttle limit.

Requests with DryRun enabled are subject to a dedicated throttle limit of 1 transaction per second (TPS) per stream, separate from the stream's normal per-shard throughput limits. This limit is shared across all supported dry-run APIs (PutRecord, PutRecords, GetRecords, GetShardIterator, and SubscribeToShard) on the same stream. If you exceed this limit, the API returns a ThrottlingException.

To observe the actual behavior, we submitted 60 requests with 5-byte records at parallelism 12. Each run was executed once, varying only the presence or absence of DryRun, with AWS_MAX_ATTEMPTS=1 to disable CLI automatic retries.

export AWS_MAX_ATTEMPTS=1

seq 1 60 | xargs -P 12 -I{} aws kinesis put-record \
    --stream-name kds-dryrun-test \
    --data aGVsbG8= --cli-binary-format base64 \
    --partition-key fixed-key \
    --dry-run --region ap-northeast-1
Run Result Duration
Without DryRun All 60 succeeded 01:42:21.595Z – 01:42:26.990Z
With DryRun 32 DryRunOperationException, 28 ProvisionedThroughputExceededException 01:43:56.992Z – 01:44:02.241Z

Under the same conditions, only the side with DryRun was throttled. The ThrottlingException documented in the developer guide was never observed in this verification; ProvisionedThroughputExceededException was returned instead.

When 60 DryRun requests with 5-byte records were submitted sequentially, throttling also occurred (01:47:21.429Z – 01:47:46.045Z). In that attempt, 50 returned DryRunOperationException and 10 returned ProvisionedThroughputExceededException. The first throttling occurred on the 15th request.

ProvisionedThroughputExceededException is also returned for actual writes without DryRun. We submitted 1 MiB records with 24 requests at parallelism 8. 16 succeeded and 8 returned ProvisionedThroughputExceededException (01:40:29.428Z – 01:40:31.873Z). The completion timestamps recorded per request by the script were 14 in the 01:40:30 range and 2 in the 01:40:31 range. The exception name alone cannot be used to determine whether it originated from DryRun.

Script used for parallel submission
#!/usr/bin/env bash
STREAM=kds-dryrun-test
REGION=ap-northeast-1
PAYLOAD=payload-1mib.bin
COUNT="${1:-24}"
PARALLEL="${2:-8}"
export AWS_MAX_ATTEMPTS=1  # Disable CLI-side automatic retries to observe raw responses

seq 1 "$COUNT" | xargs -P "$PARALLEL" -I{} \
    aws kinesis put-record \
        --stream-name "$STREAM" \
        --data "fileb://$PAYLOAD" \
        --partition-key "key-{}" \
        --region "$REGION"

The above run specified 24 requests and parallelism 8 as arguments.

When 1 MiB records were submitted 20 times at 1-second intervals, all 20 succeeded with no throttling (01:49:26.356Z – 01:49:57.876Z). This attempt was started 60 seconds after the previous one.

Side Effect Verification

After running DryRun a total of 176 times through the verifications above, get-records was run three times consecutively from TRIM_HORIZON at 01:40:05Z, before any actual writes were performed.

{
  "RecordCount": 0,
  "MillisBehindLatest": 0,
  "Records": []
}

All three returned the same result: the stream contained zero records. The runs in the throttling section were all performed after this verification.

Pricing

There is no additional charge for using DryRun. Requests with DryRun enabled are charged the same as equivalent requests without it. PutRecord and PutRecords in provisioned mode are charged based on input payload size, and stream and shard hour charges apply as usual (developer guide).

The billing units for provisioned mode are shard hours and PUT payload units in 25 KB increments. In the US East calculation example on the pricing page, the rate is $0.015/hour per shard and $0.014 per million PUT payload units. Even when running permission checks repeatedly, the same charges apply as for actual writes.

Summary

Kinesis Data Streams DryRun now makes it possible to validate against a provisioned-mode stream without writing any records. You can verify whether IAM permissions and request parameters would pass, on a per-API-action basis. No test data used for verification is left behind in the stream.

Kinesis Data Streams has no operation to individually delete written records. Since records remain until the retention period expires, test data submitted for permission verification can become noise in the stream. When narrowing down an IAM policy to least privilege and wanting to confirm the result, try the DryRun support introduced in this update.

Share this article

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