AWS Storage

Amazon S3 Storage Classes and Lifecycle: Optimize Cost Without Losing Data

Quick take: S3 Standard is not the only S3. Most objects go cold within months, yet teams keep paying the top per-GB rate for data nobody reads. S3 has eight storage classes spanning a roughly 24× price range, and a lifecycle policy moves objects between them automatically by age, prefix or tag. Done right it is the single easiest FinOps win on AWS — done carelessly the retrieval fees, per-class minimums and transition request costs quietly erase the saving. This is the option-by-option playbook for getting it right.

A video platform stored every uploaded clip in S3 Standard forever. After two years, 80% of the objects had not been read once, yet they were billed the highest per-gigabyte rate every month. A single lifecycle rule — move objects to S3 Glacier Flexible Retrieval after 90 days, abort abandoned multipart uploads after 7, expire orphaned renditions after a year — cut their storage spend by roughly 60% with zero engineering changes. The data did not move servers, the application did not change a line, and nobody had to babysit it. That is the promise of S3 storage classes and lifecycle: pay for the access pattern you actually have, and let S3 do the moving.

The trap is that “cheaper per GB” is only half the price. Every colder class trades storage cost for retrieval cost, retrieval latency, a minimum billable object size, and a minimum storage duration — and lifecycle transitions are themselves billed requests. Tier a million 4 KB thumbnails into Standard-IA and you can pay more than you saved, because each one is rounded up to a 128 KB billable floor and each transition is a charged request. Move originals to One Zone-IA and a single Availability Zone failure can lose them outright. Archive to Glacier Deep Archive and a GET returns an error until you issue a restore that can take up to 48 hours. None of this is hidden — it is all in the pricing mechanics — but it is exactly where teams lose the saving they thought they were banking.

This article is the reference you keep open during a cost review. We walk every storage class (Standard, Intelligent-Tiering, Standard-IA, One Zone-IA, Glacier Instant Retrieval, Glacier Flexible Retrieval, Glacier Deep Archive, and the legacy Reduced Redundancy), the lifecycle engine that moves objects between them (transition actions, expiration actions, the abort-incomplete-multipart-upload action, current vs noncurrent versions, the filter grammar), the cost model end to end (per-GB by class, retrieval tiers, request pricing, the minimums that bite), and the failure modes that turn a saving into a regression. Every operation gets both an aws CLI snippet and a Terraform snippet. The class comparisons, the per-class minimums, the retrieval options, the lifecycle actions and the FinOps traps are all laid out as scannable tables — read the prose once, then keep the tables.

By the end you will stop defaulting everything to Standard, and you will stop the opposite mistake of archiving hot data to save a rupee and paying triple in retrieval fees. You will know which class fits each access pattern, how to express the transition as a lifecycle rule, what the minimum-duration and minimum-size floors do to small objects, how versioning doubles the rule set you need, and how to confirm the saving in Cost Explorer and S3 Storage Lens before you celebrate.

What problem this solves

Storage cost on AWS is not a flat number — it is a function of how the data is accessed, and S3 Standard charges as if every object is read constantly. The overwhelming majority of object data is written once and read rarely after the first few weeks: logs, backups, media masters, build artifacts, raw ingest, compliance archives, old user uploads. Keeping all of it on Standard means paying the premium-access price for cold data indefinitely. At petabyte scale that is a six- or seven-figure annual line item that buys nothing.

Without storage classes and lifecycle, the failure is slow and silent. There is no outage, no alert, no broken build — just a bucket that grows monotonically at the top per-GB rate while the access logs show almost no reads. Engineers do not notice because nothing is wrong; the bill is simply larger than it needs to be, every month, forever. When someone finally looks, they find that a one-line lifecycle rule applied months earlier would have saved 50–70% of the bucket’s storage cost.

Who hits this: essentially everyone who stores data on S3 at scale — but it bites hardest on media platforms (huge masters, rare re-reads), data lakes (raw zones that are read once by ETL then never again), backup/DR targets, log archives (write-heavy, read-almost-never), and SaaS products that retain user content for years. The fix is almost never “store less” — it is “store it in the class that matches how it is actually accessed, and let lifecycle move it there automatically.” The discipline is FinOps, the mechanism is storage classes plus lifecycle, and the only real skill is knowing the per-class trade-offs well enough not to trip the retrieval-fee and minimum-duration landmines.

To frame the whole field before the deep dive, here is every access pattern this article covers, the class it argues for, and the one trap to watch:

Access pattern What it looks like Right class Why The trap to watch
Hot, read constantly Active site assets, current data S3 Standard No retrieval fee, ms latency, no minimum Paying it for cold data
Unknown / changing New product, mixed objects Intelligent-Tiering Auto-moves per object, no retrieval fee Per-object monitoring fee on tiny objects
Warm, read occasionally Older but live data Standard-IA ~Half the storage cost Retrieval fee + 128 KB / 30-day minimums
Reproducible, infrequent Thumbnails, transcodes One Zone-IA ~20% cheaper than Standard-IA Single AZ — lost if that AZ dies
Archive, may need instantly Compliance kept “just in case” Glacier Instant Retrieval ms latency at archive price 90-day minimum; high retrieval fee
Archive, can wait minutes/hours Backups, old masters Glacier Flexible Retrieval Cheap archive, flexible restore Must restore before read
Cold, rarely touched, 7–10 yr Legal/regulatory retention Glacier Deep Archive Cheapest storage on AWS 180-day min; 12–48 h restore

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand S3 basics: a bucket is a regional container; an object is a blob plus metadata addressed by a key; prefixes are the slash-delimited key segments people treat as folders; and S3 gives eleven nines (99.999999999%) of durability on the multi-AZ classes by replicating each object across at least three Availability Zones in the Region. You should know how to run the AWS CLI (aws s3 / aws s3api), read JSON output, and that S3 has versioning (keep every overwrite as a noncurrent version) and server-side encryption on by default. Familiarity with how AWS bills (per-GB-month, per-request, per-GB transfer) helps a lot here, because storage classes are fundamentally a billing decision.

This sits in the Storage & FinOps track. It is upstream of backup and disaster-recovery design — once you can place data in the right class, AWS Backup and Disaster Recovery Strategies builds the retention and cross-Region story on top. It pairs with AWS Regions and Availability Zones Explained, because the One Zone-IA trade-off and the eleven-nines durability guarantee are both AZ-replication facts. Data-lake teams will reach this from Choosing Between RDS, DynamoDB and Aurora when they decide what belongs in object storage versus a database. And the access controls that keep a lifecycle policy from being the weak link sit in AWS Organizations and IAM Foundations.

A quick map of who owns what, so a cost review talks to the right people:

Layer What lives here Who usually owns it What it can cause
Producers (apps, pipelines) The PutObject and the class it writes to App / data team Writing cold data straight to Standard forever
Bucket configuration Default class, versioning, encryption Platform / cloud team Versioning on without noncurrent expiry → cost leak
Lifecycle policy Transition + expiration rules Platform + FinOps Wrong age/filter → premature archive or no saving
Retrieval path RestoreObject, retrieval tier App / on-call Surprise retrieval bill; archive-state GET errors
Cost & reporting Cost Explorer, Storage Lens FinOps Missing the small-object transition regression

Core concepts

Six mental models make every later decision obvious.

A storage class is a price/latency/durability contract, not a different place. Every object in a bucket carries a storage class attribute. The bytes live in the same S3 service; the class changes how AWS bills and serves them: the per-GB storage rate, whether a retrieval fee applies, the retrieval latency (milliseconds vs minutes vs hours), the minimum billable object size, the minimum storage duration before deletion is free, and (for One Zone-IA) the number of Availability Zones the copy spans. Choosing a class is choosing a point on those axes — there is no “best” class, only the best fit for an access pattern.

Colder is cheaper to store and more expensive to read. As you move Standard → IA → Glacier → Deep Archive, the per-GB storage rate drops sharply (Deep Archive is roughly 1/24th the Standard rate), but three costs rise: a per-GB retrieval fee appears and grows, retrieval latency goes from milliseconds to hours, and minimum-duration and minimum-size floors get longer/larger. The whole game is matching the floor to the access pattern: if data is genuinely cold for years, the floors never bite and you win big; if you misjudge and read it, the retrieval fee can dwarf the storage saving.

