|
| 1 | +# Operating Systems — 07: Storage and Disks |
| 2 | + |
| 3 | +> **Last updated:** July 6, 2026 |
| 4 | +> **Block devices, partitions, mount, and how AWS EBS volumes actually appear in Linux.** |
| 5 | +
|
| 6 | +--- |
| 7 | + |
| 8 | +## The Storage Stack |
| 9 | + |
| 10 | +From physical disk to a file you can open, there are several layers: |
| 11 | + |
| 12 | +``` |
| 13 | +Your App |
| 14 | + ↓ |
| 15 | +File (open("/data/db.sqlite")) |
| 16 | + ↓ |
| 17 | +Filesystem (ext4, xfs — manages files, directories, permissions) |
| 18 | + ↓ |
| 19 | +Block Device (/dev/nvme0n1p1 — a partition) |
| 20 | + ↓ |
| 21 | +Block Device (/dev/nvme0n1 — the whole disk) |
| 22 | + ↓ |
| 23 | +Storage driver (NVMe driver in the kernel) |
| 24 | + ↓ |
| 25 | +Physical or virtual disk (EBS volume, NVMe SSD, HDD) |
| 26 | +``` |
| 27 | + |
| 28 | +Each layer provides an abstraction. You can swap them out (different filesystems, different disks) without changing your app. |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## Block Devices |
| 33 | + |
| 34 | +A block device is a file in `/dev/` that represents a storage device. It exposes a fixed-size, random-access byte array. |
| 35 | + |
| 36 | +```bash |
| 37 | +# List all block devices: |
| 38 | +lsblk |
| 39 | + |
| 40 | +# Output on a typical EC2 instance: |
| 41 | +# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS |
| 42 | +# nvme0n1 259:0 0 20G 0 disk |
| 43 | +# └─nvme0n1p1 259:1 0 20G 0 part / ← root partition, mounted at / |
| 44 | +# nvme1n1 259:2 0 100G 0 disk ← attached EBS, not partitioned yet |
| 45 | +# xvda 8:0 0 20G 0 disk ← older EC2 uses this naming |
| 46 | + |
| 47 | +# With filesystem type: |
| 48 | +lsblk -f |
| 49 | + |
| 50 | +# NAME FSTYPE FSVER LABEL UUID MOUNTPOINTS |
| 51 | +# nvme0n1 |
| 52 | +# └─nvme0n1p1 xfs / abc-123-... / |
| 53 | +# nvme1n1 (no filesystem yet) |
| 54 | +``` |
| 55 | + |
| 56 | +### Device Naming |
| 57 | + |
| 58 | +``` |
| 59 | +NVMe (modern EC2, m5, c5, r5, etc.): |
| 60 | + nvme0n1 → first NVMe device |
| 61 | + nvme0n1p1 → first partition of nvme0n1 |
| 62 | + nvme1n1 → second NVMe device (second EBS volume) |
| 63 | +
|
| 64 | +Xen virtual disk (older EC2 instance types like m3): |
| 65 | + xvda → first virtual disk |
| 66 | + xvda1 → first partition |
| 67 | + xvdb, xvdc → additional EBS volumes |
| 68 | +
|
| 69 | +SATA/SCSI (physical servers, not EC2): |
| 70 | + sda → first disk |
| 71 | + sda1 → first partition |
| 72 | + sdb → second disk |
| 73 | +``` |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## Partitions |
| 78 | + |
| 79 | +A **partition** divides a disk into independent regions. Each partition can have its own filesystem. |
| 80 | + |
| 81 | +```bash |
| 82 | +# View partition table: |
| 83 | +sudo fdisk -l /dev/nvme1n1 |
| 84 | +# Disk /dev/nvme1n1: 100 GiB, 107374182400 bytes, 209715200 sectors |
| 85 | +# Disk model: Amazon Elastic Block Store |
| 86 | +# Disklabel type: gpt |
| 87 | +# ... (no partitions if brand new) |
| 88 | + |
| 89 | +# Create a partition (interactive): |
| 90 | +sudo fdisk /dev/nvme1n1 |
| 91 | +# Commands: |
| 92 | +# n → new partition |
| 93 | +# p → primary |
| 94 | +# 1 → partition number 1 |
| 95 | +# (accept defaults for start/end to use whole disk) |
| 96 | +# w → write and exit |
| 97 | + |
| 98 | +# Or non-interactive with parted: |
| 99 | +sudo parted /dev/nvme1n1 --script mklabel gpt mkpart primary xfs 0% 100% |
| 100 | +``` |
| 101 | + |
| 102 | +**For a single-purpose EBS volume, skip partitioning.** Create the filesystem directly on `/dev/nvme1n1` (not on a partition). It's simpler: |
| 103 | + |
| 104 | +```bash |
| 105 | +# Format the whole disk directly (no partition): |
| 106 | +sudo mkfs.xfs /dev/nvme1n1 |
| 107 | + |
| 108 | +# Mount it: |
| 109 | +sudo mkdir /data |
| 110 | +sudo mount /dev/nvme1n1 /data |
| 111 | +``` |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## Formatting and Mounting a New EBS Volume |
| 116 | + |
| 117 | +This is the complete workflow for adding an EBS volume to EC2: |
| 118 | + |
| 119 | +```bash |
| 120 | +# Step 1: Attach the EBS volume in AWS Console |
| 121 | +# (or via CLI: aws ec2 attach-volume --volume-id vol-xxx --instance-id i-xxx --device /dev/sdf) |
| 122 | + |
| 123 | +# Step 2: Find the device name (may differ from what AWS shows): |
| 124 | +lsblk |
| 125 | +# Look for the new device — usually nvme1n1 for the second volume |
| 126 | + |
| 127 | +# Step 3: Check if it has a filesystem already: |
| 128 | +sudo file -s /dev/nvme1n1 |
| 129 | +# If output is just "data" → no filesystem, proceed to format |
| 130 | +# If output shows "XFS filesystem" → already formatted, just mount |
| 131 | + |
| 132 | +# Step 4: Format (ONLY if new/empty — this DESTROYS existing data): |
| 133 | +sudo mkfs.xfs /dev/nvme1n1 # use xfs for Amazon Linux (default) |
| 134 | +sudo mkfs.ext4 /dev/nvme1n1 # use ext4 for Ubuntu |
| 135 | + |
| 136 | +# Step 5: Create mount point: |
| 137 | +sudo mkdir -p /data |
| 138 | + |
| 139 | +# Step 6: Get the UUID (stable identifier, better than /dev/nvme1n1): |
| 140 | +sudo blkid /dev/nvme1n1 |
| 141 | +# /dev/nvme1n1: UUID="a1b2c3d4-e5f6-..." TYPE="xfs" |
| 142 | + |
| 143 | +# Step 7: Mount: |
| 144 | +sudo mount /dev/nvme1n1 /data |
| 145 | +# Or by UUID: |
| 146 | +sudo mount UUID="a1b2c3d4-e5f6-..." /data |
| 147 | + |
| 148 | +# Step 8: Make it survive reboots — add to /etc/fstab: |
| 149 | +sudo nano /etc/fstab |
| 150 | +# Add: |
| 151 | +UUID=a1b2c3d4-e5f6-... /data xfs defaults,nofail 0 2 |
| 152 | + |
| 153 | +# Step 9: Verify: |
| 154 | +df -h /data |
| 155 | +# /dev/nvme1n1 100G 1.5G 99G 2% /data |
| 156 | + |
| 157 | +# Step 10: Set ownership (if needed): |
| 158 | +sudo chown -R ubuntu:ubuntu /data |
| 159 | +``` |
| 160 | + |
| 161 | +--- |
| 162 | + |
| 163 | +## Checking Disk Space |
| 164 | + |
| 165 | +```bash |
| 166 | +# Disk space by filesystem: |
| 167 | +df -h |
| 168 | +# Filesystem Size Used Avail Use% Mounted on |
| 169 | +# /dev/nvme0n1p1 20G 12G 7.7G 61% / |
| 170 | +# /dev/nvme1n1 100G 1.2G 99G 2% /data |
| 171 | +# tmpfs 7.7G 0 7.7G 0% /dev/shm |
| 172 | + |
| 173 | +# Disk space usage by directory: |
| 174 | +du -sh /var/log/ # total size of /var/log |
| 175 | +du -sh /var/log/* # size of each item in /var/log |
| 176 | +du -sh /* 2>/dev/null # size of each top-level dir |
| 177 | + |
| 178 | +# Find the largest directories (top 10): |
| 179 | +du -h / 2>/dev/null | sort -rh | head -10 |
| 180 | + |
| 181 | +# Find large files: |
| 182 | +find / -type f -size +100M 2>/dev/null -exec ls -lh {} \; |
| 183 | +# Or: |
| 184 | +find / -type f -size +100M -printf '%s %p\n' 2>/dev/null | sort -rn | head -20 |
| 185 | +``` |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## LVM — Logical Volume Manager |
| 190 | + |
| 191 | +LVM adds a layer of abstraction between physical disks and filesystems, enabling resizing, snapshots, and combining multiple disks. |
| 192 | + |
| 193 | +``` |
| 194 | +Physical disks (PV): /dev/nvme1n1 /dev/nvme2n1 |
| 195 | + ↓ |
| 196 | +Volume Group (VG): my-vg (pool of all physical storage) |
| 197 | + ↓ |
| 198 | +Logical Volumes (LV): /dev/my-vg/data (flexible partitions) |
| 199 | + /dev/my-vg/logs |
| 200 | + ↓ |
| 201 | +Filesystems: ext4 on /data, xfs on /logs |
| 202 | +``` |
| 203 | + |
| 204 | +```bash |
| 205 | +# Is LVM in use? |
| 206 | +sudo pvdisplay # physical volumes |
| 207 | +sudo vgdisplay # volume groups |
| 208 | +sudo lvdisplay # logical volumes |
| 209 | + |
| 210 | +# Create an LVM setup: |
| 211 | +sudo pvcreate /dev/nvme1n1 # mark as PV |
| 212 | +sudo vgcreate my-vg /dev/nvme1n1 # create VG |
| 213 | +sudo lvcreate -L 80G -n data my-vg # create 80GB LV |
| 214 | +sudo mkfs.xfs /dev/my-vg/data # format |
| 215 | +sudo mount /dev/my-vg/data /data # mount |
| 216 | + |
| 217 | +# Extend an LV when you add more disks: |
| 218 | +sudo pvcreate /dev/nvme2n1 # add new disk as PV |
| 219 | +sudo vgextend my-vg /dev/nvme2n1 # add to VG |
| 220 | +sudo lvextend -L +50G /dev/my-vg/data # extend LV by 50GB |
| 221 | +sudo xfs_growfs /data # expand filesystem to fill LV |
| 222 | +# (for ext4: sudo resize2fs /dev/my-vg/data) |
| 223 | +``` |
| 224 | + |
| 225 | +**When LVM is worth it:** |
| 226 | +- You have multiple physical disks to combine |
| 227 | +- You need online resize (expanding without unmounting) |
| 228 | +- You want filesystem snapshots for backups |
| 229 | + |
| 230 | +**When it's not worth it:** |
| 231 | +- Single EBS volume on EC2 — you can just resize the EBS volume and grow the filesystem directly (simpler) |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +## Resizing an EBS Volume (Without LVM) |
| 236 | + |
| 237 | +AWS lets you resize an EBS volume while the instance is running. |
| 238 | + |
| 239 | +```bash |
| 240 | +# Step 1: Resize the EBS volume in AWS Console (or CLI) |
| 241 | +# aws ec2 modify-volume --volume-id vol-xxx --size 200 |
| 242 | + |
| 243 | +# Step 2: After AWS reports resize complete, check in the OS: |
| 244 | +lsblk |
| 245 | +# nvme0n1: shows new size (200GB) |
| 246 | +# nvme0n1p1: still shows old size (20GB) — partition hasn't grown yet |
| 247 | + |
| 248 | +# Step 3: Grow the partition (if using partition + filesystem): |
| 249 | +sudo growpart /dev/nvme0n1 1 # grow partition 1 to fill the disk |
| 250 | + |
| 251 | +# Step 4: Grow the filesystem to fill the partition: |
| 252 | +sudo xfs_growfs / # for XFS (can be done while mounted!) |
| 253 | +# or for ext4: |
| 254 | +sudo resize2fs /dev/nvme0n1p1 # for ext4 |
| 255 | + |
| 256 | +# If the filesystem is directly on the disk (no partition): |
| 257 | +sudo xfs_growfs /data # just grow the filesystem |
| 258 | +``` |
| 259 | + |
| 260 | +This is live, zero-downtime — no unmounting required (for XFS and ext4 online resize). |
| 261 | + |
| 262 | +--- |
| 263 | + |
| 264 | +## RAID (Brief Overview) |
| 265 | + |
| 266 | +**RAID** (Redundant Array of Independent Disks) combines multiple disks for redundancy or performance. |
| 267 | + |
| 268 | +``` |
| 269 | +RAID 0: Striping — data spread across disks for speed. No redundancy. |
| 270 | + 2× 500GB → 1TB usable, 2× read/write speed |
| 271 | + One disk fails → all data lost |
| 272 | +
|
| 273 | +RAID 1: Mirroring — identical copies on all disks. |
| 274 | + 2× 500GB → 500GB usable, writes to both |
| 275 | + One disk fails → still works |
| 276 | +
|
| 277 | +RAID 5: Striping + parity — N disks, N-1 usable, 1 can fail. |
| 278 | + 4× 500GB → 1.5TB usable, survives 1 disk failure |
| 279 | +
|
| 280 | +RAID 10: Mirroring + Striping. Performance + redundancy. |
| 281 | + 4× 500GB → 1TB usable, survives 1+ disk failure |
| 282 | +``` |
| 283 | + |
| 284 | +**On AWS:** EBS already handles redundancy internally — you don't need RAID for data safety. RAID 0 is sometimes used on EC2 with multiple EBS volumes for maximum IOPS. |
| 285 | + |
| 286 | +--- |
| 287 | + |
| 288 | +## Performance Monitoring for Disk |
| 289 | + |
| 290 | +```bash |
| 291 | +# Live I/O stats (1 second interval): |
| 292 | +iostat -xz 1 |
| 293 | + |
| 294 | +# Output: |
| 295 | +# Device r/s w/s rMB/s wMB/s await r_await w_await util |
| 296 | +# nvme0n1 5.00 20.00 0.04 0.31 0.60 0.50 0.63 2.0% |
| 297 | + |
| 298 | +# Key columns: |
| 299 | +# r/s, w/s = reads/writes per second |
| 300 | +# rMB/s, wMB/s = throughput in MB/s |
| 301 | +# await = average I/O time (ms) — should be < 10ms for SSD |
| 302 | +# r_await/w_await = separate read/write latency |
| 303 | +# util% = how busy the device is (100% = saturated → bottleneck) |
| 304 | + |
| 305 | +# Check EBS volume type limits: |
| 306 | +# gp3: 125 MB/s base, 3000 IOPS base (can configure higher) |
| 307 | +# io2: up to 64,000 IOPS (for high-performance databases) |
| 308 | + |
| 309 | +# Watch disk I/O in real time: |
| 310 | +iotop -o # only show processes doing I/O |
| 311 | +# (sudo apt install iotop or sudo dnf install iotop) |
| 312 | +``` |
| 313 | + |
| 314 | +--- |
| 315 | + |
| 316 | +## /proc/mounts and /proc/diskstats |
| 317 | + |
| 318 | +```bash |
| 319 | +# All current mounts (kernel view, more accurate than /etc/fstab): |
| 320 | +cat /proc/mounts |
| 321 | + |
| 322 | +# Disk I/O statistics (what iostat reads from): |
| 323 | +cat /proc/diskstats |
| 324 | +# 259 0 nvme0n1 12345 678 1234567 45678 ... |
| 325 | +# Fields: major minor name reads_completed reads_merged sectors_read time_reading ... |
| 326 | +``` |
| 327 | + |
| 328 | +--- |
| 329 | + |
| 330 | +## tmpfs — RAM as a Filesystem |
| 331 | + |
| 332 | +`tmpfs` is a filesystem stored in RAM. Reading/writing it is as fast as RAM (not disk). |
| 333 | + |
| 334 | +```bash |
| 335 | +# /tmp and /run are usually tmpfs: |
| 336 | +mount | grep tmpfs |
| 337 | +# tmpfs on /tmp type tmpfs (rw,nosuid,nodev,size=7901740k) |
| 338 | +# tmpfs on /run type tmpfs (rw,nosuid,nodev,size=1580348k,mode=755) |
| 339 | + |
| 340 | +# Create your own tmpfs (useful for temp data in high-performance scenarios): |
| 341 | +sudo mount -t tmpfs -o size=2G tmpfs /mnt/fast-scratch |
| 342 | +df -h /mnt/fast-scratch |
| 343 | +# tmpfs 2.0G 0 2.0G 0% /mnt/fast-scratch |
| 344 | + |
| 345 | +# WARNING: All data in tmpfs is lost on reboot or unmount. |
| 346 | +# Use for: build artifacts, temporary processing files, test databases |
| 347 | +``` |
| 348 | + |
| 349 | +--- |
| 350 | + |
| 351 | +## Disk I/O Scheduler |
| 352 | + |
| 353 | +The I/O scheduler decides the order in which I/O requests are sent to the disk. |
| 354 | + |
| 355 | +```bash |
| 356 | +# See current scheduler: |
| 357 | +cat /sys/block/nvme0n1/queue/scheduler |
| 358 | +# [none] mq-deadline kyber bfq |
| 359 | + |
| 360 | +# For NVMe SSDs on EC2: "none" is usually optimal |
| 361 | +# (NVMe SSDs have their own internal queue management) |
| 362 | + |
| 363 | +# For HDDs: "mq-deadline" or "bfq" can improve performance |
| 364 | +echo "mq-deadline" | sudo tee /sys/block/sda/queue/scheduler |
| 365 | +``` |
| 366 | + |
| 367 | +→ Continue to: `08-package-management.md` |
0 commit comments