Storage Cost Optimization — S3 Lifecycle, Azure Blob Tiers, GCP Storage Classes, Archival
Cloud storage costs grow silently — every GB stored, every API call, every byte transferred adds up. This guide covers storage optimization across AWS S3, Azure Blob Storage, and GCP Cloud Storage, with lifecycle policies, tiering strategies, and archival patterns.
What You’ll Learn
You’ll configure S3 lifecycle policies for automatic tiering, use Intelligent-Tiering for unknown access patterns, set up Azure Blob tiering (hot/cool/archive), implement GCP storage class transitions, and design cost-effective archival strategies that balance access speed against storage cost.
Why Storage Cost Optimization Matters
Storage costs are the second largest cloud expense after compute. Unlike compute, storage costs compound — old data accumulates forever. A single S3 bucket with 100TB of infrequently accessed data costs $2,300/month in Standard tier vs $400/month in Glacier Deep Archive. The right tiering saves 80%+ without losing access.
Learning Path
flowchart LR
A[Right-Sizing] --> B[Storage Optimization<br/>You are here]
B --> C[Data Transfer]
C --> D[Archival Strategy]
style B fill:#f90,color:#fff
AWS S3 Storage Classes
| Class | Durability | Min Duration | Retrieval | Cost/GB/month | Use Case |
|---|---|---|---|---|---|
| S3 Standard | 11 9s | None | Instant | $0.023 | Active data, frequent access |
| S3 Intelligent-Tiering | 11 9s | 30 days | Instant | $0.023 + monitoring | Unknown or changing access |
| S3 Standard-IA | 11 9s | 30 days | Instant | $0.0125 | Infrequent access |
| S3 One Zone-IA | 11 9s | 30 days | Instant | $0.01 | Non-critical, infrequent |
| S3 Glacier Instant | 11 9s | 90 days | Instant | $0.004 | Archival, instant access |
| S3 Glacier Flexible | 11 9s | 90 days | 1-5 min | $0.0036 | Archival, can wait |
| S3 Glacier Deep Archive | 11 9s | 180 days | 12 hours | $0.00099 | Compliance, long-term |
Lifecycle Policy
{
"Rules": [
{
"Id": "TierByAge",
"Status": "Enabled",
"Filter": {"Prefix": "logs/"},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_INSTANT_RETRIEVAL"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 2555 // 7 years
}
}
]
}Apply via AWS CLI
aws s3api put-bucket-lifecycle-configuration \
--bucket my-company-logs \
--lifecycle-configuration file://lifecycle.jsonIntelligent-Tiering
For buckets with unpredictable access patterns:
# Create S3 Intelligent-Tiering archive configuration
aws s3api put-bucket-intelligent-tiering-configuration \
--bucket my-data \
--id AutoTier \
--intelligent-tiering-configuration '{
"Id": "AutoTier",
"Status": "Enabled",
"Tierings": [
{"Days": 90, "AccessTier": "ARCHIVE_ACCESS"},
{"Days": 365, "AccessTier": "DEEP_ARCHIVE_ACCESS"}
]
}'Azure Blob Storage Tiers
| Tier | Min Duration | Access Cost | Storage Cost | Use Case |
|---|---|---|---|---|
| Hot | None | Low | High | Active, frequently accessed |
| Cool | 30 days | Medium | Medium | Infrequent (< once/month) |
| Cold | 90 days | High | Low | Rarely accessed |
| Archive | 180 days | Very high | Very low | Compliance, 2+ years |
Lifecycle Management Policy
{
"properties": {
"policy": {
"rules": [
{
"name": "TierToCool",
"enabled": true,
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["backups/"]
},
"actions": {
"baseBlob": {
"tierToCool": {"daysAfterModificationGreaterThan": 30},
"tierToArchive": {"daysAfterModificationGreaterThan": 180},
"delete": {"daysAfterModificationGreaterThan": 2555}
}
}
}
}
]
}
}
}# Apply lifecycle policy
az storage account management-policy create \
--account-name mystorageaccount \
--policy @policy.json \
--resource-group my-rgGCP Storage Classes
| Class | Min Duration | Retrieval | Cost/GB/month | Use Case |
|---|---|---|---|---|
| Standard | None | Instant | $0.020 | Active data |
| Nearline | 30 days | Instant | $0.010 | < once/month |
| Coldline | 90 days | Instant | $0.004 | < once/quarter |
| Archive | 365 days | 12+ hours | $0.0012 | < once/year |
Object Lifecycle Management
# Create lifecycle rule
gcp storage buckets update gs://my-bucket \
--lifecycle-file=lifecycle.json
# lifecycle.json
{
"lifecycle": {
"rule": [
{
"action": {"type": "SetStorageClass", "storageClass": "NEARLINE"},
"condition": {"age": 30}
},
{
"action": {"type": "SetStorageClass", "storageClass": "COLDLINE"},
"condition": {"age": 90}
},
{
"action": {"type": "SetStorageClass", "storageClass": "ARCHIVE"},
"condition": {"age": 365}
},
{
"action": {"type": "Delete"},
"condition": {"age": 2555}
}
]
}
}Cross-Cloud Storage Cost Comparison
| Scenario | AWS | Azure | GCP |
|---|---|---|---|
| 1TB active, 1 month | $23.00 | $20.80 | $20.00 |
| 10TB cool, 1 month | $125.00 | $104.00 | $100.00 |
| 100TB archive, 1 year | $1,188.00 | $840.00 | $1,440.00 |
| 1M PUT requests | $5.00 | $4.68 | $0.50 |
| 10M GET requests | $4.00 | $0.91 | $0.40 |
Archival Strategies
Automated Archival with Lambda (AWS S3 → Glacier)
import boto3
import json
s3 = boto3.client('s3')
def lambda_handler(event, context):
"""Auto-archive objects older than 90 days"""
bucket = event['bucket']
prefix = event.get('prefix', '')
paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket=bucket, Prefix=prefix)
archived = 0
for page in pages:
for obj in page.get('Contents', []):
# Check if object is older than 90 days
age = (datetime.now(obj['LastModified'].tzinfo)
- obj['LastModified']).days
if age > 90 and obj['StorageClass'] not in ['GLACIER', 'DEEP_ARCHIVE']:
s3.copy_object(
Bucket=bucket,
Key=obj['Key'],
CopySource={'Bucket': bucket, 'Key': obj['Key']},
StorageClass='DEEP_ARCHIVE'
)
archived += 1
return {
'bucket': bucket,
'archived_objects': archived,
'timestamp': datetime.now().isoformat()
}Scheduled Archival Script
#!/bin/bash
# archive_old_data.sh — Archive files older than N days to Glacier
BUCKET="my-company-data"
DAYS_OLD=90
echo "Archiving objects in s3://$BUCKET older than $DAYS_OLD days..."
aws s3api list-objects-v2 --bucket "$BUCKET" --query 'Contents[?!IsTruncated]' \
--output json | jq -c '.[]' | while read obj; do
key=$(echo "$obj" | jq -r '.Key')
last_modified=$(echo "$obj" | jq -r '.LastModified')
storage_class=$(echo "$obj" | jq -r '.StorageClass')
# Calculate age in days
age=$(( ($(date +%s) - $(date -d "$last_modified" +%s)) / 86400 ))
if [ "$age" -gt "$DAYS_OLD" ] && \
[ "$storage_class" != "GLACIER" ] && \
[ "$storage_class" != "DEEP_ARCHIVE" ]; then
echo "Archiving: $key (age: ${age}d, class: ${storage_class})"
aws s3 cp "s3://$BUCKET/$key" "s3://$BUCKET/$key" \
--storage-class DEEP_ARCHIVE
fi
doneCommon Storage Cost Mistakes
1. Not Configuring Lifecycle Policies
Objects stay in Standard tier forever. Always set lifecycle policies when creating a bucket. Start with 30-day to IA, 90-day to Glacier, 365-day to Deep Archive.
2. Ignoring Request Costs
S3 charges per PUT ($0.005/1k) and GET ($0.0004/1k). High-throughput workloads (logs, IoT) can have request costs exceeding storage costs. Use S3 Batch Operations or multipart uploads to minimize requests.
3. Using Standard Tier for Backups
Backups are rarely accessed. Store backups in S3 Glacier or Azure Archive from day one. The 90-day minimum cost is outweighed by 90%+ savings compared to Standard.
4. Not Cleaning Up Old Objects
Orphaned objects — old versions, incomplete multipart uploads, expired data — accumulate silently. Enable S3 versioning lifecycle to delete old versions after N days. List and abort incomplete multipart uploads monthly.
5. Overlooking Storage in Snapshots
EBS snapshots and AMIs are stored in S3 but not visible in bucket listings. Use Amazon Data Lifecycle Manager (DLM) to automate snapshot cleanup. Delete AMIs older than N days.
6. Cross-Region Replication Costs
Cross-region replication (CRR) doubles storage costs and adds transfer costs. Only replicate critical data. Use same-region replication (SRR) for compliance within a region.
7. Not Compressing Before Uploading
Compression before upload reduces storage by 3-10x for text data (logs, JSON, CSV). Enable S3 server-side compression or compress client-side before uploading.
Practice Questions
1. What’s the cost difference between S3 Standard and Glacier Deep Archive? Standard = $0.023/GB/month. Deep Archive = $0.00099/GB/month. A 1PB dataset costs $23,000/month in Standard vs $990/month in Deep Archive — a 95% reduction.
2. When should you use S3 Intelligent-Tiering? When access patterns are unknown or change over time. It starts objects in Standard, moves them to IA after 30 days of no access, and charges a monitoring fee ($0.0025/1k objects). Best for data with unpredictable access.
3. What is the 30-day minimum in S3 Standard-IA? Objects stored in Standard-IA for less than 30 days are charged for 30 days. This prevents frequent tiering changes. Lifecycle policies must account for this minimum charge.
4. How do Azure Blob tiers compare to AWS S3 classes? Azure Hot ≈ S3 Standard, Azure Cool ≈ S3 Standard-IA, Azure Archive ≈ S3 Glacier. Azure has a unique Cold tier between Cool and Archive. GCP Nearline ≈ S3 Standard-IA.
5. Challenge: Your application generates 500GB of logs daily. Logs are accessed frequently for 7 days, occasionally for 30 days, and rarely thereafter. Compliance requires 7-year retention. Design a cost-optimized storage strategy. Answer: (1) Write logs to S3 Standard for first 7 days. (2) Lifecycle to S3 Standard-IA after 7 days. (3) Lifecycle to S3 Glacier after 30 days. (4) Lifecycle to S3 Deep Archive after 365 days. (5) Delete after 2555 days (7 years). Estimated savings: 85% vs keeping everything in Standard.
Mini Project: Storage Cost Dashboard
Create a script that analyzes storage costs across buckets:
#!/bin/bash
# storage_cost_report.sh — Analyze S3 storage costs
echo "=== S3 Storage Cost Report ==="
echo "Generated: $(date)"
echo ""
# List all buckets
for bucket in $(aws s3 ls | awk '{print $3}'); do
echo "--- Bucket: $bucket ---"
# Get object count and total size
total_objects=$(aws s3api list-objects-v2 --bucket "$bucket" \
--query 'KeyCount' --output text 2>/dev/null || echo "0")
total_size=$(aws s3api list-objects-v2 --bucket "$bucket" \
--query 'sum(Contents[].Size)' --output text 2>/dev/null || echo "0")
size_gb=$(echo "scale=2; $total_size / 1073741824" | bc)
echo "Objects: ${total_objects:-0}"
echo "Total size: ${size_gb:-0} GB"
# Storage class breakdown
echo "Storage class breakdown:"
aws s3api list-objects-v2 --bucket "$bucket" \
--query 'Contents[].StorageClass' --output text 2>/dev/null | \
tr '\t' '\n' | sort | uniq -c | sort -rn | while read count class; do
rate=0.023
case "$class" in
"STANDARD") rate=0.023 ;;
"INTELLIGENT_TIERING") rate=0.023 ;;
"STANDARD_IA") rate=0.0125 ;;
"ONEZONE_IA") rate=0.01 ;;
"GLACIER") rate=0.0036 ;;
"DEEP_ARCHIVE") rate=0.00099 ;;
esac
estimated_cost=$(echo "scale=2; $size_gb * $rate" | bc)
printf " %-20s %d objects ~$%.2f/month\n" "$class" "$count" "$estimated_cost"
done
echo ""
doneFAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro