AWS: Zero to Hero — Week 5: Storage & Databases — S3, EBS, RDS, and DynamoDB
Week 5 of learning AWS from zero, with Claude as my research partner. Storage and databases: S3's 8 storage classes, EBS, RDS, Aurora and the new Aurora DSQL, DynamoDB, and the real SQL-vs-NoSQL decision. Then a hands-on lab: uploading and tiering an S3 object with a presigned URL, a DynamoDB table, an attached EBS volume, and a real RDS instance created, confirmed, and deleted.
Series: AWS: Zero to Hero (9 articles)
- …
- 5AWS: Zero to Hero — Week 5: Storage & Databases — S3, EBS, RDS, and DynamoDB This article
Compute doesn't mean much without somewhere to put the data it's working on. This week is about the four services that come up in almost every AWS architecture diagram ever drawn — two storage services with very different jobs, and the SQL-vs-NoSQL decision that trips up more beginners than it probably should. And like last week, this isn't just definitions — everything below gets actually built, used, and torn down in the lab section.
The Deep Dive
S3: object storage, and the tier system underneath it
S3 (Simple Storage Service) stores objects — files, essentially, along with metadata — inside buckets, with no real limit on how much you can store. What actually makes S3 interesting isn't the storage itself, it's that AWS offers eight different storage classes for the exact same service, and picking the right one is entirely about one question: how often will this data actually get read?
- S3 Standard — the default. Frequently accessed data, millisecond access, spread across at least 3 Availability Zones.
- S3 Express One Zone — the newest and fastest class: single-digit millisecond access, up to 10x faster than Standard, built for latency-sensitive or extremely request-heavy workloads (think an AI training pipeline hammering the same bucket thousands of times a second). The tradeoff is right there in the name — single-AZ, so it doesn't survive an AZ-level disaster.
- S3 Intelligent-Tiering — for data where you genuinely don't know the access pattern in advance. It automatically shifts objects between frequent and infrequent tiers based on actual usage, for a small monitoring fee, removing the guesswork entirely.
- S3 Standard-IA / One Zone-IA — for data you access rarely but still need at millisecond notice, with One Zone-IA costing less by giving up multi-AZ durability (fine for anything reproducible, risky for anything irreplaceable).
- S3 Glacier Instant Retrieval, Flexible Retrieval, and Deep Archive — long-term archival, progressively cheaper to store and progressively slower (and pricier) to retrieve, down to Deep Archive's multi-hour restore time for data you might genuinely never touch again.
The durability number worth actually understanding: most S3 classes are designed for 99.999999999% durability — 11 nines — meaning if you stored 10 million objects, statistically you'd expect to lose one roughly once every 10,000 years. That number is about AWS never losing your data due to hardware failure; it says nothing about availability (whether you can reach it right now) or about you accidentally deleting it yourself — durability and availability are genuinely different guarantees, and mixing them up is an easy beginner mistake.
EBS: the disk your EC2 instance actually uses
If S3 is for objects you access over the network, EBS (Elastic Block Store) is the much more literal idea: a virtual hard drive, attached to a single EC2 instance, in a specific AZ. This is what last week's EC2 instances actually boot from and write to.
Current EBS volume types split into two families:
- SSD-backed (gp3, io2 Block Express) — for latency-sensitive, random-access workloads. gp3 is the default choice for most workloads now: single-digit millisecond latency, a predictable performance baseline regardless of volume size, and performance you can tune independently of storage size. io2 Block Express exists for the small slice of workloads that genuinely need sub-millisecond latency and 99.999% durability — mission-critical databases, not general use.
- HDD-backed (st1, sc1) — built for throughput over latency: large sequential reads and writes, like log processing or big-data pipelines, at a noticeably lower price per GB.
One genuinely useful feature: Elastic Volumes let you resize a volume, change its type, or adjust its performance live, with no downtime — so "I picked the wrong volume type at launch" isn't the costly mistake it used to be.
RDS: relational databases, with AWS doing the boring parts
RDS (Relational Database Service) is a managed database service supporting several familiar engines — MySQL, PostgreSQL, MariaDB, Oracle, SQL Server, and AWS's own Aurora. "Managed" is doing a lot of work in that sentence, and it's directly Week 3's sliding shared-responsibility line in action: AWS now handles OS patching, database engine patching, automated backups, and failover — work you'd otherwise own entirely on a self-hosted database — while you stay responsible for your schema, your data, and who's allowed to access it.
Aurora, and the genuinely new thing AWS just shipped: Aurora DSQL
Aurora is AWS's own database engine, MySQL- and PostgreSQL-compatible, built to outperform the standard open-source engines on AWS's infrastructure. Aurora Serverless v2 adds auto-scaling compute on top of that, so capacity adjusts automatically instead of being manually sized.
Worth knowing about because it's genuinely recent: AWS shipped something architecturally different in 2025 called Aurora DSQL — a distributed SQL database, PostgreSQL-compatible, but built from the ground up for multi-region, active-active workloads with strong consistency (not the "eventual consistency" compromise most distributed databases make). It bills on a single combined compute+I/O metric (DPUs), scales all the way to zero when idle, and — unlike Aurora Serverless v2's roughly 15-second resume from a paused state — resumes instantly with no cold start at all.
DynamoDB: when you don't need SQL at all
DynamoDB is AWS's fully managed NoSQL database — key-value and document data, not relational tables — built for single-digit millisecond performance at any scale, from a handful of requests to millions per second, without you managing any servers.
It offers two throughput models:
- On-demand mode — pay per request, no capacity planning at all. This is now the default and recommended mode for most workloads, precisely because guessing your future throughput correctly is hard and getting it wrong is expensive either direction.
- Provisioned mode — you specify expected read/write throughput ahead of time, priced by the hour regardless of actual usage. Makes sense only when a workload is genuinely steady and predictable.
The actual SQL vs. NoSQL decision (not just the textbook version)
- Reach for RDS/Aurora (SQL) when your data has real relationships between entities, you need complex or unpredictable ad-hoc queries, or you need strict transactional guarantees across multiple related pieces of data at once.
- Reach for DynamoDB (NoSQL) when your access patterns are known and consistent in advance, you need to scale horizontally to a size a single relational database would struggle with, your schema is likely to evolve, and raw, predictable low-latency access matters more than flexible querying.
Cost Reality Check for This Week
Quick recap of Week 4's Free Tier explainer, applied to this week's four services specifically — since none of them cost the same:
- DynamoDB is genuinely Always Free, permanently, for every account regardless of when it was created: 25GB of storage plus a generous monthly request allowance that never expires. The safest service in this entire article to leave running by mistake.
- S3 has a small Always Free allowance (a few GB and a few thousand requests), so this week's lab stays essentially free either way.
- EBS bills per GB-month regardless of account age — small for a tiny test volume, but it's the one that's easy to forget about since it silently survives even after you terminate the instance it was attached to, if you don't delete it separately.
- RDS is the one to actually pay attention to: no permanent free tier on new-model accounts, drawing from your $200 credit instead. The good news — a
db.t4g.microruns at roughly $0.016/hour, so create-test-delete in one sitting costs a fraction of a cent, not a meaningful dent in your credit balance.
Hands-On: Actually Using All Four
Part 1 — S3: upload, tier, and a presigned URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BUCKET="zero-to-hero-$(date +%s)"
aws s3 mb s3://$BUCKET
echo "Week 5 test object" > test.txt
aws s3 cp test.txt s3://$BUCKET/test.txt
# Check what storage class it landed in (Standard, by default)
aws s3api head-object --bucket $BUCKET --key test.txt --query 'StorageClass'
# Move it to Intelligent-Tiering, to see storage class is something you control per-object
aws s3 cp s3://$BUCKET/test.txt s3://$BUCKET/test.txt \
--storage-class INTELLIGENT_TIERING
# Generate a temporary, expiring link instead of making the object public
aws s3 presign s3://$BUCKET/test.txt --expires-in 300
That last command is worth pausing on: it proves you never need to make an S3 bucket public just to share one file temporarily — a presigned URL grants time-limited access to a single object without touching the bucket's actual permissions at all.
Clean up:
1
2
aws s3 rm s3://$BUCKET/test.txt
aws s3 rb s3://$BUCKET
Part 2 — DynamoDB: create, write, read, query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
aws dynamodb create-table \
--table-name ZeroToHeroUsers \
--attribute-definitions AttributeName=UserId,AttributeType=S \
--key-schema AttributeName=UserId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
aws dynamodb wait table-exists --table-name ZeroToHeroUsers
aws dynamodb put-item \
--table-name ZeroToHeroUsers \
--item '{"UserId":{"S":"bharath01"},"Name":{"S":"Bharath"},"Series":{"S":"AWS Zero to Hero"}}'
aws dynamodb get-item \
--table-name ZeroToHeroUsers \
--key '{"UserId":{"S":"bharath01"}}'
Notice there was no capacity to plan, no throughput to guess —
PAY_PER_REQUEST (on-demand mode) just worked immediately, which is exactly the point made in the theory section above.Clean up (optional — this table costs nothing sitting idle, but good hygiene anyway):
1
aws dynamodb delete-table --table-name ZeroToHeroUsers
Part 3 — EBS: create, attach, use, detach
This reuses the same instance-launch pattern from Week 4. If you still have an instance running from that lab, skip straight to creating the volume.
1
2
3
4
5
6
7
8
9
10
# Get an AZ from your default VPC's subnets to match the volume to the instance
AZ=$(aws ec2 describe-subnets --subnet-ids <your-subnet-id> \
--query 'Subnets[0].AvailabilityZone' --output text)
VOLUME_ID=$(aws ec2 create-volume \
--availability-zone $AZ --size 5 --volume-type gp3 \
--query 'VolumeId' --output text)
aws ec2 attach-volume \
--volume-id $VOLUME_ID --instance-id <your-instance-id> --device /dev/sdf
From inside the instance (connect with
aws ssm start-session --target <instance-id>, same as Week 4):1
2
3
4
sudo mkfs -t xfs /dev/xvdf
sudo mkdir /data
sudo mount /dev/xvdf /data
echo "hello from EBS" | sudo tee /data/hello.txt
Clean up:
1
2
aws ec2 detach-volume --volume-id $VOLUME_ID
aws ec2 delete-volume --volume-id $VOLUME_ID
That last delete matters more than it looks — EBS volumes billed silently for weeks after their instance was terminated are one of the most common beginner AWS bill surprises, precisely because deleting an instance does not automatically delete a volume that was manually attached to it.
Part 4 — RDS: create, confirm, delete (the priciest one, kept short on purpose)
1
2
3
4
5
6
7
8
9
10
aws rds create-db-instance \
--db-instance-identifier zero-to-hero-db \
--db-instance-class db.t4g.micro \
--engine postgres \
--master-username ztheroadmin \
--master-user-password 'ReplaceWithYourOwnPassword123!' \
--allocated-storage 20 \
--no-multi-az \
--publicly-accessible \
--backup-retention-period 0
Important:
--publicly-accessible is only for this quick, throwaway lab, since we're on the default VPC without the private subnets Week 6 covers. A real database should never be publicly reachable — that's precisely the private-subnet pattern from Week 6's traffic-flow walkthrough.1
2
3
4
5
# This CLI command blocks until the DB is actually ready — usually 5-10 minutes
aws rds wait db-instance-available --db-instance-identifier zero-to-hero-db
aws rds describe-db-instances --db-instance-identifier zero-to-hero-db \
--query 'DBInstances[0].[DBInstanceStatus,Endpoint.Address]'
That confirms it's real and reachable. Now delete it immediately — there's no reason to let this one sit around:
1
2
3
4
aws rds delete-db-instance \
--db-instance-identifier zero-to-hero-db \
--skip-final-snapshot \
--delete-automated-backups
My Take
Really fascinated by the range of storage and database services AWS offers, like S3, EBS, RDS, DynamoDB, Amazon Aurora, and more. It’s pretty amusing how there’s seemingly a service for every possible storage need. **
Next week — Part 6: Networking, properly — VPCs, subnets, Route 53, and CloudFront, and how traffic actually finds its way to any of the services covered so far.
Series: AWS: Zero to Hero (9 articles)
- …
- 5AWS: Zero to Hero — Week 5: Storage & Databases — S3, EBS, RDS, and DynamoDB This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article