Skip to main content
emnode
Cost

Transition S3 objects to cheaper storage classes

When access is predictable, lifecycle transition rules slide aging objects down the storage-class price ladder, but the wrong threshold can cost more than it saves.

14 min·10 sections·AWS

Last reviewed

Storage-class transitions: the basics

Why a single bucket has six different prices per GB

S3 isn't one storage product, it's a ladder of storage classes at very different prices, all in the same bucket. The rungs, with rough US-East-1 per-GB-month rates: S3 Standard ($0.023), Standard-Infrequent Access ($0.0125), One Zone-IA ($0.01), Glacier Instant Retrieval ($0.004), Glacier Flexible Retrieval ($0.0036), and Glacier Deep Archive ($0.00099). That's a 23× spread between the top and bottom rungs for bytes that are physically identical, the only difference is how fast and how cheaply you can read them back.

A lifecycle transition rule automatically moves objects down this ladder as they age. You write a rule that says "after 30 days move to Standard-IA, after 90 days move to Glacier Instant Retrieval, after 365 days move to Deep Archive," and S3 evaluates it once a day and relocates the matching objects for you. The data is still there, still in the same bucket, still at the same key, it just costs a fraction of what it did when it was hot.

The catch is the ladder is a trade-off, not a free lunch. As storage gets cheaper, retrieval gets slower and more expensive: Standard reads are instant and free of retrieval fees, IA charges a per-GB retrieval fee, Glacier Flexible takes minutes-to-hours to restore, and Deep Archive can take up to 12 hours. Transitions are the right tool only when access is predictable, data you know cools off on a schedule. If access is unknown or erratic, that's what Intelligent-Tiering is for (a separate lesson); transitions are for data that ages on a clock you can name.

In this lesson you'll learn the S3 storage-class price ladder and the retrieval-cost-versus-storage-cost trade-off that defines it, how to use lifecycle Transition rules to step objects down the ladder on an age schedule, and the gotchas that turn a savings rule into a cost increase, minimum storage-duration charges, per-object transition request fees, and the 128 KB minimum billable size for IA classes. You'll see the AWS CLI to apply a multi-step transition policy and how to enable S3 Storage Class Analysis to pick the right age threshold from real access data instead of guessing. You'll also see where transitions end and Intelligent-Tiering begins.

Fun fact

The transition that cost more than the storage

A media company wrote a tidy-looking lifecycle rule: transition everything to Standard-IA after 30 days. It looked perfect, until the next bill arrived higher than before. The bucket held 90 million thumbnail images averaging 12 KB each. Two things bit at once: each transition is a billable request (~$0.01 per 1,000 to IA), so moving 90 million objects cost roughly $900 in one-time request fees, and IA bills a 128 KB minimum per object, so each 12 KB thumbnail was charged as if it were 128 KB, a 10× inflation on the per-object storage. The 'savings' rule raised the monthly bill. The fix: exclude the small-object prefix with a ObjectSizeGreaterThan filter and only transition the large video files, where the math actually works.

Tiering a bucket in action

Marco runs cost optimisation for a data platform team. The dashboard flags acme-prod-events-archive, 84 TB sitting entirely in S3 Standard at roughly $1,978/month, as a tiering candidate. The bucket holds JSON event exports: queried heavily for the first week, occasionally through the first month, and effectively never after 90 days.

Rather than guess the right age threshold, Marco enables S3 Storage Class Analysis on the bucket and lets it observe access for a couple of weeks. The report confirms the intuition: 96% of bytes go untouched after day 45, and 88% are never read after day 120. That's a textbook predictable-aging pattern, exactly what transition rules are built for.

He writes a three-step rule: Standard → Standard-IA at day 30, → Glacier Instant Retrieval at day 90, → Deep Archive at day 365. He checks object sizes first (the events average 2-4 MB, comfortably over the 128 KB IA minimum, so no small-object penalty) and confirms the data lives long past the 30/90/180-day minimum-duration charges, so no early-deletion fees. Projected steady-state storage drops from $1,978 to roughly $190/month once the back catalogue ages through.

Before writing rules, enable S3 Storage Class Analysis so the age threshold comes from real access data, not a guess. It watches the prefix and reports how much data goes cold and when.

$ aws s3api put-bucket-analytics-configuration --bucket acme-prod-events-archive --id events-cold-analysis --analytics-configuration '{"Id":"events-cold-analysis","Filter":{"Prefix":"events/"},"StorageClassAnalysis":{"DataExport":{"OutputSchemaVersion":"V_1","Destination":{"S3BucketDestination":{"Format":"CSV","Bucket":"arn:aws:s3:::acme-analytics-reports","Prefix":"sca/"}}}}}'
# No output on success, analysis now runs daily and exports CSV after ~24-48h.
# Reading the export after two weeks:
ObjectAge StorageInGB Accessed%
0-30 days 28160 71%
30-90 days 21504 12%
90-365 days 30720 3%
365+ days 5728 0%
# Access falls off a cliff after day 30, transition thresholds confirmed.