The lifecycle engine moves objects on a schedule you define. A lifecycle configuration is a set of rules, each with a filter (by prefix, by tag, by object size, or the whole bucket) and one or more actions: transition (move to a colder class after N days), expiration (delete after N days), noncurrent-version transition/expiration (the same for old versions under versioning), and abort incomplete multipart upload (clean up half-finished uploads). S3 evaluates the rules asynchronously, once per day — transitions are not instant at the stroke of midnight; the object’s creation date (not last-access) drives the age clock.

Intelligent-Tiering is the “I don’t know the pattern” class. Instead of you deciding by age, S3 Intelligent-Tiering monitors each object’s access and moves it between a Frequent, Infrequent (after 30 days no access), and optional Archive tiers automatically — with no retrieval fees and no per-class transition charges. The cost is a small per-object monthly monitoring-and-automation fee, which is negligible for large objects and ruinous for millions of tiny ones. It is the safe default for unpredictable workloads and the wrong choice for huge counts of small objects.

Durability is eleven nines everywhere except One Zone-IA — availability differs by class. All the multi-AZ classes (Standard, Intelligent-Tiering, Standard-IA, all three Glacier classes) give the same 99.999999999% durability by spreading copies across ≥3 AZs. One Zone-IA keeps a single-AZ copy: same per-object durability given the AZ survives, but no protection against losing that AZ — designed for data you can regenerate. Availability SLAs differ too (Standard 99.9%, IA classes 99%), which matters for read-availability, not data loss.

Archive classes require an explicit restore before you can read them. Objects in Glacier Flexible Retrieval and Glacier Deep Archive are not directly downloadable — a plain GET fails with InvalidObjectState until you issue a RestoreObject that creates a temporary readable copy for a number of days you choose. Glacier Instant Retrieval is the exception: millisecond reads at archive-ish storage price, with a high per-GB retrieval fee. Knowing which archive class needs a restore (and how long that restore takes) is the difference between a planned retrieval and a 3 a.m. surprise.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary at the end repeats these for lookup; this table is the mental model side by side:

Concept One-line definition Where it lives Why it matters to cost
Storage class Price/latency/durability tier of an object Per-object attribute Sets the per-GB rate and retrieval fee
Retrieval fee Per-GB charge to read from a colder class IA / Glacier classes Can exceed the storage saving if you read
Minimum storage duration Days you’re billed even if you delete early 30 / 90 / 180 d by class Early-delete fee on short-lived data
Minimum billable object size Floor each object is billed at 128 KB (IA classes) Tiny objects cost as if 128 KB
Lifecycle rule Filter + action(s) applied daily Bucket lifecycle config Automates transitions/expirations
Transition action Move to a colder class after N days Lifecycle rule The saving mechanism; each move is billed
Expiration action Delete an object after N days Lifecycle rule Stops paying for data you don’t need
Noncurrent version A previous version kept by versioning Versioned bucket Silent cost leak without its own rule
Abort incomplete MPU Delete half-finished multipart uploads Lifecycle rule Orphaned parts you pay for invisibly
RestoreObject Temporarily un-archive a Glacier object Glacier Flexible/Deep Required before read; has latency + cost
Intelligent-Tiering Auto-moves objects by observed access Storage class No retrieval fee; per-object monitoring fee
Object Lock WORM retention (governance/compliance) Bucket + object Blocks deletion for compliance

The storage-class reference

Before the per-class detail, here is the lookup table you scan first: every storage class, its design intent, retrieval latency, the durability/AZ story, and the minimums that bite. (Prices move and vary by Region; the relationships — and the minimums — are what you memorise. Treat the rupee figures later as ap-south-1-ish order-of-magnitude, not a quote.)

Storage class Designed for Retrieval latency Durability / AZs Min storage duration Min billable object size Retrieval fee
S3 Standard Hot, frequently accessed Milliseconds 11 nines / ≥3 AZ None None None
S3 Intelligent-Tiering Unknown / changing access Milliseconds (Frequent/Infrequent) 11 nines / ≥3 AZ None (90 d for Archive tiers) None (monitoring fee applies) None for IT tiers
S3 Standard-IA Warm, infrequent, needs ms Milliseconds 11 nines / ≥3 AZ 30 days 128 KB Per-GB retrieved
S3 One Zone-IA Reproducible, infrequent Milliseconds 11 nines within 1 AZ 30 days 128 KB Per-GB retrieved
S3 Glacier Instant Retrieval Archive, ms access, rare Milliseconds 11 nines / ≥3 AZ 90 days 128 KB High per-GB retrieved
S3 Glacier Flexible Retrieval Archive, minutes–hours OK Minutes to 12 h (by tier) 11 nines / ≥3 AZ 90 days 40 KB (overhead) Per-GB + per-request by tier
S3 Glacier Deep Archive Cold archive, 7–10 yr 12–48 h (by tier) 11 nines / ≥3 AZ 180 days 40 KB (overhead) Per-GB + per-request by tier
S3 Reduced Redundancy (legacy) (deprecated — do not use) Milliseconds 4 nines / lower None None None

Three reading notes that save the most money:

Distinction The trap How to tell them apart
Glacier Instant vs Flexible vs Deep All called “Glacier”; wildly different latency Instant = ms (no restore); Flexible = minutes–12 h (restore); Deep = 12–48 h (restore)
One Zone-IA vs Standard-IA ~20% cheaper looks like a free win One Zone-IA is a single AZ — lose the AZ, lose the data; only for reproducible data
“Cheaper per GB” vs total cost Colder always looks cheaper on the storage line Add retrieval fee × expected reads + minimum-duration floor before comparing

And the relationships that drive every decision, ranked:

If the data is… …and you’ll read it… Then choose Because
Hot constantly S3 Standard Retrieval fees would dominate any saving
Of unknown temperature unpredictably Intelligent-Tiering Auto-optimizes with no retrieval fee
Warm a few times a month Standard-IA Half the storage, retrieval fee tolerable
Reproducible & warm rarely One Zone-IA Cheapest IA, AZ loss is acceptable
Archive but might need now almost never, but instantly Glacier Instant Retrieval ms latency at archive storage price
Archive rarely, can wait Glacier Flexible Retrieval Cheap, restore latency acceptable
Deep-cold compliance almost never, hours OK Glacier Deep Archive Cheapest storage on AWS

The durability, availability SLA and AZ story by class — the resilience side, separate from cost (every class is 11 nines durability except One Zone-IA and legacy RRS):

Storage class Design durability Availability SLA AZs Loses data if one AZ fails?
S3 Standard 11 nines 99.9% ≥ 3 No
S3 Intelligent-Tiering 11 nines 99.9% ≥ 3 No
S3 Standard-IA 11 nines 99% ≥ 3 No
S3 One Zone-IA 11 nines (within the AZ) 99.5% (single AZ) 1 Yes
S3 Glacier Instant Retrieval 11 nines 99.9% ≥ 3 No
S3 Glacier Flexible Retrieval 11 nines 99.99% (after restore) ≥ 3 No
S3 Glacier Deep Archive 11 nines 99.99% (after restore) ≥ 3 No
Reduced Redundancy (legacy) 4 nines 99.99% reduced Higher risk — migrate off

And the same logic applied to real workload types you’ll actually meet — keep this as the “what goes where” cheat sheet:

Workload Typical access after week 1 Recommended placement Lifecycle move
Active website / app assets Constant S3 Standard None (stays hot)
User uploads (originals) Rare re-download Standard → IA (30 d) → Glacier (120 d) Age-based transition
Transcoded media renditions Rare, reproducible One Zone-IA (30 d) Reproducible → single AZ OK
Thumbnails / sprites (tiny) Occasional Standard or Intelligent-Tiering Skip IA (128 KB floor)
Application / access logs Read once by ETL, then never Standard → Glacier (30 d) → expire (400 d) Archive then expire
Database / EBS backups Restore rarely, within hours Standard-IA → Glacier Flexible (90 d) Tier then archive
Data-lake raw zone Read once by pipeline Intelligent-Tiering Auto-tier (unknown re-read)
Build / CI artifacts Hot for days, then dead Standard → expire (30–90 d) Expiration only
Compliance / legal records Essentially never; hours OK Glacier Deep Archive + Object Lock Direct archive, 7–10 yr
Analytics query results Re-queried for weeks Standard → Standard-IA (30 d) Mild tiering
Disaster-recovery copies Read only in a DR event Glacier Flexible / Deep Archive Archive, restore on DR
Big-data intermediate output Transient, re-derivable One Zone-IA or expire fast Cheap or short-lived

