I Tried Out "DryRun," the New Feature Now Supported by Kinesis Data Streams

I Tried Out "DryRun," the New Feature Now Supported by Kinesis Data Streams

I tried the Amazon Kinesis Data Streams DryRun with three IAM roles of differing permissions on a 1-shard CloudFormation stream, checking permission and parameter validation, throttling responses, and whether the stream stays empty.
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. It is a parameter that lets you check only whether a request would succeed, without reading or writing any data.

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
How to validate permissions You had to send a request that was intentionally designed to fail (for example, a payload exceeding the limit) You can validate just by specifying DryRun
Risk of accidental writes If service limits changed, a "request expected to fail" could unintentionally succeed and write a record to the production stream A dry run performs no data reads or writes at all, so there is no risk of accidental writes
Response on success The DryRun parameter itself did not exist A DryRunOperationException (HTTP 400) is returned, indicating that the request would have succeeded

In this article, I checked the validity of permissions and request parameters, the response under throttling, and the state of the stream after execution.

Verification

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

Test environment

Whether you can specify the DryRun flag depends on the AWS CLI version. In aws-cli/2.36.38, which I used for this verification, the help for aws kinesis put-record includes [--dry-run | --no-dry-run]. The region is ap-northeast-1.

Using CloudFormation, I created one provisioned-mode stream with 1 shard and three IAM roles. kds-dryrun-allow-role was allowed data plane operations. kds-dryrun-deny-role was allowed only DescribeStreamSummary and ListShards. kds-dryrun-partial-role was allowed only read operations. Because named IAM roles are created, CAPABILITY_NAMED_IAM is specified at deployment.

CloudFormation template for the test 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

Just before running the command it was 2026-09-03T01:30:57Z, and the CLI returned at 01:32:04Z when stack creation completed, so it took 67 seconds. With polling at 5-second intervals, the first check in which StreamStatus returned ACTIVE was at 01:32:13Z, 76 seconds after just before the command was run.

In this verification, a DryRun executed after AssumeRole at 01:32:36Z, 32 seconds after stack creation completed, succeeded, so I was able to run it without waiting for IAM role propagation.

For the per-permission verification, I assumed each role and ran the commands. The temporary credentials were passed as environment variables and were not written to files. Because the template's trust policy allows only one specified principal, I unset the environment variables before switching to another 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")

Delete the stack when verification is finished. Shard hours are billed until it is deleted.

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

I ran PutRecord with DryRun using the three roles, and for the role allowed only read operations I also tried GetShardIterator. First, PutRecord with the role allowed 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 printed it as an error, and the exit code was 254.

Next, I ran the same command with the role allowed 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. You cannot tell success from failure by the exit code, so you need to distinguish them by the type of exception.

I ran DescribeStreamSummary 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
    }
}

Because DescribeStreamSummary succeeded, the earlier denial was caused by a lack of PutRecord permission, not because the role itself was unusable.

When I ran GetShardIterator with DryRun using the role allowed only read operations, a DryRunOperationException was returned and the 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 an AccessDeniedException. Since these results are for the same stream, the determination is made 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 verification

I checked how the validity of request parameters is handled using the record size limit. I prepared two payloads: 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

When I ran DryRun with the 1,048,577-byte payload, the following error was returned.

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, a DryRunOperationException was returned and the validation succeeded. The constraint value shown in the error message was 1048576, and the same stream's MaxRecordSizeInKiB was 1024. The response headers captured with --debug included x-amzn-RequestId, confirming that the determination was made on the service side.

The PutRecord API reference documents 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 on 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 check the actual behavior, I submitted 60 requests of 5-byte records with a concurrency of 12. I ran it once each, changing only whether DryRun was specified, and disabled the CLI's automatic retries with AWS_MAX_ATTEMPTS=1.

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 Elapsed
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 described in the developer guide was never observed in this verification; ProvisionedThroughputExceededException was returned instead.

Throttling also occurred when I ran DryRun sequentially 60 times with 5-byte records (01:47:21.429Z – 01:47:46.045Z). In this trial, 50 were DryRunOperationException and 10 were ProvisionedThroughputExceededException. The first throttling occurred on the 15th request.

ProvisionedThroughputExceededException is also returned for real writes without DryRun. I submitted 24 requests of 1 MiB records with a concurrency of 8. 16 succeeded and 8 returned ProvisionedThroughputExceededException (01:40:29.428Z – 01:40:31.873Z). The completion times recorded by the script for each request were 14 in the 01:40:30 range and 2 in the 01:40:31 range. The exception name alone cannot tell you whether it originated from DryRun.

Script used for the 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 the 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 run above was done by specifying 24 requests and a concurrency of 8 as arguments.

When I submitted 1 MiB records 20 times at 1-second intervals, all 20 succeeded and no throttling occurred (01:49:26.356Z – 01:49:57.876Z). This trial was started after leaving a 60-second gap from the previous trial.

Checking for side effects

After running DryRun a total of 176 times in the verification so far, at 01:40:05Z, before performing any real writes, I ran get-records from TRIM_HORIZON three times in a row.

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

The result was the same all three times, and there was not a single record in the stream. The runs in the throttling section were all performed after this check.

Pricing

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

The billing units in provisioned mode are shard hours and PUT payload units of 25 KB. In the US East calculation example on the pricing page, it is $0.015/hour per shard, and PUT payload units are $0.014 per million units. If you run permission checks repeatedly, the same charges as real writes apply.

Summary

With DryRun in Kinesis Data Streams, you can now validate against provisioned-mode streams without writing any records. You can check whether IAM permissions and request parameters pass, per API action. No test data for verification is left in the stream.

Kinesis Data Streams has no operation to delete written records individually. They remain until the retention period passes, so test data submitted to check permissions can become noise in the stream. When you want to narrow IAM policies down to least privilege and verify the result, try the newly supported DryRun.

Share this article

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