A three-broker Kafka cluster that seemed like a fixed, predictable line item starts moving once real traffic hits it: someone bumps broker size to clear a throughput warning, a topic gets split into more partitions than it needs "for headroom," and a Kafka Connect worker that was supposed to run for a two-week migration is still up eight months later. None of that shows up as one big number — it shows up as MSK quietly becoming one of the larger lines on the bill, with no single change anyone would point to as the cause.
Why does MSK have so many separate meters?
MSK Provisioned bills broker instance-hours, storage, and (for Express brokers) data ingested, each on its own rate. MSK Serverless replaces broker-hours with a per-partition meter instead. They are different products with different bills, not two pricing tiers of the same thing:
| What you pay for | Rate |
|---|---|
Standard broker, kafka.m7g.large (Provisioned) | $0.204 per broker-hour |
Standard broker, kafka.m5.large (Provisioned) | $0.21 per broker-hour |
Express broker, express.m7g.large (Provisioned) | $0.408 per broker-hour |
| EBS storage (Standard or Express) | $0.10 per GB-month |
| Provisioned storage throughput (Standard brokers, optional) | $0.08 per MB/s-month |
| Data ingested (Express brokers only) | $0.01 per GB |
| MSK Serverless — cluster | $0.75 per cluster-hour |
| MSK Serverless — partition | $0.0015 per partition-hour |
| MSK Serverless — data in | $0.10 per GB |
| MSK Serverless — data out | $0.05 per GB |
| MSK Serverless — storage | $0.10 per GB-month |
| MSK Connect | $0.11 per MCU-hour |
Two things in that table are easy to miss. Standard brokers don't charge for data ingested — that meter only exists on Express brokers, which trade a higher hourly rate for higher throughput per broker and a simpler capacity model. And provisioned storage throughput is opt-in on Standard brokers: if you never requested extra throughput, you aren't paying that line, but if a past incident led someone to raise it "to be safe," it keeps billing at $0.08 per MB/s-month whether or not the cluster still needs it.
Provisioned and Express broker rates above are standard us-east-1 rates; MSK Serverless rates are
from AWS's us-east-2 pricing example. Rates vary by region — check the
Amazon MSK pricing page before you build a budget around
these numbers.
Why is my bill bigger than the broker-hour math suggests?
Three mechanics turn a cluster sized for "three brokers, moderate traffic" into a bigger number than that math implies.
Broker count tracks partitions, not just throughput. Each Express broker size has a recommended and a hard maximum partition count — an express.m7g.large, for instance, is recommended for around 1,000 partitions and capped at 1,500. A team that keeps adding topics with generous partition counts "in case we need to scale later" can hit that ceiling well before the broker's CPU or network is under real pressure, and the fix AWS's own console suggests first is adding brokers — which is a broker-hour cost increase driven by partition sprawl, not traffic.
MSK Serverless's per-partition meter runs whether or not a topic is busy. Every partition on a Serverless cluster bills $0.0015 per hour regardless of how much data flows through it — a topic created with 24 partitions for a workload that only ever needed 4 is paying for 20 idle partition-hours a day, every day, with no broker to right-size because there is no broker.
MSK Connect workers bill by the hour whether the sync is still needed. A connector deployed for a one-time backfill or a migration that finished months ago keeps its worker count billing at $0.11 per MCU-hour until someone deletes it — and because Connect workers don't show up on the same dashboard as broker or Serverless spend, they're the piece most likely to be running with nobody currently accountable for it.
How do I find what's actually driving the number?
Start in Cost Explorer, filtered to Amazon MSK and grouped by Usage Type — that split separates broker-hour charges, storage, Serverless partition-hours, and MSK Connect onto their own lines, so you know which meter to chase before digging into any one cluster.
To see whether a cluster's broker count is driven by partition volume rather than throughput, pull its current partition count and compare it against the broker size's recommended ceiling:
aws kafka list-nodes \
--cluster-arn arn:aws:kafka:us-east-1:123456789012:cluster/my-cluster/abcd1234 \
--query 'NodeInfoList[].BrokerNodeInfo.BrokerId' \
--output table
Then check GlobalPartitionCount and GlobalTopicCount in CloudWatch under the AWS/Kafka namespace, alongside BytesInPerSec and BytesOutPerSec per broker, to see whether the cluster is actually throughput-bound or just partition-heavy:
aws cloudwatch get-metric-statistics \
--namespace AWS/Kafka \
--metric-name GlobalPartitionCount \
--dimensions Name="Cluster Name",Value=my-cluster \
--start-time "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 86400 \
--statistics Maximum \
--region us-east-1 \
--output table
For MSK Connect, list every connector and its worker count so idle ones stop hiding among active ones:
aws kafkaconnect list-connectors \
--query 'connectors[].{Name:connectorName,State:connectorState,Created:creationTime}' \
--output table
How do I bring the bill down without breaking producers or consumers?
Deleting an MSK Connect connector stops whatever sync it runs immediately, and reducing a topic's partition count isn't something Kafka supports in place — you'd need to recreate the topic. Confirm a connector or topic is genuinely unused (check consumer lag and last-write time, not just "looks quiet this week") before removing either.
Delete MSK Connect connectors that outlived their job. A migration or backfill connector with zero recent throughput on its source or sink is pure worker-hour cost; list every connector's state and last-active metrics before assuming any of them are still needed, then delete the ones that aren't.
Size new topics for the partition count you need, not the one that feels safe. Partitions are cheap to add later if a topic's throughput actually grows, and expensive to carry idle in the meantime — on Serverless it's a direct per-hour charge, and on Provisioned it eats into the broker's recommended partition ceiling, pushing you toward a broker-count increase sooner than real traffic would.
Turn off provisioned storage throughput you no longer need. If a Standard-broker cluster has this set from a past incident and current BytesInPerSec/BytesOutPerSec don't come close to needing it, dropping it removes an entire $0.08-per-MB/s-month line with no effect on a cluster that isn't using the extra headroom.
Move spiky, low-baseline workloads to Serverless; move steady, high-volume ones to Provisioned. A cluster provisioned for a peak that only happens a few hours a week pays broker-hours the rest of the time for capacity nobody's using; Serverless's per-partition-hour and per-GB meters track actual usage instead. The reverse is also true — a cluster running near-constant high throughput usually costs less on Provisioned's flat broker-hour rate than on Serverless's per-GB-in and per-GB-out charges.
What else should I check?
MSK is rarely the last hop for the data it carries. If a Kafka topic feeds an EventBridge Pipe to fan out to multiple targets without a consumer group for each one, that Pipe runs its own per-request meter on top of whatever MSK already charged for the read — see how EventBridge pricing works for the rate on that second meter.
The same stacking shows up on the simpler side of the pipeline. A Lambda consumer that can't keep up with a topic's throughput and drops events into an SQS queue for retry pays SQS's own per-request charge on every message that takes that path, separate from whatever the Kafka consumer group already cost to run — see why SQS costs more than you budgeted for what that queue-side meter looks like once retries pile up.
More broadly, a streaming layer sitting between producers and consumers is one piece of a bill that usually has bigger, easier line items sitting elsewhere in the account — a full pass over an AWS bill in the order it pays off to check it works through compute, storage, and databases before you spend more time tuning a Kafka cluster that may not be the biggest number on the page.
How do I catch this before it becomes a real number?
MSK spend rarely jumps in one visible step — it climbs when a topic's partition count gets set generously "for later" and never gets revisited, when a Connect worker outlives the migration it was built for, or when broker count creeps up to clear a partition ceiling that had nothing to do with actual load. None of that reads as an incident at the time.
Connect your AWS account read-only, and Parsivex's daily anomaly checks compare every service's spend — MSK included — against its own trailing baseline, so a genuine jump in broker-hours, storage, or Connect worker charges surfaces the next morning instead of at the end of the billing cycle. For how those daily checks and severity thresholds work, see cost anomaly alerts, or read how scans work for what a connected account scan reads across the rest of your bill.