Chapter 17: Object/Blob Storage

Object vs File vs Block Storage

Property Block Storage File Storage Object Storage
Access Method Block-level (LUN) File path/hierarchy HTTP REST API (key)
Data Unit Fixed blocks (512B-64KB) Files in directories Objects with metadata
Metadata None Basic (name, date, size) Rich (unlimited key-value)
Performance Lowest latency (sub-ms) Low latency (1-10ms) Higher latency (10-100ms)
Scalability Limited (TB) Medium (PB with NAS) Unlimited (EB)
Cost High ($0.10-0.20/GB/mo) Medium ($0.08-0.15/GB/mo) Low ($0.023-0.10/GB/mo)
Mutability Random R/W anywhere Random R/W Immutable (write-once)
Durability Depends on RAID Replication (99.9%) 11 nines (99.999999999%)
Use Cases Databases, VMs, boot volumes Shared filesystems, home dirs Backups, media, data lakes
Examples EBS, SAN, iSCSI EFS, NFS, SMB S3, Azure Blob, GCS
Consistency Strong Strong Strong (S3 since Dec 2020)
Interview: "Why use object storage over file storage?" → Unlimited scalability, durability (11 9's), metadata richness, HTTP access (no VPC), lifecycle automation, far cheaper at scale. Trade-off: higher latency, immutable objects.

S3 Architecture

Components

graph TB Client[Client] Router[Request Router
Rate Limiting] Meta[Metadata Store
Distributed KV] Auth[IAM/Auth] Node1[Storage Node 1
Data Shards] Node2[Storage Node 2
Parity Shards] Node3[Storage Node 3
Data Shards] Repl[Cross-Region Replication] Client -->|PUT/GET| Router Router --> Auth Router --> Meta Meta -->|Lookup Location| Router Router --> Node1 Router --> Node2 Router --> Node3 Node1 -.->|Erasure Coding| Node2 Node2 -.->|Erasure Coding| Node3 Node1 -.->|Optional| Repl style Meta fill:#e1f5ff style Router fill:#fff4e1 style Node1 fill:#e8f5e9 style Node2 fill:#e8f5e9 style Node3 fill:#e8f5e9

Strong Consistency Model

Since December 2020, S3 provides read-after-write and list consistency for all operations:

Operation Pre-2020 Post-2020
PUT then GET Eventual consistency Strong consistency
Overwrite PUT Eventual consistency Strong consistency
DELETE then GET May return old object 404 immediately
LIST after PUT May not appear Appears immediately
Interview: "Is S3 strongly consistent?" → YES (since Dec 2020). All reads reflect latest write. No eventual consistency lag. Applies to all operations, all regions, no performance cost.

Durability (11 Nines)

Erasure Coding (Reed-Solomon)

S3 uses erasure coding to achieve 99.999999999% durability (lose 1 object per 100 billion per year).

graph LR Obj[Object 12MB] D1[Data 1
1MB] D2[Data 2
1MB] D3[Data 3
1MB] D8[Data 8
1MB] P1[Parity 1
1MB] P4[Parity 4
1MB] Obj --> D1 Obj --> D2 Obj --> D3 Obj --> D8 Obj --> P1 Obj --> P4 D1 -.->|XOR + Matrix| P1 D2 -.->|XOR + Matrix| P1 D8 -.->|XOR + Matrix| P4 style Obj fill:#e1f5ff style D1 fill:#e8f5e9 style D2 fill:#e8f5e9 style D3 fill:#e8f5e9 style D8 fill:#e8f5e9 style P1 fill:#fff4e1 style P4 fill:#fff4e1

Replication vs Erasure Coding

Property 3x Replication Erasure Coding (8+4)
Storage Overhead 3x (200% overhead) 1.5x (50% overhead)
Fault Tolerance Lose 2 of 3 copies Lose 4 of 12 shards
Read Performance Fast (read 1 copy) Fast (read 8 shards)
Write Performance Slower (write 3 copies) Faster (write 12 shards parallel)
Rebuild Cost High (copy full object) Lower (reconstruct from 8 shards)
Durability 99.9999% (6 nines) 99.999999999% (11 nines)
Interview: "How does S3 achieve 11 9's?" → Erasure coding (Reed-Solomon). Split object into 8 data + 4 parity shards across AZs. Can lose 4 shards and reconstruct. 50% overhead vs 200% for 3x replication. Background scrubbing detects bit rot.

Storage Tiers

Tier Use Case Durability Availability AZs Min Duration Retrieval Time Storage Cost Retrieval Cost
Standard Frequent access 11 9's 99.99% ≥3 None ms $0.023/GB Free
Intelligent-Tiering Unknown/changing 11 9's 99.9% ≥3 None ms $0.023/GB + monitor Free
Standard-IA Infrequent access 11 9's 99.9% ≥3 30 days ms $0.0125/GB $0.01/GB
One Zone-IA Reproducible data 11 9's 99.5% 1 30 days ms $0.01/GB $0.01/GB
Glacier Instant Archive, instant 11 9's 99.9% ≥3 90 days ms $0.004/GB $0.03/GB
Glacier Flexible Archive, rare access 11 9's 99.99% ≥3 90 days 1-5 min (Expedited)
3-5 hrs (Standard)
5-12 hrs (Bulk)
$0.0036/GB $0.03/GB (Exp)
$0.01/GB (Std)
$0.0025/GB (Bulk)
Glacier Deep Archive Long-term, compliance 11 9's 99.99% ≥3 180 days 12 hrs (Standard)
48 hrs (Bulk)
$0.00099/GB $0.02/GB (Std)
$0.0025/GB (Bulk)

Cost Example (1TB for 1 year)

Tier Storage Cost Retrieval (10% monthly) Total
Standard $276 $0 $276
Standard-IA $150 $123 $273
Glacier Instant $48 $369 $417
Glacier Flexible $43 $123 $166
Glacier Deep Archive $12 $246 $258

Lifecycle Policies

Automatically transition objects between tiers based on age:

graph LR S[Standard] IA[Standard-IA] GI[Glacier Instant] GF[Glacier Flexible] GD[Deep Archive] Del[Delete] S -->|30 days| IA IA -->|60 days| GI GI -->|90 days| GF GF -->|365 days| GD GD -->|7 years| Del style S fill:#e8f5e9 style IA fill:#fff4e1 style GI fill:#e1f5ff style GF fill:#f3e5f5 style GD fill:#fce4ec style Del fill:#ffebee
{
  "Rules": [{
    "Id": "Archive-old-data",
    "Status": "Enabled",
    "Transitions": [
      { "Days": 30, "StorageClass": "STANDARD_IA" },
      { "Days": 90, "StorageClass": "GLACIER_IR" },
      { "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
    ],
    "Expiration": { "Days": 2555 }
  }]
}
Interview: "Design a cost-optimized backup system" → Use lifecycle policies: Standard (0-30d active), IA (30-90d), Glacier Flexible (90-365d), Deep Archive (1-7y), then delete. Add Intelligent-Tiering for unpredictable access. Monitor with S3 Analytics.

Performance

Request Rate Limits

Pattern Max Throughput Notes
bucket/file-{1..10000} 3.5K PUT/s, 5.5K GET/s Single prefix
bucket/{userID}/file 3.5K PUT/s × N users N prefixes (scales)
bucket/{hash[0:2]}/file 3.5K PUT/s × 256 256 prefixes (880K PUT/s)

Multipart Upload

Required for objects > 5GB, recommended for > 100MB.

sequenceDiagram participant C as Client participant S3 as S3 C->>S3: CreateMultipartUpload S3-->>C: UploadID par Part 1 C->>S3: UploadPart (Part 1, UploadID) S3-->>C: ETag1 and Part 2 C->>S3: UploadPart (Part 2, UploadID) S3-->>C: ETag2 and Part N C->>S3: UploadPart (Part N, UploadID) S3-->>C: ETagN end C->>S3: CompleteMultipartUpload (UploadID, ETags) S3-->>C: 200 OK
# AWS CLI multipart upload
aws s3api create-multipart-upload --bucket my-bucket --key large-file.zip

# Upload parts in parallel
aws s3api upload-part --bucket my-bucket --key large-file.zip \
  --part-number 1 --body part1.dat --upload-id XYZ

# Complete
aws s3api complete-multipart-upload --bucket my-bucket --key large-file.zip \
  --upload-id XYZ --multipart-upload '{"Parts":[{"ETag":"abc","PartNumber":1}]}'

Byte-Range Fetches

Download specific byte ranges in parallel (improves large file downloads).

# Download first 1MB
curl -H "Range: bytes=0-1048575" https://bucket.s3.amazonaws.com/file

# Parallel download (4 threads)
bytes_per_chunk=$((file_size / 4))
for i in {0..3}; do
  start=$((i * bytes_per_chunk))
  end=$(((i+1) * bytes_per_chunk - 1))
  curl -H "Range: bytes=$start-$end" ... > part$i &
done
cat part* > complete_file

Transfer Acceleration

Use CloudFront edge locations to accelerate uploads/downloads (50-500% faster for distant regions).

Interview: "How to upload 1TB file to S3 from Asia?" → Use Transfer Acceleration (edge upload), multipart upload (100MB parts = 10K parts), parallel upload (10+ threads), retry logic per part. Expected: 100-200 Mbps = 11-22 hrs.

Presigned URLs

Generate temporary URLs that grant time-limited access to private S3 objects without AWS credentials.

How It Works

sequenceDiagram participant U as User participant App as Backend participant S3 as S3 U->>App: Request file access App->>App: Generate presigned URL
(signs with IAM creds) App-->>U: Presigned URL (expires 1hr) U->>S3: GET with presigned URL S3->>S3: Validate signature & expiry S3-->>U: File content (200 OK)

Generation Example

# Python (boto3)
import boto3
from botocore.client import Config

s3 = boto3.client('s3', config=Config(signature_version='s3v4'))
url = s3.generate_presigned_url(
    'get_object',
    Params={'Bucket': 'my-bucket', 'Key': 'private/file.pdf'},
    ExpiresIn=3600  # 1 hour
)

# Upload example
upload_url = s3.generate_presigned_post(
    'my-bucket', 'uploads/${filename}',
    ExpiresIn=3600
)

Security Considerations

Risk Mitigation
URL leaked/shared Short expiry (≤1 hour), single-use tokens
Credential compromise Use IAM role (not permanent keys), rotate regularly
Bucket policy bypass Presigned URL inherits signer permissions (IAM > bucket policy)
Upload abuse Set content-type, size limits in presigned POST

Direct Browser Upload

<form action="https://bucket.s3.amazonaws.com" method="post" enctype="multipart/form-data">
  <input type="hidden" name="key" value="uploads/${filename}">
  <input type="hidden" name="AWSAccessKeyId" value="AKIAIOSFODNN7EXAMPLE">
  <input type="hidden" name="policy" value="eyJleHBpcmF0aW9uIjoi...">
  <input type="hidden" name="signature" value="s8Qg9...">
  <input type="file" name="file">
  <input type="submit" value="Upload">
</form>
Interview: "Design file upload for mobile app" → Backend generates presigned POST URL (1hr expiry) with size/type constraints. App uploads directly to S3 (no backend proxy = saves bandwidth). S3 event triggers Lambda to process. Store metadata in DB.

Event Notifications

Trigger workflows when objects are created, deleted, or restored.

Event Types

Destinations

Destination Use Case Delivery Notes
Lambda Immediate processing Async invoke 15min timeout, auto-retry
SQS Buffered processing At-least-once Decouples, handles spikes
SNS Fan-out to multiple At-least-once Multiple subscribers
EventBridge Complex routing At-least-once Filtering, cross-account

Event-Driven Patterns

graph LR S3[S3 Bucket] Lambda[Lambda] SQS[SQS Queue] SNS[SNS Topic] DDB[DynamoDB] ES[Elasticsearch] S3 -->|ObjectCreated| Lambda S3 -->|ObjectCreated| SQS S3 -->|ObjectCreated| SNS Lambda --> DDB Lambda --> ES SQS --> Lambda SNS --> Lambda SNS -->|Email| User[User] style S3 fill:#e8f5e9 style Lambda fill:#fff4e1 style SQS fill:#e1f5ff style SNS fill:#f3e5f5

Configuration Example

{
  "LambdaFunctionConfigurations": [{
    "Id": "process-images",
    "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123:function:ProcessImage",
    "Events": ["s3:ObjectCreated:*"],
    "Filter": {
      "Key": {
        "FilterRules": [
          {"Name": "prefix", "Value": "uploads/"},
          {"Name": "suffix", "Value": ".jpg"}
        ]
      }
    }
  }]
}

Common Use Cases

Interview: "Design image upload service (10K uploads/min)" → Upload to S3 → S3 event → SQS (buffer spikes) → Lambda (batch=10) → resize → save to S3. Use SQS for buffering (Lambda 1000 concurrent limit). Set DLQ for failures. CloudWatch alarms on queue depth.

Versioning

Keep multiple variants of an object in the same bucket (protect from accidental deletion/overwrite).

States

Version IDs

graph TD V1[file.txt
Version: v1
Size: 10KB] V2[file.txt
Version: v2
Size: 12KB] V3[file.txt
Version: v3
Size: 15KB] DM[file.txt
Delete Marker
Version: v4] V1 -->|PUT overwrites| V2 V2 -->|PUT overwrites| V3 V3 -->|DELETE| DM style V1 fill:#e8f5e9 style V2 fill:#e8f5e9 style V3 fill:#e8f5e9 style DM fill:#ffebee

Delete Markers

Operation Versioning Off Versioning On
PUT object Overwrites, no version ID Creates new version ID
GET object Returns current Returns latest version
GET object?versionId=X N/A Returns specific version
DELETE object Permanently deletes Creates delete marker
DELETE object?versionId=X N/A Permanently deletes version
Storage cost 1x object size Sum of all versions

Cross-Region Replication (CRR)

Automatically replicate objects to another region (requires versioning enabled on both buckets).

{
  "Role": "arn:aws:iam::123:role/s3-replication",
  "Rules": [{
    "Status": "Enabled",
    "Priority": 1,
    "Filter": {"Prefix": "documents/"},
    "Destination": {
      "Bucket": "arn:aws:s3:::backup-bucket",
      "ReplicationTime": {"Status": "Enabled", "Time": {"Minutes": 15}},
      "Metrics": {"Status": "Enabled", "EventThreshold": {"Minutes": 15}}
    }
  }]
}
Interview: "Protect critical data from accidental deletion" → Enable versioning + lifecycle policy (delete noncurrent versions after 90 days) + CRR to different region + MFA Delete (require MFA to delete versions). Costs: storage × versions, but bounded by lifecycle.

CDN Integration (CloudFront + S3)

Cache S3 objects at edge locations for low-latency global delivery.

Architecture

graph TB User1[User US] User2[User EU] User3[User Asia] CF[CloudFront CDN
Edge Locations] OAI[Origin Access Identity
IAM Role] S3[S3 Bucket
us-east-1] User1 --> CF User2 --> CF User3 --> CF CF -->|Cache Miss| OAI OAI --> S3 CF -.->|Cache Hit
Latency: 10-50ms| User1 S3 -.->|Direct Access Blocked| User1 style CF fill:#fff4e1 style S3 fill:#e8f5e9 style OAI fill:#e1f5ff

Origin Access Control (OAC)

Restrict S3 access to only CloudFront (prevents direct S3 access).

# S3 bucket policy (only allow CloudFront)
{
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "cloudfront.amazonaws.com"},
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-bucket/*",
    "Condition": {
      "StringEquals": {
        "AWS:SourceArn": "arn:aws:cloudfront::123:distribution/EDFDVBD6"
      }
    }
  }]
}

Performance Gains

Metric Direct S3 CloudFront + S3
Latency (US → S3 us-east-1) 10-50ms 10-30ms (edge cache)
Latency (EU → S3 us-east-1) 100-150ms 10-50ms (edge cache)
Latency (Asia → S3 us-east-1) 200-300ms 20-80ms (edge cache)
Data Transfer Out (S3) $0.09/GB $0.02/GB (CF to S3 free)
Cache Hit Rate (static) 0% 80-95%

Caching Strategy

# S3 object metadata (set Cache-Control)
aws s3 cp file.jpg s3://bucket/file.jpg \
  --metadata-directive REPLACE \
  --cache-control "max-age=31536000, immutable"

# CloudFront invalidation (bust cache)
aws cloudfront create-invalidation \
  --distribution-id EDFDVBD6 \
  --paths "/images/*"
Interview: "Serve 10TB video library globally with low latency" → S3 Standard (origin) + CloudFront (cache 80% hit rate) + versioned URLs (cache forever) + HLS streaming (byte-range) + OAC (block direct S3). Cost: S3 storage $230/mo + CF transfer $500-1000/mo (vs $900/mo direct S3).

Alternatives to S3

Service Provider Durability Consistency Storage Cost Egress Cost Features Notes
S3 AWS 11 9's Strong $0.023/GB $0.09/GB Lifecycle, versioning, events, CDN Industry standard, most mature
Azure Blob Azure 11 9's (LRS)
16 9's (GRS)
Strong $0.018/GB (Hot)
$0.01/GB (Cool)
$0.087/GB Tiers, lifecycle, immutable, CDN Hot/Cool/Archive tiers, cheaper storage
Google Cloud Storage GCP 11 9's (regional)
16 9's (multi-region)
Strong $0.02/GB (Standard)
$0.01/GB (Nearline)
$0.12/GB (worldwide) Lifecycle, versioning, Pub/Sub events Higher egress cost
Cloudflare R2 Cloudflare 11 9's Strong $0.015/GB $0 (free egress) S3-compatible API, CDN integration Zero egress cost, no storage tiers
Backblaze B2 Backblaze 11 9's Strong $0.005/GB $0.01/GB (3x free of storage) S3-compatible, lifecycle Cheapest storage, good for backups
Wasabi Wasabi 11 9's Strong $0.0059/GB (1TB min) $0 (free egress) S3-compatible, immutable No tiers, no API charges, 90d min
MinIO Self-hosted Depends on setup Strong HW cost Free S3-compatible, K8s native, erasure coding On-prem/private cloud, OSS
Ceph Self-hosted Configurable Strong HW cost Free Block/file/object, RADOS, replication Complex, high-scale on-prem

When to Use Each

Interview: "Why choose Cloudflare R2 over S3?" → Zero egress cost (S3 charges $0.09/GB). Best for high-traffic public assets (images, videos, downloads). Trade-off: fewer features (no Glacier, no advanced analytics), smaller ecosystem. Use S3-compatible API for easy migration.

Design Patterns

1. Data Lake Architecture

graph TB Sources[Data Sources
Logs, DBs, APIs] S3Raw[S3: Raw Zone
Unprocessed data] Glue[AWS Glue
ETL Jobs] S3Processed[S3: Processed Zone
Parquet, partitioned] Athena[Athena
SQL queries] Redshift[Redshift Spectrum
Analytics] QuickSight[QuickSight
Dashboards] Sources --> S3Raw S3Raw -->|Event trigger| Glue Glue --> S3Processed S3Processed --> Athena S3Processed --> Redshift Athena --> QuickSight Redshift --> QuickSight style S3Raw fill:#e8f5e9 style S3Processed fill:#e8f5e9 style Glue fill:#fff4e1

Key Patterns:

2. Media Processing Pipeline

graph LR Upload[User Upload] S3Raw[S3: Raw
raw/video.mp4] Event[S3 Event] SQS[SQS Queue] Lambda[Lambda
Transcode Job] MediaConvert[MediaConvert
HLS, MP4, Thumb] S3Out[S3: Processed
hls/, mp4/, thumb/] CF[CloudFront CDN] User[User Playback] Upload --> S3Raw S3Raw --> Event Event --> SQS SQS --> Lambda Lambda --> MediaConvert MediaConvert --> S3Out S3Out --> CF CF --> User style S3Raw fill:#e8f5e9 style S3Out fill:#e8f5e9 style SQS fill:#e1f5ff style CF fill:#fff4e1

Key Patterns:

3. Backup and Disaster Recovery

graph TB App[Application
Primary Region] S3Primary[S3: Primary
us-east-1] CRR[Cross-Region
Replication] S3DR[S3: DR Backup
us-west-2] Glacier[Glacier Deep Archive
7-year retention] Lifecycle[Lifecycle Policy] App -->|Backup| S3Primary S3Primary --> CRR CRR --> S3DR S3Primary --> Lifecycle Lifecycle -->|90d| Glacier S3DR -.->|Disaster| App style S3Primary fill:#e8f5e9 style S3DR fill:#fff4e1 style Glacier fill:#e1f5ff

Key Patterns:

Common Anti-Patterns

Anti-Pattern Problem Solution
Single prefix hotspot Hit 3.5K/5.5K req/s limit Hash prefix (e.g., {hash[0:2]}/ = 256x scale)
Large objects without multipart Upload fails → restart from 0 Use multipart (>100MB), parallel parts, resume
Listing all objects repeatedly Expensive, slow (1000 keys/request) Use S3 Inventory (daily snapshots), event-driven
Versioning without lifecycle Storage cost explodes (N versions) Delete noncurrent versions after 90d
Direct S3 access from browser Expose bucket, high egress cost CloudFront + OAC (cache, cheaper egress)
Using S3 as database No indexes, no transactions, slow Use DynamoDB/RDS, store large blobs in S3
Public bucket with secrets Data breach Block public access, use presigned URLs
Interview: "Design a scalable backup system for 10 PB database" → Incremental backups to S3 (multipart, parallel upload). Partition by date (db/2024/05/12/shard-01.bak). Lifecycle: Standard (7d) → IA (30d) → Glacier Flexible (90d) → Deep Archive (1y). Enable versioning (protect from operator error). CRR to DR region. Test quarterly restore.

Key Takeaways

Production Deep Dive

S3 Durability Calculation: Reed-Solomon Erasure Coding

S3 achieves 11 nines durability (99.999999999%) using Reed-Solomon (8,4) erasure coding:

Component Value Explanation
Configuration 8 data shards + 4 parity shards Object split into 12 total shards
Fault Tolerance Can lose any 4 shards Need any 8 of 12 shards to reconstruct
Shard Failure Rate p = 0.01/year Each shard fails independently (1% annual failure)
Object Loss Probability C(12,5) × 0.01^5 × 0.99^7 Lose >4 shards: 792 × 10^-10 × 0.932 ≈ 2.6 × 10^-10
Durability 1 - 2.6×10^-10 = 0.99999999974 11 nines durability (lose 1 object per 100B stored per year)
Interview Math: "How does S3 calculate 11 9's durability?" Walk through the combinatorics: With (8,4) erasure coding, object is lost only if >4 of 12 shards fail. C(12,5) = 792 ways to lose 5 shards. Probability: 792 × (0.01)^5 × (0.99)^7 ≈ 2.6×10^-10 failure rate per object per year.

Multipart Upload Logic (Pseudocode)

Production-grade multipart upload with retry and idempotency:

# Step 1: Initiate multipart upload
response = s3.initiate_multipart_upload(bucket, key)
upload_id = response.upload_id

# Step 2: Upload parts in parallel (100MB chunks)
part_size = 100 * 1024 * 1024  # 100MB
total_parts = ceil(file_size / part_size)
etag_map = {}  # {part_number: ETag}

def upload_part_with_retry(part_number, data):
    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = s3.upload_part(
                bucket=bucket,
                key=key,
                upload_id=upload_id,
                part_number=part_number,
                body=data
            )
            return response.etag  # Idempotent: same part_number overwrites
        except NetworkError as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(2 ** attempt)  # Exponential backoff

# Parallel upload (4 threads)
with ThreadPoolExecutor(max_workers=4) as executor:
    futures = []
    for part_num in range(1, total_parts + 1):
        offset = (part_num - 1) * part_size
        data = file.read_chunk(offset, part_size)
        future = executor.submit(upload_part_with_retry, part_num, data)
        futures.append((part_num, future))

    for part_num, future in futures:
        etag_map[part_num] = future.result()

# Step 3: Complete multipart upload
parts = [{"PartNumber": num, "ETag": etag} for num, etag in sorted(etag_map.items())]
s3.complete_multipart_upload(
    bucket=bucket,
    key=key,
    upload_id=upload_id,
    multipart_upload={"Parts": parts}
)

# Error handling: If upload abandoned or fails
# - Retry individual parts only (idempotent)
# - Abort upload: s3.abort_multipart_upload(bucket, key, upload_id)
# - Or set lifecycle rule: auto-delete incomplete uploads after 7 days
Interview Question: "What happens if multipart upload is interrupted?" Answer: Each part is individually retryable (idempotent by part number). On complete failure, call AbortMultipartUpload or set lifecycle rule to auto-clean incomplete uploads after N days. Parts are billed until cleaned up.

S3 Request Rate Optimization: Prefix Partitioning

S3 request rate limits (3500 PUT, 5500 GET per prefix) can be scaled horizontally:

Pattern Max Rate Scalability
bucket/images/user123/photo.jpg 3500 PUT/s, 5500 GET/s Single prefix = bottleneck
bucket/a7f3/images/user123/photo.jpg 3500 × 16 PUT/s, 5500 × 16 GET/s 16 prefixes (56K PUT/s, 88K GET/s)
bucket/{hash[0:2]}/images/user123/photo.jpg 3500 × 256 PUT/s, 5500 × 256 GET/s 256 prefixes (896K PUT/s, 1.4M GET/s)

Hash Prefix Strategy:

import hashlib

def get_s3_key(user_id, filename):
    # Add 2-character hash prefix for even distribution
    hash_prefix = hashlib.md5(user_id.encode()).hexdigest()[:2]
    return f"{hash_prefix}/images/{user_id}/{filename}"

# Example: user_id=123 → hash=202cb962ac59 → prefix=20
# Distributes across 256 prefixes (00-ff) → 256x scale

Historical Context: Pre-2018, S3 had 100 TPS per prefix limit. July 2018 update increased to 3500 PUT + 5500 GET, making prefix partitioning less critical but still valuable at extreme scale.

Storage Tier Cost Comparison

Tier Storage ($/GB/mo) Retrieval ($/GB) Break-Even Access Frequency
S3 Standard $0.023 $0 (free) Accessed >1/month
S3 IA $0.0125 (46% savings) $0.01 Accessed <1/month (store 30d, retrieve 1x = $0.0225 vs $0.023)
Glacier Instant $0.004 (83% savings) $0.03 Accessed <1/quarter (store 90d, retrieve 1x = $0.0316 vs $0.023×3=$0.069)
Glacier Deep $0.00099 (96% savings) $0.02 Accessed <1/year (compliance, backups)

Cost Calculation Example (1TB archive, 1 retrieval/year):

Interview Question: "When would S3 IA cost more than Standard?" Answer: If accessed frequently. Break-even: Storage savings ($0.023 - $0.0125 = $0.0105/GB/mo) must exceed retrieval cost ($0.01/GB per access). If accessed >1x/month, IA costs more. Use Intelligent-Tiering for unpredictable access.