Class by class: every option, end to end

This is the bulk. For each class: what it is, the access pattern it fits, the minimums that bite, how to write to it (CLI + IaC), and the gotcha that costs people money.

S3 Standard — the default you should stop defaulting to

S3 Standard is the general-purpose, hot class: millisecond first-byte latency, no retrieval fee, no minimum duration, no minimum object size, eleven nines across ≥3 AZs, 99.9% availability SLA. It is correct for data read frequently — current website assets, active application data, the landing zone for new uploads before you know their temperature. Its only failure mode is using it for cold data, which is the most common S3 cost mistake in existence.

Write an object explicitly to Standard (it is also the bucket default):

# PutObject defaults to STANDARD; --storage-class is explicit/optional here
aws s3api put-object --bucket media-prod --key uploads/clip-001.mp4 \
  --body clip-001.mp4 --storage-class STANDARD

There is no per-object minimum to worry about; the discipline is purely “don’t leave cold data here.” The properties that make it the baseline every other class is measured against:

Property S3 Standard value Why it matters
First-byte latency Milliseconds The reference for “fast”
Retrieval fee None The reason hot data belongs here
Minimum storage duration None Delete anytime, billed per second of storage
Minimum billable object size None Tiny objects billed at actual size
Availability SLA 99.9% Higher than IA classes (99%)
Durability / AZs 11 nines / ≥3 The multi-AZ baseline
Typical relative storage price 1.0× (baseline) Everything below is a discount with strings

S3 Intelligent-Tiering — let S3 decide, for a small fee

S3 Intelligent-Tiering is the answer when you genuinely do not know the access pattern. S3 monitors each object and moves it between access tiers automatically: Frequent Access (default), Infrequent Access after 30 consecutive days with no access, Archive Instant Access after 90 days, and optionally the deeper, opt-in Archive Access (90+ days) and Deep Archive Access (180+ days) tiers. Crucially there are no retrieval fees and no inter-tier transition charges — if an object in Infrequent gets read, it pops back to Frequent for free. You pay a small per-object monthly monitoring-and-automation fee for the tracking.

That fee is the whole story: it is per object, so for a million 2 KB objects it can exceed the storage cost, while for terabyte-scale media it is a rounding error. The Archive opt-in tiers do add restore latency (they behave like Glacier), so enable them only if you accept that.

aws s3api put-object --bucket data-lake --key raw/2026/06/event.json \
  --body event.json --storage-class INTELLIGENT_TIERING
# Opt into the deeper Intelligent-Tiering archive tiers (these add restore latency)
resource "aws_s3_bucket_intelligent_tiering_configuration" "deep" {
  bucket = aws_s3_bucket.data_lake.id
  name   = "archive-after-quarter"

  tiering {
    access_tier = "ARCHIVE_ACCESS"
    days        = 90
  }
  tiering {
    access_tier = "DEEP_ARCHIVE_ACCESS"
    days        = 180
  }
}

The Intelligent-Tiering tiers and what each one is, end to end:

IT access tier Object moves here after Retrieval latency Retrieval fee Opt-in?
Frequent Access Default on write / on access Milliseconds None No (default)
Infrequent Access 30 consecutive days no access Milliseconds None No (automatic)
Archive Instant Access 90 consecutive days no access Milliseconds None No (automatic)
Archive Access 90+ days (configurable) Minutes to hours None (restore needed) Yes
Deep Archive Access 180+ days (configurable) 12 h class None (restore needed) Yes

When Intelligent-Tiering wins, and when it loses:

Situation Intelligent-Tiering verdict Why
New product, unknown access Use it Auto-optimizes; no retrieval-fee risk
Mixed bag of object temperatures Use it Each object tiers independently
Millions of tiny (<128 KB) objects Avoid Per-object monitoring fee dominates
Predictable, age-based cold-down Avoid An explicit lifecycle rule is cheaper (no monitoring fee)
Short-lived objects (deleted in days) Avoid Never live long enough to benefit
Large media, unknown re-read Use it Monitoring fee negligible; safe from retrieval fees

S3 Standard-IA — warm data, with two floors that bite small objects

S3 Standard-Infrequent Access is for data read occasionally but needing millisecond access when it is: older-but-live user files, recent backups you might restore, datasets queried a few times a month. Storage is roughly half of Standard, but three strings attach: a per-GB retrieval fee on every read, a 30-day minimum storage duration (delete or transition earlier and you still pay 30 days), and a 128 KB minimum billable object size (a 4 KB object is billed as 128 KB). Those last two are why Standard-IA is wrong for small or short-lived objects — the floors erase the storage discount.

aws s3api put-object --bucket backups-prod --key db/2026-06-01.dump \
  --body 2026-06-01.dump --storage-class STANDARD_IA

Standard-IA is almost always reached via a lifecycle transition (after 30 days on Standard), not written directly — see the lifecycle section. Its parameters in full:

Property Standard-IA value The bite
Storage price (relative) ~0.5× Standard The headline saving
Retrieval fee Per-GB on every read Read it often and you lose the saving
Minimum storage duration 30 days Early delete still billed 30 days
Minimum billable object size 128 KB Small objects billed as 128 KB
First-byte latency Milliseconds Same as Standard for reads
Durability / AZs 11 nines / ≥3 Full multi-AZ protection
Availability SLA 99% Lower than Standard’s 99.9%

The arithmetic that decides Standard-IA vs Standard for a class of objects:

Factor Favors Standard-IA Favors Standard
Object size ≥ 128 KB (no min-size penalty) < 128 KB (penalty erases saving)
Read frequency A few times a month or less Frequently (retrieval fees pile up)
Object lifetime ≥ 30 days Short-lived (min-duration fee)
Access latency need Milliseconds (IA gives ms) Milliseconds (either works)
Predictability Known to be warm Unknown → use Intelligent-Tiering instead

S3 One Zone-IA — the same as Standard-IA, minus a Region’s worth of safety

S3 One Zone-IA is Standard-IA stored in a single Availability Zone instead of three. It is about 20% cheaper than Standard-IA, with the same millisecond latency, the same 30-day and 128 KB minimums, the same retrieval fee — and one enormous difference: if that AZ is destroyed, the data is gone. The eleven-nines number assumes the AZ survives; there is no cross-AZ copy to fall back on. It is designed exclusively for reproducible or re-derivable data: thumbnails you can regenerate from the master, transcoded renditions, secondary copies, intermediate pipeline outputs.

aws s3api put-object --bucket thumbs-prod --key derived/clip-001-thumb.jpg \
  --body clip-001-thumb.jpg --storage-class ONEZONE_IA

The single decision rule is brutal and simple: never put data here you cannot recreate. Standard-IA vs One Zone-IA, the only comparison that matters:

Dimension Standard-IA One Zone-IA Verdict
AZ copies ≥ 3 1 One Zone-IA loses data on AZ failure
Storage price Baseline IA ~20% cheaper One Zone-IA only for reproducible data
Retrieval fee Per-GB Per-GB (same) No difference
Minimums 30 d / 128 KB 30 d / 128 KB No difference
Availability SLA 99% 99.5%→ effectively lower (single AZ) Standard-IA more available
Right data Originals, anything irreplaceable Thumbnails, transcodes, derivatives Match data replaceability

S3 Glacier Instant Retrieval — archive price, instant read, steep retrieval fee

S3 Glacier Instant Retrieval (GIR) is the newest archive class and the one people misunderstand: it gives millisecond retrieval with no restore step, like Standard, but at archive-tier storage price (cheaper than Standard-IA). The catch is a 90-day minimum storage duration, a 128 KB minimum object size, and a high per-GB retrieval fee — higher than Standard-IA’s. It is built for archives you access about once a quarter or less but, when you do, need immediately: medical images, news media archives, compliance records pulled on demand.

aws s3api put-object --bucket archive-prod --key records/2025/case-7741.pdf \
  --body case-7741.pdf --storage-class GLACIER_IR

GIR vs Standard-IA is the subtle call — GIR wins on storage, loses on retrieval:

