Course outline

9.3

Shared Storage and Launching Fast

EBS vs EFS for an app tier, golden AMIs, user data and restoring from snapshots.

18 min Reading + animations

Tip: tap or hover any dotted-underlined term to see what it means.

By the end of this lesson you will be able to

  • Explain why a WordPress-style application is stateful in its files (uploads) as well as in its database and sessions, and why that breaks when you add servers.
  • Say why an EBS volume cannot be the shared uploads folder of a web fleet (one AZ, one instance, Multi-Attach is not a shared file system), and how EFS solves it with mount targets in every AZ.
  • Choose between EBS, EFS and S3 (with CloudFront) for user uploads, with the cost of each.
  • Move the database from RDS Multi-AZ to Aurora MySQL with readers, and say what each step buys.
  • Cut the time it takes a new instance to become useful: golden AMI, user data, the hybrid of both, warm pools, and data restored from RDS and EBS snapshots without the first-read penalty.
  • Draw the typical 3-tier web architecture and follow a request, a write and an AZ failure through every tier.

#A blog that stores files, not only rows

#Three kinds of state

Lesson 9.1 grew a stateless app: any server could answer any request. Lesson 9.2 had a shop whose state was a session (the shopping cart) and rows in a database, and moved both off the web servers into ElastiCache and RDS. This lesson’s app has a third kind of state: files.

Think of a blogging platform built on WordPress, the most common content management system (CMS) on the web. We will call it Kolom.example (“kolom” is Bengali for pen). It has to:

  • scale out and in freely behind a load balancer, across several Availability Zones;
  • store posts, comments and users in a MySQL database;
  • let authors upload pictures, and show them correctly to every reader.

WordPress keeps the first two in MySQL, but uploaded images (and installed themes and plugins) are ordinary files in a folder on the server’s disk, wp-content/uploads. That folder is state too. If two servers have two different folders, they show two different websites.

Kind of stateExample in KolomWhere it must live to scaleLesson
SessionWho is logged inElastiCache or DynamoDB, not server memory9.2, 7.5
RowsPosts, comments, usersA database: RDS or Aurora7.1–7.3
FilesUploaded photos, themes, pluginsShared storage: EFS (or S3)this lesson, 5.4

The lesson tells the story in two parts. First the architecture: the database tier, then the file problem and its fix. Then a toolbox for a different question that every scaling design runs into: how quickly can a new instance actually serve traffic?

#The database tier: from RDS Multi-AZ to Aurora

The first version of Kolom uses RDS for MySQL with Multi-AZ: a primary in one AZ and a synchronous standby in another (lesson 7.1). That gives high availability: if the primary fails, RDS fails over to the standby in about one to two minutes and the endpoint follows.

It does not give read scaling. A blog is read far more often than it is written, perhaps a hundred page views per comment. A Multi-AZ standby (the classic single-standby deployment) serves no traffic at all, so all the reads land on the primary. When a post goes viral, the primary’s CPU climbs and every page slows down while the standby sits idle.

The next step is Amazon Aurora MySQL (lesson 7.3):

  • It is MySQL-compatible, so WordPress connects to it without code changes.
  • Up to 15 Aurora Replicas (readers) share one storage volume that keeps six copies across three AZs. Replica lag is usually milliseconds, because readers do not replay a copy of the data; they read the same storage.
  • The cluster endpoint always points at the writer; the reader endpoint spreads connections over the readers. Replica auto scaling can add readers when their CPU rises.
  • If the writer fails, Aurora promotes a reader, typically in under 30 seconds, and moves the cluster endpoint.

So “Multi-AZ and read replicas, with the least effort” is Aurora’s strength. Plain RDS can do it too, with read replicas that each have their own endpoint, or a Multi-AZ DB cluster with two readable standbys; Aurora packages it most neatly. A cache in front (ElastiCache, lesson 7.5) removes even more reads; you will see it in the 3-tier picture at the end.

The blog’s database: from RDS Multi-AZ to Aurora

Posts, comments and users live in MySQL. Both web servers talk to the database tier below them; watch what happens when reads grow and when the database fails.

