블로그Cloud & Infrastructure
Cloud & Infrastructure

Cloud Storage Cost Guide: S3, EBS, EFS, and FSx — Choosing the Right Tier

Storage costs are invisible until they are not. S3 Intelligent-Tiering, EBS gp3 migration, and choosing EFS Standard vs IA can easily cut storage bills by 40-70%. This guide maps every AWS storage service to its cost profile, access pattern, and optimization strategies.

E

Emily Watson

Technical Writer and Developer Advocate who simplifies complex technology for everyday readers.

April 9, 2026
21 분 읽기

The Storage Cost Problem

Storage costs compound silently. Engineers provision volumes, load data into S3, and forget about it. Over months, EBS snapshots accumulate, S3 buckets fill with never-accessed objects, and EFS mounts hold gigabytes of stale build artifacts. Storage spend can easily reach 15-25% of total AWS bill for data-heavy workloads.

The good news: storage optimization has some of the best ROI of any cloud cost work. S3 Intelligent-Tiering can automatically move objects to 68-99% cheaper storage classes. EBS gp3 delivers the same or better performance as gp2 at 20% lower cost. These are mostly configuration changes, not architectural rewrites.

S3 Storage Classes: Full Cost Comparison

S3 Storage Class Pricing (us-east-1, per GB/month):

Storage Class           | Storage   | Retrieval  | Min Duration | Use Case
------------------------|-----------|------------|--------------|------------------
S3 Standard             | $0.023    | Free       | None         | Frequently accessed
S3 Intelligent-Tiering  | $0.023    | Free       | None         | Unknown patterns
  - Frequent tier       | $0.023    | Free       | -            | Auto-managed
  - Infrequent tier     | $0.0125   | Free       | 30 days      | Auto after 30d
  - Archive Instant     | $0.004    | Free       | 90 days      | Auto after 90d
S3 Standard-IA          | $0.0125   | $0.01/GB   | 30 days      | Monthly access
S3 One Zone-IA          | $0.01     | $0.01/GB   | 30 days      | Reproducible data
S3 Glacier Instant      | $0.004    | $0.03/GB   | 90 days      | Quarterly access
S3 Glacier Flexible     | $0.0036   | $0.01/GB   | 90 days      | Archival (hr delay)
S3 Glacier Deep Archive | $0.00099  | $0.02/GB   | 180 days     | Compliance archives

Key insight: Intelligent-Tiering monitoring fee is $0.0025/1K objects
For objects > 128KB, Intelligent-Tiering almost always pays off.
For objects < 128KB, the per-object fee makes it expensive — use lifecycle rules instead.

S3 Lifecycle Rules: Automate Tier Transitions

# Terraform: S3 lifecycle policy for logs bucket
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    id     = "log-lifecycle"
    status = "Enabled"

    filter {
      prefix = "logs/"  # Apply only to logs/ prefix
    }

    # Transition to IA after 30 days ($0.023 → $0.0125)
    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }

    # Transition to Glacier after 90 days ($0.0125 → $0.004)
    transition {
      days          = 90
      storage_class = "GLACIER"
    }

    # Transition to Deep Archive after 365 days ($0.004 → $0.00099)
    transition {
      days          = 365
      storage_class = "DEEP_ARCHIVE"
    }

    # Delete after 7 years (compliance)
    expiration {
      days = 2555  # 7 years
    }

    # Delete failed multipart uploads (common cost leak)
    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}

# For analytics/ML datasets: Intelligent-Tiering
resource "aws_s3_bucket_intelligent_tiering_configuration" "datasets" {
  bucket = aws_s3_bucket.ml_datasets.id
  name   = "auto-tiering"
  status = "Enabled"

  # Auto-move to Archive Instant after 90 days of no access
  tiering {
    access_tier = "ARCHIVE_ACCESS"
    days        = 90
  }

  # Auto-move to Deep Archive after 180 days
  tiering {
    access_tier = "DEEP_ARCHIVE_ACCESS"
    days        = 180
  }
}

EBS: Migrate gp2 to gp3

gp2 vs gp3 cost comparison:
  
  gp2: $0.10/GB/month, IOPS burst to 3,000 (capped)
  gp3: $0.08/GB/month, 3,000 IOPS baseline included
  
  For same IOPS performance: gp3 is 20% cheaper
  For high-IOPS needs: gp2 costs $0.065/IOPS above 3K, gp3 $0.005/IOPS
  
Example: 1TB database volume needing 10,000 IOPS
  gp2: $0.10 × 1024GB = $102.40/month (cannot get 10K IOPS)
       Must use io1/io2: $125 + $0.065 × 10,000 = $775/month
  gp3: $0.08 × 1024GB + $0.005 × 7,000 IOPS = $81.92 + $35 = $116.92/month
  
  Saving vs io1: $658/month (85%!)
  Saving vs gp2 same IOPS: impossible with gp2
# Find all gp2 volumes and calculate migration savings
aws ec2 describe-volumes   --filters "Name=volume-type,Values=gp2"   --query 'Volumes[*].{ID:VolumeId,Size:Size,IOPS:Iops,State:State,AZ:AvailabilityZone}'   --output table

# Migrate a single volume from gp2 to gp3 (online, no downtime)
aws ec2 modify-volume   --volume-id vol-0123456789abcdef0   --volume-type gp3   --iops 3000   --throughput 125

# Bulk migrate all gp2 volumes to gp3
aws ec2 describe-volumes   --filters "Name=volume-type,Values=gp2"   --query 'Volumes[*].VolumeId'   --output text | tr '	' '
' | while read vol_id; do
    echo "Migrating $vol_id to gp3..."
    aws ec2 modify-volume       --volume-id "$vol_id"       --volume-type gp3       --iops 3000       --throughput 125
