With usage-billed observability services like Datadog, cost optimization starts with your code. It's not just that applications emit the metrics; applications spend. Every statsd.increment, every tag, every instrumented code path contributes to your bill—and one careless tag can contribute more than the rest of the service combined.
The key to Datadog metrics billing: you pay per unique timeseries, not per datapoint. A "custom metric" is every distinct combination of metric name and tag values (~$0.05 per timeseries per month past your per-host allotment). Send checkout.latency a million times with three tags and it's a handful of billable series; send it once with a user_id tag and it's a new billable series per user. Cardinality is the price, and cardinality lives in your code.
Here's a practical walk-through of what to look for and how to optimize Datadog metrics costs at the code and configuration level.
| Cost Trap | Efficiency Pattern |
|---|---|
| Custom metrics duplicating integration metrics | Use the free metrics the Agent already collects |
| High-cardinality tags | Tag with bounded sets; never IDs |
| Ephemeral infrastructure tags | Don't tag by container/pod/run identity |
| Per-event gauges at high frequency | Aggregate client-side; let counters count |
| Histogram aggregation multiplication | Emit only the aggregations you query |
| Every replica reporting a shared value | One reporter for shared state, not N |
| Metrics nobody queries | Audit usage; drop unqueried series and tags |
| Dev/staging emitting at production volume | Trim tag sets and metric sets outside prod |
Attribute the Costs
Start with your bill. Break down costs by metric name to understand where spend concentrates. Datadog's own tooling does the heavy lifting: the Plan & Usage page and the Metrics Summary rank metric names by cardinality—the count of distinct timeseries each generates—and the estimated custom metrics usage per name. The distribution is reliably brutal: a handful of metric names, usually the ones with the richest tags, account for the vast majority of billable series.
Then attribute those names to code. The metric with 400,000 series isn't 400,000 lines of instrumentation—it's one line, with one tag whose values don't stop. Which tag is unbounded? Which emitter runs in every replica? Which metrics exist because someone copied an instrumentation block three years ago? This granular attribution reveals which of the 8 efficiency patterns below deliver the highest impact.
Tackling Cardinality Costs
Cardinality is the bill. These four patterns are where it comes from.
Use the free metrics first
Cost impact: Custom metric count
The Agent's integrations already collect system CPU, memory, disk, and network, plus deep metric sets for databases, queues, and web servers—free, not custom. Publishing your own version of them pays custom-metric rates for data you already have:
# Bad: hand-rolled system metrics ($0.05/series/month, per host)
statsd.gauge('app.cpu.usage', psutil.cpu_percent(), tags=[f'host:{host}'])
statsd.gauge('app.mem.used', psutil.virtual_memory().used, tags=[f'host:{host}'])
# Good: delete the code. system.cpu.* and system.mem.* are already there.
Before instrumenting anything infrastructure-shaped, check the integration's metric list. The answer is usually "already collected."
Benefit: Deleted metrics cost nothing and were duplicates anyway.
Tag with bounded sets, never IDs
Cost impact: Custom metric count (multiplicative)
Every distinct tag value mints a new billable timeseries, and tag cardinalities multiply. A metric tagged by endpoint (30 values) × status (5) × region (4) is 600 series—fine. Add user_id and it's 600 × your user count:
# Bad: one series per user, per endpoint, per status...
statsd.increment('api.requests', tags=[
f'endpoint:{path}', f'status:{code}', f'user_id:{user.id}'])
# Good: bounded tags on the metric; IDs belong in logs and traces
statsd.increment('api.requests', tags=[
f'endpoint:{route_template}', f'status_family:{code // 100}xx',
f'tier:{user.tier}'])
Note route_template, not path—raw URL paths with embedded IDs are the stealth version of the same trap. The rule: a tag's value set should be knowable in advance. If you can't enumerate it, it doesn't belong on a metric.
Benefit: The difference between hundreds of series and hundreds of thousands, on a single metric name.
Don't tag by ephemeral infrastructure
Cost impact: Custom metric count (churning)
Container IDs, pod names, task ARNs, CI run numbers—each deploy or restart mints fresh tag values, so the series count grows with churn even while steady-state traffic doesn't. Kubernetes deployments cycling pods a few times a day multiply every pod-tagged metric by every cycle. Tag by the stable identity (service, deployment, version) and let Datadog's own container tagging handle the ephemeral layer where you actually need it.
Benefit: Series count tracks your architecture, not your restart count.
Emit only the histogram aggregations you query
Cost impact: Custom metric count (silent multiplier)
A DogStatsD histogram ships as multiple series per tag combination—avg, count, median, 95percentile, max by default. Instrument one timing, get five series; most dashboards query one or two of them. Configure histogram_aggregates (and percentiles) down to what you actually chart, or use distributions and enable only the percentiles you need.
Benefit: 40-80% reduction in series from timing metrics, with identical dashboards.
Tackling Volume & Duplication Costs
Aggregate client-side; let counters count
Cost impact: Application and Agent overhead
Datadog aggregates to ~10-second flush intervals anyway—emitting a gauge per event at thousands of events per second doesn't buy resolution, it just burns application CPU, network, and Agent throughput before collapsing into the same datapoints:
# Bad: a gauge write per processed message (5,000/sec)
for msg in messages:
process(msg)
statsd.gauge('worker.processed_total', counter.next())
# Good: a counter, incremented (and batched by the client library)
for msg in messages:
process(msg)
statsd.increment('worker.processed')
Counters and the client's buffering exist precisely so high-frequency events become low-frequency submissions. Per-event gauges of a running total are a counter implemented badly, at gauge prices in overhead.
Benefit: 80-99% less submission traffic for the same charts.
One reporter for shared state
Cost impact: Custom metric count and misleading data
When 200 replicas each gauge the depth of the same shared queue, you get 200 series (tagged per host or per pod) of the same number—paying 200x for data that also reads wrong when aggregated. Report shared state from one place: a leader, a scheduled job, or the integration for the queue itself.
Benefit: One series where there were N; charts that mean what they say.
Drop what nobody queries
Cost impact: Custom metric count
Instrumentation accretes. Metrics outlive the dashboards that justified them, and tags outlive the debugging session that added them. The Metrics Summary shows which metrics haven't been queried in months; Metrics without Limits™ lets you keep ingesting a metric while indexing only the tag combinations anyone actually uses—cardinality control without touching code. Do the audit quarterly: unqueried metrics get deleted at the source, over-tagged metrics get a tag allowlist.
Benefit: Typically 20-50% of custom metric spend, recovered from series with zero readers.
Trim the non-production tax
Cost impact: Custom metric count (doubled and tripled)
Dev and staging environments running the same instrumentation with the same tag richness emit near-production series counts for traffic nobody monitors at 3am. Keep the metric names (parity matters for testing dashboards and monitors) but cut the tag depth and histogram aggregations outside prod—an environment-aware tag list in your metrics wrapper is a one-file change.
Benefit: Non-prod metric spend drops to a fraction while alerting parity survives.
Closing Thoughts
Cost-effective Datadog metrics requires two steps. First, let observed costs guide what needs optimization. Attribute your bill down to metric names by cardinality—the Metrics Summary hands you the ranking—then to the specific tags and code paths minting the series. Which tag is unbounded, which emitter runs in every replica, which histogram ships five aggregations for one chart, which metrics haven't been queried since the dashboard was deleted. This granular attribution reveals which of the 8 efficiency patterns above matter most. Second, apply the optimizations that address your concentrations: delete duplicates of integration metrics, bound every tag set, stop tagging by ephemeral identity, aggregate client-side, trim histogram aggregations, elect one reporter for shared state, and run the quarterly audit on what nobody queries.
You don't lose observability—you lose noise you were paying to index. A metric exists to be looked at; the discipline is spending on the series someone will actually query, and nothing else.
Looking for help with cost optimizations like these? Book a Demo or Explore the Sandbox to see Frugal in action. Frugal attributes Datadog 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.