Scenario

AZ a
AZ b
web-a
web-b
RDS primaryMySQL
RDS standbyno traffic
Step 1 of 3
1

The first database: RDS for MySQL with Multi-AZ. A primary in AZ a, and a synchronous standby in AZ b that serves no traffic at all (lesson 7.1).

#Stage 1: one server, uploads on its EBS volume

With one web server, the simplest place for uploads is the server’s own disk: an EBS volume. An author uploads a photo, the server writes it into wp-content/uploads, and later every reader’s request reaches the same server, which reads the same disk. It works, it is cheap, and for a single-instance application it is the right answer.

Remember what EBS is (lesson 5.1): a network disk that lives in one Availability Zone and is attached to one instance. Those two facts do not matter while there is one instance. They are the whole problem as soon as there are two.

#Stage 2: two servers, and the photo vanishes

Kolom gets popular, and an Auto Scaling group now runs instances in two AZs behind an Application Load Balancer. Each instance was launched from the same AMI and got its own EBS volume.

  1. An author uploads sunset.jpg. The load balancer sends that request to web-a, which saves the file on its volume in AZ a.
  2. A reader opens the post. The load balancer sends this request to web-b, which looks in its uploads folder.
  3. The file is not there. The reader sees a broken image. Depending on which server each request reaches, half the readers see the photo and half do not.

Where do uploaded images live?

A blog on EC2 behind a load balancer. Each post can have photos that users upload. Follow one photo: saved by one server, requested later through another.

Scenario

The first version: one instance with its uploads folder on its own EBS volume.

Regionus-east-1
AZ a
Users
ALB
web-a
EBS volumeAZ a
Step 1 of 4
1

The blog starts small: one instance in one AZ. Posts and users live in a MySQL database (next animation); the uploaded photos are files in a folder on the instance’s EBS volume.

#Why the volume cannot simply be shared

  • One AZ. An EBS volume can only be attached to instances in its own Availability Zone. web-b in AZ b cannot attach a volume in AZ a at all.
  • One instance. A normal volume attaches to one instance at a time.
  • Multi-Attach is not a shared folder. EBS Multi-Attach lets one io1 or io2 (Provisioned IOPS) volume attach to up to 16 Nitro instances in the same AZ. But ordinary file systems such as ext4 and XFS are not designed for several servers writing at once; Multi-Attach needs a cluster-aware file system or an application that coordinates writes itself. It cannot cross AZs and cannot be a boot volume. It is for clustered databases and similar software, not for a WordPress fleet.

#Workarounds that only look like fixes

  • Sticky sessions (lesson 6.4) keep one user on one server. Here the uploader and the readers are different users, so they still land on different servers. And when scale-in terminates web-a, its volume, with the photos, can be deleted with it.
  • Copying files between servers with a cron job or a sync tool: always a little behind, breaks quietly, and every new instance starts empty.
  • Your own NFS server on one EC2 instance: now that instance, its disk and its AZ are a single point of failure, and you patch and scale it yourself.

What the fleet needs is one file system that every instance, in every AZ, can mount at the same time.

#Stage 3: EFS, one file system for every AZ

Amazon EFS (lesson 5.4) is a managed NFS file system. Move wp-content/uploads (or the whole wp-content folder) onto it and every server sees the same files:

  • Regional. A Regional file system stores data redundantly in at least three AZs, and instances in any AZ of the Region can mount it at the same time.
  • Mount targets. The file system reaches your VPC through one mount target per AZ: an ENI with a private IP in a subnet of that AZ. Each instance mounts the file system’s DNS name, which resolves to the mount target in its own AZ, so traffic stays local and avoids cross-AZ charges.
  • Security groups on the mount targets. Each mount target has a security group that must allow inbound TCP 2049 (NFS) from the web servers’ security group. A missing rule does not give a clear error: the mount hangs and times out.
  • Pay per GB used. No size to provision; it grows and shrinks with the files. EFS Standard costs about $0.30 per GB-month in us-east-1, almost four times gp3’s $0.08, but you pay only for what is stored, once, instead of a full-size volume per instance. Lifecycle management moves old, unread files to Infrequent Access or Archive for much less.
  • Linux only, POSIX permissions, many thousands of clients. For a Windows fleet the answer is FSx for Windows File Server.