Storage Class Analysis turns a guessed age threshold into a measured one before you commit to a rule.

Now apply a three-step transition policy. Note the ObjectSizeGreaterThan filter, it keeps tiny objects out of IA so the 128 KB minimum and per-object request fees don't erase the savings.

$ aws s3api put-bucket-lifecycle-configuration --bucket acme-prod-events-archive --lifecycle-configuration file://transitions.json
# transitions.json applied. Re-read to confirm:
{
"Rules": [{
"ID": "tier-down-aging-events",
"Filter": { "And": { "Prefix": "events/", "ObjectSizeGreaterThan": 131072 } },
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
]
}]
}
# Objects start migrating within 24h. Bill drops on the next cycle, not instantly.

A three-rung transition with a size floor, the size filter is what keeps the gotchas from biting.

Transitions under the hooddeep dive

A transition is a Transitions entry inside a lifecycle rule: a target StorageClass plus either Days (object age) or a Date. S3 evaluates lifecycle rules once per day per bucket, so a 30-day transition fires at day 30 plus up to 24 hours, there's no real-time trigger. Multiple transitions in one rule must move strictly down the ladder in increasing-day order; S3 rejects a rule that tries to move an object to a more expensive class or out of age order. The object's key, metadata, and bucket never change, only the billing class and retrieval characteristics.

Three charges decide whether a transition saves or costs money. First, the transition itself is a billable request: roughly $0.01 per 1,000 objects to IA classes and $0.05 per 1,000 to Glacier/Deep Archive. For millions of small objects those one-time fees can exceed months of storage savings. Second, every class below Standard carries a minimum storage duration, 30 days for IA, 90 days for Glacier Flexible/Instant, 180 days for Deep Archive, and deleting or re-transitioning before that elapses still bills the full minimum. Third, the IA classes bill a 128 KB minimum object size: a 10 KB object in Standard-IA is charged as 128 KB. Combine small objects with short lifetimes and a transition rule can multiply your bill.

This is exactly why transitions suit predictable aging and not unknown access. If you can name the schedule on which data cools, logs, event dumps, backups, compliance archives, an explicit transition ladder is the cheapest possible answer because you pay zero monitoring overhead. If access is erratic or you genuinely don't know, Intelligent-Tiering (covered in its own lesson) monitors each object and moves it automatically for a small per-object metadata fee; it trades a little overhead for not having to guess. The decision rule: known aging clock → transitions; unknown or changing access → Intelligent-Tiering.

# Inspect the current storage-class distribution before deciding thresholds.
# CloudWatch publishes BucketSizeBytes per StorageType once a day.
for TYPE in StandardStorage StandardIAStorage GlacierInstantRetrievalStorage DeepArchiveStorage; do
  echo "== $TYPE =="
  aws cloudwatch get-metric-statistics \
    --namespace AWS/S3 --metric-name BucketSizeBytes \
    --dimensions Name=BucketName,Value=acme-prod-events-archive \
                 Name=StorageType,Value=$TYPE \
    --start-time $(date -u -d '2 days ago' +%FT%TZ) \
    --end-time   $(date -u +%FT%TZ) \
    --period 86400 --statistics Average \
    --query 'Datapoints[0].Average' --output text
done

# A bucket that is 100% StandardStorage on aging data is the textbook
# transition candidate. One that is already spread across IA/Glacier is
# being managed, leave it alone.

What is the impact of leaving data in the wrong storage class?

The direct impact is paying premium rates on cold bytes. Standard at $0.023/GB versus Glacier Instant Retrieval at $0.004/GB is nearly 6×; versus Deep Archive at $0.00099/GB it's 23×. On the 84 TB events archive above, all-Standard is ~$1,978/month while a tiered policy settles around $190/month once the back catalogue ages through, over $20k per year saved on one bucket. Multiply across an enterprise's hundreds of buckets and untiered cold data is routinely the largest single S3 cost line.

The opposite-direction impact is the trap finance people should know about: a careless transition can raise the bill. Transitioning millions of small objects incurs per-object request fees that can dwarf the storage saving, and the IA classes' 128 KB minimum billable size inflates every small object's storage cost. The media-company thumbnail story isn't rare, "transition everything after 30 days" is the single most common mis-step, because the rule looks frugal and the gotchas are invisible until the next invoice.

