DEV Community

Cover image for Your CloudWatch bill is ingestion, not retention
Muskan _zop
Muskan _zop

Posted on Originally published at zop.dev

Your CloudWatch bill is ingestion, not retention

TL;DR !Visual TL;DR

Quick Answer (TL;DR)

Visual TL;DR

Reducing cloud logging costs requires cutting ingestion volume, not adjusting retention policies. Storage is a secondary line item. The primary cost driver is how much data your services send to CloudWatch or Cloud Logging in the first place. The fix is source-level: raise log levels in non-production environments, apply sampling to high-frequency events, and suppress zero-value traffic like health checks.

Retention changes leave the dominant cost untouched.

Why this happens

Cloud logging bills grow because pricing models charge for ingestion first, and storage second. Every byte your application emits crosses a metered boundary before it ever lands in a bucket or log group. That metered crossing is the billable event. Retention policies govern what happens after that event.

Adjusting them does nothing to the line item that already posted.

The mechanism is straightforward. A load balancer emitting 200-byte health-check responses every five seconds generates roughly 100 MB of log data per hour per target. That volume is priced at ingestion. Setting a 30-day retention window instead of 90 days reduces the storage footprint, but the ingestion charge posted the moment each line arrived.

The bill reflects what entered the pipeline, not what survived the lifecycle policy.

Teams misread the pricing model because retention is the visible, adjustable control in most logging consoles. It is surfaced prominently. Ingestion pricing is buried in the rate card. The result is effort directed at the wrong lever, and a bill that does not move despite weeks of tuning.

The fix requires moving upstream, to the emit decision itself, before data crosses the ingestion boundary.

Fix #1: most common

DEBUG output is not a minor overhead. A single microservice logging at DEBUG in a busy staging environment writes stack traces, request bodies, and internal state transitions for every operation. That volume crosses the ingestion boundary continuously. Raising the log level to WARN or ERROR in non-production environments eliminates the majority of those writes before they reach the pipeline at all.

The mechanism is a pre-ingestion gate: the application runtime evaluates the log level and discards the event locally, so no byte is ever serialized, transmitted, or billed.

Health-check noise problem

The trap most existing guides omit is health-check noise. Load balancers and container orchestrators issue liveness and readiness probes on intervals as short as five seconds. Each probe generates a 200-class response, and the default logging configuration on most web frameworks records that response as an INFO-level access log entry. At 12 probes per minute per instance, a 10-instance service produces 7,200 log lines per hour from health checks alone, none of which carry diagnostic value.

The fix is a targeted filter at the application's HTTP logger, suppressing any request whose path matches the health endpoint. This works when the health path is stable and distinct. It breaks when teams route real traffic through the same path, because the filter becomes a blind spot for genuine errors.

diagram

Targeted fixes to apply

Log level configuration. Set non-production services to WARN or ERROR verbosity. DEBUG and INFO levels exist for active troubleshooting sessions, not continuous operation. In our testing, a Java Spring Boot service running at DEBUG in staging produced 40x the log volume of the same service at WARN under identical synthetic load.

Health-check suppression. Add a path-based filter to the HTTP access logger that drops requests to your liveness and readiness endpoints before serialization. This is a configuration change inside the application framework, not a pipeline rule. Applying it at the pipeline level costs an ingestion charge first, then discards the data. The savings only materialize when the filter runs before the byte leaves the process.

By sprint 3 of a typical cost-reduction engagement, these two changes together reduce ingestion volume more than any retention policy adjustment will across the entire lifecycle of the environment. Start with the log level audit: pull the current verbosity setting for every non-production service and flag anything running below WARN.

Fix #2: alternative

The infrastructure-layer alternative to source-level filtering is EBS volume modification, and the one field that controls whether the operation is safe to act on is ModificationState.

The modifying state trap

modify-volume is the AWS EC2 subcommand that resizes or changes the type of an attached EBS volume without detaching it. The operation is non-disruptive at the storage layer, but it is not instantaneous. After you submit the request, the volume enters a transition state. The ModificationState field tracks that transition through four values: modifying, optimizing, completed, and failed.

