Benchmark EBS Volume Types

Benchmark gp2, gp3, and io2 EBS volumes and monitor performance in CloudWatch.

Introduction

30 Second Summary

When you choose a storage volume for a cloud server, you are picking the engine that determines how fast your database responds, how quickly your logs process, and whether your application stutters under load. Making the wrong choice means paying more for less performance.

In this project, you will create multiple EBS volume types, benchmark them with a professional I/O testing tool, and watch their performance characteristics unfold in real-time through CloudWatch metrics.

What You'll Build

A hands-on EBS performance lab where you create gp2, gp3, and io2 volumes, benchmark them with fio, and monitor their behavior through CloudWatch dashboards.

By the end of this project, you'll have:

  • Benchmark results comparing gp2 burst behavior, gp3 consistent baseline, and io2 provisioned IOPS performance side-by-side.
  • CloudWatch dashboards showing BurstBalance depletion, VolumeReadOps, VolumeWriteOps, and VolumeQueueLength across volume types.
  • A validated volume selection decision tree for SAA-C03 exam scenarios backed by empirical evidence from your own tests.
  • Secret Mission: Build a RAID 0 stripe across two gp3 volumes and benchmark the combined performance.

Are there any prerequisites?

You need an AWS account, an existing SSH key pair in us-east-1, and familiarity with launching EC2 instances and attaching EBS volumes. This project costs approximately $0.30 to $0.50 if completed and cleaned up within one hour.

Launch a Nitro-Based EC2 Instance and Install fio

To benchmark EBS volumes accurately, you need an instance that will not bottleneck your tests. The instance's own network and I/O subsystem must handle more traffic than the volumes you are testing.

In this step, you will launch a c5.xlarge instance. This is a Nitro-based instance type that is EBS-optimized by default, meaning it dedicates separate bandwidth for EBS traffic so your benchmarks reflect true volume performance.

In this step, get ready to:
  • Launch a c5.xlarge EC2 instance in us-east-1a.
  • SSH into the instance and install fio.
  • Verify the instance is EBS-optimized.

Cost warning

The c5.xlarge costs approximately $0.17/hour. Complete the project and clean up within 1 hour to keep total costs under $0.50.

Launch a c5.xlarge instance

You will use the EC2 console to launch the instance. The launch wizard lets you configure the instance type, AMI, key pair, security group, and availability zone step by step.

  • In your browser, open the AWS Management Console.
  • Type EC2 in the top search bar and select the EC2 service.
  • Click Launch instances.
  • Under Name and tags, enter EBS-Benchmark-Lab.
  • Under Application and OS Images, confirm Amazon Linux 2023 AMI is selected.
  • Under Instance type, click the dropdown, type c5.xlarge, and select it.
  • Under Key pair (login), select your existing key pair from the dropdown.
  • Under Network settings, click Edit and select a security group that allows inbound SSH (TCP port 22) from your IP address.

What do these settings mean?

  • c5.xlarge selects a Nitro-based compute-optimized instance with dedicated EBS bandwidth.
  • Amazon Linux 2023 AMI gives you a current, well-supported OS with fio available in the default package manager.
  • The key pair allows SSH access to the instance after launch.
  • The security group must allow inbound TCP on port 22 from your current IP for SSH to work.
  • Pinning to us-east-1a ensures all EBS volumes you create later share the same Availability Zone as this instance.
  • Scroll down and expand Advanced details.
  • Set the Availability Zone dropdown to us-east-1a.
  • Click Launch instance.
  • Click View all instances to return to the instances list.
  • Wait for the Instance state column to show Running. This takes 1-2 minutes.
  • Note the Instance ID from the table (e.g. i-0abc123def456). You will need it in the next substeps.

Getting an error when launching?

If you see an InsufficientInstanceCapacity error, the c5.xlarge instance type may not be available in us-east-1a. Try changing the Availability Zone to us-east-1b or us-east-1c in the Advanced details section.

Also confirm your security group allows inbound TCP on port 22 from your current IP address. Help me troubleshoot my EC2 launch error

SSH in and install fio

With the instance running, you can connect via SSH and install the benchmarking tool. fio (Flexible I/O Tester) is the industry-standard tool for measuring storage performance.

  • Connect to your instance by running this command:
ssh -i [[PEM_PATH="path to your .pem key file"]] ec2-user@[[EC2_PUBLIC_IP="your instance public IPv4 address"]]

Where do I find the public IP?

In the EC2 console, click on your instance ID in the Instances list. The Public IPv4 address field appears in the instance details panel under the Details tab.

  • Once connected, install fio by running:
sudo yum install -y fio
  • Verify the installation by running:
fio --version

You should see a version string printed (e.g. fio-3.36), confirming fio is installed and ready.

SSH connection refused or timed out?

Make sure your security group has an inbound rule allowing TCP port 22 from your current IP address. Your IP may have changed if you switched networks since creating the rule.