There's a retrieval-cost impact that surfaces only when you actually read the data back. Glacier and Deep Archive charge per-GB retrieval fees and restore latency (minutes to 12 hours). If data you tiered to Deep Archive turns out to be needed weekly, the retrieval fees plus the cost of pulling it back to a hot tier can exceed what you'd have paid leaving it in Standard. Transitions assume the data really is cold; misjudging that converts a saving into a recurring penalty.

Finally there's an operational and forecasting impact. Untiered storage grows linearly with data volume, every gigabyte ingested adds full-rate cost forever, so an untiered estate makes the S3 line impossible to forecast except "up." A tiered estate decouples cost growth from data growth: new data is hot and expensive, but the bulk ages into cheap tiers, so the bill flattens even as volume climbs. That predictability is as valuable to planning as the raw saving.

How do you transition storage classes safely?

Tiering is a four-step loop: measure the real access pattern, design a transition ladder that dodges the minimum-charge and small-object traps, apply it with a size-filtered lifecycle rule, and verify against the next billing cycle's storage-class breakdown.

1. Measure the access pattern before picking thresholds

Enable S3 Storage Class Analysis (or S3 Inventory + Athena) on the bucket or prefix and let it observe for at least two weeks. It reports how much data goes untouched at each age band, that's your transition threshold, measured rather than guessed. If access turns out to be erratic or unpredictable across the prefix, that's the signal to use Intelligent-Tiering instead of explicit transitions; transitions only pay off when the aging clock is real.

2. Design a ladder that respects the minimums and the small-object floor

Match each rung to how the data is read: Standard-IA or Glacier Instant Retrieval for data you might still read instantly but rarely; Glacier Flexible for restore-in-hours-is-fine; Deep Archive only for compliance-grade cold data you'll almost never touch. Keep transition ages comfortably above the minimum durations (IA 30 days, Glacier 90, Deep Archive 180) so you never re-transition or delete inside a minimum and pay the penalty. Crucially, add an ObjectSizeGreaterThan filter (≥128 KB) on any rule targeting an IA class so tiny objects, which the 128 KB minimum and per-object request fees would make more expensive, are left in Standard.

3. Apply with PutBucketLifecycleConfiguration and watch the request-fee one-time hit

Lifecycle changes are atomic, the call replaces the entire policy, so always pass the complete rule set, not a delta. Be aware that the initial migration of a large back catalogue incurs one-time per-object transition request fees; on a multi-million-object bucket that can be a visible spike on the bill the first month before steady-state savings kick in. That's expected and self-correcting, just don't be alarmed by a one-month bump, and don't re-run transitions repeatedly.

4. Verify against the storage-class breakdown, not just the API

Re-read the policy with GetBucketLifecycleConfiguration to confirm it's applied, then watch CloudWatch BucketSizeBytes per StorageType over the following weeks as objects migrate. Confirm the saving showed up on the next billing cycle's Cost Explorer storage-class breakdown. If a chunk of data is stuck in Standard, check that it isn't below your size filter or under Object Lock (which silently blocks transitions until retention expires), both are common reasons a rule appears to do nothing.

# A size-filtered, minimum-aware transition ladder. The size filter is the
# guardrail against the IA 128 KB minimum and per-object request fees.
cat > transitions.json <<'EOF'
{
  "Rules": [
    {
      "ID": "tier-down-aging-events",
      "Status": "Enabled",
      "Filter": {
        "And": {
          "Prefix": "events/",
          "ObjectSizeGreaterThan": 131072
        }
      },
      "Transitions": [
        { "Days": 30,  "StorageClass": "STANDARD_IA" },
        { "Days": 90,  "StorageClass": "GLACIER_IR" },
        { "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
      ]
    }
  ]
}
EOF

aws s3api put-bucket-lifecycle-configuration \
  --bucket acme-prod-events-archive \
  --lifecycle-configuration file://transitions.json

# Verify it applied, then track migration via per-class CloudWatch metrics.
aws s3api get-bucket-lifecycle-configuration \
  --bucket acme-prod-events-archive

Quick quiz

Question 1 of 5

A bucket holds 60 million log fragments averaging 8 KB each, mostly never read after a week. You want to cut storage cost. What's the right move?

You've completed Transition S3 objects to cheaper storage classes. You now know the storage-class price ladder and the storage-versus-retrieval trade-off that defines it, how to write a transition rule that steps data down it on a measured age schedule, and the three gotchas, minimum storage durations, per-object request fees, and the 128 KB IA minimum, that turn a careless rule into a cost increase. The next time the dashboard flags a bucket sitting in premium storage on cold data, you'll have a defensible, size-filtered tiering plan ready to apply.

Back to the library