Cloud Platform Engineering · Chapter 6 of 9
Storage and databases: choosing, and the traps
The four charges that make a cheap storage tier expensive, and the database selection criteria that actually decide it.
Storage and database selection is where cloud costs quietly compound, because the wrong choice does not fail — it works, and it bills. This chapter is the decision criteria and the specific traps.
Object storage: the per-gigabyte rate is the least interesting number
Every cloud offers cheaper tiers for less-accessed data, and every one of them carries the same four hidden charges:
- A minimum billable object size. Infrequent-access tiers bill a floor of around 128 KB. A 10 KB object is billed as 128 KB — nearly thirteen times the storage you actually use, in exchange for a rate around 45% lower. You lose money before anyone reads it.
- A minimum storage duration. Thirty days for infrequent access, ninety for archive, a hundred and eighty for deep archive. Delete on day five and you still pay the minimum. Lifecycle rules applied to short-lived data are a straightforward loss.
- Per-object metadata on archive tiers — roughly forty kilobytes of index per object, part of it billed at the standard rate. Archiving millions of small files is dominated by metadata rather than by data.
- Transition requests, billed per object. Moving a hundred million small objects into an archive tier can cost more in transition requests than a year of the storage it saves.
The rule that follows, and the arithmetic
Tier on object size first, access pattern second. Small objects should almost never leave the standard tier regardless of how rarely they are read.
Before writing any lifecycle rule, compute: number of objects × transition request price, against bytes × rate difference × months you will keep them. If the first number is larger, the correct action is to do nothing.
And read the availability figure on archive tiers carefully. A tier quoted at four nines of availability is available after restore — it is archived, and restore is a separate operation with its own fee and its own latency, measured in hours. People read the number and plan as though the data were online.
The intelligent-tiering option, and when it is free money
Automatic tiering monitors access and moves objects between tiers for you, with no retrieval fees. It charges a monitoring fee per object rather than per byte, and that single design choice determines whether it helps:
- A bucket of a few very large objects — media files, backups, model artefacts. The monitoring fee is negligible and the tiering saves real money. This is close to free money and it is under-used.
- A bucket of many tiny objects — the monitoring fee scales with object count and can exceed the tiering saving entirely.
- And a detail worth knowing: objects below the minimum billable size are never monitored at all and stay in the frequent tier permanently, so you pay a monitoring fee for objects that will never move.
Relational: the two traps in serverless databases
Serverless relational databases scale capacity automatically in fine-grained units, which is genuinely useful for variable load. Two things about them are consistently under-appreciated.
The cost trap: your floor is multiplied by your instance count.
A cluster with a writer and two readers, each with a minimum capacity setting, consumes approximately instances × minimum at complete idle. Set the minimum to a comfortable value on three instances and you have set a floor you pay every hour of every night, forever.
Worse, on some platforms the readers designated to take over on failover have a floor equal to the writer's current capacity — so when the writer scales up, they scale up too, whether or not they are serving anything.
The performance trap points the other way, which is why this is a genuine tension. The provider's own guidance is to set the minimum high enough that each instance can hold the working set in its buffer pool — because if capacity scales down overnight, the buffer pool is discarded and every query the next morning is cold.
So: set the minimum too low and you pay for it in morning latency; set it too high and you pay for it every night. The right answer comes from measuring your working set, and the wrong answer is picking a round number, which is what most teams do.
On the choice itself, the criteria that actually decide it: a managed conventional engine when you want the database you already know and nothing more; a cloud-native variant with separated compute and storage when you have outgrown a single writer's vertical ceiling, or when you have analytical queries running against operational data; and a horizontally-scaling distributed database only when you genuinely need write scaling across regions.
That last point deserves emphasis because it is an expensive mistake. A globally distributed relational database is a different programming model, not a bigger database. Interleaved table layouts, primary keys that must not be monotonic or you create a hotspot, and query patterns you design around from the start. Choosing one for ‘future scale’ without those constraints in hand is the most common expensive architecture decision on that cloud.
Key-value and document stores
- Capacity is metered in units tied to item size. A write unit covers one kilobyte or part thereof, so a 1.1 KB item costs two units — item-size distribution matters more than item count, and a schema change that pushes items over a boundary doubles your write bill silently.
- Every secondary index multiplies write cost. A table with three indexes costs roughly four times as much to write to. Indexes are the single largest cost driver in these databases and they are added casually.
- Read consistency is a price. Eventually-consistent reads cost half; transactional reads cost double. Defaulting to strong consistency everywhere is a real and invisible cost decision.
- On-demand versus provisioned capacity breaks even at surprisingly low sustained utilisation — and provisioned with autoscaling is worse than on-demand for genuinely spiky traffic, because autoscaling reacts in minutes and spikes arrive in seconds.
- And on the multi-region document store: multi-region write multiplies your throughput cost by the number of write regions. Enabling it for resilience without noticing that is a common and large surprise. Its autoscale mode bills the maximum at 1.5× the manual rate, so it only wins below about two-thirds average utilisation.
The universal failure in this family is the hot partition. These databases spread load by a partition key, and a key with poor cardinality — a status field, a date, a tenant identifier where one tenant is enormous — concentrates traffic onto one partition that throttles at a fraction of your provisioned capacity while the metrics show plenty of headroom. Choosing the partition key is the single most consequential design decision, and it is close to irreversible.
Analytical stores
The one thing worth internalising: where the billing unit is bytes scanned, the dominant cost driver is query shape rather than data volume. An unfiltered select across a wide table scans everything and bills for everything, and the same question written with a partition filter and explicit columns can cost a hundredth as much.
Which means the highest-value governance control on such a platform is not a quota — it is partitioning and clustering the tables so that the naive query is also the cheap one. As ever: make the correct path the easy path rather than blocking the easy one.
Check yourself