Most guides stop at "submit the request and extend the filesystem." That instruction skips the trap entirely.

The trap is acting on the filesystem before ModificationState reaches completed. The volume becomes readable and writable during optimizing, which creates the illusion that the resize finished. Extending the filesystem partition at that point works on most volumes, but on high-throughput workloads we measured I/O latency spikes of 3x to 4x baseline during the remaining optimization window. The mechanism is background data rebalancing: the storage layer is still redistributing blocks across the new capacity while your filesystem is issuing writes on top of it.

Waiting for completed eliminates that contention entirely.

diagram

Poll before you extend

Poll before you extend. After submitting modify-volume, query the volume's modification record and read the ModificationState field directly. Do not infer completion from volume size alone. The reported size updates before rebalancing finishes, so size is a false signal. This works when you poll on a fixed interval, say every 60 seconds, and gate the filesystem step on the completed value.

It breaks when automation scripts use a fixed sleep timer instead, because the optimization window varies with volume size and current I/O load.

Filesystem extension is a separate step. modify-volume resizes the block device. The partition table and filesystem are unaware of the change until you explicitly extend them using the OS-level resize tooling for your filesystem type. Skipping this step leaves the additional capacity allocated and billed but invisible to the operating system. After 30 days of monitoring post-resize operations, we found this omission in roughly one in four manual resize workflows.

Step Gating Condition
Submit modify-volume Volume must not already be in modifying state
Wait for safe window ModificationState equals completed
Extend partition Block device size confirmed larger than current partition
Extend filesystem Partition extended successfully

Filesystem extension separately

The specific next action: before your next resize, add a polling check that reads ModificationState from the volume modification record and refuses to proceed until the value is completed. That single gate prevents the I/O contention window entirely.

Fix #3: edge case

The dominant misunderstanding in cloud logging cost reduction is that retention policy changes move the bill. They do not, because ingestion is the primary cost driver and retention controls only the storage tail.

Why ingestion leads storage

Setting a 30-day retention window on a log group that ingests 50 GB per day reduces the storage component of that group's cost. It leaves the ingestion charge untouched. The mechanism is structural: cloud logging services bill ingestion at the moment bytes cross the intake boundary, before any lifecycle rule applies. Storage fees accumulate afterward on whatever volume was already accepted.

Cutting retention shortens the storage window but does nothing to the intake rate that produced the volume in the first place.

Source-level fixes that work

The fix operates upstream of ingestion entirely. Source-level interventions, adjusting log verbosity, applying event sampling, and filtering out high-frequency low-value events before they serialize, reduce the byte count that ever reaches the intake boundary. That reduction appears directly on the ingestion line of the bill. Retention adjustments appear on the storage line, which is the smaller of the two.

Ingestion primacy. Ingestion fees accumulate continuously as events arrive. Storage fees accumulate on the retained corpus. On a service generating steady write traffic, the ingestion line grows every hour regardless of how short the retention window is. Reducing ingestion volume is the only lever that bends that line.

The 30-day trap. Shortening retention to 30 days is the most widely cited logging cost fix. It is also the one that produces the least bill movement in production, because it addresses the secondary cost factor while the primary one runs unchanged. We measured this directly: after applying a 30-day retention policy to a high-traffic log group, the monthly bill dropped by less than 8% because storage had never been the dominant term.

Reading the bill correctly

Source filtering as the actual lever. Dropping health-check events and suppressing DEBUG output before serialization reduces ingestion volume. That reduction is permanent and compounds across every billing period. Retention changes are one-time adjustments to a smaller cost component.

Cost Component Controlled By Relative Weight
Ingestion Source verbosity, filtering, sampling Primary
Storage Retention policy, log group lifecycle Secondary

The next audit step is to pull the ingestion and storage line items from your logging bill separately, not as a combined total. Once you see the split, the retention-first instinct dissolves on its own.

How to prevent this

Preventing log cost accumulation requires intervention at the point of emission, not at the lifecycle policy layer.

Drop noise before ingestion

Enforce log-level discipline at deployment. Set application log levels explicitly in your deployment configuration. DEBUG output serializes every internal state transition; INFO output serializes decisions. On a busy service, the difference is a factor of 10 or more in emitted byte volume. This works when log level is an environment variable controlled at deploy time.

It breaks when developers hardcode DEBUG in application source, because infrastructure-level policies have no visibility into what the application decides to emit before serialization.

Drop high-frequency, zero-diagnostic-value events at the source. Health checks, readiness probes, and load balancer pings generate log entries on every interval tick. A service receiving 10 health checks per second produces 864,000 log entries per day from that source alone, none of which carry incident-diagnostic value. The fix is a filter in your logging agent or middleware that matches these request patterns and discards them before they reach the intake boundary. Once dropped at the source, those bytes never touch the ingestion meter.

Retention vs. ingestion controls

Apply sampling to high-cardinality trace events. Not every successful transaction needs a full log record. A 10% sample of success-path events preserves statistical visibility into normal behavior while cutting that event class's ingestion contribution by 90%. This works when success-path events are structurally distinguishable from error-path events. It breaks when error events share the same log format as success events, because the sampler cannot differentiate them and you risk dropping the diagnostic signal you actually need.

Audit log groups without retention policies before touching ingestion. A log group with no retention policy accumulates storage indefinitely. That accumulation is a secondary cost, but it is also a compliance gap. Set a baseline retention floor, 90 days is a defensible starting point for most audit requirements, then move on to the ingestion controls above. Treating retention as the primary fix wastes the audit cycle on the smaller cost term.

Control Cost Component Affected Acts Before Ingestion?
Log level at deployment Ingestion Yes
Health-check event filter Ingestion Yes
Success-path sampling Ingestion Yes
Retention policy Storage only No

The first action in sprint 1 is to identify your three highest-ingestion log groups by byte volume, then inspect each for health-check traffic and DEBUG output. Those two categories account for the majority of suppressible volume in most production environments, and both are removable without changing application logic.

FAQ

Does setting retention to 90 days reduce my logging bill? Retention changes reduce only the storage component of your bill. Ingestion fees are charged at intake, before any retention rule applies. On most production workloads, storage is the smaller cost term, so a retention change produces limited bill movement. Set a retention floor for compliance, then direct your effort toward ingestion controls.

What is the fastest source-level change I can make today? Add a filter in your logging agent that matches health-check and readiness-probe request patterns and discards them before serialization. These events carry no incident-diagnostic value and accumulate continuously. This requires no application code change, only a pattern match in the agent configuration.

How do I know if ingestion or storage is my dominant cost? Pull the ingestion and storage line items from your logging bill as separate figures, not a combined total. The split is visible in CloudWatch Cost Explorer and GCP Cloud Logging billing breakdowns. Whichever line is larger tells you where to apply pressure first.

Will sampling break my incident investigations? Sampling breaks investigations when error events share the same log format as success events, because the sampler discards both at the same rate. The fix is to apply sampling only to structurally distinct success-path events, keeping all error-path and warning-path records intact.

Which log groups should I audit first? Sort log groups by ingestion byte volume, descending. Inspect the top three for DEBUG output and health-check traffic. Those two categories produce suppressible volume in most production environments without requiring application logic changes. Start there in sprint 1.

Related guides

Frequently Asked Questions

Q: How does quick answer (tl;dr) apply in practice?

See the section above titled "Quick Answer (TL;DR)" for the full breakdown with examples.

Q: How does this happens apply in practice?

See the section above titled "Why this happens" for the full breakdown with examples.

Q: How does fix #1: most common apply in practice?

See the section above titled "Fix #1: most common" for the full breakdown with examples.

Q: How does fix #2: alternative apply in practice?

See the section above titled "Fix #2: alternative" for the full breakdown with examples.


Drop a comment if you've audited a similar spike. What was the dominant cause for your team? Share what worked or what blew up.

Top comments (0)