Athena bills on bytes scanned, so a single careless SELECT *
against a Cost and Usage Report can cost more than the report does to store.
Athena has two built-in cost controls that address this, they behave
completely differently, and only one of them actually stops anything.
What does a runaway query cost?
Athena pricing charges on data scanned, and works its example at $5 per TB. The same page shows the two levers that move that number, reporting "3x savings from compression and 4x savings for reading only one column" after converting a dataset to Parquet.
A CUR is exactly the shape that punishes carelessness: wide, with 125 possible columns in CUR 2.0, and long, particularly once split cost allocation data is adding tens of thousands of rows a day per cluster. Scanning all of it repeatedly, from a dashboard that refreshes on a timer, is how a query bill becomes a line item somebody asks about.
What are the two controls?
Per the per-query and per-workgroup data usage controls documentation: "Athena allows you to set two types of cost controls: per-query limit and per-workgroup limit. For each workgroup, you can set only one per-query limit and multiple per-workgroup limits."
| Per-query limit | Per-workgroup limit | |
|---|---|---|
| Measures | Data scanned by one query | Data scanned by all queries in a period |
| On breach | Cancels the query | Sends an alert. Cancels nothing. |
| How many | One per workgroup | Multiple per workgroup |
| Period | Per query | Hourly or daily aggregate |
| Configurable action | No: "this setting cannot be changed" | Yes: SNS, Lambda, disable the workgroup |
The distinction in the second row is the one that costs money when it is misread. AWS is explicit about the workgroup limit: "Queries are not automatically canceled when a threshold is reached." It is an alarm, not a brake. If you configured a 1 TB daily workgroup limit believing it was a cap, you have a notification that your budget was exceeded, not a mechanism that prevented it.
The per-query limit is the one with teeth: "if any query that runs in the workgroup exceeds the limit, it is canceled", and "the default action is to cancel the query if it exceeds the limit. This setting cannot be changed."
How do you set the per-query limit?
On the workgroup's Execution controls tab, as a Data scanned limit. AWS documents the accepted range as "between 10 MB (minimum) and 7 EB (maximum)".
Pick the number from what your legitimate queries actually scan, not from what
feels safe. A monthly CUR aggregate over one billing period scans far less
than an unpartitioned full-table scan, and the gap between those two is where
the limit belongs. If you do not know the figure, run your normal workload and
read DataScannedInBytes, which
QueryExecutionStatistics
defines as "the number of bytes in the data that was queried", before
choosing.
aws athena get-work-group --work-group awsinsights_cur \
--query 'WorkGroup.Configuration.BytesScannedCutoffPerQuery'
WorkGroupConfiguration defines that field as "the upper data usage limit (cutoff) for the amount of bytes a single query in a workgroup is allowed to scan", with a valid range starting at 10,000,000 bytes, which is the 10 MB floor the console shows.
A null result means no per-query limit is set, which is the default and is worth checking on any workgroup that a dashboard or a scheduled job points at.
Do you pay for a query that gets cancelled?
Yes. This is the sentence that changes how you think about the limit: "Canceled queries are charged according to Amazon Athena pricing."
So the per-query limit caps your exposure rather than eliminating it. Set it at 1 TB and a runaway query costs you up to a terabyte of scanning before it dies. That is still far better than the unbounded alternative, but it means the limit is a circuit breaker, not a refund.
Athena's own metrics agree. The ProcessedBytes metric counts bytes scanned per DML query, and explicitly: "for queries that were canceled (either by the users, or automatically, if they reached the limit), this includes the amount of data scanned before the cancellation time." The bytes are counted, so they are billed.
There is a second, quieter cost. AWS notes that "in the case of canceled or failed queries, Athena may have already written partial results to Amazon S3. In such cases, Athena does not delete partial results from the Amazon S3 prefix where results are stored. You must remove the Amazon S3 prefix with partial results."
Those partial results are yours to clean up, and they accumulate silently in the results bucket. AWS recommends a specific remedy: "Athena uses Amazon S3 multipart uploads to write data to Amazon S3. We recommend that you set the bucket lifecycle policy to end multipart uploads in cases when queries fail." Incomplete multipart uploads are storage you are billed for and cannot see in the object listing, which makes them one of the better-hidden line items in S3.
It gets one turn worse: "under certain conditions, Athena may automatically
retry query executions. In most cases, these queries are able to complete
successfully and the query ID is marked as Completed. These
queries might have written partial results during the initial attempts and may
generate incomplete multipart uploads." So a query that shows as successful
can still have left debris behind.
What can the workgroup alert actually do?
More than send an email, if you wire it up. The threshold sets "multiple thresholds on hourly or daily aggregates on data scanned by queries running in the workgroup", and on breach an SNS notification fires. From there AWS documents two escalations worth knowing:
- Invoke a Lambda function, through an SNS subscription.
- Disable the workgroup "to stop any further queries from running".
That second one is the missing brake. The workgroup limit cannot cancel a query, but an SNS-triggered Lambda that disables the workgroup stops the next one. It is a blunt instrument and it will interrupt legitimate work, so it belongs on a workgroup used by scheduled jobs rather than one people query interactively.
One race condition to be aware of, which AWS states directly: "if two or more users run queries at the same time in the same workgroup, it is possible that each query does not exceed any of the specified limits, but the total sum of data scanned exceeds the data usage limit per workgroup." Per-query limits do not compose into a per-workgroup guarantee.
Why separate workgroups at all?
Because the per-query limit is one per workgroup, and one number rarely fits every consumer. A dashboard issuing narrow, partitioned queries every fifteen minutes and an analyst doing exploratory work have completely different legitimate ceilings. Putting them in one workgroup forces the limit up to the analyst's needs, which leaves the dashboard unprotected.
Splitting by consumer also gives you per-consumer attribution, since
WorkGroup is a documented dimension on Athena's
AWS/Athena metrics, alongside QueryState
(SUCCEEDED, FAILED or CANCELED) and
QueryType. It also lets you disable one group's access without
touching the other.
Those metrics are opt-in per workgroup rather than automatic:
PublishCloudWatchMetricsEnabled "indicates that the Amazon
CloudWatch metrics are enabled for the workgroup". If a workgroup's Metrics
tab is empty, check that flag before concluding nobody is querying.
The controls are the last line, not the first
Every control here limits damage after a query is already scanning. The cheaper interventions come earlier and are all about scanning less:
- Always filter on a partition column. A query with no partition predicate scans everything, whatever else is configured.
-
Never
SELECT *on a CUR. Columnar storage means you are billed for the columns you name, so naming all 125 forfeits the entire benefit. - Use a columnar format. Athena's own example shows 3x from compression and 4x from reading one column.
- Remove the catalog lookup too, with partition projection, which is free and eliminates the crawler.
- Cache results in the application. The cheapest scan is the one that does not run: our own CUR queries go through a 15-minute cache, so a dashboard refresh costs nothing after the first hit.
Set the per-query limit anyway. It is a single number on a workgroup, it costs nothing, and it converts an unbounded mistake into a bounded one. Just do not mistake the workgroup threshold for the same thing.