← All posts

Local NVMe vs EBS: why the 'd' suffix is AWS's best storage deal

Published January 22, 2025

In AWS instance naming, a single letter dictates storage architecture. An m7g.xlarge relies entirely on network-attached EBS volumes for disk I/O. Add a d to the name—m7gd.xlarge—and AWS includes a dedicated 237 GB NVMe SSD directly attached to the physical host via PCIe.

The price difference in us-east-1 is $0.0504 per hour.

For roughly $37 per month, you get raw, direct-attached flash capable of hundreds of thousands of IOPS with sub-100 microsecond latencies. Replicating that performance on Elastic Block Store (EBS) requires expensive provisioned IOPS tiers or multi-volume striping that costs 3x to 35x more.

Despite this, many engineering teams default to EBS-only instances and fight throughput bottlenecks, EBS bandwidth contention, and unpredictable I/O latency. Here is a breakdown of the economics, the performance physics, and the architectural patterns that make instance storage AWS’s most underutilized compute option.

The unit economics: m7g vs m7gd

Let’s look at the baseline Linux on-demand pricing in us-east-1:

Instance Type vCPU RAM Local Storage Hourly Price Monthly Cost (730h)
m7g.xlarge 4 16 GiB EBS-only $0.1632 $119.14
m7gd.xlarge 4 16 GiB 1x 237 GB NVMe $0.2136 $155.93
Delta - - +237 GB NVMe +$0.0504 +$36.79

The incremental cost for 237 GB of dedicated NVMe flash works out to $0.155 per GB-month ($36.79 / 237 GB).

At first glance, $0.155/GB-month looks higher than baseline gp3 storage ($0.080/GB-month). But baseline gp3 comes with strict limits: 3,000 baseline IOPS and 125 MB/s throughput.

When you factor in the cost of provisioning IOPS and throughput on EBS to match actual NVMe flash performance, the economic comparison flips immediately.

The EBS pricing trap: matching NVMe speeds

Suppose your workload needs 20,000 IOPS and 500 MB/s of sustained throughput for a 237 GB working set.

On gp3, you pay for the base capacity plus provisioned performance ($0.005/IOPS-month above 3,000, and $0.040/MB/s-month above 125 MB/s):

On io2, Provisioned IOPS SSDs charge $0.125/GB-month for storage and $0.065/IOPS-month for the first 32,000 IOPS:

Storage Option Provisioned Capacity & Speed Monthly Storage Cost Latency Profile Network Contention Risk
m7gd.xlarge NVMe 237 GB local SSD (~100k–400k IOPS, >1 GB/s) $36.79 Sub-100 µs None (Local PCIe)
gp3 (Provisioned) 237 GB (20,000 IOPS, 500 MB/s) $118.96 1.0–2.0 ms Subject to EBS bandwidth caps
io2 (Provisioned) 237 GB (20,000 IOPS) $1,329.63 Sub-millisecond (~0.8–1.5 ms) Subject to EBS bandwidth caps

The local NVMe SSD on m7gd.xlarge is 3.2x cheaper than provisioned gp3 and 36x cheaper than io2, while providing lower latency and higher IOPS than either EBS option can physically deliver to a 4-vCPU instance.

You can inspect global pricing across all volume types in our storage section.

The physics: PCIe bus vs network-attached storage

The cost gap exists because EBS and instance storage operate on fundamentally different hardware paths.

+------------------------------------------------------------------------+
|                          PHYSICAL AWS SERVER                           |
|                                                                        |
|   +-------------------+                     +----------------------+   |
|   |  Graviton3 CPU    |                     | Local NVMe SSD Drive |   |
|   |  (m7gd.xlarge)    | <== PCIe Gen4 ====> | (237 GB hardware)    |   |
|   +-------------------+  (sub-100µs)        +----------------------+   |
|            |                                                           |
|     Nitro Card (PCIe)                                                  |
|            |                                                           |
|            | 12.5 Gbps Network Link                                    |
+------------|-----------------------------------------------------------+
             |
             v (Network packet transit: 800µs to 2ms + jitter)
+------------------------------------------------------------------------+
|                     AWS EBS STORAGE FLEET (gp3/io2)                    |
+------------------------------------------------------------------------+

1. The latency floor

Every read or write to an EBS volume leaves the physical EC2 host, traverses the data center network via the AWS Nitro System, executes on an EBS storage node, and sends an acknowledgment back across the network.

Under ideal conditions, an EBS round-trip takes between 800 microseconds and 2 milliseconds. Under load or cross-rack network congestion, tail latencies (p99/p99.9) regularly spike into tens of milliseconds.

Direct-attached NVMe drives communicate directly over the motherboard’s PCIe bus. I/O requests bypass the network stack entirely, completing in 50 to 90 microseconds with almost zero tail latency jitter.

2. Dedicated EBS bandwidth limits

Every EC2 instance size has an architectural ceiling on how much traffic it can send to EBS, regardless of how many IOPS you pay for on your volumes.