Also confirm the instance has fully started. It can take 1-2 minutes after the state shows running before SSH is ready. Help me fix my SSH connection issue

Verify EBS-optimized status

The c5.xlarge is EBS-optimized by default, meaning its network traffic and EBS I/O do not compete for the same bandwidth. This is the exam concept of EBS-optimized instances acting as the performance ceiling for your volumes.

  • In the EC2 console, click on your instance ID in the Instances list to open its details.
  • In the Details tab, scroll down to find the EBS optimized field.

You should see EBS optimized showing true.

What is EBS-optimized?

EBS-optimized instances have dedicated bandwidth between the instance and its EBS volumes. Without this, regular network traffic and EBS I/O share the same pipe, which can bottleneck your benchmarks.

All current-generation instance types (c5, m5, r5, etc.) are EBS-optimized by default. For older instance types, you would need to enable it explicitly. On the SAA-C03 exam, remember that even if your volume supports high IOPS, the instance type caps the actual throughput.

🙋‍♀️ Seeing false instead of true?

If EBS optimized shows false, double-check that you selected c5.xlarge as the instance type and not an older type like t2.micro. The c5 family is always EBS-optimized by default.

Help me understand why my instance is not EBS-optimized

Your benchmarking lab is ready to go. Next up, you will create a gp2 volume, mount it, and run your first fio benchmark to see burst credits in action.

Benchmark gp2 and Observe Burst Credits

The gp2 volume type is the legacy general-purpose SSD in EBS. Its defining characteristic is the burst credit system. A small gp2 volume earns I/O credits over time and spends them when demand exceeds the baseline.

In this step, you will create a 100 GiB gp2 volume, push it to its burst limit with fio, and watch the BurstBalance metric drop in CloudWatch. This gives you empirical proof of the credit depletion behavior that the SAA-C03 exam tests heavily.

In this step, get ready to:
  • Create and mount a 100 GiB gp2 volume on your EC2 instance.
  • Run a 3-minute fio random write benchmark that pushes the volume to burst IOPS.
  • Observe the BurstBalance metric declining in CloudWatch.
Create and mount a 100 GiB gp2 volume

A 100 GiB gp2 volume has a baseline of 300 IOPS (3 IOPS per GiB). It can burst to 3,000 IOPS by spending I/O credits. The volume starts with 5.4 million credits, enough for 30 minutes of sustained burst at 3,000 IOPS.

  • In the EC2 console, click Volumes in the left sidebar under Elastic Block Store.
  • Click Create volume.
  • Set Volume type to General Purpose SSD (gp2).
  • Set Size to 100 GiB.
  • Set Availability Zone to us-east-1a.
  • Click Create volume.
  • Note the Volume ID shown in the success banner at the top (it starts with vol-). You will need it shortly.

Why these settings?

The volume type gp2 selects the legacy general-purpose SSD. The size of 100 GiB determines the baseline IOPS (3 IOPS per GiB = 300 IOPS). Pinning the Availability Zone to us-east-1a ensures the volume is in the same AZ as your instance, which is required for attachment.

  • Back in the Volumes list, wait for the volume State to show Available.
  • Select the checkbox next to your new gp2 volume.
  • Click the Actions dropdown and select Attach volume.
  • In the Instance field, search for and select your EBS-Benchmark-Lab instance.
  • Leave the Device name as the suggested value (e.g. /dev/sdf).
  • Click Attach volume.
  • Wait a few seconds for the volume to attach (the State changes to In-use), then identify the device name on your instance by running this in your SSH session:
lsblk
  • You should see a new nvme1n1 device with 100G size in the output. On Nitro-based instances, /dev/xvdf appears as /dev/nvme1n1.
  • Format the volume with XFS and mount it by running these commands:
sudo mkfs -t xfs /dev/nvme1n1
sudo mkdir /mnt/gp2vol && sudo mount /dev/nvme1n1 /mnt/gp2vol

What do these commands do?

The mkfs -t xfs command creates an XFS filesystem on the raw EBS device. The mkdir and mount commands create a directory and attach the filesystem there so fio can write files to it.

  • Confirm the mount is working by running:
df -h /mnt/gp2vol
  • You should see /dev/nvme1n1 mounted at /mnt/gp2vol with approximately 100G available.

Volume not showing in lsblk?

In the EC2 console under Volumes, confirm your volume shows In-use state and is in us-east-1a. Volumes can only attach to instances in the same Availability Zone.

If the volume still shows Available, the attach may not have completed. Wait 10 seconds and refresh the page.

Help me troubleshoot my EBS volume attachment. The lsblk command does not show my gp2 volume after attaching.

Run the fio random write benchmark

You will now push the gp2 volume to its burst limit. The benchmark uses 16 jobs writing 16 KiB random blocks for 3 minutes. This generates enough load to consume burst credits faster than they replenish.

  • Start the 3-minute random write benchmark by running this command:
sudo fio --directory=/mnt/gp2vol --ioengine=psync --name=gp2_test --direct=1 --rw=randwrite --bs=16k --size=1G --numjobs=16 --time_based --runtime=180 --group_reporting --norandommap

What does this fio command do?

  • --direct=1 bypasses the OS page cache so you measure actual EBS performance, not memory speed.
  • --rw=randwrite performs random write operations, which is the hardest workload for SSD storage.
  • --bs=16k sets the block size to 16 KiB. Each 16 KiB write counts as one I/O operation.
  • --numjobs=16 runs 16 parallel workers to saturate the volume.
  • --runtime=180 runs the test for 180 seconds (3 minutes).
  • Wait for the benchmark to complete. It will take 3 minutes.
  • When it finishes, look at the output line that says write: IOPS= followed by a number. This should be approximately 3,000 IOPS (the burst ceiling for volumes under 1 TiB).

Why approximately 3,000 IOPS?

The gp2 burst limit is 3,000 IOPS for any volume smaller than 1 TiB. Your 100 GiB volume has a baseline of only 300 IOPS, but it spends accumulated I/O credits to reach the 3,000 IOPS burst ceiling. The credits are finite.

During this 3-minute test, the volume consumes credits at approximately 2,700 credits per second (3,000 demanded minus 300 earned). That depletes roughly 486,000 credits from the starting 5.4 million pool.

🙋‍♀️ Seeing IOPS well below 3,000?

If fio reports significantly fewer IOPS, check that you used --direct=1 (without it, the OS cache absorbs writes and the numbers are unreliable).

Also confirm the --directory flag points to /mnt/gp2vol (the mounted volume) and not a local filesystem.

Help me understand my low fio IOPS results on gp2.

Check BurstBalance in CloudWatch

The BurstBalance metric shows the percentage of I/O credits remaining on a gp2 volume. After your benchmark, this metric should show a decline from 100%.

  • Open the AWS Management Console in your browser.
  • Navigate to the CloudWatch service by searching for CloudWatch in the top search bar and selecting it.
  • In the left navigation pane, click Metrics, then click All metrics.
  • In the Browse tab, type EBS in the search field and select the EBS namespace from the results.
  • Select Per-Volume Metrics.
  • Find your gp2 volume ID in the list. Check the box next to BurstBalance for that volume.
  • A graph appears showing the BurstBalance percentage over time. You should see it declining from 100% during the period when fio was running.

What is BurstBalance?

BurstBalance is a CloudWatch metric that reports the percentage of I/O credits remaining in the volume's burst bucket. It only exists for gp2, st1, and sc1 volume types. The gp3 and io2 volume types have no burst bucket and do not report this metric at all.

This is the key exam takeaway: if a workload consistently exceeds baseline IOPS on a gp2 volume, the burst credits will eventually deplete and performance drops to baseline. This is exactly why gp3 exists as a replacement.

💡 Metrics may take a few minutes to appear

EBS volumes send metrics to CloudWatch at 1-minute granularity. If you do not see the BurstBalance metric immediately, wait 3-5 minutes after the fio run completes and refresh the page.

🙋‍♀️ BurstBalance metric not showing?

Make sure you selected the correct volume ID (the gp2 volume, not the root volume). The root volume is also gp2, but its BurstBalance will not have changed since you did not benchmark it.

If no EBS metrics appear at all, confirm the volume is attached to a running instance. CloudWatch only reports EBS metrics when the volume is attached.

Help me find the BurstBalance metric for my gp2 volume in CloudWatch.

You have now seen gp2 burst credits in action. The volume delivered 3,000 IOPS by spending from a finite credit pool. Next, you will benchmark a gp3 volume and see how it delivers the same 3,000 IOPS without any burst bucket at all.

Benchmark gp3 and Compare Consistent Performance

In the previous step, you watched your gp2 volume burn through burst credits while delivering 3,000 IOPS. The critical question for the SAA-C03 exam is: what happens when those credits run out?

The answer is gp3. In this step, you will create a gp3 volume, run the exact same benchmark, and prove that gp3 delivers consistent 3,000 IOPS indefinitely with no burst bucket involved. You will also use Elastic Volumes to increase provisioned IOPS on the fly.

In this step, get ready to:
  • Create and mount a 100 GiB gp3 volume alongside your existing gp2 volume.
  • Run an identical fio benchmark and compare the results against gp2.
  • Modify the gp3 volume to 6,000 provisioned IOPS and confirm higher performance.
Create and mount a 100 GiB gp3 volume

Your gp3 volume delivers 3,000 IOPS as a sustained baseline regardless of volume size. There is no burst bucket and no credits to earn or deplete.

  • In the EC2 console, click Volumes in the left sidebar under Elastic Block Store.
  • Click Create volume.
  • Set Volume type to General Purpose SSD (gp3).
  • Set Size to 100 GiB.
  • Set Availability Zone to us-east-1a.
  • Click Create volume.

Note the Volume ID from the success banner (it starts with vol-).

Why these settings?

This creates a 100 GiB gp3 volume in us-east-1a. Unlike the gp2 volume you created earlier, this one starts with 3,000 IOPS and 125 MiB/s as its sustained baseline. These numbers never drop because there is no burst mechanism.

  • Back in the Volumes list, wait for the volume State to show Available.
  • Select the checkbox next to your new gp3 volume.
  • Click Actions and select Attach volume.
  • In the Instance field, search for and select your EBS-Benchmark-Lab instance.
  • Leave the Device name as the suggested value.
  • Click Attach volume.

Wait for the volume State to change to In-use.

  • Identify the new device on your instance by running:
lsblk
  • You should see a new 100 GiB device listed as nvme2n1 (or similar).
  • Format and mount the volume by running these commands:
sudo mkfs -t xfs /dev/nvme2n1
sudo mkdir /mnt/gp3vol
sudo mount /dev/nvme2n1 /mnt/gp3vol

What do these commands do?

The first command formats the raw block device with the XFS filesystem. The second creates a directory to serve as the mount point. The third mounts the volume so you can read and write files to it.

  • Verify the mount succeeded by running:
df -h /mnt/gp3vol
  • You should see /dev/nvme2n1 mounted at /mnt/gp3vol with approximately 100 GiB of space.

Don't see the new device in lsblk?

Confirm your volume shows in-use status in the EC2 console under **Elastic Block Store** > **Volumes**.

If it still shows available, double-check that the Availability Zone matches us-east-1a and that you used the correct instance ID in the attach command.

help me attach my gp3 EBS volume to my EC2 instance

Run the fio benchmark and compare results

Now you will run the exact same random write benchmark that you ran on the gp2 volume. This creates an apples-to-apples comparison.

  • Run the 3-minute random write benchmark on the gp3 volume:
sudo fio --directory=/mnt/gp3vol --ioengine=psync --name=gp3_test --direct=1 --rw=randwrite --bs=16k --size=1G --numjobs=16 --time_based --runtime=180 --group_reporting --norandommap

What does this benchmark test?

This is the identical benchmark you ran on gp2: 16 parallel jobs performing random 16 KiB writes for 180 seconds. The --direct=1 flag bypasses the OS page cache so you measure actual EBS performance.

The key difference is what happens under the hood. Your gp2 volume was spending burst credits to hit 3,000 IOPS. This gp3 volume delivers 3,000 IOPS as its sustained baseline with no credits involved.

  • When the benchmark completes (after 3 minutes), look for the IOPS value in the output line that starts with write:.
  • Record your gp3 IOPS result. It should be approximately 3,000 IOPS, matching your gp2 result.
  • In the AWS Console, navigate to **CloudWatch** > **Metrics** > **EBS** > **Per-Volume Metrics**.
  • Search for BurstBalance for your gp3 volume ID.
  • Notice that the BurstBalance metric does not exist for this volume. This confirms there is no burst bucket.

Why does gp3 replace gp2 for most workloads?

Your 100 GiB gp2 volume has a 300 IOPS baseline (3 IOPS/GiB) and can only burst to 3,000 IOPS by spending credits. It costs $0.10/GB-month ($10/month for 100 GiB).

Your 100 GiB gp3 volume delivers 3,000 IOPS sustained (10x more consistent performance) at $0.08/GB-month ($8/month for 100 GiB). That is 20% cheaper with dramatically better performance guarantees.

This is why AWS recommends migrating gp2 volumes to gp3. For the SAA-C03 exam, remember: gp3 is the default choice for general-purpose workloads.

🙋‍♀️ Seeing significantly fewer than 3,000 IOPS?

If your results are well below 3,000 IOPS, confirm you are writing to /mnt/gp3vol (not /mnt/gp2vol). Also verify that --direct=1 is present in your command to bypass the OS cache.

help me troubleshoot low IOPS on my gp3 EBS volume fio benchmark

Modify gp3 to increase provisioned IOPS

One of gp3's most powerful features is that IOPS and throughput are provisionable independently of volume size. You can scale performance without resizing the volume.

  • In the Volumes list, select the checkbox next to your gp3 volume.
  • Click Actions and select Modify volume.
  • Change the IOPS field from 3000 to 6000.
  • Click Modify.
  • Confirm the modification when prompted.

The Volume state will briefly show Modifying, then Optimizing, and finally In-use. The new IOPS setting takes effect during the Optimizing phase.

What is Elastic Volumes?

Elastic Volumes lets you modify EBS volume type, size, IOPS, and throughput on a live attached volume with no downtime. For gp3, you can provision up to 80,000 IOPS and 2,000 MiB/s independently of volume size.

Additional provisioned IOPS above the 3,000 baseline cost $0.005 per IOPS-month. So adding 3,000 extra IOPS costs $15/month on top of the storage price.

  • Wait until the volume state reaches Optimizing or returns to In-use (refresh the page if needed). This typically takes 1-2 minutes.
  • Re-run a shorter 60-second benchmark to confirm the higher IOPS ceiling:

Look for the write: IOPS= value in the output. It should be noticeably higher than the original 3,000 IOPS baseline.

sudo fio --directory=/mnt/gp3vol --ioengine=psync --name=gp3_modified_test --direct=1 --rw=randwrite --bs=16k --size=1G --numjobs=16 --time_based --runtime=60 --group_reporting --norandommap

What should the results show?

Your IOPS should now be higher than the previous 3,000 baseline, approaching 6,000. The exact number depends on how quickly the modification fully applies.

This demonstrates gp3's independence between size and performance. A 100 GiB gp3 volume can deliver 6,000 IOPS without needing to be resized. A gp2 volume would need 2,000 GiB (at 3 IOPS/GiB) to reach 6,000 IOPS natively.

🙋‍♀️ Still seeing approximately 3,000 IOPS after modification?

In the EC2 console under Volumes, check if your gp3 volume still shows Modifying or Optimizing in the Volume state column.

help me verify my gp3 volume modification reached 6000 IOPS

You now have empirical proof that gp3 delivers consistent performance without any burst mechanism. Next, you will benchmark an io2 volume to see how provisioned IOPS and sub-millisecond latency compare for database-class workloads.

Benchmark io2 and Master Volume Selection

You have empirical proof that gp2 relies on a depleting burst bucket while gp3 delivers consistent baseline IOPS without one. Now it is time to test the premium tier of EBS storage and understand when the extra cost is justified.

io2 Block Express volumes target latency-sensitive database workloads that need sub-millisecond response times and 99.999% durability. In this step, you will benchmark io2 latency against your gp3 results and build the volume selection decision tree that covers every SAA-C03 scenario.

In this step, get ready to:
  • Create and attach a 10 GiB io2 volume with 3,000 provisioned IOPS.
  • Run a latency-focused fio benchmark on the raw device.
  • Review the EBS volume selection decision tree for exam scenarios.
Create and attach a 10 GiB io2 volume

io2 volumes use a provisioned IOPS model where you pay for a guaranteed IOPS level rather than relying on burst credits. The IOPS-to-GiB ratio for io2 is 1,000:1, meaning a 10 GiB volume supports up to 10,000 provisioned IOPS.

You will provision 3,000 IOPS to create a fair comparison with your gp3 baseline results.

What is io2 Block Express?

As of April 30, 2025, all io2 volumes are io2 Block Express volumes automatically. There is no longer a separate legacy io2 tier. Block Express delivers up to 256,000 IOPS on Nitro-based instances with sub-500 microsecond average latency for 16 KiB I/O.

Common workloads include Oracle, SAP HANA, Microsoft SQL Server, and SAS Analytics.

  • In the EC2 console, click Volumes in the left sidebar under Elastic Block Store.
  • Click Create volume.
  • Set Volume type to Provisioned IOPS SSD (io2).
  • Set Size to 10 GiB.
  • Set IOPS to 3000.
  • Set Availability Zone to us-east-1a.
  • Click Create volume.
  • Note the Volume ID from the success banner (it starts with vol-).
  • Back in the Volumes list, wait for the volume State to show Available.
  • Select the checkbox next to your new io2 volume.
  • Click the Actions dropdown and select Attach volume.
  • In the Instance field, search for and select your EBS-Benchmark-Lab instance.
  • Leave the Device name as the suggested value.
  • Click Attach volume.
  • Wait for the volume State to change to In-use, then confirm the volume appears on your instance by running:
lsblk

You should see a new nvme3n1 device listed with a size of 10 GiB. You will not format this volume because benchmarking the raw device produces more accurate latency measurements.

Why skip the filesystem?

A filesystem (like XFS) adds its own overhead to every I/O operation. When measuring pure storage latency, bypassing the filesystem and writing directly to the block device eliminates that variable. This gives you the truest comparison of the hardware-level difference between io2 and gp3.

🙋‍♀️ Volume not showing in lsblk?

Make sure the volume's Availability Zone matches your instance (both must be us-east-1a). Also confirm the volume state shows in-use in the AWS Console under EC2 > Volumes.

Help me troubleshoot my io2 volume not appearing after attach

Run fio latency-focused benchmark on raw device

The key differentiator for io2 is not just IOPS capacity. It is latency. io2 Block Express targets sub-500 microsecond average latency for 16 KiB I/O operations.

To measure single-operation latency accurately, you will use --iodepth=1. This sends one I/O request at a time rather than queuing many in parallel. Higher iodepth increases throughput but also increases measured latency.

  • Run this latency-focused benchmark on the raw io2 device:
sudo fio --filename=/dev/nvme3n1 --ioengine=libaio --name=io2_test --direct=1 --rw=randwrite --bs=16k --size=1G --numjobs=4 --time_based --runtime=60 --group_reporting --iodepth=1

