CloudWatch Metrics
A metric is simply a number measured over time — for example, the CPU usage of a server every minute. Amazon CloudWatch (AWS’s monitoring service) collects these numbers automatically from most AWS services, and lets you publish your own. Metrics are the raw data that everything else in monitoring is built on: alarms watch them, dashboards chart them, and you can query them to understand how your system behaves. This page explains how metrics are organized, how often they are sampled (resolution), and how to publish your own custom metrics without accidentally running up a big bill.
How a metric is identified
Every metric in CloudWatch is uniquely identified by three things working together:
- Namespace — a container that groups related metrics, like a folder. AWS services use names that start with
AWS/, for exampleAWS/EC2orAWS/Lambda. Your own metrics should use your own namespace, likeMyApp/Checkout. - Metric name — what you are measuring, such as
CPUUtilizationorOrderCount. - Dimensions — name/value pairs that describe which specific thing the number applies to. For example, the dimension
InstanceId=i-0a1b2c3d4e5ftells CloudWatch the CPU reading belongs to one particular server.
The important rule to remember: a metric is the unique combination of namespace + name + every dimension. If you publish OrderCount with Region=us-east-1 and also with Region=eu-west-1, those are two separate metrics. This matters a lot for cost, which we cover below.
Resolution: standard, detailed, and high-resolution
Resolution is how often a data point is recorded. Choosing the right one balances visibility against cost.
| Type | Sampling period | How to enable | Typical use |
|---|---|---|---|
| Basic / standard | Every 5 minutes | Default for most AWS services (free) | General health monitoring |
| Detailed | Every 1 minute | Opt-in per resource (extra charge) | Faster alarms, autoscaling |
| High-resolution | Down to 1 second | Set StorageResolution=1 on custom metrics | Spiky, latency-sensitive workloads |
When to use detailed monitoring: turn it on for EC2 (Elastic Compute Cloud, AWS’s virtual servers) instances that scale automatically or run critical workloads, so alarms react within a minute instead of five. When NOT to: leave it off for low-priority or batch instances — 1-minute detailed monitoring costs roughly $2.10 per instance per month, which adds up across a fleet.
When to use high-resolution: only for short bursts you must catch, like sub-second latency spikes on a payment API. When NOT to: for slow-moving metrics like daily disk usage — you would pay for granularity you never look at.
Cost gotcha — retention rollups. CloudWatch keeps high-resolution (1-second) data for only 3 hours, 1-minute data for 15 days, 5-minute data for 63 days, and 1-hour data for 15 months. Older data is automatically rolled up to a coarser resolution, not deleted. So a query for last year’s data will only return hourly points — plan dashboards and exports accordingly.
Publishing custom metrics
AWS only knows about the resources it manages. To track business numbers (orders placed, queue depth, cache hit rate), you publish custom metrics. There are two ways.
Option 1: PutMetricData (the direct API)
You call the PutMetricData API (or CLI) and push a value. This is simple and works from anywhere.
Console steps (custom metrics cannot be created in the console, but you can view them once published):
- Open the CloudWatch console.
- In the left menu choose Metrics → All metrics.
- Under Custom namespaces click your namespace (e.g.
MyApp/Checkout) to browse the metrics your code published.
AWS CLI (v2):
aws cloudwatch put-metric-data \
--namespace "MyApp/Checkout" \
--metric-name OrderCount \
--unit Count \
--value 1 \
--dimensions Service=checkout,Environment=prod
Output:
(no output on success; HTTP 200 returned)
To confirm the data landed, read it back:
aws cloudwatch get-metric-statistics \
--namespace "MyApp/Checkout" \
--metric-name OrderCount \
--dimensions Name=Service,Value=checkout Name=Environment,Value=prod \
--start-time 2026-06-15T00:00:00Z \
--end-time 2026-06-15T01:00:00Z \
--period 300 \
--statistics Sum
Output:
{
"Label": "OrderCount",
"Datapoints": [
{
"Timestamp": "2026-06-15T00:30:00Z",
"Sum": 42.0,
"Unit": "Count"
}
]
}
Tip — aggregate before you send. Calling
PutMetricDataonce per order is slow and costs an API request each time. Instead, count orders in memory for, say, 60 seconds and publish one summarized value. You can also batch up to 1,000 data points in a single API call.
Option 2: Embedded Metric Format (EMF)
The Embedded Metric Format (EMF) lets you write a specially structured JSON log line, and CloudWatch automatically extracts metrics from it. This is the recommended approach for AWS Lambda (AWS’s serverless functions) because there is no separate network call — you just log.
{
"_aws": {
"Timestamp": 1718409600000,
"CloudWatchMetrics": [
{
"Namespace": "MyApp/Checkout",
"Dimensions": [["Service"]],
"Metrics": [{ "Name": "OrderCount", "Unit": "Count" }]
}
]
},
"Service": "checkout",
"OrderCount": 1
}
When to use EMF: in Lambda and containers where you already ship logs to CloudWatch Logs — it avoids extra latency and request charges. When to use PutMetricData: from scripts, cron jobs, or anywhere you are not already logging to CloudWatch.
The high-cardinality cost trap
Because every unique dimension combination is its own billable custom metric, adding a high-cardinality dimension explodes both cost and clutter. Cardinality means the number of distinct values a dimension can have.
Imagine you publish RequestLatency with a UserId dimension and you have 100,000 users. That is 100,000 separate custom metrics, each billed (roughly $0.30 per metric per month in us-east-1 for the first tier) — over $30,000/month for one metric name.
Do this instead:
- Use low-cardinality dimensions:
Environment,Region,Service,StatusCode. These have a handful of values. - Push per-user detail to CloudWatch Logs and query it with Logs Insights when you need it — logs are far cheaper for high-cardinality data.
- If you only need percentiles, publish the raw value and let CloudWatch compute p50/p90/p99; you do not need a dimension per user.
Best practices
- Use a clear, consistent custom namespace per application or team (e.g.
MyApp/Checkout) so metrics are easy to find. - Keep dimensions low-cardinality; never use IDs like user, request, or order IDs as dimensions.
- Aggregate values in your code and batch up to 1,000 data points per
PutMetricDatacall to cut API costs. - Reserve detailed (1-minute) and high-resolution (1-second) metrics for resources where fast alarms truly matter.
- Always set a meaningful
Unit(Count, Milliseconds, Bytes) so charts and alarms read correctly. - Remember retention rollups: export long-term data to S3 or a data warehouse if you need fine-grained history beyond 15 months.