New instances mount EFS at boot, from user data or an /etc/fstab entry, and see every file at once. Scaling out, scaling in and losing an AZ no longer lose a single photo.

#EBS, EFS or S3 for uploaded files?

EFS is the answer the slides and the exam give for this architecture, because it needs no change to the application: WordPress still writes to a folder. Modern designs often go one step further and keep uploads in Amazon S3, served through Amazon CloudFront, AWS’s content delivery network. S3 and CloudFront each get their own chapters later in the course; here is the comparison you need now:

EBSEFSS3 + CloudFront
What it isBlock disk for one instanceShared NFS file systemObject storage + CDN
Shared by many instancesNo (Multi-Attach: same AZ, io1/io2, cluster-aware only)Yes, thousands, across AZsYes, through the S3 API or a URL
ScopeOne AZRegion (Regional type)Region (S3), edge locations worldwide (CloudFront)
App change neededNoneNone: it is a folderYes: upload through the S3 API (WordPress uses an offload plugin)
Price per GB-month (us-east-1)gp3 $0.08, provisioned, per instanceStandard $0.30, usedS3 Standard about $0.023, used (plus requests and transfer)
Who serves the imageThe web serverThe web serverCloudFront, without touching the web tier
Best forOne instance, databases, boot disksShared files a legacy app writes as filesLarge media libraries, global readers

A worked example: Kolom has 120 GB of photos and runs four instances.

  • EBS: each instance needs its own copy on, say, a 200 GB gp3 volume: 4 × 200 × $0.08 = $64 per month, and the copies still are not shared.
  • EFS Standard: 120 × $0.30 = $36 per month, one shared copy. With lifecycle management moving old photos to IA, much less.
  • S3 Standard: 120 × $0.023 ≈ $2.76 per month for storage, plus requests and data transfer; with CloudFront the web servers stop serving images altogether.

Self-checkQuestion

A WordPress site runs on six EC2 instances in an Auto Scaling group across three AZs. Users report that newly uploaded images appear only sometimes. The team wants a fix that needs no change to the WordPress code. What should they do?

#The WordPress journey in one view

StageDesignProblem it leaves
1One instance, MySQL on RDS, uploads on the instance’s EBS volumeNo scaling, one AZ
2Auto Scaling group across AZs, each instance with its own EBSUploads visible only on the server that received them
3Uploads on EFS, mounted in every AZ through mount targetsDatabase reads concentrate on one primary
4Aurora MySQL: writer + readers across AZs, reader endpoint(Optional) images still served by web servers → S3 + CloudFront later

Summing up the slides’ lesson: EBS for single-instance data, EFS for data that a distributed application shares, Aurora for easy Multi-AZ and read replicas.

#Launching fast: where the minutes go

The second half of this lesson is a toolbox. Every design in this chapter depends on Auto Scaling adding instances when load rises (lesson 6.5). An instance added during a spike only helps once it serves traffic, and everything it does before that is time when users get slow pages or errors. Launching a full stack (EC2, EBS, RDS) can take a long time because of four kinds of work:

  1. Installing the operating system packages, runtime, application and agents.
  2. Inserting data: the initial data set, or the data to recover from.
  3. Configuring everything for this environment: endpoints, file systems, feature flags, secrets.
  4. Starting the application and passing health checks.

The cloud lets you do most of that work once, ahead of time, and reuse the result. For EC2 that means a golden AMI and a small user data script; for data, it means restoring from snapshots.

#Golden AMI: bake the slow part once

A golden AMI is your own image with everything that is slow and the same everywhere already installed: OS patches, web server, PHP, the application code, plugins, the monitoring and security agents (lesson 4.3). Instances launched from it skip the install step entirely.

