Photo by John Adams on Unsplash
Idle Kinesis data stream cost is the charge a stream keeps paying with no traffic: $0.015 per shard-hour for a provisioned stream, plus extended retention and enhanced fan-out consumers, or $0.04 per stream-hour in on-demand mode (us-east-1, September 2026). Find idle streams by summing IncomingRecords and GetRecords.Records in CloudWatch over 14 days.
Kinesis streams outlive the projects that created them. The producer is switched off, the consumer is moved to a new stream, and the old one keeps its shards, its 7-day retention and its enhanced fan-out consumer, billed every hour. Unlike a stopped instance, an idle stream has no “stopped” state, so the idle Kinesis data stream cost never drops to zero until you change or delete it.
This example is for engineers reviewing streaming spend. It lists every stream in a Region, reads 14 days of write and read metrics from CloudWatch, and estimates the fixed monthly charge each stream carries. It changes nothing. For writing to streams, see the guide to Kinesis PutRecords with AWS SDK v3, which also compares on-demand and provisioned costs for busy streams.
What does an idle Kinesis data stream cost?
Only the time-based charges apply to a stream with no traffic. The per-GB and per-record charges are zero when nothing is written or read.
| Charge (US East, N. Virginia) | Provisioned | On-demand (standard) |
|---|---|---|
| Base charge while the stream exists | $0.015 per shard-hour | $0.04 per stream-hour |
| Retention from 24 hours to 7 days | $0.020 per shard-hour | $0.10 per GB-month of stored data |
| Retention beyond 7 days | $0.023 per GB-month | $0.023 per GB-month |
| Enhanced fan-out consumer | $0.015 per consumer-shard-hour, plus $0.013 per GB read | $0.05 per GB read |
| Writes | $0.014 per million 25 KB PUT payload units | $0.08 per GB |
| Idle charges that remain | Shards, extended retention, fan-out consumers | Stream-hours only |
Prices are as of September 2026, from the AWS Price List API file for Amazon Kinesis (published 11 September 2026) and the Kinesis Data Streams pricing page. The pricing page also lists an account-level On-demand Advantage mode with no per-stream-hour charge but a minimum throughput commitment; the script’s estimate assumes the standard modes above.
Worked example
From the sample output below, legacy-orders is a provisioned stream with 4 shards, 168-hour retention and 1 enhanced fan-out consumer. Using 730 hours per month:
- Shards: 4 × $0.015 × 730 = $43.80
- Extended retention: 4 × $0.020 × 730 = $58.40
- Fan-out consumer: 1 consumer × 4 shards × $0.015 × 730 = $43.80
- Total: $146.00 per month, with no data flowing
The on-demand stream iot-test costs $0.04 × 730 = $29.20 per month idle. Together that’s $175.20 a month, or $2,102.40 a year, for two streams nobody uses. Notice that extended retention costs more than the shards themselves here.
What does the script do?
- Lists streams
paginateListStreams, using theStreamSummariesit returns. - Reads configuration
DescribeStreamSummaryfor mode,OpenShardCountandRetentionPeriodHours, thenpaginateListStreamConsumersfor enhanced fan-out consumer names. - Sums traffic
GetMetricDatain theAWS/Kinesisnamespace with one period covering the whole window:IncomingRecordsfor writes,GetRecords.Recordsfor polling reads andSubscribeToShardEvent.Recordsper consumer for fan-out reads. - Labels and prices
IDLE(no writes, no reads),no readers(writes nobody reads),no new data(reads but no writes) oractive, with the fixed monthly charge from the table above.
These stream-level metrics are sent to CloudWatch every minute at no charge. Shard-level metrics are optional and billed as custom metrics, so an idle stream with enhanced monitoring switched on costs a little more than the script shows.
Prerequisites
- Node.js 18 or later with
tsx, plus@aws-sdk/client-kinesisand@aws-sdk/client-cloudwatch. - A read-only profile, set up as in the guide to AWS SDK v3 credential providers.
- A window that covers your slowest consumer. A batch job that reads once a month needs a longer
--days(up to 63).
Which IAM permissions does it need?
All read-only. ListStreams and GetMetricData don’t support resource-level permissions.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListStreamsAndMetrics",
"Effect": "Allow",
"Action": ["kinesis:ListStreams", "cloudwatch:GetMetricData"],
"Resource": "*"
},
{
"Sid": "DescribeStreams",
"Effect": "Allow",
"Action": ["kinesis:DescribeStreamSummary", "kinesis:ListStreamConsumers"],
"Resource": "arn:aws:kinesis:us-east-1:111122223333:stream/*"
}
]
}
AWS’s ReadOnlyAccess managed policy also covers these calls. To check a modified script, paste it into the IAM policy generator for TypeScript code.
The script to find idle Kinesis data streams
// find-idle-kinesis-data-streams.ts
// Lists Kinesis data streams in one Region, sums 14 days of writes (IncomingRecords), GetRecords reads and
// enhanced fan-out reads from CloudWatch, and estimates the fixed monthly charge each stream keeps paying.
// Report only: it never changes or deletes a stream.
// Usage:
// npx tsx find-idle-kinesis-data-streams.ts [--region us-east-1] [--days 14]
import {
KinesisClient,
DescribeStreamSummaryCommand,
paginateListStreamConsumers,
paginateListStreams,
} from "@aws-sdk/client-kinesis";
import { CloudWatchClient, GetMetricDataCommand, type MetricDataQuery } from "@aws-sdk/client-cloudwatch";
const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
const i = args.indexOf(name);
return i >= 0 ? args[i + 1] : undefined;
};
const region = flag("--region") ?? process.env.AWS_REGION ?? "us-east-1";
const days = Number(flag("--days") ?? 14);
const HOURS_PER_MONTH = 730;
// us-east-1 prices from the AWS Price List API (AmazonKinesis offer), September 2026.
const PRICE = {
shardHour: 0.015, // provisioned shard-hour
extendedRetentionShardHour: 0.02, // provisioned, retention above 24 hours up to 7 days
efoConsumerShardHour: 0.015, // provisioned, enhanced fan-out consumer-shard hour
onDemandStreamHour: 0.04, // on-demand (standard) stream-hour
};
const kinesis = new KinesisClient({ region });
const cloudwatch = new CloudWatchClient({ region });
interface Row {
Stream: string;
Mode: string;
Shards: number;
RetentionH: number;
EFO: number;
Writes: number;
Reads: number;
Verdict: string;
FixedUsdPerMonth: number;
}
const errText = (err: unknown): string => (err instanceof Error ? `${err.name}: ${err.message}` : String(err));
// Sums one metric per query over the whole window (a single period covering all the days).
async function sums(queries: { id: string; metric: string; dims: Record<string, string> }[]): Promise<Map<string, number>> {
const end = new Date();
const start = new Date(end.getTime() - days * 86_400_000);
const MetricDataQueries: MetricDataQuery[] = queries.map((q) => ({
Id: q.id,
MetricStat: {
Metric: {
Namespace: "AWS/Kinesis",
MetricName: q.metric,
Dimensions: Object.entries(q.dims).map(([Name, Value]) => ({ Name, Value })),
},
Period: days * 86_400,
Stat: "Sum",
},
}));
const result = new Map<string, number>();
let NextToken: string | undefined;
do {
const out = await cloudwatch.send(new GetMetricDataCommand({ StartTime: start, EndTime: end, MetricDataQueries, NextToken }));
for (const r of out.MetricDataResults ?? []) {
const total = (r.Values ?? []).reduce((a, b) => a + b, 0);
result.set(r.Id ?? "", (result.get(r.Id ?? "") ?? 0) + total);
}
NextToken = out.NextToken;
} while (NextToken);
return result;
}
async function inspect(name: string): Promise<Row> {
const s = (await kinesis.send(new DescribeStreamSummaryCommand({ StreamName: name }))).StreamDescriptionSummary;
const mode = s?.StreamModeDetails?.StreamMode ?? "PROVISIONED";
const shards = s?.OpenShardCount ?? 0;
const retention = s?.RetentionPeriodHours ?? 24;
const consumers: string[] = [];
if (s?.StreamARN) {
for await (const page of paginateListStreamConsumers({ client: kinesis }, { StreamARN: s.StreamARN })) {
for (const c of page.Consumers ?? []) if (c.ConsumerName) consumers.push(c.ConsumerName);
}
}
const queries = [
{ id: "writes", metric: "IncomingRecords", dims: { StreamName: name } },
{ id: "reads", metric: "GetRecords.Records", dims: { StreamName: name } },
...consumers.map((c, i) => ({ id: `efo${i}`, metric: "SubscribeToShardEvent.Records", dims: { StreamName: name, ConsumerName: c } })),
];
const m = await sums(queries);
const writes = m.get("writes") ?? 0;
let reads = m.get("reads") ?? 0;
consumers.forEach((_c, i) => (reads += m.get(`efo${i}`) ?? 0));
let fixed: number;
if (mode === "ON_DEMAND") {
fixed = PRICE.onDemandStreamHour * HOURS_PER_MONTH;
} else {
fixed = shards * PRICE.shardHour * HOURS_PER_MONTH;
if (retention > 24) fixed += shards * PRICE.extendedRetentionShardHour * HOURS_PER_MONTH;
fixed += consumers.length * shards * PRICE.efoConsumerShardHour * HOURS_PER_MONTH;
}
const verdict = writes === 0 && reads === 0 ? "IDLE" : writes > 0 && reads === 0 ? "no readers" : writes === 0 ? "no new data" : "active";
return {
Stream: name, Mode: mode, Shards: shards, RetentionH: retention, EFO: consumers.length,
Writes: writes, Reads: reads, Verdict: verdict, FixedUsdPerMonth: Number(fixed.toFixed(2)),
};
}
async function main(): Promise<void> {
if (!Number.isFinite(days) || days < 1 || days > 63) throw new Error("--days must be between 1 and 63");
const rows: Row[] = [];
for await (const page of paginateListStreams({ client: kinesis }, {})) {
for (const s of page.StreamSummaries ?? []) {
if (!s.StreamName) continue;
try {
rows.push(await inspect(s.StreamName));
} catch (err) {
console.error(`Skipping ${s.StreamName}: ${errText(err)}`);
}
}
}
rows.sort((a, b) => Number(b.Verdict === "IDLE") - Number(a.Verdict === "IDLE") || b.FixedUsdPerMonth - a.FixedUsdPerMonth);
console.table(rows);
const idle = rows.filter((r) => r.Verdict === "IDLE");
const cost = idle.reduce((sum, r) => sum + r.FixedUsdPerMonth, 0);
console.log(`${rows.length} streams in ${region}; ${idle.length} idle for ${days} days, about $${cost.toFixed(2)}/month in fixed charges. Nothing was changed.`);
}
main().catch((err) => {
console.error(errText(err));
process.exit(1);
});
How do you run it?
npm install @aws-sdk/client-kinesis @aws-sdk/client-cloudwatch
npm install --save-dev tsx typescript @types/node
# Last 14 days in us-east-1
AWS_PROFILE=readonly npx tsx find-idle-kinesis-data-streams.ts --region us-east-1
# A longer window for monthly consumers
AWS_PROFILE=readonly npx tsx find-idle-kinesis-data-streams.ts --region us-east-1 --days 45
Sample output
┌─────────┬─────────────────┬───────────────┬────────┬────────────┬─────┬──────────┬──────────┬──────────────┬──────────────────┐
│ (index) │ Stream │ Mode │ Shards │ RetentionH │ EFO │ Writes │ Reads │ Verdict │ FixedUsdPerMonth │
├─────────┼─────────────────┼───────────────┼────────┼────────────┼─────┼──────────┼──────────┼──────────────┼──────────────────┤
│ 0 │ 'legacy-orders' │ 'PROVISIONED' │ 4 │ 168 │ 1 │ 0 │ 0 │ 'IDLE' │ 146 │
│ 1 │ 'iot-test' │ 'ON_DEMAND' │ 4 │ 24 │ 0 │ 0 │ 0 │ 'IDLE' │ 29.2 │
│ 2 │ 'clickstream' │ 'ON_DEMAND' │ 4 │ 24 │ 0 │ 48211930 │ 96420011 │ 'active' │ 29.2 │
│ 3 │ 'audit-events' │ 'PROVISIONED' │ 2 │ 24 │ 0 │ 120455 │ 0 │ 'no readers' │ 21.9 │
└─────────┴─────────────────┴───────────────┴────────┴────────────┴─────┴──────────┴──────────┴──────────────┴──────────────────┘
4 streams in us-east-1; 2 idle for 14 days, about $175.20/month in fixed charges. Nothing was changed.
Names are illustrative. clickstream has twice as many reads as writes because two applications consume it. audit-events is written to but never read: either a consumer is missing, or you’re paying to store records that expire unread after 24 hours.
What should you do with an idle stream?
The script is report-only on purpose: a stream’s producers and consumers live in other code, and deleting a stream deletes its data. Work through these in order:
- Confirm the owners. Look for Lambda event source mappings, Firehose delivery streams and applications configured with the stream name. Tags help; the script to find untagged AWS resources shows which streams have none.
- Cut the extras first.
DecreaseStreamRetentionPeriodback to 24 hours removes the extended retention charge, andDeregisterStreamConsumerremoves a fan-out consumer nobody reads from. Onlegacy-ordersthat alone saves $102.20 of the $146.00. - Delete it when you’re sure.
DeleteStreamremoves the stream and every record in it. Set a budget alert with AWS SDK v3 first if you want a signal when streaming spend comes back. - Check the alarms. Alarms on a deleted stream stop receiving data and usually end up in
INSUFFICIENT_DATA; the script to find CloudWatch alarms with insufficient data finds them afterwards.
The same idle-resource pattern applies to other always-on services: the scripts to find idle EMR clusters and find idle ElastiCache clusters work the same way.
Troubleshooting
- Every stream shows 0 reads and writes. Check the Region. CloudWatch metrics are Regional, and a profile’s default Region may not be the one your streams live in.
- A stream you know is used shows no reads. Its consumer may run less often than your window, such as a monthly batch job. Run again with a larger
--days. AccessDeniedonGetMetricData. The profile has Kinesis read access but not CloudWatch. Addcloudwatch:GetMetricData, or see how to troubleshoot IAM AccessDenied errors.- Costs don’t match your bill. The estimate covers fixed charges only and assumes 730 hours per month and us-east-1 prices. Stored data beyond 7 days and enhanced monitoring are extra.
Ask ChatWithCloud instead
For a quick look, ask ChatWithCloud “Which Kinesis streams in us-east-1 had no incoming records in the last 14 days, and how many shards does each have?” It writes AWS SDK for JavaScript v2 code that calls Kinesis and CloudWatch, runs it on your machine with your profile and explains the result. It’s a good follow-up when you ask AI why your AWS bill increased and Kinesis shows up. See ChatWithCloud pricing and the free trial runs for the plans and the 15 free runs.
Frequently asked questions
What is the idle Kinesis data stream cost per month?
In us-east-1 as of September 2026, a provisioned shard costs $0.015 per hour, about $10.95 per month, and an on-demand stream costs $0.04 per hour, about $29.20 per month, before retention and fan-out charges.
Does an on-demand Kinesis stream cost money with no traffic?
Yes. On-demand standard mode charges per stream-hour while the stream exists, even with no data written or read.
Which CloudWatch metrics show whether a Kinesis stream is used?
IncomingRecords for writes, GetRecords.Records for polling consumers, and SubscribeToShardEvent.Records per consumer for enhanced fan-out. Sum each over the window.
Can I pause a Kinesis data stream?
No. There’s no stopped state. You can lower retention, remove fan-out consumers, reduce shards or delete the stream.
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud