The Database Bill Has Two Shapes

Aurora charges $0.20 per million I/Os, and two clusters with the same engine, the same instance class and the same 200 GB of data can differ threefold on the invoice. This is the arithmetic behind the I/O-Optimized configuration announced last month: where the crossover actually sits, what moves it, and why the vendor's 25 percent rule of thumb is wrong by a factor of two for a storage-heavy cluster. It goes wrong because nobody owns the query plan and the invoice at the same time.

Three Lines, and Only Two Are Yours

Two clusters, same engine, same instance class, same 200 GB of data, same number of rows. One of them costs roughly what you would guess from the instance price. The other costs three times that, and the difference is not on the invoice as anything you can point at, because it is a line called I/O that reads as a technical detail rather than as the largest number on the page.

The reason is that Aurora Standard bills three things and only two of them are properties of the database. You pay for the instance, by the hour, which is a decision somebody made in a console. You pay for storage, by the gigabyte-month, which is a fact about how much data you have. And you pay $0.20 per million I/O requests, which is not a fact about the database at all. It is a fact about what your application asks the database to do.

That third one behaves differently from the other two. Instance and storage costs move when somebody changes something deliberately. The I/O line moves when a query plan changes, when a table grows past the buffer cache, when a nightly job that used to run against a replica gets pointed at the writer, or when a release adds a page that fetches one row at a time in a loop. None of those are budget decisions and all of them are billing events.

So here is the whole thing in one sentence: a managed database is quoted per hour and per gigabyte, and priced by neither. Aurora I/O-Optimized, announced on 11 May, is AWS offering to sell you the other shape - no per-request charge, a higher hourly rate and more than double the storage rate - and the entire decision between them is a piece of arithmetic that takes ten minutes and that almost nobody does.

What Actually Counts as an I/O

A read is a page your memory did not have. AWS documents the billed read metric, VolumeReadIOPs, as accruing for queries that request database pages which are not in the buffer cache and must be loaded from storage. Reads served from memory cost nothing. That makes instance memory a thing you buy twice: once as latency, which everyone knows, and once as suppression of a per-request charge, which almost nobody puts in the sizing spreadsheet.

A write is a record reaching the storage layer. The write side is not page writes. AWS describes its write metric as the number of Aurora storage write records generated, more or less the number of log records the database produced, and notes explicitly that these do not correspond to page writes. The consequence lands on schema design: every secondary index you add generates its own records on every insert and update, forever, at a rate nothing in the query plan tells you.

Fast and cheap are different axes. AWS's own documentation gives the cleanest example. Aurora MySQL's parallel query does not use the buffer pool, so it can make a scan dramatically faster while raising billed read operations and the charge that follows them. A change that improves latency and increases cost is not a contradiction here, and it is exactly the kind of change that gets shipped on the strength of a benchmark nobody costed.

The meter is on the cluster, not the instance. Billed read operations are calculated at the cluster volume level, aggregated across every instance, and reported in five-minute totals. So a read replica added to take load off the writer does not divide the bill; it adds a second cache that must warm up, and the misses it takes while warming are billed to the same volume. Splitting reads across more instances moves latency around and adds I/O.

Doing the Crossover

Take the published us-east-1 on-demand rates. A db.r6i.large is listed at $0.29 per hour on Aurora Standard and $0.377 on I/O-Optimized. Storage is $0.10 per GB-month against $0.225. I/O is $0.20 per million requests on Standard and nothing at all on I/O-Optimized. Everything below is those five numbers and a month of 730 hours, so anyone can redo it with their own instance class and their own region.

For a cluster with 200 GB of data, the premium you are being asked to pay is the compute difference plus the storage difference:

  • Compute: $0.377 minus $0.29, times 730 hours, is $63.51 a month.
  • Storage: $0.125 per GB-month, times 200 GB, is $25.00 a month.
  • Premium: $88.51 a month, which at $0.20 per million buys 442 million I/Os.
  • Spread over 730 hours, that is about 168 I/Os per second.

So for that cluster the answer is: above roughly 168 I/Os per second sustained, I/O-Optimized is cheaper. That is a low number. It is the kind of rate a moderately busy application produces without anybody describing it as I/O-intensive, which is the first uncomfortable finding, because I/O-intensive is the phrase the product was announced under and it sets an expectation that this is somebody else's problem.

Check it against AWS's published heuristic and the two agree. At that crossover the Standard bill is $211.70 of compute, $20.00 of storage and $88.51 of I/O, so I/O is about 28 percent of the total - close to the 25 percent AWS names in its own announcement as the point where I/O-Optimized becomes the better choice. The vendor's rule of thumb is honest arithmetic rather than marketing, which is worth confirming rather than assuming.

Now change one thing. Keep the same instance and make the cluster 5 TB. The storage premium is now $625 a month rather than $25, the total premium is $688.51, and the crossover moves to roughly 1,310 I/Os per second - almost eight times higher. At that point I/O is 49 percent of the Standard bill, not 28. AWS's 25 percent rule is a good approximation for a compute-heavy cluster and wrong by a factor of two for a storage-heavy one, and nothing in the announcement tells you which one you have.

What Moves Your Break-Even

The ratio of storage to compute matters above everything else. The compute premium is a percentage of an hourly rate; the storage premium is $0.125 for every gigabyte you keep. A small database on a large instance crosses over almost immediately. A large database on a modest instance may never cross at all. If you only check one thing before running the numbers, check which of those two shapes you have, because it moves the answer further than the workload does.

Buffer cache is something you can buy. Since a billed read is a page memory did not have, moving to an instance class with more memory converts read I/O into hourly cost at a rate you can measure. That is a real option and it is the one people reach for anyway, for latency. What it also does is move you back down across the crossover, which means a resize done for performance quietly changes the correct answer to a pricing question nobody re-asked.

Indexes pull in both directions at once. An index that removes a scan cuts read I/O, sometimes by a lot. The same index adds write records on every insert and update to that table, forever. Which effect dominates depends on the read-to-write ratio of the table rather than on anything visible in the query being fixed, and the person adding it is looking at a slow query, not at a ratio.

Commitment discounts work in the direction people do not expect. To the extent a reserved-instance or commitment discount applies to both configurations, it shrinks the compute half of the premium while leaving the storage half untouched. A smaller premium buys fewer I/Os, so the crossover moves down and I/O-Optimized becomes attractive at a lower request rate. Discounting your compute makes the case for the flat-rate configuration stronger, which is the opposite of the intuition.

The Plan and the Invoice

The query plan is read in a code review or a performance ticket, by an engineer, on the day something is slow. The invoice is read at the end of the month, by somebody in finance or a platform lead with a spreadsheet, and what they see is a line item called I/O with a number next to it and no attribution to any query, table or release. There is no artifact anywhere in the process that joins the two, and no meeting where both are on the table.

That gap has a specific shape, and it is not laziness. Latency and cost are correlated but not the same, and the thing that separates them is the buffer cache. Two queries that both return in 40 milliseconds can differ by orders of magnitude in billed I/O, depending on whether the pages were in memory. Performance work optimizes what people can feel. The bill responds to misses, which nobody can feel, so the feedback loop that would connect them does not physically exist.

The asymmetry runs the other way too. When an engineer adds an index that turns a scan into a seek, the query gets faster, the ticket closes, and the write cost lands on a line they will never be shown, in a month they have moved on from. Every incentive in the review says this change was unambiguously good. Sometimes it was. The point is that nothing in the process is capable of telling the difference.

And the configuration switch itself is not a lever you can pull reactively. AWS documents that a cluster can move from Standard to I/O-Optimized once every 30 days, though it can move back at any time. So this is a monthly decision at best, made on data somebody has to go and collect, about a variable that changes when a release ships. It is precisely the kind of decision that gets made once, at creation, by the default.

What I Would Actually Do

Measure over a billing period, not an afternoon. Pull VolumeReadIOPs and VolumeWriteIOPs for a full month. Both are five-minute totals, so divide by 300 to get a rate. A week is not enough if anything in your business has a monthly close, a billing run or a reporting cycle, and those are exactly the jobs that scan large tables that are not in anybody's working set.

Compute your own crossover before reading the marketing. Compute premium plus storage premium, divided by the I/O rate, spread over the hours in a month. It is four lines of arithmetic with your own instance class and your own region. Do it before looking at the 25 percent heuristic, then compare, because the point of the comparison is finding out whether your cluster is the shape that heuristic was calibrated for.

Look at the distribution, not the mean. An average of 200 I/Os per second made of a flat 200 is a different cluster from one made of an idle day and a four-hour batch window. Both cross over, but only one of them has a bill that a forecast can survive. The flat-rate configuration prices out the peak, which is worth more to a cluster whose peaks are the interesting part.

Treat a close call as insurance rather than a tie. Where the arithmetic lands near parity, the flat configuration still buys something: a bill that cannot be surprised by a release. Whoever has to commit to a number for next quarter is buying predictability, and predictability has a value even when the expected costs match. That is a legitimate reason to pick the more expensive option, as long as it is said out loud rather than smuggled in as savings.

Re-run it after anything that touches the shape. An instance resize, a new index, an added replica, a data retention change, a batch job moved between endpoints - each of those moves the crossover, and none of them will be described in the change request as a pricing change. Put the I/O line next to the slow query log in the same review, quarterly. That single artifact is the whole organizational fix, and it costs an hour.

I want to keep this in proportion. For most clusters the crossover is worth a couple of hundred dollars a month, and the larger levers are the ones nobody frames as pricing: whether the instance is twice the size it needs, whether four services should be sharing one database, and whether the cluster still running since a project ended should exist at all. Those are worth more than every I/O decision combined.

What survives is the structure rather than the sum. A price that moves with an access pattern is a price set by whoever last shipped a query, and there is no version of the org chart where that person and the person reading the invoice are the same. The bill is a measurement of your application's behavior, taken by somebody else's meter, delivered a month late to a reader who cannot see what it measured.