What StorageOps is
Link to section “What StorageOps is”In 30 seconds: the definition
Link to section “In 30 seconds: the definition”StorageOps is an informal name for storage operations, usually called storage administration: choosing the right storage for each workload, provisioning it, keeping it fast, intact and large enough, and retiring it safely, for block volumes, shared file systems and object stores, on premises, in clouds and under Kubernetes. This page treats it as storage physics put to work: every workload has an access pattern, every medium and protocol has limits, and the job is to match them and prove it with measurements. Beginners can stop after this part.
At a glance
Link to section “At a glance”| Question | Answer |
|---|---|
| Goal | Every workload gets storage whose latency, throughput, durability and capacity are known, measured and paid for on purpose |
| Who | Storage, infrastructure and platform engineers; SREs; architects who choose storage services |
| In scope | Media and protection; block, file and object protocols; SAN and NAS; cloud volume types and storage classes; performance; capacity and tiering; snapshot and replication mechanics; Kubernetes volumes and CSI; retirement |
| Out of scope | Backup policy and restore tests (BackupOps); service recovery, RTO and RPO (DisasterRecoveryOps); database engines (DBOps); day-to-day host filesystems (SysOps); data access policy (DataSecOps); spend decisions (FinOps) |
| Prerequisites | Linux administration basics (SysOps), basic networking |
| Outputs | A storage class catalogue, performance and capacity dashboards, forecasts, runbooks, sanitisation records |
| Term status | Overlapping synonym for storage administration |
The problem it solves
Link to section “The problem it solves”Storage rarely fails loudly; it fails quietly, because a workload was never matched to its storage or a limit went unwatched.
- Slow without being broken. Driving more IOPS than a cloud volume has “can cause increased I/O latency”; a volume restored from a snapshot is slow until its blocks arrive.[7][8]
- Full at the worst moment. A full LVM thin pool queues writes for up to 60 seconds and then fails them, which “can result in file system damage that requires repair”.[9]
- Redundancy that runs out of room. In AWS’s 2011 EBS outage, mass re-mirroring exhausted free space (case 2).[10]
- Silent damage. A study of 1.53 million drives found over 400,000 checksum mismatches and warned that RAID “may also be unable to detect these problems”.[11]
- Costs hidden in rules. Colder tiers charge minimum durations and object sizes;[12] Azure’s archive can take up to 15 hours to rehydrate.[13]
With StorageOps, storage comes from priced classes with measured limits, growth is forecast against lead time, and redundancy is scrubbed and healthy.
What StorageOps is not: naming, boundaries and misconceptions
Link to section “What StorageOps is not: naming, boundaries and misconceptions”Not backup. A replica copies deletions faithfully. AWS calls an EBS snapshot “an incremental backup”,[14] yet any user whose IAM permissions allow it can delete an unlocked one;[15] whether a snapshot is a backup depends on its independence, which BackupOps tests. Not disaster recovery: failover decisions and RTO/RPO targets live in DisasterRecoveryOps. Not protection from your own deletes: “eleven nines” (S3 lists 99.999999999% for most classes) is a design target against hardware loss.[12]
Editorial distinction: StorageOps here owns the storage system and its mechanics, BackupOps the independent copies and restore evidence, DisasterRecoveryOps service recovery. No standard draws these lines.
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”The label has no history; the practice has turning points. A 1987 Berkeley report by Patterson, Gibson and Katz proposed “Redundant Arrays of Inexpensive Disks” and five RAID levels.[16] Networked file sharing was already in use: when RFC 1094 documented Sun’s NFS in 1989, implementations existed “for a variety of machines”;[17] it grew into today’s NFS 4.1/4.2, alongside SMB 2 and 3.[18][19][20] Block storage moved onto dedicated networks: Fibre Channel SANs, then iSCSI over TCP (RFC 3720 in 2004, consolidated in RFC 7143 in 2014).[21] Ceph and similar systems spread data across servers,[22] cloud object stores became strongly consistent,[23] and the Container Storage Interface let one storage plugin serve many container orchestrators.[24]
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning | Example |
|---|---|---|
| IOPS | I/O operations completed per second | 3,000 IOPS baseline on gp3[25] |
| Throughput | Bytes per second (IOPS × I/O size) | 125 MiB/s |
| Latency | Time from issuing an I/O to completion, queueing included | 0.5 ms |
| Block, file, object | Fixed-size blocks; files in directories; objects with IDs in a flat namespace[5] | Volume; NFS share; S3 bucket |
| SAN, NAS, LUN | Storage area network (block access over Fibre Channel or iSCSI); network-attached storage (files over NFS or SMB); logical unit number, a block device on a SAN[5] | iSCSI SAN |
| Zoning, masking | Which host and storage ports may talk (fabric switch); which hosts see a LUN (array)[5] | One zone per host |
| Multipath | Several paths (cables, switches, controllers) to one LUN, shown as one device[26] | /dev/mapper/mpatha |
| CMR, SMR, HAMR | HDD tracks side by side; overlapping (shingled); laser heat-assisted writing[27] | SMR for archives |
| RAID | Redundancy across a disk group by mirroring or parity | RAID 6: two parity blocks per stripe |
| Erasure coding (k+m) | k data chunks plus m coding chunks; survives m losses[22] | 4+2 |
| Failure domain | Components that can fail together: a host, rack, power supply or site[28] | Each copy on its own host |
| Snapshot | Point-in-time image, usually sharing blocks with its source | EBS or LVM thin snapshot |
| Thin provisioning | Space allocated on write | 10 TiB of volumes on 6 TiB |
| Durability vs availability | Chance of not losing data vs of reaching it now | 11 nines vs 99.9% |
| PV, PVC, StorageClass, CSI | Kubernetes volume, claim, provisioning class, driver interface | gp3-fast |
| Scrub | Reading all data to find and repair silent errors | zpool scrub |
Mental model: three numbers and a queue. Throughput = IOPS × I/O size. Little’s law says the average number of items in a queuing system equals their arrival rate times the time each spends there,[29] so I/Os in flight = IOPS × latency: a device completing 3,000 I/Os per second with 30 in flight gives about 10 ms latency.
Four questions per workload (the Atlas’s checklist): shape (I/O size, randomness, read/write mix, concurrency), size (capacity and growth), safety (failures to survive, integrity checks) and spend (per TiB-month, per request, per retrieval, minimums included).
How StorageOps relates to other Ops
Link to section “How StorageOps relates to other Ops”| Neighbour | Shared ground | The neighbour’s focus | StorageOps’ focus |
|---|---|---|---|
| SysOps | Disks, filesystems | Hosts: full filesystems, inodes, mounts | Arrays, pools, volume types, protocols |
| BackupOps | Snapshots, replication | Independent copies, restore tests | The mechanics underneath |
| DisasterRecoveryOps | Replication, RPO | Failover, RTO and RPO targets | Sync vs async behaviour |
| DBOps | Database volumes | Engine tuning, point-in-time recovery | The I/O the engine generates |
| KubernetesOps | StatefulSets, drains | Scheduling, cluster operations | CSI, classes, attach failures |
| CloudOps | Cloud accounts | Quotas, guardrails | Volume types, lifecycle rules |
| FinOps, GreenOps | Spend, energy | Cost and carbon decisions | Unit costs and their rules |
| CloudSecOps | Encryption, exposure | Security guardrails | Storage-layer controls |
| ResearchDataOps | HPC storage | Research data management | Scratch, striping, archives |
Diagram source (Mermaid)
flowchart TB W["Workloads"] -->|"access pattern, growth"| S K["KubernetesOps"] -->|"volume claims"| S Y["SysOps"] <-->|"disks, mounts"| S C["CloudSecOps"] -.->|"guardrails"| S S["StorageOps: media, protection, protocols, performance, capacity"] S -->|"snapshots, replicas"| B["BackupOps: tested copies"] S -->|"replication behaviour"| R["DisasterRecoveryOps: service recovery"] S -->|"unit costs"| F["FinOps: spend decisions"] classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class S core class W,K,Y,C,B,R,F neutral
Text equivalent: workloads (access patterns, growth), KubernetesOps (volume claims), SysOps (disks, mounts) and CloudSecOps (guardrails) feed StorageOps, which hands snapshots and replicas to BackupOps, replication behaviour to DisasterRecoveryOps and unit costs to FinOps.
Principles and operating model
Link to section “Principles and operating model”A proposed charter and principles
Link to section “A proposed charter and principles”This charter is the Atlas’s proposal; no official StorageOps manifesto exists. Mission: every byte kept sits on storage chosen for its workload, with watched limits and a recorded retirement.
- Shape before product. Know the access pattern before choosing medium, protocol or service.
- Name every limit, and alert on approach, not arrival.
- Count failure domains, not copies. Disk failures are correlated and rise with age;[30] spread copies across hosts, racks or zones.
- Verify integrity continuously with checksums and scrubs.[11][31]
- Headroom is capacity. A full pool cannot re-protect itself: Ceph refuses backfill (recovery that copies a placement group’s entire contents) to nearly full devices.[32][33]
- Destructive operations are slow, scoped and reversible, with floors and rate limits.[34]
- The price includes the rules: requests, retrievals, minimum durations and sizes.
- Retire deliberately: sanitise owned media, and plan cryptographic erase for cloud storage, as the data’s sensitivity requires.[35]
Building blocks
Link to section “Building blocks”| Layer | Examples | Design rule |
|---|---|---|
| Media | HDD (CMR, SMR), SSD, tape | Choose by access pattern and cost |
| Protection | RAID, erasure coding, replication | Spread across failure domains |
| Presentation | Block, file, object protocols | Match the semantics the application needs |
| Data services | Snapshots, clones, thin provisioning, tiering | Each costs space, I/O or risk |
| Control plane | APIs, CSI drivers, classes | Self-service in a catalogue; guarded deletes |
| Telemetry | /proc/diskstats, cloud metrics, scrub status |
Alert on saturation and degradation |
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”NIST SP 800-209 recommends at least the four ISO/IEC 27040 roles: security administrator; storage administrator, with “view” and “modify” rights over storage but none over security; security auditor; and storage auditor, with view-only rights over configuration and health logs.[5] One stolen account then cannot both change storage and hide it. R = responsible, A = accountable, C = consulted, I = informed.
| Activity | Storage or platform team | Application or data owner | Security | FinOps | SRE or on-call |
|---|---|---|---|---|---|
| Define and price the class catalogue | A/R | C | C | C | C |
| Request storage, state the access pattern | C | A/R | I | I | I |
| Provision and expand | A/R | I | I | I | C |
| Performance and capacity alerts | A | I | – | I | R |
| Snapshot and replication policies | A/R | C | C | – | I |
| Encryption, access, audit logging | R | I | A | – | I |
| Lifecycle and tiering rules | R | A | C | C | – |
| Decommission and sanitise media | R | I | A | I | – |
Lifecycle of a volume or bucket
Link to section “Lifecycle of a volume or bucket”Day 0 designs the catalogue and telemetry, Day 1 provisions from it, and Day 2 runs everything after.
Diagram source (Mermaid)
stateDiagram-v2 direction TB [*] --> Requested: intake form Requested --> Provisioned: class chosen Provisioned --> InService: mounted or bound InService --> Changing: expand, retier, retune Changing --> InService: change verified InService --> Degraded: device or node lost Degraded --> Rebuilding: spare or new replica Rebuilding --> InService: redundancy restored Rebuilding --> Lost: failure beyond tolerance Lost --> Provisioned: restore from BackupOps InService --> Released: owner releases Released --> Retained: policy says keep Released --> Sanitised: sanitise or crypto-erase Retained --> Sanitised: retention ends Sanitised --> [*]: record kept
Text equivalent: a request is provisioned once a class is chosen and enters service when mounted or bound, where it may be expanded, retiered or retuned. A lost device degrades it until a rebuild restores redundancy; a failure beyond what the protection tolerates loses the data, which is restored from BackupOps copies into new storage. On release, policy retains or deletes it; owned media are then sanitised, or cloud data crypto-erased by destroying its key, and a record kept.
Operating model and governance
Link to section “Operating model and governance”| Cadence | Activity | Output |
|---|---|---|
| Continuous | Alerts: degraded redundancy, stalled I/O, latency, pool thresholds | Pages, tickets |
| Weekly | Saturation vs class limits; unattached volumes, old snapshots | Rightsizing, clean-up |
| Monthly | Runway vs lead time; scrub and rebuild report | Forecast, orders |
| Quarterly | Catalogue, access and security review (checklists) | Class and access changes |
| On change | Destructive operations: second person, dry run, scope limit | Change record |
Exceptions are recorded with an owner, a reason and an expiry date.
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture: the storage stack
Link to section “Reference architecture: the storage stack”The same stack exists on premises, in clouds and in clusters; only ownership changes. In a cloud the provider runs media and protection; you still own class, performance settings, capacity and lifecycle.
Diagram source (Mermaid)
flowchart TB
C["Control plane: APIs, CSI drivers, classes"]
C -->|"provision, expand, retune"| P
C -->|"policies"| D
subgraph DP["Data path"]
direction TB
A["Consumers: VMs, pods, applications"] --> P["Presentation: block, file or object protocol"]
P --> D["Data services: snapshots, replication, tiering"]
D --> R["Protection: RAID, erasure coding, replicas"]
R --> M["Media: SSD, HDD, tape"]
end
M -->|"health, errors"| T["Telemetry: latency, IOPS, capacity, scrubs"]
P -->|"I/O statistics"| T
T -.->|"alerts and forecasts feed back"| C
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
class A,P,D,R,M core
class C decision
class T neutral
Text equivalent: consumers reach storage through a block, file or object protocol, above data services, protection and media. The control plane provisions, retunes and sets policies; telemetry collects media health and I/O statistics and feeds alerts and forecasts back to it.
Trust boundaries. One management call can delete a pool, so NIST SP 800-209 recommends separating data, management, replication and backup traffic and restricting which hosts see each device and each copy: “In many cases, a host granted access to a device should not be allowed to access a copy.”[5]
Media and protection physics
Link to section “Media and protection physics”| Medium | Strong at | Weak at | Typical fit |
|---|---|---|---|
| SSD | Small random I/O, low latency | Cost per TiB | Databases, metadata |
| HDD (CMR) | Cost per TiB, sequential I/O | Random I/O: each seek costs milliseconds | Bulk data, streaming, backups |
| HDD (SMR) | Roughly 10–25% more capacity[27] | “frequent small rewrites create more work”[27] | Append-only and archive data |
| Tape | Cheap cold capacity, offline by nature | Slow first access; many small files “very inefficient”[36] | Archives |
Cloud types encode the same physics: st1 and sc1 are throughput-oriented HDD volumes;[37] io2 Block Express targets average latency under 500 microseconds for 16 KiB I/O.[38]
Parity costs writes. In the 1987 RAID report, a small write to a parity array “uses 2 disks to perform 4 accesses—2 reads and 2 writes”, because new parity = (old data XOR new data) XOR old parity.[16] RAID 5 rotates the parity but keeps that cost.
Rebuilds are exposure. In a field study of about 100,000 drives, annual replacement rates of 2–4% were common, against
datasheet figures implying at most 0.88%, and replacements were correlated in time.[30] Linux md’s sync_speed_min trades
application I/O for a shorter rebuild.[39] Long rebuilds of large drives favour double parity or m ≥ 2 (the Atlas’s inference). Erasure coding has overhead (k+m)/k, 1.5 for 4+2 against 3.0 for three
replicas; Ceph strongly discourages m=1 in production and wants at least k+m failure domains, preferably k+m+1.[22]
Replication lag is exposure. Azure’s zone-redundant writes return “only after the data is written to all replicas across the three availability zones”; asynchronous geo-replication “might result in data loss”.[40] S3 Replication Time Control copies “99.9 percent of those objects within 15 minutes”.[41]
Snapshots have mechanics. A classic copy-on-write snapshot (device-mapper’s original target, used by
lvcreate --snapshot -L <size>) copies old data into every snapshot’s COW area as the origin is overwritten, so each snapshot
adds write work, and a full COW area disables its snapshot.[42] Thin snapshots share blocks with their origin, and
snapshots of snapshots no longer slow down with chain depth as the older chained tables did.[43] Any block snapshot holds
only what reached the volume; Ceph calls its own “merely crash-consistent” unless I/O is paused first (for example with
fsfreeze).[44] Application-consistent copies are BackupOps work. Volumes
that must agree, such as a database’s data and log, need quiescing or a group snapshot, which CSI requires to be write-order
consistent.[24]
Integrity needs checksums. RAID cannot tell which of two disagreeing copies is right. An OpenZFS scrub “verifies each block’s
checksum” and repairs redundant pools;[31] Linux md’s check action counts mismatches in mismatch_cnt.[39]
What the datasheet leaves out.
- Acknowledged is not durable: drives with volatile write caches report completion before data reaches non-volatile media,
so
fsyncmust flush them;[45] Ceph expects drives with power-loss protection,[46] and an NFS export withasynccan lose acknowledged writes in a server crash.[47] - Flash wears out: endurance is rated in drive writes per day (DWPD); Ceph finds 1 DWPD enough for most data drives, but not 0.3 DWPD under a block pool serving hundreds of VMs.[46]
- Instance-store NVMe is ephemeral: its data “does not persist if the instance is stopped, hibernated, or terminated”.[48]
- Data reduction is a forecast: plan on the ratio your data achieves (ZFS reports
compressratio), not a quoted “effective” capacity; encrypted data barely reduces (the Atlas’s reasoning).[49]
Protocols and presentation: block, file, object
Link to section “Protocols and presentation: block, file, object”| Interface | Protocols | Consumer sees | Good for | Watch out |
|---|---|---|---|---|
| Block | iSCSI over TCP,[21] Fibre Channel, NVMe over Fabrics,[50] cloud volumes | A raw disk | Databases, VM disks | One writer per filesystem |
| File | NFS 4.1/4.2 (with parallel NFS),[18][19] SMB 2 and 3[20] | Shared directories | Home and project shares | Locking, metadata load, root squashing |
| Object | S3-compatible HTTP APIs | Buckets of keyed objects | Unstructured data, lakes, archives | No partial update; request rates; per-request cost |
S3 scales to “at least 3,500 PUT/COPY/POST/DELETE or 5,500 GET/HEAD requests per second per partitioned Amazon S3 prefix”, gradually, answering 503 “Slow Down” meanwhile,[51] and is strongly consistent for PUT and DELETE.[23]
SAN and NAS on premises
Link to section “SAN and NAS on premises”- Zoning and masking: NIST asks for both, a default zone of “deny all”, and masking “as close to the data as possible”.[5]
- Multipathing moves I/O to another path when a cable, switch or controller fails,[26] and does it silently:
alert on paths
multipath -llreports asfaultyorshaky.[52] If all paths fail,no_path_retrydecides whether I/O fails at once (the upstream default; built-in array profiles may differ) or queues, and unlimited queueing “can lead to D state processes being hung and not killable”. On arrays with SCSI-3 ALUA (Asymmetric Logical Unit Access), path priorities come from the array.[53] - NAS exports and shares are file-side masking: list the clients, keep root squashed (the NFS default; NIST also asks for
nosuid),[5] and usesec=krb5pwhere the network is untrusted, because the defaultsysflavour has “no cryptographic security”.[47] SMB 3 can encrypt traffic per share.[54]
Example status: checked against exports(5); not executed (no NFS server on the research host; krb5p needs Kerberos).
# /etc/exports: one project share, read-write for one subnet, read-only for another.
# sync and root_squash are the defaults, spelled out so a reviewer sees them.
/srv/projects 10.20.0.0/24(rw,sync,root_squash,sec=krb5p) \
10.20.1.0/24(ro,sync,root_squash,sec=krb5p)
Clients outside both subnets get no access. Apply with exportfs -ra.[47]
End-to-end workflow: from request to retirement
Link to section “End-to-end workflow: from request to retirement”Happy path.
- The owner submits the intake form (templates).
- The storage team maps it to a class or says why not.
- Self-service provisioning applies encryption and tags.
- A check confirms class limits and dashboards.
- Alerts, forecasts, scrubs and lifecycle rules run.
- Changes follow the class runbook.
- On release: retention, then deletion, media sanitisation or key destruction, and a record.
Exception path. No class fits: measure the real need, then add a priced class or split the workload. A pool degrades: rebuild before any other change. Runway falls below lead time: escalate to the budget owner that day.
Practices, methods and tools
Link to section “Practices, methods and tools”Core practices and playbooks
Link to section “Core practices and playbooks”| Practice | Trigger | Owner | Output | Verification |
|---|---|---|---|---|
| Characterise the workload | New request, incident | Storage team, app owner | I/O size, mix, peaks | Metrics over a busy week |
| Maintain the catalogue | Quarterly | Storage team | Priced classes with limits | Each class benchmarked |
| Forecast capacity | Monthly | Storage team | Runway vs lead time | Forecast error tracked |
| Scrub and rebuild | Schedule; device lost | Storage team, on-call | Scrub and rebuild logs | Pools scrubbed in interval |
| Tier and expire data | Quarterly | Data owner | Lifecycle rules | Savings after minimum durations |
| Sanitise and decommission | Release, failure | Storage team, security | Record per device, volume or bucket | Matches inventory |
Performance engineering: three numbers and a queue
Link to section “Performance engineering: three numbers and a queue”Rules of the road for cloud block storage (AWS EBS, as of October 2026; other clouds publish similar rules):
- IOPS count I/Os of up to 256 KiB (SSD) or 1,024 KiB (HDD); small sequential I/Os merge, random ones each count. Large I/O hits the throughput limit first: 1,000 IOPS of 256 KiB is already 250 MiB/s.[7]
- Queue depth sets achievable IOPS. AWS advises an average queue of one per 1,000 provisioned IOPS (“for a volume provisioned with 3,000 IOPS, the queue depth average must be 3”);[7] by Little’s law that is the queue for about 1 ms per I/O (the Atlas’s arithmetic: 3,000 × 0.001 = 3).
- gp3: 3,000 IOPS and 125 MiB/s baseline, up to 80,000 IOPS and 2,000 MiB/s at extra cost; gp2 bursts on credits, then slows.[25] The instance’s bandwidth can also be the limit.[7]
Read the right counters. /proc/diskstats counts I/Os, sectors (always 512 bytes), time spent and a weighted time that
measures “both I/O completion time and the backlog”;[55][56] iostat turns them into await (queue plus service time)
and aqu-sz (queue length).[57] Averages hide tails, which at scale “may come to dominate overall service performance”.[58]
On EBS, per-minute averages also hide micro-bursts, which VolumeIOPSExceededCheck and VolumeThroughputExceededCheck flag;
on Nitro instances, detailed performance statistics add the latency histograms needed for a 99th percentile.[7]
Benchmark honestly. Use fio with direct=1 and an asynchronous engine (iodepth above 1 does nothing on synchronous engines,
and libaio needs direct=1 to be asynchronous).[60] Google’s guide adds: fill the disk with non-zero data first, and measure
latency below the limits, because a saturated disk “pushes back on incoming I/Os”.[61] Never write-test a device holding
data; fio refuses only if it appears mounted.[60]
Kubernetes storage: PV, PVC, StorageClass and CSI
Link to section “Kubernetes storage: PV, PVC, StorageClass and CSI”A pod claims storage with a PersistentVolumeClaim (PVC); a StorageClass tells a CSI driver how to create the
PersistentVolume (PV) through calls such as CreateVolume, ControllerPublishVolume (attach) and NodePublishVolume (mount).[24]
Diagram source (Mermaid)
flowchart TB
C["PVC created with a zonal class"] --> M{"volumeBindingMode?"}
M -->|"Immediate"| I["Volume created at once, perhaps in zone b"]
I --> X["Pod needs zone a: Pending, unschedulable"]
M -->|"WaitForFirstConsumer"| W["PVC Pending: waiting for first consumer"]
W --> P["Pod created; scheduler picks a node in zone a"]
P --> V["CSI CreateVolume in zone a; PV bound to PVC"]
V --> A["Attach: ControllerPublishVolume"]
A --> N["Kubelet: NodeStageVolume, NodePublishVolume"]
N --> R["Pod Running with the volume mounted"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class M decision
class I,X risk
class C,W,P,V,A,N,R core
Text equivalent: with Immediate binding, a zonal volume is created at once, perhaps in zone b, and a pod that must run in zone a stays unschedulable. With WaitForFirstConsumer the claim waits for a pod; the volume is created in the pod’s zone, bound, attached, staged and published, and the pod runs.
Set these deliberately in every class (Kubernetes 1.37 is current in October 2026):[62]
- Binding mode.
Immediate, the default, provisions “without knowledge of the Pod’s scheduling requirements”;WaitForFirstConsumerwaits for the pod.[63] - Reclaim policy. Dynamic volumes inherit the class policy, “which defaults to Delete”: deleting the claim deletes the disk.[64]
- Access mode.
ReadWriteOncemeans one node;ReadWriteOncePod(RWOP, CSI only) means one pod; modes “do not enforce write protection once the storage has been mounted”.[64] - Expansion. Needs
allowVolumeExpansion: trueand XFS, ext3 or ext4; claims never shrink (FAQ).[64] - Snapshots. CRDs whose controller is “the responsibility of the Kubernetes distribution”; CSI drivers only.[65]
- Mutable performance. A
VolumeAttributesClass(GA since 1.34) moves a claim between settings such as IOPS and throughput when the driver implementsModifyVolume.[66] - StatefulSets.
persistentVolumeClaimRetentionPolicydecides whether claims survive deletion or scale-down (retained by default).[67]
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Source | Kind | Use it for |
|---|---|---|
| NIST SP 800-209 (2020)[5] | US federal guidance | Roles, zoning, masking, logging, isolated recovery copies |
| ISO/IEC 27040:2024 (edition 2)[6] | International standard | Storage security; NIST cites the four roles from its 2015 edition |
| NIST SP 800-88 Rev. 2 (September 2025)[35] | US federal guidance | Clear, purge (incl. cryptographic erase), destroy |
| CSI specification v1.13.0 (July 2026)[24] | Open specification | Driver behaviour |
| RFCs 7143, 8881, 7862; [MS-SMB2] | Protocol specifications | iSCSI, NFS, SMB behaviour |
| Little’s law;[29] Google SRE capacity planning[68] | Theorem; method | Queues; forecasts beyond lead time |
None is mandatory in general; sector regulation may make some so.
Tooling landscape (as of October 2026)
Link to section “Tooling landscape (as of October 2026)”| Category | Verified examples | Watch out |
|---|---|---|
| Cloud block | Amazon EBS (gp3, io2, st1, sc1),[37] Azure managed disks,[69] Google Persistent Disk[61] | Per-volume and per-instance limits; initialisation after restore |
| Cloud object | Amazon S3,[12] Azure Blob Storage,[13] Google Cloud Storage[70] | Minimum durations, sizes and retrieval times differ |
| Software-defined | Ceph,[22] Rook to run Ceph on Kubernetes (CNCF graduated)[71] | You own failure domains, upgrades, recovery |
| Volume managers, filesystems | LVM thin pools,[9] XFS, ext4, OpenZFS[31] | Thin pools must not fill; XFS is effectively grow-only[72] |
| SAN and multipath | Fibre Channel or iSCSI arrays; Linux dm-multipath[26] | Zoning and masking drift; silent path loss |
| Parallel filesystems | Lustre[73] | Striping choices; small-file metadata load |
| Kubernetes drivers | The CSI project’s list, e.g. ebs.csi.aws.com, rbd.csi.ceph.com, driver.longhorn.io[74] |
The list “has not been validated by Kubernetes SIG-Storage” |
| Measurement | fio,[60] iostat,[57] /proc/diskstats,[55] VolumeStalledIOCheck[7] |
Benchmarks on empty or saturated disks mislead |
Select by, in order: semantics and limits; failure behaviour; whether your team can run it at 03:00; unit cost; exit cost.
Automation and AI
Link to section “Automation and AI”Automate provisioning from the catalogue (InfraOps covers infrastructure as code), expansion, lifecycle rules and waste reports. Automation that deletes, detaches, shrinks or removes capacity needs a scope limit, a rate limit, a floor and a dry run (case 1); keep humans on deleting pools or buckets, shortening retention, forcing detaches and touching last copies.
AI assistants can read logs and propose causes; the PyPI storageops package does this for S3-compatible storage, and its vendor
notes that “Logs, errors and configs you give Pi go to the model provider you configure”.[75] Scrub names, keys and customer
identifiers first, and test suggestions against counters.
StorageOps in practice
Link to section “StorageOps in practice”Reference implementation: a small storage class catalogue
Link to section “Reference implementation: a small storage class catalogue”Assumptions: an organisation on AWS with one Kubernetes platform; five classes cover almost every request; FinOps sets prices.
Example status: syntax-checked (YAML parsed), illustrative; limits from AWS documentation as of October 2026.
# storage-catalogue.yaml: one entry per class; reviewed quarterly
classes:
- name: block-fast
for: "online transaction processing (OLTP) databases, latency-sensitive queues"
backing: "EBS io2 Block Express"
limits: {latency_target_ms: 1, iops_max_per_volume: 256000}
protection: "provider durability 99.999%; snapshots hourly (not a backup)"
k8s_storage_class: io2-fast
- name: block-general
for: "most VM and pod volumes"
backing: "EBS gp3"
limits: {iops_baseline: 3000, iops_max: 80000, mibps_baseline: 125, mibps_max: 2000}
protection: "provider durability 99.8-99.9%; snapshots daily"
k8s_storage_class: gp3-standard
- name: block-throughput
for: "large sequential scans, logs being processed"
backing: "EBS st1"
limits: {note: "throughput-oriented HDD; poor for random I/O"}
- name: file-shared
for: "home and project directories"
backing: "NFS 4.1 service"
rules: ["root_squash on every export", "no SMBv1, no NFSv2/3 on new shares"]
- name: object-tiered
for: "logs, media, data lake files"
backing: "S3 with lifecycle rules logs-tier and logs-expire"
rules:
- "objects of 128 KiB or less stay in STANDARD and still expire"
- "noncurrent versions expire after 30 days"
review:
owner: storage-platform-team
next_review: 2027-01-15
Object lifecycle rules. A rule’s filter scopes every action in the rule,[76] so logs/ gets two rules. logs-tier
moves objects larger than 128 KiB (131,072 bytes) to Standard-IA at 30 days and Glacier Flexible Retrieval at 120; its explicit
size filter is in line with S3’s default since September 2024 and still holds if the configuration’s default is
varies_by_storage_class, which lets small objects into Glacier.[77][78] logs-expire, filtered by prefix only, expires
objects at 400 days and old versions 30 days after they become noncurrent, and aborts uploads left incomplete for 7 days (their
parts are billed until then).[79] With the expiry actions in the size-filtered rule, objects of 128 KiB or less and their old
versions would never expire.
Example status: JSON validated against botocore’s current S3 API model on 2026-10-18; not executed against AWS.
{
"Rules": [
{
"ID": "logs-tier",
"Status": "Enabled",
"Filter": {"And": {"Prefix": "logs/", "ObjectSizeGreaterThan": 131072}},
"Transitions": [
{"Days": 30, "StorageClass": "STANDARD_IA"},
{"Days": 120, "StorageClass": "GLACIER"}
]
},
{
"ID": "logs-expire",
"Status": "Enabled",
"Filter": {"Prefix": "logs/"},
"Expiration": {"Days": 400},
"NoncurrentVersionExpiration": {"NoncurrentDays": 30},
"AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}
}
]
}
Example status: syntax-checked, not executed against AWS.
bucket=example-logs-bucket
# 1. Save the current rules, if any.
aws s3api get-bucket-lifecycle-configuration --bucket "$bucket" > lifecycle-before.json
# 2. Merge them into lifecycle.json; a second person reviews the merged file.
# 3. Apply, then read back.
aws s3api put-bucket-lifecycle-configuration --bucket "$bucket" \
--lifecycle-configuration file://lifecycle.json
aws s3api get-bucket-lifecycle-configuration --bucket "$bucket"
Capacity runway. A straight-line fit compared with lead time, as Google’s SRE book requires: a forecast “which extends beyond the lead time required for acquiring capacity”.[68]
Example status: verified by execution on 2026-10-18 (Python 3.8) with a generated 90-day illustrative series and with one-row, blank-line and same-date inputs.
#!/usr/bin/env python3
"""Capacity runway: fit daily usage, compare runway with lead time.
Input CSV lines: YYYY-MM-DD,used_tib (one per day, oldest first)
Usage: python3 runway.py usage.csv USABLE_TIB LEAD_TIME_DAYS [threshold]
"""
import csv
import sys
from datetime import date
def load(path):
rows = []
with open(path, newline="") as f:
for rec in csv.reader(f):
if not rec or not rec[0].strip():
continue # tolerate blank lines
rows.append((date.fromisoformat(rec[0].strip()), float(rec[1])))
if len({d for d, _ in rows}) < 2:
sys.exit("need rows on at least two different dates to fit a trend")
return rows
def fit(xs, ys):
n = len(xs)
mx, my = sum(xs) / n, sum(ys) / n
sxx = sum((x - mx) ** 2 for x in xs)
slope = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / sxx
return slope, my - slope * mx
def main():
path, usable, lead = sys.argv[1], float(sys.argv[2]), int(sys.argv[3])
threshold = float(sys.argv[4]) if len(sys.argv) > 4 else 0.80
rows = load(path)
xs = [(d - rows[0][0]).days for d, _ in rows]
ys = [u for _, u in rows]
slope, _ = fit(xs, ys)
used, limit = ys[-1], usable * threshold
print(f"used {used:.1f} TiB of {usable:.1f} TiB usable ({used / usable:.0%})")
print(f"growth {slope:.3f} TiB/day over {len(rows)} days")
if slope <= 0:
print("no growth trend: review again next cycle")
return
runway = (limit - used) / slope
print(f"runway to {threshold:.0%}: {runway:.0f} days; lead time {lead} days")
if runway <= lead:
print("ACT NOW: runway is shorter than lead time")
elif runway <= 2 * lead:
print("ORDER: start procurement or expansion this cycle")
else:
print("OK: re-forecast next cycle")
if __name__ == "__main__":
main()
On 90 days growing from about 300 to 353 TiB on a 500 TiB pool with a 60-day lead time it printed runway to 80%: 78 days; lead time 60 days and ORDER: start procurement or expansion this cycle. Add known one-off demand by hand.[68]
Grow a volume end to end
Link to section “Grow a volume end to end”Growth touches four layers: device, partition, volume manager, filesystem. On a SAN the device step has three parts: the LUN on the array, each path, and the multipath map.[52] SysOps covers the common EBS-plus-partition case; this is the full chain with LVM.
Example status: syntax-checked, not executed (needs root on a disposable VM). Names are placeholders.
# 0. Snapshot first (cloud) or confirm a tested backup exists (BackupOps).
# 1. Grow the device: cloud console/API, or the array; then confirm the kernel sees it.
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT /dev/nvme1n1
# 1b. SAN LUN on multipath: rescan every path, then resize the map, and use the
# /dev/mapper device in place of the NVMe device and partition below.
for p in sdc sdd; do echo 1 | sudo tee "/sys/block/$p/device/rescan"; done
sudo multipathd resize map mpatha
sudo multipath -ll
# 2. Grow the partition if the PV sits on one (growpart needs some free space in /tmp).
sudo growpart /dev/nvme1n1 1
# 3. Tell LVM the physical volume is bigger, then grow the LV and its filesystem together.
sudo pvresize /dev/nvme1n1p1
sudo lvextend -r -L +200G /dev/vg_data/lv_pg
# 4. Verify at every layer.
sudo pvs; sudo vgs; sudo lvs vg_data; df -hT /var/lib/pgsql
lvextend -r grows the filesystem with the logical volume through fsadm;[81] pvresize works on a physical volume that
already holds active logical volumes.[82] Without LVM, grow XFS with xfs_growfs <mount point> (mounted) and ext4 with
resize2fs <device> (online growth supported).[72][83][84] Sizes are one-way (FAQ). With
active/passive multipath arrays, also filter the underlying path disks out of lvm.conf, then rebuild the initrd
(update-initramfs -u -k all on Ubuntu).[26]
Hands-on labs
Link to section “Hands-on labs”Lab 1 (beginner): read the three numbers and the queue from the kernel
Link to section “Lab 1 (beginner): read the three numbers and the queue from the kernel”Objective: compute IOPS, throughput, I/O size, latency, queue and utilisation from /proc/diskstats and check Little’s law.
Prerequisites: any Linux host with Python 3.6+; no privileges; 10 minutes. Cost and risk: none; read-only.
Example status: verified by execution on 2026-10-18 (Ubuntu, kernel 5.15, Python 3.8, unprivileged).
#!/usr/bin/env python3
"""Sample /proc/diskstats twice and print the three numbers plus the queue.
Usage: python3 diskstats_sample.py [seconds] [device ...]
Read-only; needs no privileges. Field numbers follow the kernel's iostats.rst.
"""
import sys
import time
def read_stats():
stats = {}
with open("/proc/diskstats") as f:
for line in f:
p = line.split()
stats[p[2]] = [int(x) for x in p[3:14]] # fields 1..11
return stats
def main():
secs = float(sys.argv[1]) if len(sys.argv) > 1 else 5.0
wanted = sys.argv[2:]
a = read_stats()
t0 = time.monotonic()
time.sleep(secs)
b = read_stats()
ms = (time.monotonic() - t0) * 1000.0
print(f"{'device':<12}{'r/s':>8}{'w/s':>8}{'MiB/s':>8}{'avgKiB':>8}"
f"{'await':>8}{'aqu-sz':>8}{'util%':>7}{'L=XW':>7}")
for dev in sorted(b):
if (wanted and dev not in wanted) or dev.startswith(("loop", "ram")):
continue
d = [y - x for x, y in zip(a.get(dev, b[dev]), b[dev])]
reads, sect_r, ms_r, writes, sect_w, ms_w = d[0], d[2], d[3], d[4], d[6], d[7]
io_ms, weighted_ms = d[9], d[10]
ios = reads + writes
if ios == 0:
continue
iops = ios / (ms / 1000.0)
mib_s = (sect_r + sect_w) * 512 / 1048576 / (ms / 1000.0) # 512-byte units
avg_kib = (sect_r + sect_w) * 512 / 1024 / ios
await_ms = (ms_r + ms_w) / ios
aqu = weighted_ms / ms # average I/Os in flight
util = 100.0 * io_ms / ms
little = iops * await_ms / 1000 # Little's law: L = X * W
print(f"{dev:<12}{reads / (ms / 1000):>8.1f}{writes / (ms / 1000):>8.1f}"
f"{mib_s:>8.2f}{avg_kib:>8.1f}{await_ms:>8.2f}{aqu:>8.2f}"
f"{util:>7.1f}{little:>7.2f}")
if __name__ == "__main__":
main()
- Save as
diskstats_sample.py; runpython3 diskstats_sample.py 5. - Repeat during busy work, or name a device:
python3 diskstats_sample.py 10 nvme0n1.
Expected output (shape): one line per active device and partition; on the research host under light load
nvme0n1 1.8 15.0 0.30 18.2 0.99 0.02 3.1 0.02.
Validation: aqu-sz and L=XW agree to rounding; they come from different counters,[55] so agreement shows the law at work.
Troubleshooting: no line → no I/O completed; lengthen the window. Counters reset at boot or re-attach.[55] util% near
100 with low latency is normal on parallel devices.[57]
Teardown: none.
Lab 2 (intermediate): watch latency follow queue depth with fio
Link to section “Lab 2 (intermediate): watch latency follow queue depth with fio”Objective: 4 KiB random reads at queue depths 1, 4, 16 and 64: watch IOPS level off while latency rises. Prerequisites: a disposable Linux VM with 6 GiB free and fio; 20 minutes. Cost and risk: uses provisioned IOPS and may slow neighbours on shared storage; writes only its own file.
Example status: syntax-checked, not executed (fio was not available on the research host).
#!/usr/bin/env bash
# lab2.sh: random-read IOPS and latency versus queue depth on a test file.
set -euo pipefail
dir=${1:?usage: lab2.sh /path/on/test/filesystem}
file="$dir/fio-lab2.dat"
fio --name=fill --filename="$file" --size=4G --rw=write --bs=1M \
--direct=1 --ioengine=libaio --iodepth=8 >/dev/null # fill with data first
for qd in 1 4 16 64; do
fio --name="qd$qd" --filename="$file" --size=4G --rw=randread --bs=4k \
--direct=1 --ioengine=libaio --iodepth="$qd" --runtime=30 --time_based \
--ramp_time=5 --group_reporting --output-format=json > "qd$qd.json"
python3 - "qd$qd.json" "$qd" <<'PY'
import json, sys
job = json.load(open(sys.argv[1]))["jobs"][0]["read"]
lat_ms = job["clat_ns"]["mean"] / 1e6
p99_ms = job["clat_ns"]["percentile"]["99.000000"] / 1e6
print(f"qd={sys.argv[2]:>3} iops={job['iops']:>9.0f} mean_ms={lat_ms:6.2f} "
f"p99_ms={p99_ms:6.2f} little_qd={job['iops'] * lat_ms / 1000:5.1f}")
PY
done
rm -f "$file"
Expected output (shape): IOPS rise, then flatten while mean and p99 latency climb; on a 3,000-IOPS gp3 volume IOPS cap near
3,000 beyond a depth of about 3.
Validation: little_qd ≈ qd; the flattening point matches the documented limit.[25]
Troubleshooting: the same IOPS at every depth means a synchronous engine or buffered I/O; check --ioengine=libaio --direct=1.[60] A missing percentile key means an older fio; read clat_ns by hand.
Teardown: the script deletes its file; delete the JSON files and the VM.
Lab 3 (advanced): a Kubernetes class with snapshot, restore and expansion
Link to section “Lab 3 (advanced): a Kubernetes class with snapshot, restore and expansion”Objective: a zonal gp3 class that waits for the first consumer; write data, snapshot, restore into a new claim, expand online.
Prerequisites: a disposable EKS cluster (Kubernetes 1.35–1.37, not Auto Mode) with the EBS CSI driver ebs.csi.aws.com,
snapshot CRDs and controller, and an IAM role for the driver’s controller (AWS suggests EKS Pod Identity; managed policy or a
narrower one);[65][85] 30 minutes. Cost and risk: two small volumes and a snapshot for an hour. Tip: the driver’s encrypted default is false;
EBS encryption by default, set per Region, encrypts every new volume regardless.[86][87]
Example status: YAML and bash syntax-checked, not executed (no cluster). Parameter names from the EBS CSI driver documentation (release v1.67.0).[86]
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-lab
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "4000"
throughput: "250"
encrypted: "true"
reclaimPolicy: Delete # lab only; production data classes often use Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ebs-lab
driver: ebs.csi.aws.com
deletionPolicy: Delete
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: storage-lab
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-lab
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
name: writer
namespace: storage-lab
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "date > /data/hello.txt && sleep 3600"]
volumeMounts:
- {name: data, mountPath: /data}
volumes:
- name: data
persistentVolumeClaim: {claimName: data}
kubectl create namespace storage-lab
kubectl apply -f lab3.yaml
kubectl -n storage-lab get pvc data -w # Pending, then Bound; Ctrl-C to stop watching
kubectl -n storage-lab exec writer -- cat /data/hello.txt
cat <<'YAML' | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata: {name: data-snap, namespace: storage-lab}
spec:
volumeSnapshotClassName: ebs-lab
source: {persistentVolumeClaimName: data}
YAML
kubectl -n storage-lab get volumesnapshot data-snap -w # wait for READYTOUSE true
cat <<'YAML' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: data-restored, namespace: storage-lab}
spec:
storageClassName: gp3-lab
accessModes: ["ReadWriteOnce"]
resources: {requests: {storage: 10Gi}}
dataSource: {name: data-snap, kind: VolumeSnapshot, apiGroup: snapshot.storage.k8s.io}
---
apiVersion: v1
kind: Pod
metadata: {name: reader, namespace: storage-lab}
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "cat /data/hello.txt && sleep 3600"]
volumeMounts: [{name: data, mountPath: /data}]
volumes: [{name: data, persistentVolumeClaim: {claimName: data-restored}}]
YAML
kubectl -n storage-lab wait --for=condition=Ready pod/reader --timeout=300s
kubectl -n storage-lab logs reader # the date the writer pod stored
kubectl -n storage-lab patch pvc data \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
until [ "$(kubectl -n storage-lab get pvc data \
-o jsonpath='{.status.capacity.storage}')" = "20Gi" ]; do sleep 10; done
kubectl -n storage-lab exec writer -- df -h /data
Expected output (shape): data is Pending (“waiting for first consumer to be created before binding”) until the pod exists,
then Bound;[88] cat prints a date; the snapshot becomes READYTOUSE true; the resize is asynchronous, so the loop
waits until capacity reads 20Gi, and df then shows about 20G.
Validation: reader prints the same date as writer (data-restored binds only once reader exists; EBS restores start
slow).[8]
Troubleshooting: snapshot never ready → controller or CRDs missing (kubectl get crd | grep snapshot); ProvisioningFailed →
driver permissions or parameters; no expansion → allowVolumeExpansion missing or unsupported filesystem.[64]
Teardown: kubectl delete namespace storage-lab, then the cluster-scoped classes: kubectl delete storageclass gp3-lab and
kubectl delete volumesnapshotclass ebs-lab. Confirm with kubectl get pv and the console that volumes and snapshot are gone.
Use cases and scenarios
Link to section “Use cases and scenarios”1. Capacity removal that went too far (sourced: AWS S3, 2017). An engineer “using an established playbook” mistyped an input and removed servers behind the index subsystem, which serves every GET, LIST, PUT and DELETE in the region; it needed “a full restart”, and PUT returned at 13:54 PST, more than four hours after the 09:37 removal. AWS made the tool remove capacity more slowly and refuse to take “any subsystem below its minimum required capacity level”.[34] Success criterion: every destructive tool has a floor, a rate limit and a tested refusal.
2. Replication without headroom (sourced: Amazon EBS, 2011). Nodes that lost their replicas all searched for space to re-mirror, exhausted free capacity and “did not back off aggressively enough”; about 13% of the zone’s volumes stuck, and 0.07% “could not be restored for customers in a consistent state”.[10] Trade-off: self-healing that suits one failure swamps a cluster in a thousand. Success criterion: a per-pool headroom target (re-protect the largest failure domain) and recovery traffic that backs off.
3. Shared scratch for supercomputing (sourced: NERSC Perlmutter). A 44 PB all-flash Lustre scratch can purge files unread for eight weeks once it passes a fullness threshold, stripes files over one storage target by default (most are small) and lets users stripe large files wider before writing;[89][73] its tape archive wants small files bundled.[36] Lesson: the policy is part of the design (ResearchDataOps covers what researchers keep).
4. A colder class that raised the bill (illustrative). An application wrote its 50 KB log objects straight to Standard-IA, where each is billed as 128 KB:[12] billed bytes rose 2.56-fold. Bundling first fixed it (break-even).
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”| KPI | Formula | Source | Pitfall | Cadence |
|---|---|---|---|---|
| Utilisation | used ÷ usable (after protection overhead) | Array, Ceph, cloud | Raw capacity overstates usable space | Daily |
| Subscription ratio | provisioned ÷ physical (thin pools) | lvs, array reports |
Above 1 is a bet | Weekly |
| Runway | (threshold × usable − used) ÷ daily growth, vs lead time | Forecast | Misses step changes | Monthly |
| Performance headroom | peak IOPS ÷ provisioned; peak MiB/s ÷ provisioned | Volume metrics | Hourly averages hide peaks | Weekly |
| Latency SLI | share of minutes with read latency under the class target | Volume metrics, latency histograms | Per-minute averages hide micro-bursts[7] | Continuous |
| Redundancy exposure | hours per month any pool ran degraded | md degraded,[39] Ceph health |
One long rebuild outweighs many short | Monthly |
| Scrub currency | share of pools scrubbed in interval; errors found | zpool status, mismatch_cnt |
Zero errors may mean no scrubs | Monthly |
| Unit cost | spend ÷ usable TiB-month; per million requests | Billing | Ignores retrieval and minimums | Monthly |
| Waste | unattached volumes, orphaned snapshots, incomplete uploads ÷ spend | Inventory | Deleting a snapshot may not free space[14] | Monthly |
Runway, headroom and subscription ratio lead; incidents from full or slow storage lag. Avoid vanity numbers: raw petabytes, average
latency across all volumes, and %util on SSDs.[57]
Worked calculation (the Atlas’s arithmetic). A Ceph cluster has 12 hosts × 10 drives × 20 TB = 2,400 TB ≈ 2,183 TiB raw. With erasure coding 4+2, overhead 1.5,[22] usable ≈ 1,455 TiB (three replicas: ≈ 728 TiB). If a host fails, the 11 survivors must absorb its data, and by default Ceph refuses backfill to a device above 90% full and stops client I/O at 95%.[32] So re-protection needs used ≤ 11/12 × 0.90 ≈ 82.5% before any imbalance between devices, and below about 78% if the 85% nearfull warning should stay quiet. An 80% threshold gives plannable ≈ 1,164 TiB with a thin margin. With 1,020 TiB used and 1.2 TiB/day growth, runway = (1,164 − 1,020) ÷ 1.2 = 120 days; with a 90-day lead time, order within 30 days.
Security, privacy and compliance
Link to section “Security, privacy and compliance”NIST SP 800-209 is a detailed public checklist for storage security;[5] the quarterly review applies its essentials, plus separate traffic networks (trust boundaries) and iSCSI authentication, which “does not provide encryption or integrity protection to the channel”. Encrypt at rest by default and leave key custody with security: a lost key is lost data. New S3 buckets block public access by default;[90] exposure guardrails belong to CloudSecOps. Erasure requests reach storage as deletes, versions and snapshots; policy belongs to PrivacyOps.
Retiring storage. Owned media leaving service are cleared, purged or destroyed (NIST SP 800-88 Rev. 2), with each device’s method and result recorded. Cloud volumes and buckets differ: the data owner “has no direct access to the physical ISM” (information storage media), so for such logical storage “cryptographic erase may be the only viable purge sanitization technique option”.[35] Decide before sensitive data arrives: encrypt from the first write under a customer-managed key whose every copy you can destroy; at retirement delete volumes, snapshots and object versions, destroy the key where the data’s sensitivity requires it. NIST warns against trusting cryptographic erase where data or keys were backed up or escrowed without firm knowledge of where the keys are.[35]
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”Cost drivers: capacity after protection overhead, provisioned performance, requests, retrievals, transfer, and rules:
| Provider (October 2026) | Minimum storage duration in colder tiers | Other rules |
|---|---|---|
| Amazon S3[12] | Standard-IA, One Zone-IA 30 days; Glacier Instant and Flexible Retrieval 90; Deep Archive 180 | 128 KB minimum billable size (IA, Instant Retrieval); 40 KB metadata per archived object |
| Azure Blob Storage[13] | Cool 30 days; cold 90; archive 180 | Early-deletion charges; archive rehydration “can take up to 15 hours” |
| Google Cloud Storage[70] | Nearline 30 days; Coldline 90; Archive 365 | Retrieval fees on colder classes |
Break-even for small objects (the Atlas’s arithmetic). Let p < 1 be the colder class’s price per GB relative to the hot class. An object of s KB billed as 128 KB saves storage only if p × 128 < s: at p = 0.5 it must exceed 64 KB, before transition and retrieval charges. AWS’s stated reason for its default since September 2024 (no transitions under 128 KB) is the per-object transition charge: “the transition costs can outweigh the storage savings”.[77] The billable minimum is a second.
Capacity. Derive alerts from runway: a 70% alert on a pool growing two points a week leaves about six weeks before an 82.5% re-protection ceiling, less than an eight-week lead time.
Sustainability. Storage draws power around the clock, so the cost levers also save energy: delete what nobody needs, tier cold data to denser media, prefer erasure coding to triple replication where performance allows; GreenOps measures.
Assurance of the operating model
Link to section “Assurance of the operating model”Track catalogue conformance, forecast accuracy, drills (fail a disk or node in a test pool quarterly and time the rebuild; AWS Fault Injection Service can pause I/O or inject latency on EBS to test alerts),[7] change records for destructive operations, and sanitisation records matched to inventory.
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Remediation | Prevention |
|---|---|---|---|---|
| High latency, IOPS flat at a round number | IOPS limit | IOPS vs provisioned; queue far above 1 per 1,000[7] | Raise IOPS or cut I/O | Alert at 80% |
| Throughput flat, IOPS below limit | Throughput limit, large I/O | I/O size × IOPS ≈ MiB/s limit[7] | Raise throughput; check instance | Size classes for both |
| New volume slow for hours | Initialising from snapshot | Created from snapshot?[8] | Wait; initialisation rate; fast restore | Pre-initialise |
| gp2 fast, then slow | Burst credits gone | Burst balance near zero[25] | Move to gp3 | No credit classes for steady load |
| Writes fail on thin volume | Thin pool full | lvs Data% 100[9] |
Extend pool; repair filesystems | Autoextend; runway alerts |
| Array slow after disk loss | Rebuild competing | /proc/mdstat, Ceph recovery |
Tune rebuild rate[39] | Spares; double parity |
| Checksum errors in scrub | Corruption, failing device or path | zpool status -v, mismatch_cnt[31][39] |
Repair; replace device | Regular scrubs |
| SAN volume slow or hung after a fabric event | Paths down; I/O queued with none left | multipath -ll: paths faulty or shaky[52] |
Restore paths; review no_path_retry[53] |
Two fabrics; path alerts |
| S3 503 Slow Down | Request rate above prefix capacity | 503s by prefix | Back off; spread prefixes[51] | Key layout for parallelism |
FailedAttachVolume |
Still attached elsewhere | Events; VolumeAttachments | Runbook below | Proper drains; RWOP |
Pod Pending, volume in another zone |
Immediate binding |
PV vs pod affinity | New class; move data | Class review |
Worked runbook: a volume that is slow, full or will not attach
Link to section “Worked runbook: a volume that is slow, full or will not attach”Trigger: an alert or a user report about one volume. Rule zero: delete, detach or rebuild nothing until you have named the layer at fault.
Diagram source (Mermaid)
flowchart TB
Q{"What is wrong?"} -->|"slow"| S["Measure the three numbers; compare with class limits"]
Q -->|"full"| F["Find the full layer; grow bottom-up; thin pool first"]
Q -->|"will not attach"| A1["Read pod events and VolumeAttachments"]
A1 --> A2{"Old node state?"}
A2 -->|"alive"| A3["Stop the old pod properly; wait for detach"]
A2 -->|"confirmed off"| A4["Taint out-of-service; volume detaches"]
A2 -->|"unknown"| A5["Escalate; never force a detach"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class Q,A2 decision
class A5 risk
class S,F,A1,A3,A4 core
Text equivalent: slow, measure the three numbers against class limits; full, find the full layer and grow bottom-up, thin pool first; will not attach, read pod events and VolumeAttachments, then act on the old node’s state: alive, stop the old pod and wait for detach; confirmed off, apply the out-of-service taint; unknown, escalate and never force.
Slow. (1) Measure on the host (Lab 1 or iostat -x 1) and in provider metrics for the same minutes. (2) Compare with class
limits: IOPS-bound or throughput-bound?[7] (3) Neither: check instance bandwidth, snapshot initialisation,[8] gp2
credits,[25] rebuilds, and VolumeStalledIOCheck, which flags a volume that cannot complete I/O.[7] (4) Fix the named
limit and verify latency.
Full. (1) df -hT and df -i (SysOps covers inodes and deleted-but-open files). (2) For thin pools,
lvs -o name,data_percent,metadata_percent; at 100%, writes queue for 60 seconds by default and then fail,[9] so extend the
pool first (lvextend -L +<size> <vg>/<pool>), then check filesystems. (3) Grow layers bottom-up
(grow a volume end to end); EBS allows four modifications per volume in 24 hours.[59]
Will not attach (Kubernetes). (1) kubectl describe pod: for a volume attached elsewhere, FailedAttachVolume starts
“Multi-Attach error” up to Kubernetes 1.36 and “Waiting for detach” from 1.37;[91][92] FailedMount points at the node side.[93]
(2) kubectl get volumeattachment names the holding node; check its CSI driver logs and attach limit (kubectl get csinode).[94]
(3) Old node healthy: drain or delete the old pod properly and wait. (4) Node confirmed shut down: verify first that it “is
already in shutdown or power off state”,[95] then add the out-of-service taint with the NoExecute effect (below); pods
are force-deleted and volumes detached at once.[96] Remove the taint once the node has recovered (a trailing - removes
it).[97] (5) Unknown: see the judgement call.
Example status: syntax-checked, not executed (no cluster).
node=NODE_NAME # only a node confirmed powered off
kubectl taint nodes "$node" node.kubernetes.io/out-of-service=nodeshutdown:NoExecute
# after the node has recovered and its pods run elsewhere:
kubectl taint nodes "$node" node.kubernetes.io/out-of-service=nodeshutdown:NoExecute-
Afterwards: record the layer at fault and add the alert that would have caught it.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- One class for everything: some workloads slow, others overpaid. Build a small catalogue on access patterns.
- RAID 5 under write-heavy databases: four disk operations per small write.[16] Use mirrors or SSD classes.
- Tiering by age alone: small or short-lived objects can cost more in colder classes.
- Destructive tools without floors: a typo should not remove most of a system’s capacity.[34]
- Myth: “A million-hour MTTF means disks rarely fail.” Field rates ran several times the datasheet figure (rebuilds are exposure).[30]
Deciding and adopting
Link to section “Deciding and adopting”When to use StorageOps and when not to
Link to section “When to use StorageOps and when not to”A named storage practice pays off with several storage systems; latency-sensitive stateful workloads; growth where lead time matters; self-run storage where you own rebuilds; Kubernetes platforms serving many teams; or regulated data that must be sanitised and audited.
When not to: a small team on managed services with modest data can keep storage as a checklist inside CloudOps or SysOps: the provider’s general class, encryption, lifecycle rules, two alerts. Do not self-run software-defined storage to save money unless you can staff its 03:00 failures. If the problem is lost data, start with BackupOps.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
S["New workload"] --> Q4{"`Can the team run storage
at 03:00, at a scale that pays?`"}
Q4 -->|"no"| M["Managed services only"]
Q4 -->|"yes"| SR["`Self-run allowed:
Ceph, ZFS, arrays;
you own failure domains,
headroom and rebuilds`"]
M --> Q1
SR --> Q1{"Whole objects fetched by key?"}
Q1 -->|"yes"| O["`Object storage: cold classes
only for objects of 128 KB or more`"]
Q1 -->|"no"| Q2{"Files shared by many hosts?"}
Q2 -->|"yes"| F["File storage: NFS or SMB"]
Q2 -->|"no"| Q3{"`Small random I/O,
latency-critical?`"}
Q3 -->|"yes"| B1["Block: provisioned-IOPS SSD"]
Q3 -->|"no"| Q5{"`Only large
sequential streams?`"}
Q5 -->|"yes"| B2["Block: throughput HDD"]
Q5 -->|"no"| B3["`Block: general-purpose SSD,
the default`"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class Q1,Q2,Q3,Q4,Q5 decision
class S,O,F,B1,B2,B3,M core
class SR risk
Text equivalent: a team that cannot run storage at 03:00, at a scale that pays, uses managed services only; otherwise self-run Ceph, ZFS or arrays are allowed (riskier: you own failure domains, headroom and rebuilds). Either way, objects fetched whole by key go to object storage (cold classes only for objects of 128 KB or more), and files shared by many hosts to NFS or SMB. Block storage goes to provisioned-IOPS SSD for latency-critical small random I/O, throughput HDD only for large sequential streams, and general-purpose SSD, the default, for everything else.
AWS recommends its general-purpose SSD volumes “for most workloads”, and its HDD types “deliver optimal performance only when I/O operations are large and sequential”.[25][7] Self-running pays only if its lower cost per usable TiB survives the staff, hardware cycles and lock-in.
Maturity model
Link to section “Maturity model”This maturity model is the Atlas’s proposal; there is no official StorageOps maturity model.
| Level | Observable behaviour | Evidence | Exit criterion |
|---|---|---|---|
| 0 Ad hoc | Storage chosen per request; users find full disks | Incident tickets | An inventory exists |
| 1 Inventoried | Every pool, volume and bucket has an owner and class label | Inventory | Capacity and degradation alerts on all pools |
| 2 Catalogued | Priced catalogue with documented limits; scrubs scheduled | Catalogue, scrub reports | 90% of new storage from the catalogue |
| 3 Measured | Runway vs lead time; latency SLIs per class; headroom targets | Forecasts, SLI dashboard | Two quarters without capacity or saturation surprises |
| 4 Guarded self-service | Provisioning, expansion, tiering automated; destructive tools have floors; drills run | Pipelines, drill reports | Sustained; reviewed yearly |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Window | Actions | Milestone | Metric |
|---|---|---|---|
| Days 1–30 | Inventory with owners; capacity, degradation and stalled-I/O alerts; list unattached volumes | Inventory | Share of storage with an owner |
| Days 31–60 | 3–6 classes from measured patterns, each benchmarked; Kubernetes classes with explicit binding and reclaim; scrub schedules | Catalogue v1 | Share of new storage from the catalogue |
| Days 61–90 | Monthly runway review; priced lifecycle rules; floors on destructive tools; first rebuild drill | First forecast | Runway per pool; drill time |
Quick wins: delete unattached volumes and orphaned snapshots after sign-off, abort incomplete uploads, move steady gp2 volumes to gp3 (AWS cites prices up to 20% lower).[25]
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”New class readiness
- Access patterns served and refused
- Limits from the provider or a benchmark on filled data
- Failure domains, re-protection time, headroom target
- Encryption, roles, audit logging
- Snapshot and replication policy linked to the backup policy
- Unit price and lifecycle rules agreed with FinOps
- Kubernetes: binding mode, reclaim policy, expansion, snapshot class
Before a destructive change
- Scope checked by a second person; dry run done
- Floor and rate limit set; independent copy confirmed
Monthly review
- Runway vs lead time; headroom and subscription ratios
- Scrub completion and errors; degraded hours
- Waste report; last month’s forecast error
Decommissioning
- Owner released the data; retention met
- Owned media: clear, purge or destroy recorded per device (NIST SP 800-88)
- Cloud: volumes, snapshots and versions deleted; key destroyed where required; recorded per volume or bucket
Quarterly security review
- Storage administrator and auditor roles held by different people; leavers removed
- Zoning, masking and export lists match the inventory; default zone denies all
- No SMB 1 or NFS 1 and 2; root squashed; management interfaces on an admin network
- Audit logs reach the central store; key custody and deletion rights reviewed
Templates
Link to section “Templates”Storage request (intake form).
# storage-request.yaml
requester: team-payments
workload: "ledger database (PostgreSQL 17)"
interface: block # block | file | object
access_pattern:
io_size_kib: 8-16
random_or_sequential: random
read_write_ratio: "70:30"
peak_iops: 9000
peak_mibps: 150
latency_target_ms_p99: 5
size:
initial_gib: 800
growth_gib_per_month: 60
safety:
survive: ["disk", "node", "availability zone (via replica)"]
snapshot: "hourly, keep 48"
backup_policy_ref: "BKP-PAY-01" # BackupOps owns this
sensitivity: confidential
retention_after_release_days: 30
cost_centre: CC-1234
Class card (one per catalogue entry): name; workloads served and refused; backing system; limits; protection and failure domains; snapshot and replication defaults; headroom target; alerts; unit price; owner; next review.
Capacity review record.
Pool: ceph-rgw-prod-1 Date: 2026-10-18 Reviewer: <name>
Usable TiB: 1,455 Used TiB: 1,020 Threshold: 80% Re-protect ceiling: 82%
Growth (90 d): 1.2 TiB/day Runway to threshold: 120 days Lead time: 90 days
Known inorganic demand: archive migration, +80 TiB in December
Decision: order 2 hosts by 2026-11-15 Owner: <name> Next review: 2026-11-18
Destructive change record: change ID; exact scope; command and dry-run output; floor checked; rate limit; second approver; recovery path; window; result.
Deep comparisons
Link to section “Deep comparisons”| Scheme | Space overhead | Survives | Small-write cost | Best for |
|---|---|---|---|---|
| Mirroring (RAID 1/10) | 2× | One device per mirror | 2 writes | Random-write databases |
| Single parity (RAID 5) | n/(n−1) | One device | 4 I/Os[16] | Read-mostly data, small drives |
| Double parity (RAID 6) | n/(n−2) | Two devices | More than RAID 5 | Large drives |
| Erasure coding k+m | (k+m)/k, 1.5 for 4+2[22] | m chunks across hosts or racks | Higher CPU and network | Large object and file stores |
| Three replicas | 3× | Two copies | 3 writes | Metadata, small objects, simplicity |
Is StorageOps a job title? Not in our searches. The work sits with storage, infrastructure or platform engineers; NIST SP 800-209 names storage administrator and auditor roles.[5]
Is a snapshot a backup? Only if independent of its source (credentials, account, system) and proven by a restore; BackupOps sets the test.
Why do I get fewer IOPS than provisioned? Too few I/Os in flight, I/O so large that throughput binds first, an instance limit, or a volume still initialising from a snapshot.[7][8]
Can I shrink a volume? Generally not in place: EBS volumes cannot shrink, XFS shrinks only in a limited way, ext4 only offline, and Kubernetes does not shrink claims. Copy to a smaller volume.[59][72][83][64]
Should databases use Kubernetes volumes? They can, with the right class (zonal binding, Retain, tested expansion and
snapshots) and a team that understands detach on node failure; DBOps weighs managed databases.
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Denser drives: shingled (SMR) drives raise capacity but care about workload shape, which in Backblaze’s words makes qualification less “swap the drive” and more “plan the project”; HAMR raises density another way, and the two can be combined.[27]
- Changed-block metadata: CSI defines group snapshots and an alpha service reporting the blocks changed between snapshots.[24]
“Self-driving” storage claims are marketing until they publish failure behaviour.
Learning path
Link to section “Learning path”- Prerequisites: Linux filesystems and mounts (SysOps); TCP/IP basics.
- One day: this page to the decision framework; Lab 1; your cloud’s volume-type page.
- One week, on a disposable VM: Lab 2; fill an LVM thin pool on a loop device; one class card.
- Thirty days: Lab 3; a draft catalogue; one pool’s forecast; one past incident replayed against the runbook.
- Specialise: Ceph and Rook, HPC filesystems, storage security or CSI drivers. Portfolio: benchmark three classes on filled disks.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Topic | Remember |
|---|---|
| The label | Rare; means storage administration |
| The model | Throughput = IOPS × I/O size; in flight = IOPS × latency |
| Choosing | Shape, size, safety, spend; objects, files or blocks |
| Protection | Failure domains; m ≥ 2 for large pools; scrub; snapshots are not backups |
| Capacity | Runway vs lead time; room to re-protect after the largest failure; never fill thin pools |
| Cost | Minimum durations and sizes; requests; retrieval; bundle small objects |
| Kubernetes | WaitForFirstConsumer, explicit reclaim policy, expansion, snapshot CRDs |
| Danger | Floors, rate limits, dry runs, a second person |
A judgement call: force the detach or wait?
Link to section “A judgement call: force the detach or wait?”Situation (illustrative). At 02:10 a node hosting a PostgreSQL primary stops reporting. A replica has been promoted, so the
service is up with one replica fewer. The old pod is stuck Terminating; its replacement waits on FailedAttachVolume because the
EBS volume is still attached to the old node, which the console shows as “running” but unreachable.
| Option | For | Against |
|---|---|---|
A. Taint the node out-of-service now |
Immediate force-delete and detach;[95] redundancy back sooner | Requires the node to be shut down; if it still writes, two writers can corrupt the volume |
| B. Stop the instance by API, confirm, then taint | Meets the precondition; detach is then safe | Takes minutes and cloud access |
| C. Wait | No risky action | Kubernetes force-detaches after 6 minutes if the node is unhealthy, and warns volumes “might encounter data corruption” if a workload still runs there[95] |
Assumptions: service already failed over, so minutes cost redundancy, not availability; cloud API access exists; no evidence the node is off.
Decision: B. A is fast, but its safety rests on an unchecked precondition, and “running but unreachable” is exactly when the old
pod may still write; stopping the instance turns an unknown into a known. Also decide whether to keep the 6-minute forced detach
(disable-force-detach-on-timeout turns it off, at the price of manual recovery).[95] Uncertainty: had the service
not failed over, faster action would be justified, still after confirming the node is off.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you can explain what StorageOps covers and why the label is informal; calculate throughput, queue and runway; choose an interface and class; design a Kubernetes class; run the slow, full and attach runbook; decide when not to self-run storage.
- A volume shows 1,000 IOPS, 250 MiB/s and rising latency; its limits are 3,000 IOPS and 250 MiB/s. Why will more IOPS not help?
Show answer for question 1
1,000 × 256 KiB = 250 MiB/s: it is at its throughput limit with large I/O, AWS’s own example.[7] Raise throughput or reduce data moved.
- An application keeps 32 reads in flight on a device that completes at most 8,000 per second. Expected latency? And at 16?
Show answer for question 2
Little’s law: 32 ÷ 8,000 = 4 ms.[29] If 16 in flight still keep the device at 8,000 per second, latency halves to 2 ms with no loss of IOPS: the extra queue was only waiting.
- A team wants erasure coding 4+1 on five Ceph hosts. What do you say?
Show answer for question 3
No: m=1 is strongly discouraged, and 4+2 needs at least six hosts, ideally seven.[22] Five hosts allow 2+2 (overhead 2.0; five = k+m+1) or 3+2 (overhead 1.67, no spare host for rebuilds), or use replication, or add hosts.
- A pool is 70% full, grows 1.5 points a week, and expansion takes ten weeks. The alert is at 85%. Enough?
Show answer for question 4
No. From 85% the pool is full in ten weeks, exactly the lead time, with no room for rebuilds or forecast error. From 70% it reaches 90% in about 13 weeks: order now, and alert on runway versus lead time.
- You must move 50 KB objects to a colder S3 class priced at 40% of Standard per GB. Do you save on storage?
Show answer for question 5
No: each object is billed as 128 KB,[12] and 128 × 0.4 = 51.2 KB-equivalents, more than 50, before transition and retrieval charges. Bundle first or stay in Standard.
Conclusion
Link to section “Conclusion”StorageOps is an old job under a rarely used name, and its substance has not changed since the RAID report: disks are slow, they fail, and they fail together. What has changed is where the physics hides: in a cloud volume’s published limit, a cold tier’s minimum duration, a StorageClass field. The durable skill is to read those numbers as physics: measure the shape of the work, name the limit it meets, keep room to heal, and make every destructive action slow enough for a second thought.
Sources and further reading
Link to section “Sources and further reading”Re-check quarterly: EBS limits and modification rules, S3 lifecycle defaults, Kubernetes event text and feature states, and every named version.
Changelog
- 2026-10-18: first version (Lab 1 and the runway script executed), condensed the same day to the length band.
- 2026-10-18 (review r1): Ceph re-protection ceiling corrected (about 82%, was 92%); general-purpose SSD default in the decision tree; lifecycle overwrite and expiry warning; SAN and NAS operations; cloud crypto-erase; minor fixes; 17 sources added.
- 2026-10-18 (score r2): lifecycle example split so small objects also expire; snapshot mechanics and consistency; NFS export example; ALUA, backfill and failure domain glossed; duplicates cut; re-check list.
- 2026-10-18, post-score polish: S3 expiry wording; initrd step; snapshot-lock source; Figure 6 self-run note; two lists.
StorageOps Overview — Opsio (consultancy), published 2025-09-02, updated 2025-10-23. Checked 2026-10-18. ↩︎
storageops package metadata (JSON) — Python Package Index, version 0.8.0 (2026). Checked 2026-10-18. ↩︎
StorageOps Management — self-storage business, Manning, South Carolina, undated. Checked 2026-10-18. ↩︎
Holistic Views for vSAN Clusters using VCF Operations — VMware Cloud Foundation blog (Broadcom), 2025-08-07. Checked 2026-10-18. ↩︎
Security Guidelines for Storage Infrastructure, NIST SP 800-209 — R. Chandramouli, D. Pinhas, NIST, October 2020. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
ISO/IEC 27040:2024 Storage security — ISO/IEC, edition 2, January 2024 (replaces 2015); catalogue page. Checked 2026-10-18. ↩︎ ↩︎
Amazon EBS I/O characteristics and monitoring — AWS. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Initialize Amazon EBS volumes — AWS. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
lvmthin(7) — LVM2, man7.org. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region — AWS, April 2011. Checked 2026-10-18. ↩︎ ↩︎
An Analysis of Data Corruption in the Storage Stack — L. N. Bairavasundaram et al., FAST '08, USENIX, 2008. Checked 2026-10-18. ↩︎ ↩︎
Understanding and managing Amazon S3 storage classes — AWS. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Access tiers for blob data — Microsoft Learn. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Amazon EBS snapshots — AWS. Checked 2026-10-18. ↩︎ ↩︎
Amazon EBS snapshot lock — AWS. Checked 2026-10-18. ↩︎
A Case for Redundant Arrays of Inexpensive Disks (RAID) — D. A. Patterson, G. Gibson, R. H. Katz, UC Berkeley, 1987. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
RFC 1094: NFS: Network File System Protocol Specification — Sun Microsystems, March 1989. Checked 2026-10-18. ↩︎
RFC 8881: Network File System (NFS) Version 4 Minor Version 1 Protocol — IETF, August 2020. Checked 2026-10-18. ↩︎ ↩︎
RFC 7862: Network File System (NFS) Version 4 Minor Version 2 Protocol — IETF, November 2016. Checked 2026-10-18. ↩︎ ↩︎
[MS-SMB2]: Server Message Block (SMB) Protocol Versions 2 and 3 — Microsoft, revision of 2026-09-28. Checked 2026-10-18. ↩︎ ↩︎
RFC 7143: Internet Small Computer System Interface (iSCSI) Protocol (Consolidated) — IETF, April 2014. Checked 2026-10-18. ↩︎ ↩︎
Erasure code — Ceph documentation, latest. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
What is Amazon S3? (data consistency model) — AWS. Checked 2026-10-18. ↩︎ ↩︎
Container Storage Interface (CSI) specification — CSI community, release v1.13.0 (2026-07-27). Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
General Purpose SSD volumes — AWS. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Introduction to multipathing — Ubuntu Server documentation, Canonical, updated 2026-06-26. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Backblaze Drive Stats for Q2 2026 — Backblaze, 2026-09-29. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
CRUSH Maps — Ceph documentation, latest. Checked 2026-10-18. ↩︎
Little’s Law as Viewed on Its 50th Anniversary — J. D. C. Little, Operations Research 59(3), INFORMS, 2011 (UMass course copy). Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Disk failures in the real world: What does an MTTF of 1,000,000 hours mean to you? — B. Schroeder, G. A. Gibson, FAST '07, USENIX, 2007. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
zpool-scrub.8 — OpenZFS documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Monitor config reference (storage capacity) — Ceph documentation, latest. Checked 2026-10-18. ↩︎ ↩︎
Placement Group States — Ceph documentation, latest. Checked 2026-10-18. ↩︎
Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region — AWS, 2017. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Guidelines for Media Sanitization, NIST SP 800-88 Rev. 2 — NIST, September 2025. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
The HPSS Archive System — NERSC documentation. Checked 2026-10-18. ↩︎ ↩︎
Amazon EBS volume types — AWS. Checked 2026-10-18. ↩︎ ↩︎
Provisioned IOPS SSD volumes — AWS. Checked 2026-10-18. ↩︎
RAID arrays (md) — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Azure Storage redundancy — Microsoft Learn. Checked 2026-10-18. ↩︎
Meeting compliance requirements with S3 Replication Time Control — AWS. Checked 2026-10-18. ↩︎
Device-mapper snapshot support — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎
Thin provisioning — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎
Snapshots (RBD) — Ceph documentation, latest. Checked 2026-10-18. ↩︎
Explicit volatile write back cache control — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎
Hardware recommendations: storage devices — Ceph documentation, latest. Checked 2026-10-18. ↩︎ ↩︎
exports(5) — nfs-utils, man7.org. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Data persistence for Amazon EC2 instance store volumes — AWS. Checked 2026-10-18. ↩︎
zfsprops.7 — OpenZFS documentation. Checked 2026-10-18. ↩︎
NVMe over Fabrics Target — SPDK documentation, undated. Checked 2026-10-18. ↩︎
Best practices design patterns: optimizing Amazon S3 performance — AWS. Checked 2026-10-18. ↩︎ ↩︎
Multipath common tasks — Ubuntu Server documentation, Canonical, updated 2026-06-26. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
multipath.conf(5) — multipath-tools project, page dated 2026-06-13. Checked 2026-10-18. ↩︎ ↩︎
[MS-SMB2]: Overview — Microsoft. Checked 2026-10-18. ↩︎
I/O statistics fields — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Block layer statistics in /sys/block/<dev>/stat — Linux kernel documentation, latest. Checked 2026-10-18. ↩︎
iostat(1) — sysstat, man7.org (page dated July 2025). Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
The Tail at Scale — J. Dean, L. A. Barroso, Communications of the ACM 56, 2013. Checked 2026-10-18. ↩︎
Modify an Amazon EBS volume using Elastic Volumes operations — AWS. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
fio documentation — fio project, latest. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Benchmark Persistent Disk performance on a Linux VM — Google Cloud, updated 2026-10-13. Checked 2026-10-18. ↩︎ ↩︎
Releases — Kubernetes (latest 1.37.1, released 2026-09-15). Checked 2026-10-18. ↩︎
Storage Classes — Kubernetes documentation. Checked 2026-10-18. ↩︎
Persistent Volumes — Kubernetes documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Volume Snapshots — Kubernetes documentation. Checked 2026-10-18. ↩︎ ↩︎
Volume Attributes Classes — Kubernetes documentation. Checked 2026-10-18. ↩︎
StatefulSets — Kubernetes documentation. Checked 2026-10-18. ↩︎
Site Reliability Engineering: Introduction — Google, 2016. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Azure managed disk types — Microsoft Learn. Checked 2026-10-18. ↩︎
Storage classes — Google Cloud Storage documentation. Checked 2026-10-18. ↩︎ ↩︎
Rook documentation: introduction — Rook project (CNCF). Checked 2026-10-18. ↩︎
xfs_growfs(8) — xfsprogs, man7.org. Checked 2026-10-18. ↩︎ ↩︎ ↩︎
Lustre file striping — NERSC documentation. Checked 2026-10-18. ↩︎ ↩︎
Drivers — Kubernetes CSI Developer Documentation. Checked 2026-10-18. ↩︎
StorageOps · Pi Coding Agent extension — Titpea (vendor page), undated. Checked 2026-10-18. ↩︎
Lifecycle configuration elements — AWS. Checked 2026-10-18. ↩︎
Transitioning objects using Amazon S3 Lifecycle — AWS. Checked 2026-10-18. ↩︎ ↩︎
put-bucket-lifecycle-configuration — AWS CLI Command Reference. Checked 2026-10-18. ↩︎ ↩︎
Configuring a bucket lifecycle configuration to delete incomplete multipart uploads — AWS. Checked 2026-10-18. ↩︎
Expiring objects — AWS. Checked 2026-10-18. ↩︎
lvextend(8) — LVM2, man7.org. Checked 2026-10-18. ↩︎
pvresize(8) — LVM2, man7.org. Checked 2026-10-18. ↩︎
resize2fs(8) — e2fsprogs, man7.org. Checked 2026-10-18. ↩︎ ↩︎
Extend the file system after resizing an Amazon EBS volume — AWS. Checked 2026-10-18. ↩︎
Use Kubernetes volume storage with Amazon EBS — Amazon EKS User Guide. Checked 2026-10-18. ↩︎
Amazon EBS CSI driver parameters — kubernetes-sigs (release v1.67.0, 2026-10-12). Checked 2026-10-18. ↩︎ ↩︎
Enable Amazon EBS encryption by default — AWS. Checked 2026-10-18. ↩︎
pv_controller.go at v1.37.0 — Kubernetes source. Checked 2026-10-18. ↩︎
Perlmutter Scratch — NERSC documentation. Checked 2026-10-18. ↩︎
Blocking public access to your Amazon S3 storage — AWS. Checked 2026-10-18. ↩︎
Attach/detach reconciler at v1.36.0 — Kubernetes source. Checked 2026-10-18. ↩︎
Attach/detach reconciler at v1.37.0 — Kubernetes source. Checked 2026-10-18. ↩︎
Kubelet event reasons at v1.37.0 — Kubernetes source. Checked 2026-10-18. ↩︎
Node-specific Volume Limits — Kubernetes documentation. Checked 2026-10-18. ↩︎
Node Shutdowns — Kubernetes documentation. Checked 2026-10-18. ↩︎ ↩︎ ↩︎ ↩︎
Kubernetes 1.28: Non-Graceful Node Shutdown Moves to GA — Kubernetes blog, 2023-08-16. Checked 2026-10-18. ↩︎
kubectl taint — Kubernetes documentation. Checked 2026-10-18. ↩︎