What do these fio flags mean?

  • --filename=/dev/nvme3n1 targets the raw block device directly, bypassing any filesystem.
  • --ioengine=libaio uses Linux native asynchronous I/O for precise latency measurement.
  • --iodepth=1 sends one request at a time, isolating the latency of each individual operation.
  • --numjobs=4 runs four parallel workers to simulate a moderate database workload.
  • --runtime=60 limits the test to 60 seconds (enough to collect stable latency averages).

When the benchmark completes, look for the clat (completion latency) line in the output. This shows the average time from I/O submission to completion.

  • Record the clat avg value from the fio output. It should show a value under 500 microseconds.
  • Compare this against your gp3 benchmark results. gp3 typically delivers single-digit millisecond latency, which is roughly 5-10x higher than io2.

What is the latency difference in practice?

Sub-500 microsecond latency means each I/O operation completes in under half a millisecond. For a database running thousands of transactions per second, this difference compounds significantly. A query that issues 100 random reads completes in under 50 ms on io2, compared to 500+ ms on gp3.

This is why high-performance databases (Oracle, SAP HANA) require io2 Block Express.

🙋‍♀️ Seeing latency values in milliseconds instead of microseconds?

Older versions of fio report latency in microseconds by default, but newer versions may display in different units. Look at the unit label next to the clat value. If you see usec, the value is in microseconds. If you see msec, multiply by 1,000 to convert.

Help me interpret my fio latency output for the io2 benchmark

Review the EBS volume selection decision tree

You now have hands-on benchmark data for three volume types. The SAA-C03 exam tests your ability to pick the right volume type for a described workload scenario. Here is the decision tree you need to memorize.

When do I use each EBS volume type?

  • Boot volumes: Must be SSD (gp2, gp3, io1, or io2). HDD volumes (st1, sc1) cannot be boot volumes.
  • General web apps, dev/test: gp3. Best cost-to-performance ratio at $0.08/GB-month with 3,000 IOPS included.
  • High-performance databases (Oracle, SAP HANA): io2 Block Express. Sub-millisecond latency, up to 256,000 IOPS on Nitro, 99.999% durability.
  • Big data, log processing, ETL (sequential access): st1. Up to 500 MiB/s throughput at $0.045/GB-month.
  • Archival, infrequent access (sequential): sc1. Up to 250 MiB/s throughput at $0.015/GB-month. Cheapest EBS storage.
  • Multi-attach (shared volume across instances in same AZ): io1 or io2 only. Up to 16 Nitro-based instances.

💡 When should I pick io2 over gp3?

Use io2 Block Express when you need any of these: more than 80,000 IOPS, sub-millisecond latency, 99.999% durability (vs 99.8-99.9% for gp3), or multi-attach capability. If none of these apply, gp3 is more cost-effective for your workload.

The price difference is significant. Your 10 GiB io2 volume with 3,000 IOPS costs approximately $0.27/hour in provisioned IOPS charges alone. The same 3,000 IOPS on gp3 is included free with the storage price.

💡 Extra for Experts: What about io1?

io1 is still available but io2 is its recommended replacement. io2 offers the same pricing, better durability (99.999% vs 99.8-99.9%), and a higher IOPS-to-GiB ratio (1,000:1 vs 50:1). Exam questions may still reference io1 for legacy workloads or when a question specifically mentions an existing io1 volume.

You now have benchmark proof of all three SSD volume types. Next up, you will dive into CloudWatch metrics to see how AWS monitors EBS performance in real-time and identify when a volume is being throttled.

Monitor EBS Performance with CloudWatch

You now have benchmark results for all three volume types. The next question is: how do you monitor this performance over time in a real workload?

AWS provides CloudWatch metrics that show you exactly what each EBS volume is doing. In this step, you will explore the metrics that reveal whether a volume is being pushed to its limits, whether burst credits are draining, and whether the instance itself is the bottleneck.

In this step, get ready to:
  • Review per-volume CloudWatch metrics across all three EBS volumes.
  • Examine instance-level EBS bandwidth limits.
  • Identify throttling indicators using exceeded-check metrics.
Review key CloudWatch metrics across all volumes

CloudWatch automatically collects EBS metrics at 1-minute granularity for any volume attached to an instance. You do not need to enable detailed monitoring for this.

  • In the AWS Console, click the search bar at the top and type CloudWatch.
  • Select CloudWatch from the results.
  • In the left sidebar, click All metrics under the Metrics section.
  • In the Browse tab, click EBS.
  • Click Per-Volume Metrics.

You should see a list of metrics for each of your attached volumes, identified by volume ID.

  • Find your gp2 volume ID in the list and select checkboxes for these metrics: VolumeReadOps, VolumeWriteOps, VolumeReadBytes, VolumeWriteBytes, and VolumeQueueLength.
  • Also select BurstBalance for the gp2 volume.
  • Now search for your gp3 volume ID and select the same metrics (except BurstBalance).
  • Search for your io2 volume ID and select the same metrics.