Property Glacier Instant Retrieval Standard-IA Pick GIR when
Storage price Lower (archive tier) Higher Data is colder than “warm”
Retrieval latency Milliseconds (no restore) Milliseconds Either — both instant
Retrieval fee per GB Higher Lower You read ≤ ~once/quarter
Minimum duration 90 days 30 days Data lives ≥ 90 days
Minimum object size 128 KB 128 KB Same
Break-even Few reads/year More frequent reads Lower access frequency → GIR

S3 Glacier Flexible Retrieval — cheap archive, restore in minutes to hours

S3 Glacier Flexible Retrieval (formerly just “S3 Glacier”) is true cheap archive: very low storage cost, a 90-day minimum, and no direct read — you must issue a RestoreObject to create a temporary readable copy, choosing a retrieval tier that trades speed for cost: Expedited (1–5 minutes), Standard (3–5 hours), or Bulk (5–12 hours, cheapest, often free-ish for large jobs). It fits backups, old media masters, and archives where waiting minutes-to-hours to read is fine. Each restore is billed per-GB and per-request by tier, and the restored copy is itself billed for the days you keep it available.

# Transition target (usually via lifecycle), then restore when needed:
aws s3api restore-object --bucket archive-prod --key masters/2024/film.mov \
  --restore-request '{"Days":7,"GlacierJobParameters":{"Tier":"Standard"}}'
# Check whether the restore has completed (Restore header flips to ongoing-request="false")
aws s3api head-object --bucket archive-prod --key masters/2024/film.mov \
  --query 'Restore'

The retrieval tiers — the speed/cost dial you turn at restore time:

Retrieval tier Typical restore time Relative cost Use when
Expedited 1–5 minutes Highest Urgent, small objects, occasional
Standard 3–5 hours Medium (default) Normal restores, no rush
Bulk 5–12 hours Lowest (often cheapest/free-tier-ish) Large batch restores, cost-sensitive

Glacier Flexible parameters and the restore mechanics:

Property Value / behaviour Gotcha
Storage price Very low (below GIR) The reason to archive here
Read model Restore required (not directly readable) Plain GET fails with InvalidObjectState
Minimum duration 90 days Early-delete fee under 90 days
Restore tiers Expedited / Standard / Bulk Pick by urgency vs cost
Restored-copy lifetime You set Days (temporary) Billed for the temporary copy too
Provisioned capacity Optional for guaranteed Expedited Extra cost for predictable urgent restores

S3 Glacier Deep Archive — the cheapest storage on AWS, measured in hours

S3 Glacier Deep Archive is the floor: the lowest-cost storage AWS offers, intended for data retained 7–10 years for legal or regulatory reasons and almost never read. Storage is roughly 1/24th of Standard. The price of that is a 180-day minimum storage duration and restores measured in hours: Standard retrieval completes within 12 hours, Bulk within 48 hours. There is no millisecond option. It is correct for compliance archives, long-term backups you hope never to touch, and anything where “we might need it in two days, with notice” is acceptable.

aws s3api put-object --bucket compliance-prod --key ledgers/2026/q2.parquet \
  --body q2.parquet --storage-class DEEP_ARCHIVE
# Deep Archive restore (Standard ≤12h, Bulk ≤48h)
aws s3api restore-object --bucket compliance-prod --key ledgers/2020/q1.parquet \
  --restore-request '{"Days":3,"GlacierJobParameters":{"Tier":"Bulk"}}'

Glacier Flexible vs Deep Archive — both need restore; pick by patience and price:

Dimension Glacier Flexible Retrieval Glacier Deep Archive Choose Deep Archive when
Storage price Low Lowest (~1/24× Standard) Truly cold, multi-year retention
Minimum duration 90 days 180 days Data lives ≥ 6 months for sure
Fastest restore Expedited (1–5 min) Standard (≤ 12 h) Hours-to-restore is acceptable
Slowest/cheapest restore Bulk (5–12 h) Bulk (≤ 48 h) Big batch, no urgency
Typical use Backups, old masters Compliance, legal hold, 7–10 yr Regulatory retention
Read frequency A few times a year Almost never “Hope we never read it”

The full retrieval-latency matrix across all three Glacier classes — the single table that answers “if I need it back, how long and via which tier?”:

Class Expedited Standard Bulk Restore needed?
Glacier Instant Retrieval n/a (instant) n/a (instant) n/a (instant) No — millisecond GET
Glacier Flexible Retrieval 1–5 minutes 3–5 hours 5–12 hours Yes
Glacier Deep Archive n/a ≤ 12 hours ≤ 48 hours Yes

And the cost shape of those retrievals (relative, per-GB — exact prices vary by Region; the ordering is the point):

Retrieval path Relative retrieval cost Latency When it’s the right dial
Glacier IR retrieval High per-GB, no restore step Milliseconds Rare but must-be-instant reads
Glacier Flexible — Expedited Highest of Flexible tiers 1–5 min Urgent, small, occasional
Glacier Flexible — Standard Medium 3–5 h Default, no rush
Glacier Flexible — Bulk Lowest of Flexible tiers 5–12 h Large batch, cost-sensitive
Deep Archive — Standard Higher than Flexible ≤ 12 h Compliance pull with notice
Deep Archive — Bulk Lowest absolute ≤ 48 h Massive, no-urgency restore

Legacy: Reduced Redundancy Storage (RRS)

Reduced Redundancy Storage is a deprecated class offering only four nines (99.99%) of durability at a price that is no longer competitive with Standard. AWS recommends against it; there is no scenario in 2026 where you should choose it. If you find objects in RRS, transition them to Standard or an IA class. It appears here only so you recognise and migrate off it:

Property RRS (legacy) What to do
Durability 99.99% (4 nines) Far below the 11-nines norm
Price No longer advantageous Standard is comparable or cheaper
Status Deprecated Migrate off it
Found in old buckets? Possible Lifecycle-transition to STANDARD/STANDARD_IA

The lifecycle engine: transitions, expirations, and the rules that govern them

Storage classes are the destinations; lifecycle policies are how objects get there without a human moving them. A lifecycle configuration is a list of rules; each rule has a filter (what it applies to) and one or more actions (what happens, and when). S3 evaluates them once per day, asynchronously — an object eligible “after 30 days” transitions on a daily pass at or after day 30, not at a precise instant. The age clock runs from the object’s creation date, not last access (that’s Intelligent-Tiering’s job).

The lifecycle actions, end to end:

Action What it does Applies to Key parameter
Transition Move current version to a colder class Current objects Days (age) + target class
NoncurrentVersionTransition Same, for previous versions Noncurrent versions NoncurrentDays + class
Expiration Delete the current object Current objects Days or Date
NoncurrentVersionExpiration Delete old versions Noncurrent versions NoncurrentDays (+ keep N newer)
AbortIncompleteMultipartUpload Delete unfinished multipart uploads In-progress MPUs DaysAfterInitiation
ExpiredObjectDeleteMarker Clean up dangling delete markers Versioned buckets (boolean)

The fields you actually write in a rule (CLI JSON key / Terraform argument) — the authoring reference:

Field (CLI / Terraform) Purpose Required? Valid values / form
ID / id Human name for the rule Recommended Unique string per config
Status / status Enable or disable the rule Yes Enabled / Disabled
Filter / filter What the rule applies to Yes (may be empty {}) prefix / tag / size / And
Transitions[].Days / transition.days Age before transition Per transition Integer days (≥ 30 for IA)
Transitions[].StorageClass / storage_class Target class Per transition STANDARD_IA/GLACIER/DEEP_ARCHIVE/…
Expiration.Days / expiration.days Age before delete Optional Integer days, or Date
NoncurrentVersion* / noncurrent_version_* Old-version actions Optional NoncurrentDays + class/expiry
AbortIncompleteMultipartUpload Sweep stale uploads Recommended DaysAfterInitiation integer

The transition order is constrained — you can only move “downhill,” and some hops require a minimum age. The allowed transitions and their day floors:

From Allowed to (downhill only) Minimum age before this transition Notes
Standard Standard-IA, One Zone-IA 30 days (to IA classes) Cannot transition to IA before 30 days
Standard Glacier IR / Flexible / Deep Archive 0+ days (no 30-day floor) Can archive directly
Standard-IA Glacier IR / Flexible / Deep Archive After the 30-day IA minimum Pay IA minimum first
One Zone-IA Glacier Flexible / Deep Archive After the 30-day minimum One Zone-IA cannot go to GIR
Intelligent-Tiering Glacier Flexible / Deep Archive per policy IT manages its own internal tiers
Glacier (any) (no further transition) n/a Glacier is terminal for transitions

A canonical age-based policy — IA at 30 days, Glacier at 90, expire at a year, abort stale uploads at 7 — as a single JSON document applied via the CLI:

cat > lifecycle.json <<'JSON'
{
  "Rules": [
    {
      "ID": "tier-and-expire-uploads",
      "Filter": { "Prefix": "uploads/" },
      "Status": "Enabled",
      "Transitions": [
        { "Days": 30,  "StorageClass": "STANDARD_IA" },
        { "Days": 90,  "StorageClass": "GLACIER" }
      ],
      "Expiration": { "Days": 365 },
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    }
  ]
}
JSON

aws s3api put-bucket-lifecycle-configuration \
  --bucket media-prod --lifecycle-configuration file://lifecycle.json

The same policy as Terraform (the form you actually keep in version control):

resource "aws_s3_bucket_lifecycle_configuration" "media" {
  bucket = aws_s3_bucket.media.id

  rule {
    id     = "tier-and-expire-uploads"
    status = "Enabled"
    filter { prefix = "uploads/" }

    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }
    transition {
      days          = 90
      storage_class = "GLACIER"
    }
    expiration {
      days = 365
    }
    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}

Filters: prefix, tag, size, or the whole bucket

A rule’s filter decides which objects it touches. You can filter by prefix (logs/), by object tag (archive=true), by object size (ObjectSizeGreaterThan / ObjectSizeLessThan — the modern guard against transitioning tiny objects), or combine them with And. An empty filter means the whole bucket. Tag-based rules are the most flexible: tag objects at write time and let lifecycle act on the tag rather than the key layout.

  rule {
    id     = "archive-tagged-large-only"
    status = "Enabled"
    filter {
      and {
        tags = { archive = "true" }
        object_size_greater_than = 131072   # 128 KB — skip tiny objects
      }
    }
    transition {
      days          = 0
      storage_class = "GLACIER"
    }
  }

The filter options and when to reach for each:

Filter type Matches Use when Watch-out
Prefix Keys starting with a string Folder-style layout (logs/, raw/) Re-keying data changes what’s matched
Tag Objects with a tag key=value Cross-cutting policy independent of path Must tag at/after write; tag costs apply
And (prefix+tags+size) All conditions Precise targeting Every condition must match
ObjectSizeGreaterThan Objects above N bytes Avoid IA min-size penalty on small objects Combine with transitions
ObjectSizeLessThan Objects below N bytes Expire tiny junk; keep big stuff
(empty) Entire bucket Bucket-wide expiry/archive Easy to over-apply

Versioning changes everything: current vs noncurrent

Turn on versioning and every overwrite or delete keeps the old bytes as a noncurrent version you still pay for. A lifecycle policy that only addresses current objects leaves a growing pile of noncurrent versions on the expensive class — a classic silent cost leak. Under versioning you generally want two parallel sets of actions: one for current versions, one (NoncurrentVersion*) for the old ones, plus delete-marker cleanup.

  rule {
    id     = "versioned-retention"
    status = "Enabled"
    filter {}

    # Current versions: tier down over time
    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }

    # Noncurrent versions: keep 3 newest, archive the rest, delete after a year
    noncurrent_version_transition {
      noncurrent_days           = 30
      newer_noncurrent_versions = 3
      storage_class             = "GLACIER"
    }
    noncurrent_version_expiration {
      noncurrent_days           = 365
      newer_noncurrent_versions = 3
    }

    # Tidy up delete markers left with no versions behind them
    expiration {
      expired_object_delete_marker = true
    }
  }

The versioning-specific knobs and what they prevent:

Setting What it controls Default if unset Cost leak it prevents
NoncurrentVersionTransition Tier old versions to a colder class Old versions stay on current class Paying Standard rate for dead versions
NoncurrentVersionExpiration Delete old versions after N days Old versions kept forever Unbounded version accumulation
NewerNoncurrentVersions Keep the N most-recent old versions All kept Over-deleting recent history
ExpiredObjectDeleteMarker Remove dangling delete markers They linger Wasted list/space + confusion
AbortIncompleteMultipartUpload Clean half-uploaded parts Parts billed indefinitely Invisible orphaned-part cost

How long do transitions actually take, and how do you verify?

Lifecycle runs are asynchronous and daily — submitting a policy does not move objects this second. Newly-eligible objects transition over the next day or so. You verify by checking an object’s current class and by watching the storage-class breakdown in Cost Explorer / Storage Lens shift over days, not minutes.

# What class is this object in right now?
aws s3api head-object --bucket media-prod --key uploads/clip-001.mp4 \
  --query 'StorageClass'

# What does the active lifecycle config say?
aws s3api get-bucket-lifecycle-configuration --bucket media-prod

Expectations vs reality for lifecycle timing:

You’d expect What actually happens Why
Objects move at midnight on day N They move on a daily pass at/after day N Async, batched evaluation
Policy applies instantly to all objects It rolls out over the next day(s) Background processing at scale
head-object flips class immediately It flips once the transition completes Eventual, per-object
Bill drops the moment you apply It trends down over the following days Transitions then prorated billing

Architecture at a glance

Picture a single S3 bucket as a conveyor that runs left to right. On the far left, producers write objects — an application doing PutObject through the SDK, plus a CloudFront distribution that can serve as a PUT origin — and everything lands in the hot tier: S3 Standard for the first ~30 days, or Intelligent-Tiering if you’d rather let S3 watch each object and decide. That hot tier is where access is cheap to serve and expensive to store, so nothing should sit there longer than its access pattern justifies.

The middle of the diagram is the lifecycle engine — the control plane that makes the rest automatic. Once a day it scans the bucket, applies each rule’s filter (age, prefix, tag, size), and fires the matching transition and expiration actions: move to Standard-IA at 30 days, to Glacier at 90, abort half-finished multipart uploads at 7, expire orphaned objects at 365. Objects flow from there into the warm/cold tier (Standard-IA, One Zone-IA, Glacier Instant Retrieval — all still millisecond-readable) and finally into archive + governance (Glacier Flexible, Glacier Deep Archive, fronted by Object Lock for WORM compliance), where a read first requires a RestoreObject round-trip back toward the hot tier. The five numbered badges mark exactly where money and durability leak: the Intelligent-Tiering monitoring fee on swarms of tiny objects (1), the transition cost exceeding the saving on short-lived data (2), the early-delete / minimum-duration fee when you tier objects you’ll delete too soon (3), One Zone-IA losing data if its single AZ fails (4), and the restore requirement that makes an archive GET fail until you un-archive it (5). Read the path once, then use the legend as your trap checklist.

Architecture of one S3 bucket spanning eight storage classes: producers PutObject into a hot tier of S3 Standard and Intelligent-Tiering; a central lifecycle rule engine scans daily and fires transition, expiration and abort-multipart actions by age, prefix and tag; objects flow into a warm/cold tier of Standard-IA, One Zone-IA and Glacier Instant Retrieval, then into an archive-and-governance tier of Glacier Flexible Retrieval, Glacier Deep Archive and Object Lock, with a RestoreObject path back to readable; numbered badges flag the Intelligent-Tiering monitoring fee, transition-cost-exceeds-saving, early-delete minimum-duration fee, One Zone-IA single-AZ data loss, and the archive restore requirement.

Real-world scenario

StreamForge (a fictional but representative video-sharing startup) ran a 9 PB bucket — streamforge-media-prod — that had only ever used S3 Standard. Every uploaded clip, every transcoded rendition at five bitrates, every thumbnail, and every abandoned half-upload sat on the hottest, most expensive class from the moment it was written. The monthly S3 storage line had crept to roughly ₹62 lakh (~$74k), and Finance had flagged it as the fastest-growing cloud cost with no corresponding revenue driver.

The platform team pulled an S3 Storage Lens report and an access-log analysis and found the shape everyone eventually finds: of objects older than 90 days, fewer than 4% were read in any 30-day window. Originals were almost never re-downloaded after transcoding; thumbnails and renditions were re-derivable from the master; and a startling ~140 TB of orphaned multipart upload parts — failed or abandoned uploads — were being billed invisibly because nothing ever listed them.

They designed a tag- and prefix-driven lifecycle policy rather than a blunt bucket-wide one. Originals (masters/) transitioned to Standard-IA at 30 days and Glacier Flexible Retrieval at 120 days, since a re-download was rare but had to be possible within hours. Renditions and thumbnails (derived/, reproducible) went to One Zone-IA at 30 days — the ~20% extra saving was worth it because losing an AZ’s worth of derivatives just meant re-transcoding, not data loss. Compliance copies of monetised content (legal/) went straight to Glacier Deep Archive with a 7-year retention and Object Lock in compliance mode. A bucket-wide AbortIncompleteMultipartUpload at 7 days swept the orphaned parts. Crucially, they added an ObjectSizeGreaterThan 128 KB guard on the IA transitions so the millions of tiny thumbnail sprites didn’t trip the 128 KB minimum-size penalty — those tiny ones stayed on Standard or went to Intelligent-Tiering instead.

The rollout was a single put-bucket-lifecycle-configuration plus a Terraform apply, applied to both existing and new objects. Over the following two weeks (lifecycle being asynchronous and daily), Cost Explorer’s storage-class breakdown shifted: Standard dropped from ~100% to ~22% of stored bytes; the rest landed in IA, Glacier Flexible and Deep Archive. The abort-MPU action reclaimed the 140 TB of orphaned parts in the first pass.

What went wrong, and the lesson: their first draft transitioned everything — including a hot live/ prefix of currently-streaming content — to Standard-IA at 30 days. Within a day, retrieval fees on that actively-read content spiked, and the IA storage saving was swamped by per-GB retrieval charges. They caught it in Cost Explorer (a sudden “Requests-Tier2 / retrieval” line jump), excluded live/ from the rule, and the numbers settled. The final state: storage spend fell to about ₹24 lakh (~$29k)/month — a ~61% reduction — with no application change, and a standing rule that hot prefixes are never tiered by age alone. The whole exercise paid for itself in the first month and keeps paying every month after.

Advantages and disadvantages

The honest two-column trade-off, then the prose on when each side matters:

Advantages Disadvantages
Large storage-cost reduction for cold data (often 50–70%) Retrieval fees can erase the saving if you misjudge access
Zero application changes once the rule is set Minimum storage durations (30/90/180 d) penalise short-lived data
Automatic, hands-off movement by age/prefix/tag Minimum billable object size (128 KB) penalises tiny objects
Eleven-nines durability on all multi-AZ classes One Zone-IA loses data if its single AZ fails
Archive classes are dramatically cheaper (Deep Archive ~1/24×) Archive classes need an explicit restore (minutes to 48 h) before read
Object Lock + lifecycle = compliant WORM retention Lifecycle is async/daily — not instant, and transitions are billed requests
Intelligent-Tiering removes guesswork with no retrieval fee Intelligent-Tiering’s per-object monitoring fee hurts tiny-object swarms
Cost Explorer / Storage Lens make the saving auditable Misconfigured noncurrent-version rules silently leak cost

When the advantages dominate: large objects, genuinely cold data, predictable aging, and reproducible derivatives — the classic media/data-lake/backup shape, where the floors never bite and the retrieval is rare. This is where a one-line rule saves crores a year.

When the disadvantages dominate: small objects (the 128 KB floor), short-lived data (the 30-day floor), hot data masquerading as cold (retrieval fees), and irreplaceable data on One Zone-IA. The skill is recognising these before you write the rule — which is exactly what the per-class minimums tables above are for.

Hands-on lab

A free-tier-friendly walk-through: create a bucket, write objects, apply a lifecycle policy, archive one object, restore it, and tear it all down. Use a unique bucket name (S3 names are globally unique).

1. Create a versioned bucket.

BUCKET="kv-lifecycle-lab-$(date +%s)"
aws s3api create-bucket --bucket "$BUCKET" \
  --create-bucket-configuration LocationConstraint=ap-south-1
aws s3api put-bucket-versioning --bucket "$BUCKET" \
  --versioning-configuration Status=Enabled
echo "Bucket: $BUCKET"

2. Write objects to different classes.

echo "hot data"     > hot.txt
echo "warm data"    > warm.txt
echo "archive data" > cold.txt

aws s3api put-object --bucket "$BUCKET" --key hot/hot.txt   --body hot.txt   --storage-class STANDARD
aws s3api put-object --bucket "$BUCKET" --key warm/warm.txt --body warm.txt  --storage-class STANDARD_IA
aws s3api put-object --bucket "$BUCKET" --key cold/cold.txt --body cold.txt  --storage-class GLACIER

Expected: each put-object returns an ETag (and a VersionId since versioning is on).

3. Apply a lifecycle policy.

cat > lab-lifecycle.json <<'JSON'
{
  "Rules": [
    {
      "ID": "lab-tiering",
      "Filter": { "Prefix": "hot/" },
      "Status": "Enabled",
      "Transitions": [
        { "Days": 30, "StorageClass": "STANDARD_IA" },
        { "Days": 90, "StorageClass": "GLACIER" }
      ],
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    },
    {
      "ID": "lab-expire-noncurrent",
      "Filter": {},
      "Status": "Enabled",
      "NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
    }
  ]
}
JSON

aws s3api put-bucket-lifecycle-configuration \
  --bucket "$BUCKET" --lifecycle-configuration file://lab-lifecycle.json
aws s3api get-bucket-lifecycle-configuration --bucket "$BUCKET"

Expected: the second command echoes your two rules back. (Actual transitions take ~a day; you won’t see hot.txt move during the lab.)

4. Try to read the Glacier object directly — watch it fail, then restore it.

# This should FAIL with InvalidObjectState — Glacier isn't directly readable
aws s3api get-object --bucket "$BUCKET" --key cold/cold.txt out.txt || \
  echo ">> expected InvalidObjectState: object is in Glacier, restore first"

# Restore it (Bulk = cheapest; small object so it's quick)
aws s3api restore-object --bucket "$BUCKET" --key cold/cold.txt \
  --restore-request '{"Days":1,"GlacierJobParameters":{"Tier":"Bulk"}}'

# Poll restore status — 'ongoing-request="true"' means still restoring
aws s3api head-object --bucket "$BUCKET" --key cold/cold.txt --query 'Restore'

Expected: the GET errors with InvalidObjectState; the restore request is accepted; the Restore header shows an ongoing request that completes within the Bulk window.

5. Inspect the storage-class breakdown.

aws s3api list-objects-v2 --bucket "$BUCKET" \
  --query 'Contents[].{Key:Key,Class:StorageClass,Size:Size}' --output table

Expected: a table showing STANDARD, STANDARD_IA, and GLACIER against the three keys.

6. Tear down (delete all versions, then the bucket).

# Delete every version and delete-marker, then remove the bucket
aws s3api list-object-versions --bucket "$BUCKET" \
  --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}' --output json > vers.json
aws s3api delete-objects --bucket "$BUCKET" --delete file://vers.json 2>/dev/null || true

aws s3api list-object-versions --bucket "$BUCKET" \
  --query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}' --output json > marks.json
aws s3api delete-objects --bucket "$BUCKET" --delete file://marks.json 2>/dev/null || true

aws s3 rb "s3://$BUCKET" --force
rm -f hot.txt warm.txt cold.txt out.txt lab-lifecycle.json vers.json marks.json
echo "Cleaned up $BUCKET"

Expected: the bucket is removed. (Costs are negligible — a few tiny objects for a few minutes — but the Glacier object carries a 90-day minimum-duration charge prorated to almost nothing at this size.)

Common mistakes & troubleshooting

The differentiator. Each failure mode: symptom → root cause → how to confirm (exact command) → fix. Scan the playbook table, then read the detail for the row that matches.

