Skip to main content
Cloud Interviews beginner Lesson 3 of 10

Storage Classes and the Egress Bill

Object, block and file storage, when a cheaper storage class costs more, retrieval fees and minimum durations, and the CDN arithmetic that pays for itself in a week.

Storage is cheap. Moving data is not, and the exam-prep framing of “which class for which access pattern” hides the arithmetic that actually decides.

Three kinds of storage

                  OBJECT              BLOCK               FILE
example           S3, GCS, Blob       EBS, PD             EFS, Filestore
interface         HTTP API            device, needs FS    NFS / SMB
access            whole object        random byte         random byte
attached to       nothing             one instance*       many instances
capacity          effectively ∞       provisioned, fixed  elastic
price /GB-month   ~$0.023             ~$0.08              ~$0.30
latency           10-100 ms           <1 ms               1-10 ms
durability        11 nines            replicated in-AZ    multi-AZ
best for          media, backups,     databases, boot     shared config,
                  logs, data lakes    volumes             legacy apps

* Multi-attach exists for some block volume types and is a specialist feature, not a default.

The price column is the story: file storage is roughly 13x object storage per gigabyte. An answer that puts a data lake on a shared filesystem is expensive in a way that is easy to miss.

“The question I’d ask first is whether the access pattern is whole-object or random-byte. If the application reads an entire file, object storage is right and an order of magnitude cheaper. If it needs a filesystem — a database data directory, an app that mmaps — then block, and I’d only reach for a shared filesystem if several instances genuinely need the same mutable directory.”

Storage classes: the fee that is not in the headline price

# classes.py
CLASSES = {
    #                    $/GB-mo  retrieval $/GB  min days  first-byte
    "standard":          (0.0230, 0.0000,          0,   "ms"),
    "infrequent access": (0.0125, 0.0100,         30,   "ms"),
    "one-zone IA":       (0.0100, 0.0100,         30,   "ms"),
    "glacier instant":   (0.0040, 0.0300,         90,   "ms"),
    "glacier flexible":  (0.0036, 0.0100,         90,   "1-5 min"),
    "deep archive":      (0.0009, 0.0200,        180,   "12 hours"),
}

SIZE_GB = 100_000            # 100 TB
MONTHS = 12

print(f"{'class':<18} {'store/yr':>11} {'+1 full read':>13} {'+4 reads':>11} {'+12 reads':>11}")
for name, (store, retrieve, mindays, _) in CLASSES.items():
    base = SIZE_GB * store * MONTHS
    r1   = base + SIZE_GB * retrieve
    r4   = base + SIZE_GB * retrieve * 4
    r12  = base + SIZE_GB * retrieve * 12
    print(f"{name:<18} {base:>11,.0f} {r1:>13,.0f} {r4:>11,.0f} {r12:>11,.0f}")
$ python classes.py
class                 store/yr  +1 full read    +4 reads   +12 reads
standard                27,600        27,600      27,600      27,600
infrequent access       15,000        16,000      19,000      27,000
one-zone IA             12,000        13,000      16,000      24,000
glacier instant          4,800         7,800      16,800      40,800
glacier flexible         4,320         5,320       8,320      16,320
deep archive             1,080         3,080       9,080      25,080

Read the last column. Glacier Instant at monthly access costs $40,800 — 48% more than standard, despite a headline price 83% lower. Deep archive read monthly costs nearly as much as standard.

The pattern generalises: cheaper storage means a higher retrieval fee, and past a certain access frequency the retrieval fee dominates completely.

Two more traps not in the price:

  • Minimum storage duration. Deleting an infrequent-access object after 3 days still bills 30 days. A lifecycle policy that transitions objects at 7 days and expires them at 14 costs more than leaving them in standard.
  • Per-object minimum size. Infrequent-access tiers bill a minimum of 128 KB per object. Ten million 4 KB objects are billed as 128 KB each — a 32x overcharge that lifecycle policies cause routinely.
objects, real_kb, billed_kb = 10_000_000, 4, 128
print(f"real:   {objects*real_kb/1e6:>8,.1f} GB  ${objects*real_kb/1e6*0.0125:>8,.2f}/mo")
print(f"billed: {objects*billed_kb/1e6:>8,.1f} GB  ${objects*billed_kb/1e6*0.0125:>8,.2f}/mo")
real:       40.0 GB  $    0.50/mo
billed:  1,280.0 GB  $   16.00/mo

Small numbers here, but the ratio is the point, and it scales.

