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) |
S3 Architecture
Components
- Namespace: Global bucket names, region-scoped data. Flat hierarchy (keys simulate folders).
- Metadata Store: Distributed KV store (key → location mapping). Strong consistency since Dec 2020.
- Data Plane: Storage nodes with erasure coding. 11 9's durability via Reed-Solomon.
- Request Router: Routes PUT/GET to correct storage nodes. Handles rate limiting.
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:
- PUT: Immediately visible to subsequent GET/LIST
- DELETE: Immediately reflected (object disappears)
- LIST: Returns current state (no eventual consistency lag)
- COPY: Source reflects all prior writes
| 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 |
Durability (11 Nines)
Erasure Coding (Reed-Solomon)
S3 uses erasure coding to achieve 99.999999999% durability (lose 1 object per 100 billion per year).
- Data Shards: Object split into N chunks
- Parity Shards: M additional chunks generated (error correction)
- Reconstruction: Lose up to M shards, still recoverable
- Typical Config: 8 data + 4 parity (can lose 4 of 12 shards)
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) |
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:
{
"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 }
}]
}
Performance
Request Rate Limits
- PUT/COPY/POST/DELETE: 3,500 requests/sec per prefix
- GET/HEAD: 5,500 requests/sec per prefix
- Prefix: Key path before last slash (e.g.,
bucket/images/2024/) - Scaling: Add prefixes to scale horizontally (10 prefixes = 55K GET/s)
| 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.
- Part Size: 5MB - 5GB (last part can be < 5MB)
- Max Parts: 10,000 per upload
- Max Object: 5TB
- Benefits: Parallel uploads, resume on failure, upload while creating
# 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).
- Endpoint:
bucket.s3-accelerate.amazonaws.com - Cost: $0.04/GB US-EU, $0.08/GB US-Asia
- Protocol: Upload to edge → AWS backbone → S3
Presigned URLs
Generate temporary URLs that grant time-limited access to private S3 objects without AWS credentials.
How It Works
(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>
Event Notifications
Trigger workflows when objects are created, deleted, or restored.
Event Types
- s3:ObjectCreated:* (Put, Post, Copy, CompleteMultipartUpload)
- s3:ObjectRemoved:* (Delete, DeleteMarkerCreated)
- s3:ObjectRestore:* (Post, Completed for Glacier)
- s3:Replication:* (OperationFailedReplication, etc.)
- s3:LifecycleTransition
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
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
- Image Processing: Upload → Lambda resize/watermark → save to processed/
- Data Ingestion: Upload CSV → Lambda parse → load to Redshift
- Video Transcoding: Upload video → SQS → EC2 workers → MediaConvert
- Audit Logging: Any change → SNS → CloudWatch Logs
- Data Lake: Upload → EventBridge → Glue Crawler → Athena
Versioning
Keep multiple variants of an object in the same bucket (protect from accidental deletion/overwrite).
States
- Unversioned (default): No version IDs, overwrite replaces object
- Enabled: Every PUT creates new version ID
- Suspended: Stop creating versions, existing versions remain
Version IDs
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
- DELETE without version ID: Creates delete marker (object appears deleted, but versions remain)
- DELETE with version ID: Permanently deletes that version
- GET after delete marker: Returns 404
- LIST versions: Shows all versions + 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).
- Use Cases: DR, compliance, low-latency access, aggregate logs
- Scope: All objects, prefix filter, or tag filter
- Replication Time: 15 min average (S3 RTC guarantees 99.99% in 15 min for extra cost)
- Delete Replication: Optional (delete markers can replicate)
{
"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}}
}
}]
}
CDN Integration (CloudFront + S3)
Cache S3 objects at edge locations for low-latency global delivery.
Architecture
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
- Default TTL: 24 hours (configure via Cache-Control headers)
- Cache Key: URL path + query string (customize to include headers/cookies)
- Invalidation: Manual (costs $0.005 per path after 1000 free/month) or versioned URLs
- Versioned URLs: Append version to filename (e.g.,
style.v123.css) → cache forever
# 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/*"
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
- S3: AWS ecosystem, full feature set, mature ecosystem
- Azure Blob: Azure ecosystem, lower storage cost (Hot/Cool tiers)
- GCS: GCP ecosystem, multi-region durability (16 9's)
- Cloudflare R2: High egress volume, CDN integration
- Backblaze B2: Large archives, infrequent access, cost-sensitive
- Wasabi: Active archives, no API/egress charges, predictable cost
- MinIO: On-prem, air-gapped, K8s native, compliance
- Ceph: Unified storage (block+file+object), large on-prem deployments
Design Patterns
1. Data Lake Architecture
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:
- Zone Architecture: Raw (immutable) → Processed (cleaned) → Curated (aggregated)
- Partitioning:
s3://bucket/year=2024/month=05/day=12/data.parquet - File Format: Parquet/ORC (columnar, compressed) for analytics
- Lifecycle: Raw (30d Standard → Glacier), Processed (90d Standard → IA)
- Catalog: Glue Data Catalog (schema registry, discoverable)
2. Media Processing Pipeline
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:
- Presigned Upload: Direct browser → S3 (save backend bandwidth)
- Event-Driven: S3 → SQS → Lambda (handles spikes, retries)
- Outputs: HLS (streaming), MP4 (download), thumbnails (preview)
- CDN: CloudFront caches outputs (reduce S3 egress cost)
- Storage Tiers: Raw (30d → Glacier), Processed (Standard, long TTL)
3. Backup and Disaster Recovery
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:
- Versioning: Protect from accidental delete/overwrite
- CRR: Async replication to DR region (15min average)
- Lifecycle: Primary (Standard 30d → Glacier 90d → Deep Archive 365d)
- Immutability: Object Lock (WORM mode, compliance)
- Testing: Quarterly DR drill (restore from S3DR)
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 |
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
- Storage Type: Object (S3) = immutable, scalable, cheap. File (EFS) = mutable, shared. Block (EBS) = low-latency, VM.
- Durability: 11 9's via erasure coding (8 data + 4 parity). 50% overhead vs 200% for 3x replication.
- Consistency: Strong since Dec 2020 (read-after-write, no eventual lag).
- Performance: 3.5K PUT/s, 5.5K GET/s per prefix. Scale with prefixes. Multipart upload (>100MB), byte-range fetch.
- Tiers: Standard ($0.023/GB) → IA ($0.0125/GB) → Glacier ($0.004/GB) → Deep Archive ($0.001/GB). Use lifecycle policies.
- Presigned URLs: Temporary access (no AWS creds). Direct browser upload (save bandwidth).
- Events: S3 → Lambda/SQS/SNS. Build event-driven pipelines (image processing, data ingestion).
- Versioning: Protect from delete/overwrite. CRR for DR. Lifecycle to manage cost.
- CDN: CloudFront + S3 = low latency (10-50ms), cheaper egress ($0.02/GB vs $0.09/GB).
- Alternatives: R2 (free egress), B2 (cheapest storage), MinIO (self-hosted).
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) |
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
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):
- S3 Standard: 1000GB × $0.023 × 12 = $276/year
- S3 IA: 1000GB × $0.0125 × 12 + 1000GB × $0.01 = $150 + $10 = $160/year (42% savings)
- Glacier Instant: 1000GB × $0.004 × 12 + 1000GB × $0.03 = $48 + $30 = $78/year (72% savings)
- Glacier Deep: 1000GB × $0.00099 × 12 + 1000GB × $0.02 = $12 + $20 = $32/year (88% savings)