BenefitWhy it matters during a scale-out
Fast bootReady in a minute or two instead of many minutes
ConsistencyEvery instance is byte-for-byte the same; no “package version drifted on the third server”
No dependency at launchNothing to download, so a package mirror outage or a private subnet without a NAT gateway does not stop scaling
Tested onceThe image is tested before it is used; a bad build never reaches the fleet

The price is a process. A golden AMI is a snapshot in time and it goes stale: when a security patch or a new application version comes out, you rebuild the AMI, test it, point the launch template at the new version and replace the instances (an instance refresh). You do not patch the running servers by hand. EC2 Image Builder automates this pipeline: a recipe (base image plus build and test components), a schedule or a trigger when the base image changes, and distribution to other Regions and accounts. Remember that an AMI belongs to one Region; copy it to every Region you launch in.

Bake, then bootstrap

A golden AMI carries everything that is slow and the same everywhere. User data adds only what differs per environment. Follow one image from the pipeline to a serving instance.

Scenario

Image Builderpipeline
Golden AMIblog-v12
Auto Scalinglaunch template
New instance
Parameter Store/prod/blog/*
ALBhealth check
build on AL2023
Step 1 of 4
1

The pipeline starts a temporary build instance from a base image (here Amazon Linux 2023) and runs the slow steps once: OS patches, web server, PHP, the blog code and plugins, the agents.

#User data: only what differs per launch

User data is the script cloud-init runs once, at the first boot, as root (lesson 4.3). It is the right place for dynamic configuration, things a single AMI cannot know in advance:

  • which environment this is (dev, staging, production) and therefore which database endpoint, which EFS file system, which cache;
  • fetching settings from Systems Manager Parameter Store and secrets from Secrets Manager, using the instance’s IAM role, never writing passwords into the user data itself;
  • mounting EFS, registering with monitoring, joining a cluster.

User data is a poor place for the heavy work. A script that runs dnf update and installs a whole stack makes every instance spend minutes before it serves, depends on the internet at every launch, and can fail halfway while the instance still passes its EC2 status checks. Keep it short; the raw limit is 16 KB anyway.

#The hybrid: golden AMI plus a small user data script

The usual answer is both:

  • bake the slow, unchanging parts into a golden AMI;
  • bootstrap the per-environment parts with a short user data script.

One AMI then serves every environment, and each instance is ready in about the time it takes to boot. AWS Elastic Beanstalk works exactly this way: its platforms are AMIs with the runtime pre-installed, and at launch it adds your application version and configuration (you will see Beanstalk in lesson 9.4).

ApproachBoot timePer-environment configWeak point
Plain AMI + big user dataSlow (many minutes)EasySlow scale-out, depends on the internet, drift
Golden AMI onlyFastestNeeds one AMI per environment, or config baked inMany images to keep patched
Golden AMI + small user dataFastEasyNeeds an image pipeline (Image Builder)

#Side note: warm pools

When even a golden AMI is not fast enough (for example an application that loads gigabytes into memory or onto disk when it starts), an Auto Scaling group can keep a warm pool: instances launched and initialised ahead of time, then kept Stopped (cheapest: you pay only for their EBS volumes and Elastic IPs), Hibernated (memory saved to the EBS root volume) or Running (expensive, rarely worth it). On scale-out the group starts a pool instance instead of launching a new one.

  • By default the pool size is maximum capacity − desired capacity; you can set your own maximum prepared capacity and a minimum pool size.
  • Use a lifecycle hook so an instance is not stopped before its first initialisation has finished.
  • Starting a stopped instance is not a first boot, so user data does not run again unless you configure it to run on every boot.
  • Limits: no Spot Instances and no instance weighting in a mixed instances group with a warm pool; the root device must be EBS.

On the exam, “instances take too long to be ready” is answered by a golden AMI first; a warm pool is the answer when the question stresses an exceptionally long initialisation that cannot be baked away.

#Try the launch strategies

Traffic jumps from 150 to 540 requests per second; two instances can serve 200. At 2:00 the group launches four more. Change how the instances are built, add a warm pool, restore a data volume from a snapshot, or break the package repository, and see how many requests fail before the new capacity arrives:

Launch speed during a traffic spike

Traffic jumps at 1:00 and the Auto Scaling group launches four more instances at 2:00. Choose how the instances are built, then press play and count the failed requests while they get ready.

How the instance is built

Warm pool

Data volume

Package repository

Time

0:00

Demand (req/s)

150

Healthy capacity (req/s)

200

Failed requests so far

0

All 6 at full speed

not yet

0200400600web-1web-2web-3web-4web-5web-602468101214 minutes
demandhealthy capacitypendingOS bootuser data: installinguser data: configstarting from poolapp start + health checksserving, slow first readsin servicefailed health check

What happened

Nothing yet. Press play.

0:00

Two instances serve 150 requests per second. Press play: at 1:00 the demand jumps to 540 per second, and every request above the healthy capacity fails.

With this model’s timings (illustrative, not measured), the four new instances reach full speed this long after launch:

BuildTime to full speedFailed requests in 15 minutes
Plain AMI + 8 min of installs in user data9 min 45 sabout 219,000
Golden AMI + small user data2 min 15 sabout 66,000
Golden AMI1 min 45 sabout 56,000
Warm pool (any build)1 min 15 sabout 46,000
Plain AMI, repository downneverabout 291,000

The gap between the first two rows is pure arithmetic: 8 extra minutes × 340 missing requests per second = 163,200 more failed requests, all because the install happens at launch instead of before.

#Data that is ready: restoring from snapshots

The same idea applies to data. Instead of building a database or a disk and then loading data into it, start from a snapshot that already holds it.

#RDS: restore the database from a snapshot

Restoring an RDS snapshot gives you a database whose schemas and data are already in place: no installing MySQL, no importing a dump, which for a big database can take hours. Use it to create a staging copy with real data, to recover after a mistake, or to start a new environment. Remember from lesson 7.2:

  • A restore always creates a new DB instance with a new endpoint. Point the application at it with a Route 53 CNAME you own (for example db.staging.example.com) or a value in Parameter Store, and no server changes.
  • The restored instance gets the default parameter and option groups unless you name others; check the security group too.
  • Its storage is lazy loaded from S3, so the first queries are slower until the data has been read once. Warm it up before you measure.

#EBS: restore a volume from a snapshot

A volume created from an EBS snapshot is already formatted and full of data: attach it, mount it, use it. (Every AMI does this for its root volume.) But it is lazy loaded too: the volume is available in seconds, and each block is fetched from S3 the first time it is read, so first reads are slow (lesson 5.1). For a server that must be fast immediately:

OptionWhat it doesCost
Do nothingBlocks arrive on first read and in the backgroundFree, but slow and unpredictable at first
Initialise the volumeRead every block once with fio or dd before real trafficFree, takes minutes to hours
Provisioned rate for volume initialisationSet 100 to 300 MiB/s when creating the volume; it is fully initialised in a predictable timePer GiB of snapshot data
Fast Snapshot Restore (FSR)Enabled per snapshot per AZ; volumes are fully initialised at creationAbout $0.75 per snapshot per AZ per hour (us-east-1)

A 60 GiB snapshot at a provisioned rate of 300 MiB/s is fully initialised in about 61,440 ÷ 300 ≈ 205 seconds, under four minutes. FSR is faster still, but it is billed for every hour it is enabled, in every AZ, used or not, and limited by volume creation credits (lesson 5.1). If you set a provisioned rate on a snapshot that has FSR enabled, the rate wins.

A new environment from snapshots

A staging copy of the blog for tomorrow’s test, with real data. Instead of installing MySQL and importing a dump, restore the database and the data volume from snapshots.

Scenario

YouAWS CLI
Snapshotsin S3
New DBblog-staging
New volumeAZ a
App server
Route 53db.staging
restore-db-instance-from-db-snapshot
Step 1 of 4
1

You restore last night’s RDS snapshot. Schema, tables, users and data are all inside it: nothing to install, nothing to import.

#The typical 3-tier web application

Put the pieces of this chapter together and you get the architecture that appears again and again on the exam:

TierSubnetsServicesWhat it does
EntryGlobalRoute 53Alias record for the domain → the load balancer
PresentationPublic subnets in each AZELB (an ALB)Ends TLS, health-checks and spreads requests
ApplicationPrivate subnets in each AZEC2 in an Auto Scaling group, from a golden AMIRuns the app; keeps no state of its own
DataData (private) subnets in each AZElastiCache (sessions and cached data), RDS/Aurora (read/write data, replicas, Multi-AZ)Holds the state
StoragePer AZ / RegionEBS per instance, EFS sharedBoot disks; shared files
  • Every tier exists in at least two AZs, so one AZ can fail without taking the site down.
  • Security groups chain tier to tier: the ALB allows 443 from the internet, the web servers allow traffic only from the ALB’s security group, the cache and the database only from the web servers’ group (lesson 9.2).
  • The application tier is stateless: sessions in ElastiCache, rows in Aurora, files in EFS. That is what makes it safe to add and remove instances freely, and the golden AMI is what makes adding them fast.

The typical 3-tier web application

Route 53 points at the load balancer in public subnets; web servers in an Auto Scaling group in private subnets; ElastiCache and Aurora in data subnets. Follow a request through every tier.

Scenario

VPC10.0.0.0/16
AZ a
AZ b
User
Route 53
ALBpublic subnets
web-aprivate
web-bprivate
Cacheprimary
WriterAurora
ReaderAurora
Cachereplica
www A?ALB addresses
Step 1 of 5
1

The user’s browser asks for www.example.com. Route 53 answers from an alias record that points at the load balancer.

#What goes wrong

  • Uploads on the instance disk in a fleet. Images appear and disappear depending on the server; scale-in deletes them. Move them to EFS (or S3).
  • EFS mount fails in one AZ. No mount target in that AZ, or its security group lacks inbound TCP 2049 from the web servers; a NACL blocking 2049 or the return ports. The symptom is a timeout, not an error message.
  • App starts without its mount. The web server writes into the empty local folder under the mount point, and the old bug returns on that one server. Mount at boot with _netdev in /etc/fstab and make the health check fail if the mount is missing.
  • Reads overload the database while a Multi-AZ standby idles. Add readers (Aurora) and a cache.
  • Stale golden AMI. New instances are born without the latest patches. Rebuild on a schedule with Image Builder and roll out with an instance refresh.
  • Big user data at boot. Slow scale-out, version drift, and a launch that fails whenever the repository is unreachable, while EC2 status checks still look fine.
  • Secrets in user data. Readable from the instance and through the API. Use Secrets Manager or Parameter Store with the instance role.
  • Restored data is slow. Lazy loading on restored EBS volumes and RDS storage. Use FSR, a provisioned initialisation rate, or warm it up.
  • Restored database, old endpoint. The application still points at the old instance. Use a CNAME or a parameter, not a hard-coded endpoint.

#Shared storage and fast launch in system design

  • Make the web tier disposable. If killing any instance loses nothing (sessions, rows and files all live elsewhere), Auto Scaling, Spot Instances, AZ failures and deployments all become routine.
  • Pick storage by who shares it: EBS for one instance, EFS for a fleet that needs a file system, S3 for objects read by many, especially from many places.
  • Bake, then bootstrap. Golden AMIs from a pipeline, user data for environment settings, configuration in Parameter Store, secrets in Secrets Manager.
  • Measure time to useful, not time to “running”: boot, user data, app start, health checks, cache and disk warm-up. That number decides how much spare capacity you must keep, and how aggressive scaling has to be.
  • Restore instead of rebuild for data: snapshots for staging copies and recovery, with FSR or initialisation when performance matters on the first minute.

#Try it yourself

The commands use made-up IDs; replace them with your own.

# 1. EFS for the uploads folder: a file system and one mount target per AZ
aws efs create-file-system --performance-mode generalPurpose --throughput-mode elastic \
  --encrypted --tags Key=Name,Value=myblog-uploads
aws efs create-mount-target --file-system-id fs-0abc1234 \
  --subnet-id subnet-0aaa1111 --security-groups sg-0efs0001       # AZ a
aws efs create-mount-target --file-system-id fs-0abc1234 \
  --subnet-id subnet-0bbb2222 --security-groups sg-0efs0001       # AZ b

# The mount targets' security group: NFS only from the web servers' security group
aws ec2 authorize-security-group-ingress --group-id sg-0efs0001 \
  --protocol tcp --port 2049 --source-group sg-0web0001

# 2. Small user data for the hybrid approach (the AMI already has httpd, PHP and the app)
cat > user-data.sh <<'EOF'
#!/bin/bash
set -euo pipefail
ENV=prod
DB=$(aws ssm get-parameter --name /$ENV/blog/db-endpoint --query Parameter.Value --output text)
FS=$(aws ssm get-parameter --name /$ENV/blog/efs-id --query Parameter.Value --output text)
echo "$FS:/ /var/www/html/wp-content/uploads efs _netdev,tls 0 0" >> /etc/fstab
mount -a
sed -i "s/DB_HOST_PLACEHOLDER/$DB/" /var/www/html/wp-config.php
systemctl enable --now httpd
EOF

# 3. Bake: create an AMI from a configured instance, or run an Image Builder pipeline
aws ec2 create-image --instance-id i-0123456789abcdef0 --name blog-v12
aws imagebuilder start-image-pipeline-execution \
  --image-pipeline-arn arn:aws:imagebuilder:us-east-1:111122223333:image-pipeline/blog

# 4. A warm pool of stopped instances for the Auto Scaling group
aws autoscaling put-warm-pool --auto-scaling-group-name blog-asg \
  --pool-state Stopped --min-size 2

# 5. Data that is ready: a database and a volume from snapshots
aws rds restore-db-instance-from-db-snapshot --db-instance-identifier blog-staging \
  --db-snapshot-identifier blog-prod-2026-10-24 --db-subnet-group-name blog-data
aws ec2 create-volume --snapshot-id snap-0123456789abcdef0 \
  --availability-zone us-east-1a --volume-type gp3 --volume-initialization-rate 300
aws ec2 enable-fast-snapshot-restores --availability-zones us-east-1a \
  --source-snapshot-ids snap-0123456789abcdef0

On a running instance, check the mount and the boot log:

df -h /var/www/html/wp-content/uploads     # should show the EFS file system, not the root disk
sudo tail -n 50 /var/log/cloud-init-output.log

#Summary

Key takeaways

  • A WordPress-style app has three kinds of state: sessions, rows and files (uploads). All three must leave the web servers before the web tier can scale freely.
  • Database: RDS Multi-AZ gives availability but its standby serves no reads; Aurora MySQL adds up to 15 readers on shared six-copy storage, a reader endpoint and failover in under 30 seconds.
  • EBS is one AZ and normally one instance: fine for a single-instance app, wrong as a fleet’s shared folder. Multi-Attach is io1/io2, same AZ, up to 16 Nitro instances, cluster-aware software only.
  • EFS is Regional NFS: one mount target (ENI) per AZ, security group allowing TCP 2049 from the instances, mounted by every instance, pay per GB used, Linux only. The exam answer for shared WordPress uploads.
  • For uploads at scale, S3 + CloudFront is the modern design (later chapters); EFS needs no application change.
  • Launching fast: golden AMI for the slow, unchanging parts (rebuild and roll out with Image Builder and instance refresh); user data for per-environment config, once at first boot; the hybrid of both is the usual answer and how Beanstalk works. Warm pools keep pre-initialised stopped instances for very long initialisations.
  • Data that is ready: RDS restore from snapshot (schemas and data in place; new instance, new endpoint) and EBS restore from snapshot (formatted, with data); both are lazy loaded, fixed by FSR, a provisioned initialisation rate or reading every block once.
  • The 3-tier web app: Route 53 → ALB in public subnets → Auto Scaling group in private subnets → ElastiCache and RDS/Aurora in data subnets, EBS/EFS for storage, every tier in at least two AZs, security groups chained tier to tier.