“Lifecycle policies are not free. Before transitioning to a cheaper class I’d check three things: how often the data is actually read, the object size distribution against the 128 KB minimum, and whether the objects live longer than the minimum duration. I’ve seen a lifecycle policy increase a bill.”

The egress arithmetic

# egress.py
TB = 1024

TIERS = [(10*TB, 0.09), (40*TB, 0.085), (100*TB, 0.07), (float('inf'), 0.05)]

def internet_egress(gb):
    cost, remaining, prev = 0.0, gb, 0
    for limit, rate in TIERS:
        band = min(remaining, limit - prev)
        cost += band * rate
        remaining -= band
        prev = limit
        if remaining <= 0: break
    return cost

for tb in (1, 10, 50, 200, 1000):
    gb = tb * TB
    print(f"{tb:>5} TB out   internet ${internet_egress(gb):>12,.2f}"
          f"   cross-region ${gb*0.02:>10,.2f}   cross-AZ ${gb*0.01:>10,.2f}")
$ python egress.py
    1 TB out   internet $       92.16   cross-region $     20.48   cross-AZ $     10.24
   10 TB out   internet $      921.60   cross-region $    204.80   cross-AZ $    102.40
   50 TB out   internet $    4,249.60   cross-region $  1,024.00   cross-AZ $    512.00
  200 TB out   internet $   12,953.60   cross-region $  4,096.00   cross-AZ $  2,048.00
 1000 TB out   internet $   53,913.60   cross-region $ 20,480.00   cross-AZ $ 10,240.00

A petabyte out to the internet is $53,900 a month. The same petabyte stored costs 1,024,000 GB × $0.023 ≈ $23,550 — serving it out once a month costs more than twice what storing it costs, and the tiering only softens that at very high volume.

The direction matters and is a common interview probe:

DIRECTION                          TYPICAL CHARGE
internet → cloud (ingress)         free
cloud → internet (egress)          $0.05-0.09/GB, tiered
same AZ, private IP                free
cross-AZ, same region              $0.01/GB each direction
cross-region                       $0.02/GB
via NAT gateway                    $0.045/GB on top of everything else
via VPC endpoint (gateway type)    free
CDN → internet                     $0.02-0.085/GB, cheaper than origin

Ingress is free and egress is not. That asymmetry is deliberate and is the mechanism behind cloud lock-in — getting data in costs nothing, getting it out costs real money.

The NAT gateway line

aws ec2 describe-nat-gateways --query 'NatGateways[].{id:NatGatewayId,state:State}' --output table
--------------------------------------------
|          DescribeNatGateways             |
+----------------------+-------------------+
|          id          |      state        |
+----------------------+-------------------+
|  nat-0a1b2c3d4e5f6g7 |  available        |
+----------------------+-------------------+
HOURS = 730
hourly = 0.045 * HOURS
for tb in (1, 10, 50, 200):
    gb = tb * 1024
    print(f"{tb:>4} TB through NAT   hourly ${hourly:>7,.2f} + data ${gb*0.045:>10,.2f}"
          f" = ${hourly + gb*0.045:>10,.2f}")
   1 TB through NAT   hourly $  32.85 + data $     46.08 = $     78.93
  10 TB through NAT   hourly $  32.85 + data $    460.80 = $    493.65
  50 TB through NAT   hourly $  32.85 + data $  2,304.00 = $  2,336.85
 200 TB through NAT   hourly $  32.85 + data $  9,216.00 = $  9,248.85

The hourly rate is trivial. The per-gigabyte rate is not, and the traffic people forget is private instances reading from object storage — which routes through the NAT by default:

aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-0def --vpc-endpoint-type Gateway
{
    "VpcEndpoint": {
        "VpcEndpointId": "vpce-0123456789abcdef0",
        "VpcEndpointType": "Gateway",
        "State": "available"
    }
}

A gateway endpoint costs nothing and removes that traffic from the NAT entirely. It is one API call and, on a data-heavy workload, the single largest saving available.

“The first thing I’d check on a surprising bill is whether object storage traffic is going through a NAT gateway. A gateway VPC endpoint is free, takes one API call, and I’ve seen it cut a five-figure monthly line to zero.”

The CDN calculation

# cdn.py
TB = 1024
traffic_gb = 100 * TB
ORIGIN_EGRESS, CDN_EGRESS = 0.085, 0.020