done

EBS Snapshot Cleanup

import boto3
from datetime import datetime, timezone, timedelta

ec2 = boto3.client('ec2', region_name='us-east-1')

def find_orphaned_snapshots():
    """Find snapshots not attached to any AMI and older than 90 days."""
    
    # Get all snapshots owned by this account
    snapshots = ec2.describe_snapshots(OwnerIds=['self'])['Snapshots']
    
    # Get all AMI snapshot IDs
    images = ec2.describe_images(Owners=['self'])['Images']
    ami_snapshot_ids = set()
    for image in images:
        for bdm in image.get('BlockDeviceMappings', []):
            if 'Ebs' in bdm:
                ami_snapshot_ids.add(bdm['Ebs']['SnapshotId'])
    
    # Find orphaned old snapshots
    cutoff = datetime.now(timezone.utc) - timedelta(days=90)
    orphaned = []
    total_size_gb = 0
    
    for snap in snapshots:
        if (snap['SnapshotId'] not in ami_snapshot_ids and 
                snap['StartTime'] < cutoff):
            orphaned.append(snap)
            total_size_gb += snap['VolumeSize']
    
    monthly_cost = total_size_gb * 0.05  # EBS snapshot: $0.05/GB/month
    print(f"Orphaned snapshots: {len(orphaned)}")
    print(f"Total size: {total_size_gb:,} GB")
    print(f"Monthly waste: monthly_cost:,.2f")
    
    return orphaned

orphaned = find_orphaned_snapshots()
# Review list, then delete:
# for snap in orphaned:
#     ec2.delete_snapshot(SnapshotId=snap['SnapshotId'])

EFS: Standard vs Infrequent Access

EFS Storage Pricing (us-east-1):
  EFS Standard:     $0.30/GB/month (always hot)
  EFS Standard-IA:  $0.025/GB/month (retrieval: $0.01/GB)
  
  Comparison: IA is 88% cheaper for storage
  Break-even: if <42% of data is accessed monthly, IA is cheaper

EFS Intelligent Tiering (Lifecycle Management):
  Automatically moves files to IA after 7, 14, 30, 60, or 90 days of no access
  
Example: 5TB EFS mount, 20% frequently accessed, 80% stale
  All Standard:    5,120GB × $0.30 = $1,536/month
  With IA tiering: 1,024GB × $0.30 + 4,096GB × $0.025 = $307 + $102 = $409/month
  Saving: $1,127/month (73%)
# Terraform: EFS with Intelligent Tiering
resource "aws_efs_file_system" "shared" {
  creation_token   = "shared-storage"
  performance_mode = "generalPurpose"
  throughput_mode  = "elastic"  # Pay per GB transferred, not provisioned

  lifecycle_policy {
    transition_to_ia = "AFTER_30_DAYS"  # Move to IA after 30 days
  }

  lifecycle_policy {
    transition_to_primary_storage_class = "AFTER_1_ACCESS"  # Return on access
  }

  tags = {
    Name = "shared-storage"
  }
}

resource "aws_efs_mount_target" "az_a" {
  file_system_id  = aws_efs_file_system.shared.id
  subnet_id       = aws_subnet.private_a.id
  security_groups = [aws_security_group.efs.id]
}

Storage Type Decision Framework

Decision Tree: Which storage to use?

Is data block-level (OS volumes, databases)?
  YES → Use EBS
       High IOPS (>3,000)? → gp3 (up to 16,000 IOPS)
       Very high IOPS (>64,000)? → io2 Block Express
       Low cost, no IOPS requirements? → st1 (HDD, $0.045/GB)
       Archival EBS? → sc1 (cold HDD, $0.018/GB)

Is data shared across multiple EC2/ECS/EKS instances?
  YES → Use EFS (or FSx for Windows/high-perf)
       Linux workloads → EFS
       Windows workloads → FSx for Windows
       High-performance HPC → FSx for Lustre

Is data object-level (files, backups, ML datasets, media)?
  YES → Use S3
       Frequently accessed daily → S3 Standard
       Unknown access pattern → S3 Intelligent-Tiering
       Monthly/quarterly access → S3 Standard-IA
       Yearly access → S3 Glacier
       Compliance archive → S3 Glacier Deep Archive
       Reproducible data (thumbnails) → S3 One Zone-IA

Monthly cost per 1TB:
  EBS gp3:           $81.92
  EFS Standard:      $307
  EFS IA:            $25
  S3 Standard:       $23
  S3 IA:             $12.80
  S3 Glacier:        $4.10
  S3 Deep Archive:   $1.01

Conclusion

Storage optimization follows a simple pattern: analyze what you have, classify access patterns, and apply the appropriate tier. The migration from gp2 to gp3 alone is a no-downtime 20% savings. S3 Intelligent-Tiering and EFS lifecycle policies can reduce storage costs by 70%+ automatically, without manual intervention.

Audit your storage monthly. Delete orphaned EBS snapshots, enable S3 Intelligent-Tiering on large buckets, and move EFS mounts to lifecycle-managed tiers. Storage costs compound over time, but so do the savings from getting this right.

E

Emily Watson

Technical Writer and Developer Advocate who simplifies complex technology for everyday readers.

인프라를 혁신할 준비가 되셨습니까?

저희가 어떻게 귀사의 유사한 결과를 달성하도록 도울 수 있는지 논의해 봅시다.