---
title: The Frugal Approach to AWS CloudWatch Metrics Costs
description: With usage-billed observability services like CloudWatch, cost optimization starts with your code. It's not just that applications publish the metrics; app
image: https://frugal.co/hubfs/blog-images/featured-Jul-23-2026-12-48-36-4594-PM.jpg
---

[![company-logo](https://cdn.prod.website-files.com/686b121c919ed2b639e30952/686b28b7662f765388c4c0f2_Layer_1.svg)](https://frugal.co/)

[Product](https://frugal.co/product) [Pricing](https://frugal.co/pricing)

Company

[About Us](https://frugal.co/about) [Security](https://frugal.co/security)

Resources

[Blog](https://frugal.co/blog) [Library](https://frugal.co/resources)

[Contact](https://frugal.co/contact)

[Book Demo](https://frugal.co/) [Get a Free Audit](https://frugal.co/audit)

Cost Engineering

AWS

The Frugal Approach to AWS CloudWatch Metrics Costs

![Craig Conboy](https://frugal.co/hs-fs/hubfs/Craig%20Conboy%20Headshot.png?width=60&height=60&name=Craig%20Conboy%20Headshot.png)

Craig Conboy

 July 23, 2026

With usage-billed observability services like CloudWatch, cost optimization starts with your code. It's not just that applications publish the metrics; applications spend. Every `PutMetricData` call, every dimension, every dashboard refresh contributes to your bill—on both sides of the pipe, because CloudWatch charges to put metrics in *and* to get them back out.

The pricing mechanics that matter: a custom metric is every unique combination of metric name and dimension values, at ~$0.30/metric/month for the first 10,000. The `PutMetricData` API bills ~$0.01 per 1,000 calls. Reading bills too—`GetMetricData` at ~$0.01 per 1,000 metrics requested—and alarms (~$0.10 each) and dashboards (~$3 each) round out the bill. Four different meters, and application code drives all of them.

Here's a practical walk-through of what to look for and how to optimize CloudWatch metrics costs at the code and configuration level.

| Cost Trap | Efficiency Pattern |
| --- | --- |
| Custom metrics duplicating built-in metrics | Use the free metrics AWS already publishes |
| High-cardinality dimensions | Dimension with bounded sets; never IDs |
| Ephemeral dimensions | Don't dimension by instance/container/deploy identity |
| One datapoint per PutMetricData call | Batch with Values/Counts arrays and MetricData lists |
| Sub-minute publishing everywhere | Publish at 60s; reserve high-resolution for what needs it |
| High-volume metrics via the API | Use Embedded Metric Format through logs |
| Chatty dashboard and tool polling | Tune refresh rates and query scope on readers |
| Alarms and dashboards on dead metrics | Audit and delete the orphans |
| Detailed monitoring enabled fleet-wide | Enable per-service where 1-minute data earns its cost |

## Attribute the Costs

Start with your bill. Break down CloudWatch spend by usage type—the bill separates custom metrics (`MetricMonitorUsage`), API requests (`PutMetricData`, `GetMetricData`/`GMD-Metrics`), alarms, and dashboards, per account and region. Each line points at a different fix: metric-count charges point at cardinality in your dimensions, PutMetricData charges point at unbatched publishing, GetMetricData charges point at whatever's polling you (usually a third-party observability tool or an over-eager Grafana), and a long tail of alarm charges points at monitors nobody has looked at since the service they watched was decommissioned.

Then attribute the metric count to code. List metrics by namespace (`aws cloudwatch list-metrics --namespace MyApp`) and look at the distribution: one metric name exploding into thousands of entries means one dimension whose values don't stop. This granular attribution reveals which of the 9 efficiency patterns below deliver the highest impact.

---

## Tackling Metric Count Costs

At $0.30 per metric per month, cardinality is the expensive meter.

### Use the free built-ins first

**Cost impact: Custom metric count**

AWS publishes free metrics for EC2, RDS, Lambda, ELB, ECS, DynamoDB, and most everything else—CPU, network, request counts, error rates. Publishing your own copy pays $0.30/month per series for data that's already on the shelf:

```
# Bad: hand-rolled CPU metric per instance ($0.30/month each)
cloudwatch.put_metric_data(
    Namespace='MyApp',
    MetricData=[{'MetricName': 'CPUUtilization',
                 'Dimensions': [{'Name': 'InstanceId', 'Value': iid}],
                 'Value': cpu_percent}])

# Good: delete the code. AWS/EC2 CPUUtilization is free.
```

Before instrumenting anything infrastructure-shaped, check the service's built-in metric list. Custom metrics are for what only your application knows—orders placed, jobs queued, cache hit rate.

**Benefit:** Deleted metrics cost nothing and were duplicates anyway.

### Dimension with bounded sets, never IDs

**Cost impact: Custom metric count (multiplicative)**

Every unique dimension-value combination is a separate $0.30/month metric, and dimensions multiply. `Endpoint` (30) × `StatusFamily` (5) is 150 metrics—fine. Add `RequestId` and you've built a metric-minting machine:

```
# Bad: a new billable metric per request
MetricData=[{'MetricName': 'Latency',
             'Dimensions': [{'Name': 'RequestId', 'Value': request_id}],
             'Value': ms}]

# Good: bounded dimensions; IDs belong in logs and traces
MetricData=[{'MetricName': 'Latency',
             'Dimensions': [{'Name': 'Endpoint', 'Value': route_template},
                            {'Name': 'StatusFamily', 'Value': f'{code//100}xx'}],
             'Value': ms}]
```

The same trap wears an infrastructure costume: dimensions like `InstanceId`, `ContainerId`, or a per-deploy `Version` churn with every restart and rollout, so the metric count grows with churn even when traffic doesn't. Dimension by stable identity (`Service`, `Environment`) and keep one bounded `Version` window if you need deploy comparisons. The rule for both: if you can't enumerate a dimension's values in advance, it doesn't belong on a metric.

**Benefit:** The difference between hundreds of metrics and hundreds of thousands.

---

## Tackling Publishing Costs

### Batch PutMetricData

**Cost impact: API requests**

`PutMetricData` bills per call, and one datapoint per call is the default nobody meant to choose. A call carries up to 1,000 datapoints across the `MetricData` list, and the `Values`/`Counts` arrays compress repeated observations further:

```
# Bad: one API call per observation (86M calls/day at 1,000 rps)
for ms in latencies:
    cloudwatch.put_metric_data(Namespace='MyApp',
        MetricData=[{'MetricName': 'Latency', 'Value': ms}])

# Good: buffer, then one call per flush interval
cloudwatch.put_metric_data(
    Namespace='MyApp',
    MetricData=[{
        'MetricName': 'Latency',
        'Values': [12.1, 14.7, 9.3, ...],   # up to 150 distinct values
        'Counts': [40, 22, 118, ...],       # with occurrence counts
        'StorageResolution': 60
    }])
```

**Benefit:** 100-1000x fewer PutMetricData calls; 80-99% of the API line item.

### Publish at 60 seconds unless something needs faster

**Cost impact: API requests**

High-resolution metrics (1-second granularity) multiply publish calls up to 60x, and almost nothing downstream justifies it: dashboards render minutes, most alarms evaluate minutes. Publish at standard 60-second resolution by default and reserve high-resolution—with its matching ~$0.30 high-resolution alarms—for the few signals where sub-minute reaction time is the actual requirement (spiky autoscaling triggers, circuit breakers).

**Benefit:** Up to 60x fewer publish calls for signals nobody reads at 1-second grain.

### Use Embedded Metric Format for high-volume metrics

**Cost impact: API requests**

For high-throughput services—especially Lambda—skip the metrics API entirely. Embedded Metric Format (EMF) writes a structured JSON line to the log stream you already have, and CloudWatch extracts the metrics asynchronously. No PutMetricData calls, no SDK latency in the request path, and the log line doubles as a queryable record:

```
# Good: metrics ride the log pipe (aws-embedded-metrics library)
from aws_embedded_metrics import metric_scope

@metric_scope
def handler(event, context, metrics):
    metrics.set_namespace('MyApp')
    metrics.put_dimensions({'Endpoint': route})
    metrics.put_metric('Latency', elapsed_ms, 'Milliseconds')
```

You pay log ingestion on the EMF lines (~$0.50/GB—keep them lean), but the per-call metric API cost disappears and batching comes free with the log pipeline.

**Benefit:** Eliminates PutMetricData costs and API latency for high-volume publishers.

---

## Tackling Read-Side Costs

### Tune whatever's polling you

**Cost impact: GetMetricData requests**

The read meter runs quietly until a third-party tool makes it interesting: an external observability platform syncing every metric in the account, or a wall-mounted Grafana refreshing 40 panels every 10 seconds, bills `GetMetricData` per metric requested, around the clock. Scope third-party integrations to the namespaces they actually chart, drop dashboard refresh to 60s+ (matching the resolution of the data—refreshing faster than the metrics update buys literally nothing), and check the `GMD` usage line after any new tool connects.

**Benefit:** Often thousands of dollars a month on accounts with external pollers, for zero visibility loss.

### Delete orphaned alarms and dashboards

**Cost impact: Alarms and dashboards**

Alarms outlive their services. At ~$0.10 each they accumulate politely—until an account audit finds four hundred of them watching metrics that stopped reporting years ago, plus dashboards at $3 apiece nobody has opened since. Alarms in `INSUFFICIENT_DATA` state for months are the tell; list them, match against live metrics, delete the orphans.

**Benefit:** Pure waste, recovered with a list-and-delete session.

### Enable detailed monitoring deliberately

**Cost impact: Custom-metric-priced platform metrics**

EC2 detailed monitoring upgrades instance metrics from 5-minute to 1-minute resolution—at custom-metric prices per instance. Enabled fleet-wide by a Terraform default, it's a per-instance surcharge on boxes whose graphs nobody reads at minute grain. Enable it where autoscaling responsiveness or tight SLOs need 1-minute data; leave the batch fleet on the free 5-minute tier.

**Benefit:** A per-instance monthly surcharge trimmed to the instances that earn it.

---

## Closing Thoughts

Cost-effective CloudWatch metrics requires two steps. First, let observed costs guide what needs optimization. The bill itself does the attribution: metric-count charges point at dimension cardinality, PutMetricData charges at unbatched or high-resolution publishing, GetMetricData charges at whatever's polling you, alarm and dashboard lines at accumulated orphans. Trace each back to the code and configuration minting it. This granular attribution reveals which of the 9 efficiency patterns above matter most. Second, apply the optimizations that address your concentrations: delete duplicates of free built-ins, bound every dimension, batch publishing with Values/Counts (100-1000x fewer calls), default to 60-second resolution, move high-volume publishers to EMF, tame the pollers, and sweep the orphaned alarms quarterly.

You don't lose observability—you lose spend on metrics nobody reads, at resolutions nothing requires, delivered one datapoint at a time. Instrument what only your application knows, and let AWS bill you for that instead.

*Looking for help with cost optimizations like these? [Book a Demo](https://frugal.co) or [Explore the Sandbox](https://frugal.co/sandbox) to see Frugal in action. Frugal attributes AWS CloudWatch metrics costs to your code, finds inefficient usage, provides Frugal Fixes that reduce your bill, and helps keep you out of cost traps for new and changing code.*

 Share Link

[Back to Top](https://frugal.co/blog/ai-or-engineer-cloud-cost-savings-start-with-the-bill-and-end-in-the-code)

![background Image](https://frugal.co/hubfs/697fa79222025cf13e7bec1e_799f61dfa4790fe0e40a2962d5468335_Group%202040.avif)

![Robo Image](https://frugal.co/hubfs/697fadb140def08f5fc80ed3_Group%202042.avif)

### Take Frugal for a live test drive

 Explore how Frugal scans your code and cloud services to find waste, recommend optimizations, and generate ready-to-use fixes in a secure, read-only environment.

[Enter Sandbox](https://auth.frugal.co/sign-up)

![background Image](https://frugal.co/hubfs/697fa936779f0fd839545616_Group%202041.avif)

![robo image](https://frugal.co/hubfs/697fa79222025cf13e7bec1e_799f61dfa4790fe0e40a2962d5468335_Group%202040.avif)

![frugal image](https://cdn.prod.website-files.com/686b121c919ed2b639e30952/686c44713462acbdaab4a82d_f8418228347e59c4cabb651eb7f5e72b_Frame%20291022.avif)

 An Intelligent Application Cost Engineering platform that optimizes code to reduce cloud costs automatically - empowering engineers without slowing development

[Blog](https://frugal.co/blog) [Contact](https://frugal.co/contact) [careers](https://frugal.co/careers) [About Us](https://frugal.co/about)

[Privacy Policy](https://frugal.co/privacy-policy) [Terms of Use](https://frugal.co/terms-and-condition) [Trust Centre](https://trust.frugal.co/) [Active Status](https://status.frugal.co/)

[X](https://x.com/frugalaico) [LinkedIn](https://www.linkedin.com/company/frugalco/)

[Featured in AI Directory](https://aidirectory.wiki)

[Privacy Policy](https://frugal.co/privacy-policy) [Terms of Use](https://frugal.co/terms-and-condition) [Trust Centre](http://trust.frugal.co/) [Active Status](http://status.frugal.co/)

Copyright Frugal AI Inc.

©2025

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Craig Conboy",
    "url" : "https://frugal.co/blog/author/craig-conboy"
  },
  "datePublished" : "2026-07-23T12:48:37.000Z",
  "headline" : "The Frugal Approach to AWS CloudWatch Metrics Costs",
  "image" : [ "https://frugal.co/hubfs/blog-images/featured-Jul-23-2026-12-48-36-4594-PM.jpg" ],
  "mainEntityOfPage" : {
    "@id" : "https://frugal.co/blog/the-frugal-approach-to-aws-cloudwatch-metrics-costs",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://frugal.co/hubfs/Frugal%20Logo%20Transparent%20Black.png"
    }
  }
}
```