no_cdn = traffic_gb * ORIGIN_EGRESS
print(f"no CDN, all from origin:  ${no_cdn:>12,.2f}\n")
print(f"{'hit rate':>9} {'origin':>12} {'CDN':>12} {'total':>12} {'saved':>12} {'%':>6}")
for hit in (0.50, 0.70, 0.85, 0.95, 0.99):
    origin = traffic_gb * (1 - hit) * ORIGIN_EGRESS
    cdn    = traffic_gb * CDN_EGRESS
    total  = origin + cdn
    print(f"{hit:>8.0%} {origin:>12,.2f} {cdn:>12,.2f} {total:>12,.2f}"
          f" {no_cdn-total:>12,.2f} {100*(no_cdn-total)/no_cdn:>5.1f}%")
$ python cdn.py
no CDN, all from origin:  $    8,704.00

 hit rate       origin          CDN        total        saved      %
      50%     4,352.00     2,048.00     6,400.00     2,304.00  26.5%
      70%     2,611.20     2,048.00     4,659.20     4,044.80  46.5%
      85%     1,305.60     2,048.00     3,353.60     5,350.40  61.5%
      95%       435.20     2,048.00     2,483.20     6,220.80  71.5%
      99%        87.04     2,048.00     2,135.04     6,568.96  75.5%

At a realistic 85% hit rate the transfer bill drops 61%, and latency improves for every user near an edge location. Those are two separate wins and both are worth naming.

The hit rate is the variable, and it is a function of cache headers and content mix:

CONTENT                     REALISTIC HIT RATE
static assets, versioned    99%+ — immutable, cache forever
images, video               90-98%
API responses, cacheable    50-80% — depends on the TTL you can accept
personalised HTML           near 0% — do not put it behind a CDN for caching

Recognising it

SIGNAL                                          ANSWER
"we store a lot and read rarely"                archive tier — check minimum duration first
"we store a lot and read constantly"            standard; a cheaper class will cost more
"lots of tiny objects"                          watch the 128 KB per-object minimum
"the bill jumped and nothing changed"           check NAT gateway and cross-AZ transfer
"serving media / static content"                CDN, and quote the hit-rate arithmetic
"private instances reading object storage"      gateway VPC endpoint, free
"multi-region for latency"                      cross-region replication — price the transfer
"database data directory"                       block storage, not object
"several instances need one mutable dir"        shared filesystem, and question the design
"how do we get 500 TB out of this cloud"        egress is the answer nobody likes: ~$35,000

Practice

1. Price 100 TB in each storage class with 12 reads a year.
standard $27,600      glacier instant $40,800      deep archive $25,080

The headline price is 83% lower and the annual cost is 48% higher. Retrieval fees dominate past a few reads a year.

2. Bill 10 million 4 KB objects in an infrequent-access tier.
real 40 GB ($0.50/mo)      billed 1,280 GB ($16.00/mo)      32x

The 128 KB per-object minimum. Lifecycle policies cause this routinely and it does not appear in the storage class comparison table.

3. Price 200 TB flowing through a NAT gateway.
hourly $32.85 + data $9,216.00 = $9,248.85/month

A gateway VPC endpoint removes object-storage traffic from that line entirely, costs nothing, and takes one API call.

4. Compute the CDN saving at an 85% hit rate.
$8,704 → $3,354, a 61% reduction — plus latency improvement

Two separate wins. Name both, and name the content types where the hit rate will be near zero.

Next: networking and VPCs — the debug ladder for “why can’t these two things talk to each other”.

Frequently Asked Questions

When does archival storage cost more than standard?
When you read it. Archive tiers charge a per-gigabyte retrieval fee and impose a minimum storage duration — deleting or restoring early is billed as though the object stayed the full term. Data accessed more than about once a quarter is usually cheaper in a standard or infrequent-access tier.
Why is my NAT gateway bill so large?
It charges per gigabyte processed on top of an hourly rate, and private-subnet traffic to object storage or other cloud APIs routes through it by default. A gateway VPC endpoint routes that traffic privately at no per-gigabyte charge and is usually the single largest one-line cost saving available.
Object storage or a block volume?
Object storage for anything written once and read many times — media, backups, logs, data lake files — because it is cheap, effectively unlimited, and served over HTTP. Block volumes for anything needing a filesystem, low-latency random writes, or a database's data directory. The question to ask is whether the access pattern is whole-object or byte-range random write.
How much does a CDN actually save?
It replaces origin egress with usually-cheaper CDN egress and, more importantly, serves repeat requests without touching the origin at all. At an 85% cache hit rate you pay origin egress on 15% of traffic. On a serving-heavy workload that routinely cuts the data-transfer line by 60-80%.