What do these metrics tell you?

  • VolumeReadOps and VolumeWriteOps: total I/O operations in the period. Divide by the period (in seconds) to get IOPS.
  • VolumeReadBytes and VolumeWriteBytes: total bytes transferred. Divide by the period to get throughput.
  • VolumeQueueLength: the number of pending I/O requests. A consistently high queue length means the volume is saturated.
  • BurstBalance: percentage of I/O credits remaining. Only exists for gp2, st1, and sc1 volumes.
  • Look at the BurstBalance graph for your gp2 volume. You should see it declining from 100% after your earlier fio run.
  • Try searching for BurstBalance on your gp3 volume. The metric does not exist.

Why does gp3 have no BurstBalance?

gp3 volumes do not use burst performance. They can indefinitely sustain their full provisioned IOPS and throughput performance. There is no credit bucket to monitor because there is nothing to deplete.

This is the core exam distinction: gp2 relies on burst credits for performance above baseline, while gp3 delivers its provisioned IOPS consistently without any credit mechanism.

🙋‍♀️ Not seeing any metrics?

EBS metrics only appear when the volume is attached to a running instance. Make sure your instance is still running and that you are viewing the correct region (us-east-1).

Metrics may take 2-3 minutes to appear after a benchmark completes. If you ran fio recently, wait a moment and refresh the CloudWatch page.

Why aren't my EBS metrics showing in CloudWatch?

Understand instance-level EBS bandwidth limits

Even if your volume supports high IOPS, the EC2 instance type sets an upper ceiling on how much EBS bandwidth it can deliver. This is the concept of the instance being the bottleneck instead of the volume.

  • In the CloudWatch All metrics page, clear your current selections.
  • Click EC2 in the Browse tab, then select Per-Instance Metrics.
  • Find your c5.xlarge instance ID and select EBSIOBalance% and EBSByteBalance%.

What are EBSIOBalance% and EBSByteBalance%?

EBSIOBalance% shows the percentage of I/O credits remaining in the instance's burst bucket. EBSByteBalance% shows the percentage of throughput credits remaining.

If you provision a volume with 80,000 IOPS but your instance type can only deliver a fraction of that bandwidth, the instance becomes the bottleneck. These metrics tell you when the instance is hitting its EBS ceiling.

  • Check whether either metric dropped below 100% during your benchmarks. If both stayed near 100%, the c5.xlarge had plenty of bandwidth headroom for your test workloads.

The throughput formula

The exam tests this: Throughput = IOPS x I/O size. If a volume delivers 3,000 IOPS at 256 KiB I/O size, throughput = 3,000 x 256 KiB = 750 MiB/s. SSD volumes count I/O operations at 256 KiB each.

Identify throttling with exceeded-check metrics

On Nitro-based instances, CloudWatch provides binary indicators that fire when a volume exceeds its provisioned performance limits. These make it easy to spot undersized volumes without manual IOPS calculations.

  • Return to All metrics and navigate back to EBS > Per-Volume Metrics.
  • For your gp2 volume, search for VolumeIOPSExceededCheck and VolumeThroughputExceededCheck.
  • Select both metrics and review the graph.

How do the exceeded-check metrics work?

These are binary (0 or 1) indicators. A value of 0 means the provisioned performance was not exceeded within the last minute. A value of 1 means IOPS or throughput exceeded the provisioned limit.

If VolumeIOPSExceededCheck fires on your gp2 volume, it means the volume was pushed beyond its baseline IOPS (300 for a 100 GiB gp2) and was relying on burst credits. Once those credits deplete, performance is capped at baseline.

  • Now check the same metrics for your io2 volume. Also look for VolumeThroughputPercentage and VolumeConsumedReadWriteOps. These are specific to Provisioned IOPS volumes and show how close you are to your provisioned ceiling.
  • Compare across all three volumes. The gp2 volume should show signs of approaching its limits (burst credits depleting). The gp3 and io2 volumes should show steady, un-throttled performance.

Exam takeaway: volume selection from CloudWatch signals

If BurstBalance is steadily declining, the workload exceeds baseline and the volume will eventually throttle. The fix is to migrate to gp3 (no burst bucket) or increase volume size (which increases gp2 baseline IOPS at 3 IOPS/GiB).

If VolumeQueueLength is consistently high while IOPS metrics plateau, the volume is saturated. The fix is to provision higher IOPS (gp3 or io2) or stripe across multiple volumes with RAID 0.

🙋‍♀️ Don't see the exceeded-check metrics?

VolumeIOPSExceededCheck and VolumeThroughputExceededCheck are only available on Nitro-based instances. Your c5.xlarge is Nitro-based, so they should appear. If the metrics show no data points, it may mean neither limit was exceeded during your benchmark window.

Help me find the VolumeIOPSExceededCheck metric in CloudWatch

