TL;DR. Amazon Athena pricing for SQL is $5 per TB scanned, rounded up to the megabyte, with a 10 MB minimum per query. We stored one 60 million row table five ways and priced the bytes the same query would scan on each (measured locally, not on Athena): 7,541 MB billed on CSV ($0.036), 415 MB on unsorted Parquet ($0.002), and the 10 MB minimum on Parquet sorted by date ($0.00005). Sorting one file by the filter column did as well as 84 monthly partitions, because both fell under the floor. An always-on 4 DPU reservation, the smallest, beats per-TB pricing only past 175.2 TB of scans a month (52.8 TB if held 10 hours on working days), so fix the layout first.
Amazon Athena pricing is a bill for bytes: AWS charges $5.00 per TB of data scanned in US East (N. Virginia), so the table layout sets the price of every query more than the query does. The pages that rank for this term work from rate tables and AWS's hypothetical 3 TB example. This post measures one real table instead, stored five ways, and prices the bytes the same query would scan on each.
How Amazon Athena is priced: bytes scanned, a 10 MB floor, or DPU-hours
Athena has three meters, all on the pricing page and in the AWS Price List API for us-east-1 (version 20260911124408, read November 11, 2026):
| Meter | Rate | Billing rule |
|---|---|---|
| SQL, per query | $5.00 per TB scanned | rounded up to the nearest megabyte, 10 MB minimum per query |
| SQL, capacity reservation | $0.30 per DPU-hour | billed per minute, 1 minute minimum, at least 4 DPU |
| Apache Spark | $0.35 per DPU-hour | billed per second |
AWS lists what is free: "There are no charges for Data Definition Language (DDL) statements like CREATE, ALTER, or DROP TABLE statements for managing partitions, or failed queries." Canceled queries "are billed for the total amount of data scanned when the query is canceled."
The 10 MB minimum is the number most pages skip. At $5 per TB, 10 MB costs $0.0000477. A query that reads 400 KB pays the same as one that reads 10 MB, so past a certain point a better layout saves nothing.
What one query costs: CSV, Parquet, sorted and partitioned
The table is TPC-H lineitem at scale factor 10: 59,986,052 rows and 16 columns, generated with DuckDB 2.0.0. We wrote it five ways, ran one query in DuckDB, a revenue sum for a single month, and read from the files and Parquet metadata how many bytes Athena would scan:
SELECT SUM(l_extendedprice * l_discount) FROM lineitem WHERE l_shipdate BETWEEN DATE '1997-06-01' AND DATE '1997-06-30';
Every layout returned the same 748,161 rows and the same sum, 1,429,498,072.36. The partitioned table filters on its partition column instead (ship_month = '1997-06'), which selects the same rows.
| Layout | Bytes read | MB billed | Per query | Per 1,000 queries |
|---|---|---|---|---|
| CSV, uncompressed | 7,907,073,042 (whole file) | 7,541 | $0.035958 | $35.96 |
| CSV, gzip | 2,326,281,398 (whole file) | 2,219 | $0.010581 | $10.58 |
| Parquet, one file, unsorted | 435,030,048 (3 columns, 489 of 489 row groups) | 415 | $0.001979 | $1.98 |
Parquet, one file, sorted by l_shipdate | 4,873,787 (3 columns, 7 of 489 row groups) | 10 (minimum) | $0.000048 | $0.05 |
| Parquet, 84 monthly partitions | 4,238,742 (2 columns, 1 partition) | 10 (minimum) | $0.000048 | $0.05 |
The arithmetic for the CSV row: 7,907,073,042 bytes is 7,540.77 MiB, rounded up to 7,541 MB, and 7,541 / 1,048,576 MB per TB × $5 = $0.035958. The two bottom rows read about 4.6 MiB and 4.0 MiB and both bill the 10 MB floor.
Three findings the ranking pages do not have:
- Gzip cut the bill by 3.4 times and nothing more. AWS says text formats "can't be split", so Athena scans the whole compressed file: 2.33 GB instead of 7.91 GB.
- Unsorted Parquet still read every row group. Athena uses min and max statistics "to determine whether to read or skip the block", but in a file ordered by order key every row group held ship dates from early January 1992 to late November 1998. The saving (18 times) came from reading 3 of 16 columns, not from the filter.
- Sorting matched partitioning. The sorted file let the date filter skip 482 of 489 row groups and landed under the 10 MB floor, as the partitioned table did, with one file and no partitions to register. AWS's own tuning guide makes the same call: "Instead of partitioning by hour, keep the records sorted by timestamp."
Method and limits. We did not run these queries on Athena. Bytes come from file sizes (CSV) and from the Parquet metadata (column chunk sizes of the row groups whose statistics overlap June 1997), on a local disk. Athena's own DataScannedInBytes may differ by its footer reads (0.9 MB for the single file) and by how its reader fetches pages; neither changes the order of the rows. DuckDB writes row groups of 122,880 rows, about 4.5 MB each here; AWS cites a 128 MB default and advises against going below it. With row groups that large the skip is coarser: by our arithmetic each one touched would add about 25 MB for these three columns, above the floor. We count 1 MB as 1,024² bytes; in decimal units every cost above is about 10% higher.
Repetition turns cents into a line item: dashboards and agent loops
One query at $0.036 is not worth an afternoon. A query on a schedule is. Consider a dashboard tile that refreshes every 5 minutes, 10 hours a day, 22 working days: 2,640 runs a month.
| Layout | Per month for that one tile |
|---|---|
| CSV | $94.93 |
| CSV, gzip | $27.93 |
| Parquet, unsorted | $5.22 |
| Parquet, sorted or partitioned | $0.13 |
Scale the table up and the bill follows: on a 1 TB CSV table the same tile would scan 2,640 TB a month, $13,200.
Agents repeat queries too. Armin Catovic wrote on October 28, 2026 that in an A/B test of models on Reef, Funnel's internal data agent, one model "executed the same successful SQL query 113 and 254 times" in two runs, and that some saved agent instructions "referenced Athena tables and fields that didn't actually exist." Catovic did not publish what those runs cost. As a hypothetical: 254 runs of our CSV query cost $9.13, and 254 runs against a 1 TB unsorted CSV table would cost $1,270.
The strongest objection is that this is small money, and for one ad hoc table it is. A hundred queries a month on our CSV table cost $3.60, less than the time to rewrite it. The layout work pays where queries repeat: dashboards, scheduled reports and agents. Rewrite those tables first and leave the rest.
The lines besides data scanned: S3, Glue and query results
Athena's page says it plainly: "You are charged standard S3 rates for storage, requests, and data transfer." In us-east-1 the S3 price list gives:
- Storage: $0.023 per GB-month for the first 50 TB. Our table is 7.91 GB as CSV and 2.21 GB as one Parquet file, so the format also cuts storage by about 3.6 times. More on what data lakes pay for files nobody reads in S3 storage cost.
- GET requests: $0.004 per 10,000. We found no AWS figure for how many GETs a query makes. 100 GETs would cost $0.00004, close to the $0.0000477 the query itself bills at the floor, so a table of thousands of small files can cost more in requests than in scans.
- Query results: stored "in an S3 bucket of your choice and are also billed at standard S3 rates." An aggregate returns a few bytes; a
SELECT *export can write gigabytes that stay forever without a lifecycle rule. Canceled or failed queries can leave partial results behind, and AWS recommends a lifecycle rule to end the incomplete multipart uploads they leave.
The AWS Glue Data Catalog holds the table definitions. The first million objects stored and the first million requests each month are free; after that it is $1.00 per 100,000 objects and $1.00 per million requests. A partition counts as an object, and so does each table version, so our 84 monthly partitions cost nothing. A million objects is about 2,700 table-years of daily partitions or 114 of hourly ones, so this line matters for hourly partitions and heavily versioned tables.
When provisioned capacity is cheaper than $5 per TB
A capacity reservation replaces the scan charge: "There are no charges for data scanned." You pay for DPUs held, at least 4, and AWS says it "automatically allocates between 4 and 124 DPUs to DML queries based on their complexity."
| Reservation | Hours a month | Cost | Same money in per-TB scans |
|---|---|---|---|
| 4 DPU, always on | 730 | $876.00 | 175.2 TB |
| 4 DPU, 10 hours × 22 days | 220 | $264.00 | 52.8 TB |
Below 175.2 TB of scans a month, per-TB pricing is cheaper than the smallest always-on reservation. Our CSV dashboard tile scans 19.0 TB a month ($94.93), about a ninth of that line. The DPUs held also set concurrency: AWS says 256 DPUs support "approximately twice the number of concurrent queries" of 128. So the right comparison for a busy team is its own peak DPU need, which the DPUConsumed metric in CloudWatch reports once a workgroup runs on a reservation.
Redshift Spectrum is priced the same way per byte: $5 per TB scanned, "rounded up to the next megabyte, with a 10-megabyte minimum per query." Every number in this post holds there too. The difference is around it: Spectrum has its own per-TB line beside the cluster bill, AWS says it is "included with Amazon Redshift Serverless", and RG instances do not need it because they "include a built-in data lake query engine." The Redshift pricing post prices those clusters.
Measure cost per query and per workgroup from DataScannedInBytes
Athena returns DataScannedInBytes for each query execution. This prices the last 50 queries in a workgroup with the billing rules above, keeping DML queries that succeeded or were canceled:
WG=primary
aws athena list-query-executions --work-group "$WG" --max-items 50 --output json \
| jq -r '.QueryExecutionIds[]' \
| xargs aws athena batch-get-query-execution --output json --query-execution-ids \
| jq -r '.QueryExecutions[]
| select(.StatementType == "DML" and (.Status.State == "SUCCEEDED" or .Status.State == "CANCELLED"))
| ([(.Statistics.DataScannedInBytes // 0) / 1048576 | ceil, 10] | max) as $mb
| [($mb * 5 / 1048576), $mb, .Status.State, (.Query | gsub("\\s+"; " ") | .[0:70])]
| @tsv' \
| sort -g -r | head -20
The first column is dollars per query; group the output by query text and the repeated ones surface first. For a running view, turn on "publish query metrics" for the workgroup: CloudWatch then gets ProcessedBytes, "the number of bytes that Athena scanned per DML query", with a WorkGroup dimension.
Two guards stop a loop before it bills. A per-query data usage control cancels any query over a limit you set, from 10 MB to 7 EB; the canceled query is still billed for what it read. Per-workgroup alerts fire an SNS notification when the hourly or daily total crosses a threshold, but they do not cancel queries. Give every agent its own workgroup with a per-query limit sized to its biggest legitimate query.
For the rest of the stack, the data warehouse cost post prices one workload across four platforms.
Datapace is building one map of your data estate with usage and cost attached, so the tables Athena scans most, and who or what scans them, are visible in one place, and agents that propose the layout change as a pull request your team reviews and merges. If an Athena or data lake bill is growing faster than its usage, book a call.