On an m7g.xlarge, AWS allocates:

If your instance saturates its baseline EBS throughput, I/O requests queue at the Nitro controller level, adding host-side latency to every read and write. Local NVMe drives do not consume a single bit of your instance’s allocated EBS or network bandwidth.

The catch: the ephemeral lifecycle

The reason AWS prices local NVMe so aggressively is that instance storage is ephemeral. The disk lives in the physical chassis holding your VM.

Understanding when data persists and when it disappears is critical:

Event Does Local NVMe Data Persist?
Operating system reboot (sudo reboot) Yes
Software crash / Docker container restart Yes
Kernel panic / OS crash Yes
EC2 instance Stop via AWS API / Console NO (Data wiped)
EC2 instance Terminate NO (Data wiped)
Underlying physical hardware degradation / failure NO (Data wiped)
EC2 Auto Scaling scale-in NO (Data wiped)

When you stop an EC2 instance, AWS releases the physical host. When you start it again, your VM boots on a completely different server in the Availability Zone. The previous host’s NVMe drive is cryptographically wiped and assigned to another tenant.

Because of this, instance storage cannot serve as the primary storage for stateful single-node services without a replication or backup strategy.

Four production architectures for NVMe instance storage

Modern distributed systems are designed to handle node loss. By pairing software-level replication with local NVMe, you get bare-metal disk throughput without sacrificing data safety.

1. Distributed LSM stores & log engines

Databases and streaming platforms built on Log-Structured Merge (LSM) trees or distributed consensus protocols are the natural home for local NVMe.

2. Caching layers & in-memory database spills

Redis, KeyDB, and Memcached workloads often struggle with high memory costs when data sets exceed hundreds of gigabytes.

3. CI/CD build runners & container builders

Build systems are purely ephemeral workloads that perform massive amounts of small file I/O (compiling source trees, extracting container layers, running test suites, linking binaries).

4. Relational database temporary spaces

You do not have to put your entire PostgreSQL or MySQL database on ephemeral storage to benefit from NVMe.

Instance family selection guide

AWS offers local NVMe across several instance families depending on whether your workload is compute, memory, or storage bound:

Family CPU Architecture vCPU : RAM Ratio Storage per vCPU Primary Target
c7gd ARM64 (Graviton3) 1 : 2 GB ~59 GB / vCPU CI/CD build nodes, batch transcoding, web servers with local cache
m7gd ARM64 (Graviton3) 1 : 4 GB ~59 GB / vCPU Kafka brokers, general-purpose microservices, app servers
r7gd ARM64 (Graviton3) 1 : 8 GB ~59 GB / vCPU In-memory cache spills, PostgreSQL temp tables, Elasticsearch
im4gn ARM64 (Graviton2) 1 : 4 GB ~468 GB / vCPU High-density storage: ScyllaDB, ClickHouse, OpenSearch, distributed storage

For x86_64 workloads, the corresponding c6id, m6id, r6id, and i4i (Intel) or c6ad, m6ad, r6ad, and is4gen (AMD) families follow the same sizing pattern.

Implementation: Automating NVMe initialization

Local NVMe drives arrive unformatted and unmounted every time an instance starts. To use them in production, automate formatting and mounting inside your instance user data script or cloud-init configuration:

#!/usr/bin/env bash
set -euo pipefail

# Identify the local NVMe instance store device
# AWS Nitro NVMe devices expose model info in nvme list or /dev/disk/by-id
NVME_DEVICE=$(lsblk -d -n -o NAME,MODEL | grep -i "Amazon EC2 NVMe Instance Storage" | awk '{print "/dev/" $1}' | head -n 1)

if [ -n "$NVME_DEVICE" ]; then
  # Format with XFS or ext4 if not already formatted
  if ! blkid "$NVME_DEVICE"; then
    mkfs.xfs -f "$NVME_DEVICE"
  fi

  # Create mount point and mount with optimized flags
  mkdir -p /mnt/scratch
  mount -o noatime,nodiratime,discard "$NVME_DEVICE" /mnt/scratch
  chmod 777 /mnt/scratch

  echo "Mounted $NVME_DEVICE at /mnt/scratch"
fi

Using mount options like noatime and nodiratime removes unnecessary filesystem metadata writes, allowing the NVMe controller to deliver maximum raw throughput.

The takeaway

When architecting systems on EC2:

  1. Never pay for high EBS IOPS for ephemeral data. Build caches, temporary tables, sort buffers, and swap should run on local NVMe.
  2. Cluster-replicated systems belong on d-variants. If Kafka, ScyllaDB, or Elasticsearch already replicates data across 3 Availability Zones, paying for EBS network storage durability is redundant.
  3. Check the hourly delta. On modern Graviton instances, upgrading to a d variant costs pennies per hour ($0.05/hr on xlarge) and removes disk I/O bottlenecks from your infrastructure.