You now have a complete picture of how each volume type performs under load and how to monitor that performance over time. Next up: a secret mission where you push EBS performance even further with RAID 0 striping.

Secret mission

Build a RAID 0 Stripe and Benchmark Combined Performance

You have proven that a single gp3 volume delivers 3,000 IOPS baseline. But what if your workload needs more than one volume can provide? In this secret mission, you will configure a Linux software RAID 0 array using mdadm to stripe I/O across two gp3 volumes and benchmark the combined performance.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses a c5.xlarge EC2 instance and multiple EBS volumes that incur hourly charges while they exist.

Cost warning

Your running resources cost approximately $0.50 per hour in total. The io2 volume alone accounts for about $0.27/hr due to its provisioned IOPS charges. If you are not actively using these resources, stop or terminate them to avoid unexpected charges.

Resources you used:

  • 1x c5.xlarge EC2 instance (approximately $0.17/hr).
  • 1x 100 GiB gp2 EBS volume (approximately $0.014/hr).
  • 1x 100 GiB gp3 EBS volume with 6,000 provisioned IOPS (approximately $0.011/hr + IOPS charges).
  • 1x 10 GiB io2 Block Express volume with 3,000 provisioned IOPS (approximately $0.27/hr).
  • 2x 50 GiB gp3 EBS volumes used for RAID 0 (approximately $0.011/hr total).
  • 1x security group allowing SSH on port 22.

Keep everything running

No action needed. Choose this if you are still actively experimenting with the benchmarks or want to run additional tests.

  • Your total hourly cost is approximately $0.50/hr while the instance is running and all volumes exist.
  • EBS volumes incur storage charges even when the instance is stopped.
  • The io2 volume is the most expensive resource due to its provisioned IOPS pricing.

Pause - I'll come back to this later

Stop the EC2 instance to eliminate compute charges. EBS volumes will still incur storage costs while they exist, but compute charges stop immediately.

  • In the EC2 console, select the checkbox next to your EBS-Benchmark-Lab instance.
  • Click Instance state and select Stop instance.
  • Click Stop to confirm.
  • Wait for the Instance state column to show Stopped.
  • Your EBS volumes remain attached and continue to incur storage charges (approximately $0.30/hr total for all volumes).
  • When you return, select the instance and click Instance state > Start instance. Your volumes and data will still be there.

Delete - I don't want to use this again

Remove all project resources completely. This is the recommended option once you have finished experimenting.

  • In the EC2 console, select the checkbox next to your EBS-Benchmark-Lab instance.
  • Click Instance state and select Terminate instance.
  • Click Terminate to confirm.
  • Wait for the instance to reach the Terminated state (1-2 minutes).

Terminating the instance detaches all attached EBS volumes automatically. However, manually attached volumes are NOT deleted on termination. You must delete each one individually.

  • In the left sidebar, click Volumes under Elastic Block Store.
  • Select the checkbox next to each volume in the Available state that belongs to this project (the 100 GiB gp2, 100 GiB gp3, 10 GiB io2, and two 50 GiB gp3 volumes).
  • Click Actions and select Delete volume.
  • Click Delete to confirm.

How many volumes do I need to delete?

You have 5 volumes to delete in total: the 100 GiB gp2, the 100 GiB gp3, the 10 GiB io2, and the two 50 GiB gp3 volumes from the RAID 0 array.

Look for volumes in the Available state in us-east-1a. They become available after the instance terminates.

  • If you created a security group specifically for this project, delete it by navigating to Security Groups in the left sidebar under Network & Security.
  • Select your project security group, click Actions, and select Delete security groups.
  • Click Delete to confirm.
  • Verify no orphaned volumes remain by checking that no volumes in the Available state for us-east-1a belong to this project.

Volume stuck in "in-use" state?

If a volume still shows as in-use, the instance has not fully terminated yet. Wait another minute and refresh the page. Volumes can only be deleted when they are in the Available state.

If you need help cleaning up, ask here.

That's a Wrap!

That's a Wrap!

Nice work! You have built a complete EBS performance lab, benchmarked three volume types with real I/O workloads, and validated the exam-critical performance claims with your own data.

You've learned how to:

  • Benchmark EBS volume types (gp2, gp3, io2) using fio and proved the real-world difference between burst credits, consistent baseline IOPS, and sub-millisecond provisioned latency.
  • Monitored EBS performance through CloudWatch metrics including BurstBalance, VolumeReadOps, VolumeWriteOps, and VolumeQueueLength to identify throttling and verify provisioned performance.
  • Mastered the EBS volume selection decision tree for SAA-C03 scenarios, knowing exactly when to choose gp3 over gp2, when io2 Block Express is required, and why HDD types cannot serve as boot volumes.
  • Secret Mission: Built a RAID 0 stripe across two gp3 volumes using mdadm, demonstrating how striping aggregates IOPS and throughput at the cost of zero fault tolerance.

Ready to quiz yourself?