# Symptom Root cause Confirm with Fix
1 Bill rose after a lifecycle rule Tiny objects hit the 128 KB IA minimum + per-transition request cost Cost Explorer: “Requests-Tier2” + IA storage up; Storage Lens avg object size Add ObjectSizeGreaterThan 128 KB; keep small objects on Standard/IT
2 Big retrieval bill out of nowhere Hot data tiered to IA/Glacier, then read a lot Cost Explorer “retrieval” / Tier2 request line spike; map to prefix Exclude hot prefixes; never tier by age alone for live data
3 GET on an object returns InvalidObjectState Object is in Glacier Flexible / Deep Archive (not readable) aws s3api head-object --query StorageClass = GLACIER/DEEP_ARCHIVE restore-object first; choose retrieval tier by urgency
4 Restore is “taking forever” Wrong retrieval tier (Bulk on Deep Archive ≈ 48 h) head-object --query Restore shows ongoing-request true Use Standard/Expedited; size restore SLA up front
5 Objects not transitioning when expected Filter prefix/tag doesn’t match, or rule disabled get-bucket-lifecycle-configuration; check Status + Filter Fix the filter; enable the rule; remember it’s daily/async
6 Versioned bucket cost keeps climbing Noncurrent versions never expire/tier list-object-versions shows many versions per key Add NoncurrentVersion* rules; keep N newest
7 Early-delete fee on the bill Deleted/overwrote IA/Glacier objects before the minimum Cost Explorer “EarlyDelete” usage type Only tier data that lives past the floor (30/90/180 d)
8 Mystery storage you can’t see in the console Orphaned incomplete multipart upload parts list-multipart-uploads; Storage Lens “incomplete MPU bytes” Add AbortIncompleteMultipartUpload (e.g. 7 days)
9 One Zone-IA data gone after an AZ event Single-AZ class on non-reproducible data list-objects-v2 --query Contents[].StorageClass = ONEZONE_IA Move irreplaceable data to a multi-AZ class; regenerate the rest
10 Intelligent-Tiering bill higher than Standard Per-object monitoring fee on millions of tiny objects Cost Explorer “Monitoring and Automation” usage type Use IT only for large/unknown objects; lifecycle the rest
11 Transition action rejected/ignored Illegal transition (uphill, or before the 30-day floor) API error / no movement; check the allowed-transition table Respect downhill-only order and the 30-day IA minimum
12 Cost didn’t drop after applying the policy Lifecycle is async; movement takes a day+ head-object --query StorageClass still old; wait + re-check Storage Lens Be patient; verify over days, not minutes

The expanded reasoning for the ones that bite hardest:

1. The bill went up after a lifecycle rule. Root cause: you transitioned millions of objects smaller than 128 KB into Standard-IA/One Zone-IA/Glacier IR; each is now billed at the 128 KB minimum size, and each transition was a billed request, so the request cost plus inflated per-object floor exceeded the storage discount. Confirm: Cost Explorer shows a jump in Tier2 request and IA storage usage; S3 Storage Lens shows a tiny average object size. Fix: gate IA/Glacier transitions with ObjectSizeGreaterThan = 131072 and keep small objects on Standard or Intelligent-Tiering (large objects only).

2. A surprise retrieval bill. Root cause: hot, frequently-read data was tiered to IA or Glacier by an age-only rule, so every read now incurs a per-GB retrieval fee (and, for Glacier, a restore). Confirm: Cost Explorer’s retrieval / Tier2 line spikes; correlate to the prefix that got tiered. Fix: exclude hot prefixes from age-based rules; for genuinely mixed temperatures use Intelligent-Tiering (no retrieval fee) instead of a blunt age rule.

3. InvalidObjectState on a GET. Root cause: the object is in Glacier Flexible Retrieval or Deep Archive, which are not directly readable. Confirm: aws s3api head-object --query StorageClass returns GLACIER/DEEP_ARCHIVE. Fix: issue restore-object with a retrieval tier, wait for it to complete (head-object --query Restore), then GET. For data that must be instantly readable, use Glacier Instant Retrieval instead.

6. Versioned bucket cost climbs forever. Root cause: versioning keeps every overwrite as a noncurrent version, and your policy only addressed current objects, so dead versions pile up on the expensive class. Confirm: aws s3api list-object-versions shows many versions per key. Fix: add NoncurrentVersionTransition and NoncurrentVersionExpiration (keeping the N newest), plus ExpiredObjectDeleteMarker.

8. Storage you can’t see in the console. Root cause: failed/abandoned multipart uploads leave parts that are billed but don’t appear as objects. Confirm: aws s3api list-multipart-uploads --bucket <b>; Storage Lens reports incomplete-MPU bytes. Fix: add AbortIncompleteMultipartUpload with DaysAfterInitiation (7 is a common, safe value) to every bucket’s lifecycle config.

Best practices

The signals worth watching after you apply a policy — where each one lives and what a bad reading means:

Signal Where to find it Healthy reading A bad reading means
Storage by class mix Cost Explorer (group by storage class) Cold bytes leaving Standard over days Policy not matching / still async
Average object size S3 Storage Lens Comfortably > 128 KB on IA-tiered prefixes Tiny objects hitting the size floor
Tier2 / retrieval requests Cost Explorer usage type Flat after tiering hot-but-not-cold data You tiered hot data → retrieval fees
Incomplete-MPU bytes S3 Storage Lens Near zero Missing AbortIncompleteMultipartUpload
Noncurrent-version bytes S3 Storage Lens / list-object-versions Bounded, trending flat No noncurrent-version rules → leak
EarlyDelete usage type Cost Explorer Absent Tiered data deleted before its minimum
Monitoring-and-automation fee Cost Explorer usage type Tiny vs storage Intelligent-Tiering on tiny-object swarms

Security notes

The security controls that also keep the cost model honest:

Control Mechanism Secures against Also prevents
Scoped lifecycle IAM s3:Put*Lifecycle* restricted Rogue mass-delete/expire Accidental data loss
SSE-KMS on objects Customer-managed key Plaintext at rest Unauditable access (CloudTrail logs decrypts)
Object Lock (compliance) WORM retention Tampering / early deletion Lifecycle expiring data under legal hold
MFA Delete Versioning + MFA Credential-theft wipes Accidental version purge
Block Public Access Account/bucket setting Public exposure “Archive is safe” false assumption
CloudTrail data/management events API logging Untraceable changes Silent policy drift

Cost & sizing

The bill has four levers, and lifecycle touches all of them:

Request pricing by operation class — small per-request, but it dominates at billions of objects and on tiny-object transitions:

Request type Example operations Pricing tier Relative cost Notes for lifecycle
Write / list PUT, COPY, POST, LIST Tier1 Higher per-1,000 Initial uploads; lifecycle transitions billed like a COPY
Read GET, SELECT, HEAD Tier2 Lower per-1,000 Reads from any class; retrieval fee is separate
Lifecycle transition (per object moved down a class) Tier1-like Per object The cost that bites tiny-object swarms
Glacier restore request RestoreObject (Expedited/Standard/Bulk) Per request + per-GB Varies by tier Expedited dearest, Bulk cheapest
IA/Glacier data retrieval (per-GB read out of the class) Retrieval fee Rises as class gets colder Model fee × reads before tiering
Data transfer out GET egress to internet Per-GB transfer Region-dependent Independent of storage class

A rough monthly picture for 100 TB of mostly-cold media (order-of-magnitude, ap-south-1-ish, illustrative — verify with the AWS Pricing Calculator):

Layout Where the 100 TB sits Rough INR / month Notes
All S3 Standard 100 TB Standard ~₹1.9 lakh The “did nothing” baseline
20% Standard / 80% Standard-IA 20 TB Std + 80 TB IA ~₹1.2 lakh ~35% off; assumes rare reads
20% Standard / 80% Glacier Flexible 20 TB Std + 80 TB Glacier ~₹70k ~63% off; reads need restore
10% Standard / 90% Deep Archive 10 TB Std + 90 TB Deep ~₹40k ~79% off; 12–48 h restore
Intelligent-Tiering (large objects) 100 TB IT, auto-tiered ~₹0.9–1.3 lakh No retrieval-fee risk; monitoring fee tiny on big objects

The cost drivers and what each one buys (or costs) you:

Cost driver What you pay for Direction lifecycle moves it Watch-out
Per-GB storage by class Bytes stored per month Down (colder = cheaper) The whole point
Lifecycle transitions One billed request per object moved Up (one-time per move) Huge on tiny-object swarms
Retrieval fees Per-GB read from IA/Glacier Up (if you read cold data) Can exceed the storage saving
Minimum duration Floor days billed (30/90/180) Up (on short-lived data) Don’t tier soon-deleted data
Minimum object size 128 KB floor (IA classes) Up (on small objects) Gate with ObjectSizeGreaterThan
Intelligent-Tiering monitoring Per-object monthly fee Up (on tiny-object swarms) Negligible on large objects
KMS decrypts on restore Per-request KMS Up (regulated data) Budget for restore-time decrypts

Free tier: new AWS accounts get 5 GB of S3 Standard, 20,000 GET and 2,000 PUT requests per month for 12 months — enough for the lab above. There is no free tier for Glacier retrieval beyond the small Bulk allowances, so test restores deliberately.

Interview & exam questions

1. A bucket’s cost increased after applying a lifecycle policy that tiered objects to Standard-IA. Why? Almost certainly the objects are smaller than 128 KB, so each is billed at the IA minimum billable object size of 128 KB, and every transition was a billed request. The request cost plus the inflated per-object floor exceeded the storage discount. Fix by gating the transition with a minimum object size and keeping small objects on Standard or Intelligent-Tiering.

2. When would you choose Intelligent-Tiering over an explicit lifecycle policy? When the access pattern is unknown or changing and objects are reasonably large. Intelligent-Tiering moves each object between tiers based on observed access with no retrieval fees, paying only a small per-object monitoring fee — ideal when you can’t predict temperature. An explicit age-based lifecycle is better when the pattern is predictable (cheaper, no monitoring fee), and Intelligent-Tiering is a poor fit for millions of tiny objects where the per-object fee dominates.

3. Difference between the three Glacier classes? Glacier Instant Retrieval gives millisecond reads with no restore at archive storage price (high retrieval fee, 90-day minimum). Glacier Flexible Retrieval is cheaper but requires a restore (Expedited 1–5 min / Standard 3–5 h / Bulk 5–12 h, 90-day minimum). Glacier Deep Archive is the cheapest storage on AWS, requires a restore measured in hours (Standard ≤12 h, Bulk ≤48 h), with a 180-day minimum — for 7–10-year compliance archives.

4. Why is One Zone-IA cheaper, and what’s the catch? It stores a single-AZ copy instead of replicating across ≥3 AZs, so it’s ~20% cheaper than Standard-IA — but if that Availability Zone is destroyed, the data is lost. Its eleven-nines durability assumes the AZ survives. Use it only for reproducible data (thumbnails, transcodes) you can regenerate, never for originals.

5. A GET on an object returns InvalidObjectState. What happened and what do you do? The object is in Glacier Flexible Retrieval or Deep Archive, which are not directly readable. You must issue a RestoreObject with a retrieval tier, wait for it to complete (poll the Restore header on head-object), then GET the temporary readable copy. If the data must always be instantly readable, use Glacier Instant Retrieval instead, which needs no restore.

6. How do you stop versioning from leaking storage cost? Add noncurrent-version lifecycle actions: NoncurrentVersionTransition to tier old versions to a colder class and NoncurrentVersionExpiration to delete them after N days, typically keeping the N most-recent noncurrent versions, plus ExpiredObjectDeleteMarker cleanup. Without these, every overwrite accumulates billable dead versions on the expensive class.

7. What does the AbortIncompleteMultipartUpload lifecycle action do and why does it matter? It deletes the parts of failed or abandoned multipart uploads after a set number of days. Those parts are billed but invisible in the object list, so they accumulate silently and can hide terabytes of cost. Adding it (commonly 7 days) to every bucket is a free, standing cleanup.

8. Object is deleted on day 10 from Standard-IA. What’s billed? The full 30-day minimum storage duration — Standard-IA bills a minimum of 30 days per object even if deleted earlier (an “early delete” charge for the remaining 20 days). The same logic applies to Glacier (90-day) and Deep Archive (180-day) minimums. Don’t tier data you’ll delete before the floor.

9. Walk through choosing a class for “backups read a few times a year, restore within hours is fine.” That’s archive with tolerant latency → Glacier Flexible Retrieval: very low storage cost, 90-day minimum, restore via Standard (3–5 h) or Bulk (5–12 h). If restores must be instant you’d step up to Glacier Instant Retrieval (ms, higher retrieval fee); if the data is multi-year compliance and hours-to-restore is fine you’d step down to Deep Archive (cheapest, 180-day min, 12–48 h restore).

10. How do you verify a lifecycle policy actually saved money? Lifecycle is asynchronous and daily, so check over days, not minutes. Use Cost Explorer grouped by storage class to watch the mix shift from Standard to colder classes, and S3 Storage Lens for storage-class distribution, average object size, and incomplete-MPU bytes. Watch the retrieval / Tier2 request lines to ensure you didn’t trade storage saving for retrieval fees.

11. Why can’t you transition a Standard object straight to Standard-IA on day 1? S3 enforces a 30-day minimum age before transitioning to the IA classes (Standard-IA / One Zone-IA). You can archive to Glacier classes earlier, but the IA transition specifically has a 30-day floor — reflecting that IA is for data that has already been “warm” for a month. Transitions are also downhill-only (you can’t move from a colder class back to a warmer one via lifecycle).

12. What’s the difference between Object Lock governance and compliance mode? Governance mode blocks deletes/overwrites but lets users with a special permission (s3:BypassGovernanceRetention) override — useful for guardrails you can break in a pinch. Compliance mode blocks deletion by everyone, including the root account, until the retention period elapses — irreversible WORM for strict regulatory requirements. Lifecycle expiration cannot delete an object still under either lock.

These map to AWS Certified Solutions Architect – Associate (SAA-C03)design cost-optimized storage and resilient architectures — and Developer – Associate (DVA-C02)S3 storage classes, lifecycle, and the SDK/CLI operations. A compact cert-mapping for revision:

Question theme Primary cert Exam objective area
Class selection by access pattern SAA-C03 Design cost-optimized storage
Lifecycle transitions & expirations SAA-C03 / DVA-C02 Cost optimization; S3 operations
Glacier restore tiers & latency SAA-C03 / DVA-C02 Resilient/archival storage
One Zone-IA durability trade-off SAA-C03 Design resilient architectures
Versioning + noncurrent lifecycle DVA-C02 Develop with S3; data management
Object Lock / compliance retention SAA-C03 / Security Governance & compliance

Quick check

  1. A lifecycle rule tiers a million 5 KB objects to Standard-IA and the bill goes up. Name the two mechanisms that caused it, and the one filter setting that fixes it.
  2. True or false: scaling to a colder storage class is always cheaper overall, because the per-GB storage rate is lower.
  3. A GET on archive/2020/report.pdf returns InvalidObjectState. What class is it in, and what’s the exact next action?
  4. You enabled versioning months ago and bucket cost keeps climbing even though current-object count is flat. What did you forget to configure?
  5. Pick a class: “compliance ledgers kept for 7 years, read essentially never, and a 24-hour wait to retrieve is fine.” Which class, and what’s its minimum storage duration?

Answers

  1. (a) Each object hits the 128 KB minimum billable object size, so a 5 KB object is billed as 128 KB; (b) each transition is a billed request. Both can exceed the storage discount. The fix is an ObjectSizeGreaterThan (e.g. 131072 bytes) filter so tiny objects are excluded from the IA transition.
  2. False. Colder classes add retrieval fees, minimum storage durations (30/90/180 days), and minimum object sizes (128 KB). If you read the data, delete it early, or it’s tiny, total cost can be higher than a warmer class. Always model retrieval and the floors before tiering.
  3. It’s in Glacier Flexible Retrieval or Glacier Deep Archive (not directly readable). The next action is aws s3api restore-object with a retrieval tier; once the restore completes (poll the Restore header via head-object), GET the temporary copy. For always-instant reads, use Glacier Instant Retrieval instead.
  4. Noncurrent-version lifecycle actions (NoncurrentVersionTransition and NoncurrentVersionExpiration, plus delete-marker cleanup). Versioning keeps every overwrite as a billable noncurrent version; without rules for them they accumulate on the expensive class forever.
  5. Glacier Deep Archive — the cheapest storage on AWS, restore in 12–48 hours, with a 180-day minimum storage duration. Pair it with Object Lock (compliance mode) for the 7-year WORM retention.

Glossary

Next steps

You can now place any object in the class that matches its access pattern and let lifecycle move it automatically. Build outward:

AWSS3Storage ClassesLifecycle PolicyCost OptimizationGlacierIntelligent-TieringFinOps
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading