Storage Performance Tuning in Kubernetes
Why a database that flies on a laptop can crawl in a cluster, what IOPS, throughput and latency really mean, and how to find and fix the slow spot between your Pod and the disk.
Picture a busy restaurant kitchen. The chefs are quick, the stoves are hot and the recipes are perfect. Yet customers wait forty minutes for a plate of pasta. Where is the delay? It is not in the cooking. It is in the pantry. The ingredients sit in a storeroom down a long corridor, shared with three other restaurants, and a single door lets only one person through at a time. The chefs spend most of their day standing and waiting for flour.
Applications in Kubernetes often live exactly like those chefs. The processor is idle, the memory is fine, the code is clean, and still every request is slow. The reason is that the application is waiting for its data. Storage is the pantry, and in a cluster the pantry is rarely next to the kitchen. It may be a disk attached over a network, a shared file server, or a distributed system that copies every write to three machines before saying “done”.
Storage performance tuning is the craft of making that corridor shorter, the door wider and the pantry better organised. It is also the craft of knowing when the pantry is already fast enough and the real problem lies elsewhere. In this guide you will learn the words, the measurements, the moving parts and the habits that turn “the database feels slow” into a specific, fixable finding. Every new term is explained the first time it appears, and nothing is assumed.
1What Is Storage Performance, and Why Does It Matter?
Begin with the plain meaning, then add detail one layer at a time.
When an application saves or loads data, it asks the operating system to read from or write to a storage device. Storage performance describes how fast and how smoothly that request is answered. It is not one number. It is a small family of numbers that describe different kinds of speed, and a system can be excellent at one and poor at another. A road is a good comparison: you can ask how many cars pass per minute, how many tonnes of cargo move per hour, and how long one car takes to travel from the start to the end. All three questions are legitimate, and the answers can differ.
In Kubernetes, storage performance matters more than in older setups for a simple reason: Pods move. A traditional server had its own disk, bolted to the same machine as the program. A Pod is scheduled onto whichever node has room, and its data often lives somewhere else entirely. That separation brings great flexibility, since a Pod can restart on another node and find its data waiting. The price is a longer path between the application and its bytes, and every extra step on the path can add delay or limit the flow.
IOPS
How many separate read or write requests the storage can handle each second. It matters most for many small operations, such as a busy database.
Throughput
How many bytes per second can flow. It matters most for big transfers, such as backups, analytics scans and video.
Latency
How long one request takes from start to finish. It matters most when a person or another service is waiting for each answer.
Think of a water supply. Throughput is how much water per second comes out of the tap. Latency is how long you wait between turning the handle and the first drop arriving. IOPS is how many separate glasses you can fill per minute. A huge pipe with a slow valve gives lots of water but a long wait. A tiny pipe with a quick valve gives a fast first drop but little water. Real workloads need the right mix.
Why tuning is different from buying
The first instinct when storage is slow is to buy a faster disk. Sometimes that is correct. Often it is wasteful, because the real limit sits somewhere else: a per-machine cap on bandwidth, an application that asks for one block at a time, a filesystem option that forces extra work, or a startup step that recursively changes permissions on millions of files. Tuning means finding the actual limit first and then spending effort or money exactly there.
Before changing anything, write down the one sentence that describes the pain, for example “checkout writes take 80 milliseconds at the 99th percentile and should take 10”. A measurable goal tells you when to stop.
2The Vocabulary of Storage Speed
A handful of words explain almost every storage conversation. Learn them once and the rest becomes easy.
IOPS: input and output operations per second
One I/O operation is one request to read or write a chunk of data. IOPS counts how many of these the storage completes in a second. A small database that updates many rows needs a high IOPS number. The size of each chunk is called the block size, and it changes the meaning of IOPS completely. Four thousand requests of 4 kilobytes each is a very different job from four thousand requests of 1 megabyte each, even though the IOPS count is identical.
Throughput: bytes per second
Throughput, also called bandwidth, is how much data moves each second, usually in megabytes per second. It is linked to IOPS by simple arithmetic: throughput is IOPS multiplied by block size. If a disk delivers 10,000 IOPS at 4 kilobytes, that is only about 40 megabytes per second. The same disk reading 1-megabyte blocks might reach its bandwidth ceiling at a few hundred IOPS. This is why a vendor can quote a huge IOPS figure and a database can still starve: the two numbers apply to different block sizes.
Latency: how long one request takes
Latency is the time from issuing a request to receiving the answer. It is measured in milliseconds or microseconds. Local flash storage often answers in well under a millisecond, while network-attached disks commonly take one to several milliseconds, depending on the provider and the load. Latency is the number a waiting user feels.
Percentiles, not averages
An average hides pain. If ninety-nine requests take 1 millisecond and one takes 500, the average looks like 6 milliseconds, yet one in a hundred users had a terrible time. Engineers therefore look at percentiles. The 99th percentile, written p99, is the time that 99 percent of requests beat. When people talk about “tail latency” they mean these slow outliers, and tail latency is where storage problems usually show first.
Queue depth: how many requests are in flight
Queue depth is the number of requests the application has sent but not yet seen finished. Modern storage is built to work on many requests at once. A program that sends one request, waits, and then sends the next, has a queue depth of one and cannot use the full power of the device. Higher queue depth usually raises IOPS up to a point, and then only raises latency.
Roughly, IOPS equals queue depth divided by latency in seconds. With a latency of 1 millisecond and a queue depth of 1, you can do at most about 1,000 IOPS, no matter how fast the disk claims to be. With a queue depth of 32 and the same latency, up to about 32,000 IOPS are possible, if the device allows it.
Random and sequential access
Sequential access reads or writes data in order, like reading a book from page one. Random access jumps around, like looking up words in a dictionary. Spinning disks hate random access because the head must move. Flash storage handles it far better, but sequential access is still usually cheaper. Databases and key-value stores mix both, and knowing which dominates your workload shapes every later decision.
Reads, writes and the mix between them
Reads can often be served from memory caches. Writes must eventually reach durable storage, and many applications also demand that a write is truly safe before they continue. The command that forces this is called sync or flush, and it is expensive on distributed storage because every copy must confirm. A workload that is ninety percent reads behaves very differently from one that is ninety percent writes.
| Term | Plain meaning | Unit you will see |
|---|---|---|
| IOPS | Number of separate requests handled per second | operations per second |
| Throughput | Bytes moved per second | megabytes per second |
| Latency | Time for one request | milliseconds or microseconds |
| Block size | Size of each request | kilobytes |
| Queue depth | Requests in flight at the same time | count |
| p99 | Time that 99 percent of requests beat | milliseconds |
| Sync write | A write that waits until data is safely stored | operations per second |
3The Storage Path From Pod to Disk
To tune a journey, you must first see every stop on it.
When an application inside a container writes a file, the request does not leap straight to a disk. It travels through a stack of layers. Each layer does useful work, and each layer can be a source of delay or a limit on flow. The map below shows the typical stops for a volume that lives on a network-attached disk.
flowchart TD
A["Application in the container"] --> B["Container filesystem view and volume mount"]
B --> C["Node operating system: page cache, filesystem, block layer"]
C --> D["Node device driver or network storage client"]
D --> E{"Where does the data live?"}
E -->|"Local disk"| F["Physical flash or disk in the same machine"]
E -->|"Network block"| G["Network, then storage service, then replicated disks"]
E -->|"Network file"| H["Network, then file server or distributed filesystem"]
Walking down the stack
The application
The code decides block sizes, whether to wait for each write, and how many requests to send at once. Many performance problems are born here.
The mount inside the container
Kubernetes arranges for a directory in the container to point at the volume. The mount carries options that influence caching and behaviour.
The node’s page cache and filesystem
The operating system keeps recently used data in memory, merges small writes and organises files on the device. Filesystem choice and settings matter here.
The block layer and driver
Requests are queued, ordered and sent to the device or to a network storage client. The I/O scheduler and queue settings live here.
The network, if any
For remote disks, every request crosses a network that has its own bandwidth, latency and sharing rules. Many cloud machines cap their storage bandwidth separately from the disks.
The storage system and the media
Finally the request reaches a service that may copy it to several devices before answering. The type of media and the replication rules set the floor for latency.
When someone says “the disk is slow”, ask which stop they mean. The disk itself, the machine’s cap on storage bandwidth, the network, the filesystem and the application each need different cures.
4Kubernetes Storage Building Blocks
A short, friendly refresher on the objects that describe storage, because tuning means changing them.
Kubernetes separates the request for storage from the supply of storage, much like a customer who orders a table and a restaurant that decides which table to give. That split is expressed through a few objects.
PersistentVolumeClaim
A claim, written by the application team, saying how much space is needed, what access style is required and which class of storage is wanted.
PersistentVolume
The actual piece of storage in the cluster. It may be created by hand or created automatically when a claim appears.
StorageClass
A named recipe describing how to create storage: which driver, which disk type, which options. The class is the main performance dial that platform teams control.
CSI driver
The Container Storage Interface driver is the software that talks to the storage system on behalf of Kubernetes. Each vendor supplies its own driver.
Access modes
A claim also states how the volume may be shared. ReadWriteOnce means one node at a time can mount it for reading and writing, which suits network block disks. ReadWriteMany means many nodes may use it together, which needs a shared file system. ReadOnlyMany allows many readers. Newer versions also include a single-Pod variant of the first mode. The mode is not just a rule. It decides what kind of storage is even possible, and therefore what performance is available.
Dynamic provisioning
In dynamic provisioning, a developer creates a claim naming a class. The driver then creates the real disk in the background, and Kubernetes binds the claim to it. Because the class carries the parameters, changing the class changes the performance of every new volume. Existing volumes do not change by themselves, a point that surprises many teams.
The life of a volume, briefly
Claim
An application asks for storage of a class and size.
Provision
The driver creates the disk in the storage system.
Attach
When a Pod is placed on a node, the disk is attached to that node.
Mount
The node formats the disk if needed and mounts it into the Pod.
Use and move
The Pod reads and writes. If the Pod moves, the disk is detached and attached elsewhere.
Notice how many steps happen before the first byte is read. Chapter 17 returns to this, because slow Pod starts are a performance problem too.
5Volume Types and Their Speed Personalities
Not all volumes are equal. Each kind has a character, and choosing well is the cheapest tuning you will ever do.
| Kind | Where the data lives | Typical strength | Typical weakness |
|---|---|---|---|
| emptyDir on node disk | The node’s own disk, tied to the Pod’s life | Simple and quick, no network | Lost when the Pod leaves; shares the node disk with everything else |
| emptyDir in memory | The node’s RAM | Extremely fast | Counts against memory limits; lost on restart |
| hostPath | A directory on the node | Direct access to local speed | Ties the Pod to one node; weak isolation and security risks |
| Local persistent volume | A dedicated local disk, declared to Kubernetes | Lowest latency, highest IOPS | Data is lost if the node is lost; the Pod is pinned to that node |
| Network block volume | A cloud or array disk attached over the network | Survives node loss; easy to move | Higher latency; caps per disk and per machine |
| Network file share | A file server or managed file service | Shared by many Pods | Metadata-heavy work is slow; latency varies |
| Distributed storage cluster | Replicated across many nodes | Resilient, flexible, self-managed | Write amplification and network cost |
| Object storage through an API | A remote bucket service | Huge scale, cheap capacity | Not a filesystem; high per-request latency |
Local storage is a desk drawer: instant, but if you move offices the drawer stays behind. A network block disk is a private locker in the basement: a short trip, and it follows you if you change floors. A file share is the office filing room: everyone can use it, but you queue at busy times. Object storage is the warehouse across town: huge and cheap, and you do not fetch one sheet at a time.
Matching the volume to the job
- Scratch space for caches, sorting and temporary files: an emptyDir, perhaps on a fast local disk.
- A primary database: a network block volume with provisioned performance, or a local volume with replication handled by the database itself.
- Shared media or content used by many web Pods: a file share, with attention to small-file behaviour.
- Backups, logs and archives: object storage, accessed by a client library instead of a mount.
Treating a network file share like a local disk. A program that creates, opens and closes tens of thousands of tiny files will feel fine on a laptop and painful on a share, because every one of those operations is a round trip over the network.
6Local Storage Versus Network Storage
This is the biggest fork in the road, and it is a trade between speed and freedom.
A disk inside the same machine as the Pod answers in microseconds and has no network to share. A disk reached over the network answers in milliseconds and may be limited by the bandwidth of the machine. On the other hand, a network disk is independent of any one node. When the node dies, Kubernetes can start the Pod elsewhere and re-attach the disk. A local disk disappears with its node, so the data must be protected in another way, such as the application’s own replication.
Why choose local
- Lowest latency and highest IOPS per cost.
- No network bandwidth shared with other traffic.
- Predictable performance, with no remote throttling.
- Ideal for databases that already replicate across nodes.
Why avoid local
- Pods are pinned to one node, which complicates scheduling.
- Node failure loses the data unless it is copied elsewhere.
- Resizing and moving are manual or driver-dependent.
- Capacity is fixed by the machine type.
How local volumes work in Kubernetes
A local persistent volume declares which node it belongs to. The scheduler reads that and only places the Pod there. Because the placement must be known before the claim is bound, local volumes depend on a binding mode that waits for the Pod, which Chapter 8 explains. A helper program called a local static provisioner can discover disks on nodes and publish them as volumes automatically.
Making the decision
| Question | If the answer is yes | If the answer is no |
|---|---|---|
| Does the application replicate its own data across nodes? | Local storage is a strong option | Prefer network storage with its own durability |
| Is single-digit-millisecond latency required for writes? | Consider local or high-end provisioned disks | Standard network disks may be enough |
| Must Pods move freely between nodes? | Network storage | Local storage can work |
| Can you rebuild lost data quickly from other replicas? | Local storage is safer | Do not risk it |
A common production pattern
A team runs a three-node search or messaging cluster, where each node already holds a copy of the data. They place each member on a node with a fast local disk. Writes are quick, and when a node fails, the application rebuilds the lost copy from the other two. The same company runs its single-instance billing database on a network block volume, because there no second copy exists to rebuild from.
7StorageClass: The Main Performance Dial
The class is where platform teams turn performance up or down for everyone.
A StorageClass carries a driver name and a set of parameters that the driver understands. The parameters differ by vendor, and the documentation of your driver is the final authority, but a few themes repeat everywhere.
| Setting | What it controls | Performance effect |
|---|---|---|
| Disk type | The kind of media and service tier | The largest single effect on latency and cost |
| Provisioned IOPS and throughput | Explicit performance purchased for the volume, where the provider offers it | Raises the ceiling independent of size on some platforms |
| Filesystem type | How the disk is formatted when first used | Affects large-file, small-file and parallel behaviour |
| Mount options | Flags used when mounting | Can reduce metadata work or alter caching |
| Volume binding mode | When the disk is created and placed | Prevents cross-zone mismatches that add latency or block scheduling |
| Allow volume expansion | Whether claims may be enlarged later | Lets size-linked performance grow without rebuilds |
| Reclaim policy | What happens to the disk when the claim is deleted | Safety rather than speed, but important for data protection |
Offering a menu rather than one choice
A single default class forces every team into the same compromise. Mature platforms publish several classes with clear names, such as a standard tier for general use, a high-performance tier for databases, and a throughput-optimised tier for analytics. Each class documents what it costs and what it promises. Application teams then pick by need, and finance can see who is paying for premium performance.
Changing a StorageClass does not change volumes that already exist. Many parameters are read only at creation time. To move an existing workload to a better class you normally create a new volume and copy the data, or use a driver feature that modifies a volume in place, where one exists.
Naming and ownership
Treat classes as a product. Give them clear names, publish expected numbers from your own benchmarks, state which workloads they suit, and assign an owner. A well-run class catalogue prevents the common pattern where everyone copies the biggest class “just to be safe” and quietly multiplies the storage bill.
8Topology and Volume Binding: Keep Data and Pod Close
A fast disk in the wrong place is a slow disk.
Clouds are divided into zones, which are separate groups of data centres within a region. A network disk usually lives in exactly one zone. A Pod in a different zone cannot attach it at all. Even where cross-zone access is possible, the extra distance adds latency and sometimes cost. Kubernetes handles this through topology information on volumes and nodes, and through the volume binding mode of the class.
flowchart LR
subgraph IM["Immediate binding"]
A1["Claim created"] --> A2["Disk created in a zone chosen at once"]
A2 --> A3["Pod scheduled later"]
A3 --> A4{"Same zone?"}
A4 -->|"No"| A5["Pod stuck pending"]
A4 -->|"Yes"| A6["Works"]
end
subgraph WF["Wait for first consumer"]
B1["Claim created"] --> B2["Wait"]
B2 --> B3["Pod scheduled to a node"]
B3 --> B4["Disk created in that node's zone"]
B4 --> B5["Works"]
end
Why waiting is usually wiser
With immediate binding, the disk is created the moment the claim appears, before anyone knows where the Pod will run. The zone is chosen blindly. Later, the scheduler may find that the Pod must run in another zone because of CPU, memory or other rules, and the Pod then cannot start. With wait for first consumer binding, the system holds the claim until a Pod that uses it is placed. The disk is then created in the right zone. This one setting prevents a large share of “Pod stuck pending” incidents for zonal storage.
Spreading replicas across zones
For resilience you often want database members in different zones. Each member then gets its own disk in its own zone. Use topology spread rules so the replicas do not pile into one zone, and remember that a single-writer database still sends its replication traffic across zones, which has latency and cost consequences of its own.
Node-level limits that look like topology problems
Each node can attach only a limited number of disks, and the limit depends on the machine type and the driver. A Pod that needs a volume may be unschedulable on an otherwise empty node because the attach limit was reached. Kubernetes learns these limits from the driver and takes them into account when scheduling, but it is worth knowing the numbers for your machine types when you plan a dense layout.
Make wait-for-first-consumer the default for every class that uses zonal disks or local volumes. It costs nothing and removes a whole category of scheduling surprises.
9Choosing a Filesystem and Mount Options
A filesystem is the filing system on top of the raw disk. Its design shapes how the disk behaves under your workload.
A raw disk is just a long row of numbered blocks. A filesystem adds names, folders, permissions and the bookkeeping that keeps everything consistent. When Kubernetes first uses a new volume in filesystem mode, the node formats it with the type named in the class, commonly ext4 or XFS. Both are mature and widely used, and for most workloads the difference is smaller than the difference between a good and a bad disk tier. Still, the choice has consequences worth knowing.
| Filesystem | Often a good fit | Points to remember |
|---|---|---|
| ext4 | General purpose, small to medium volumes, wide tool support | The long-standing default in many distributions; predictable and well understood |
| XFS | Large files, large volumes, many parallel writers | Scales well with concurrency; a volume formatted with it cannot be shrunk |
| Others, such as btrfs or ZFS | Special needs like snapshots and checksums | More tuning knobs and more to learn; check your driver and operating system support |
Some databases publish a recommendation for the filesystem they are best tested on. Following the vendor’s advice is usually wiser than chasing tiny theoretical gains.
Mount options that matter
A class can pass mount options, which are flags used when the volume is attached to the node. The ones that most often matter are small, and each deserves a moment of thought rather than copying from a blog.
- Access time updates. By default, every read may cause a small write to record when the file was last read. Options such as noatime or relatime reduce this. Modern kernels already default to a gentle version, but explicit settings on busy shared volumes can still help.
- Discard or trim. Telling flash storage which blocks are free helps it manage itself. Doing it on every delete can add latency. Periodic trimming on a schedule is often kinder to latency-sensitive workloads.
- Barriers and journalling modes. These protect data after a crash. Turning them off is a classic trap: speed rises, and so does the chance of corruption when power or a node fails. Leave safety features on unless you fully understand the consequences.
- Network filesystem options. For shared file systems, read and write sizes, number of connections and caching rules can change results dramatically. Chapter 23 covers them.
Inodes: the quiet limit
A filesystem tracks each file with a small record called an inode. The number of inodes is often fixed when the disk is formatted. A volume holding millions of tiny files can run out of inodes while still having free space, and writes then fail strangely. Watch inode usage as well as bytes used, particularly for caches, mail queues and build systems.
10Filesystem Mode Versus Raw Block Mode
Sometimes the best way to speed up the filing system is to remove it.
Most volumes are presented in filesystem mode: the application sees a directory and works with files. Kubernetes also supports raw block mode, in which the Pod receives the disk itself as a device, with no filesystem in between. The application must then manage the layout of data on its own.
Why raw block can help
- Skips the filesystem and its metadata work.
- Gives engines that implement their own storage layout direct control.
- Can reduce latency variation for very demanding databases.
- Avoids double caching, where both the engine and the operating system keep the same data in memory.
Why most teams stay with files
- Only applications designed for raw devices can use it.
- Backups, inspection and repair tools become harder.
- Operational knowledge is thinner in most teams.
- The gain is often small compared with fixing the disk tier or the access pattern.
Treat raw block as a specialist tool for engines that explicitly document support for it. For nearly everything else, a well-chosen filesystem on a well-chosen disk is simpler and fast enough.
Direct I/O and caching
Even inside a filesystem, an application can ask to bypass the operating system’s page cache with a mode usually called direct I/O. Engines that keep their own cache use it to avoid caching the same data twice. This changes how memory is used by the node and affects how you read monitoring graphs, because the node will show little page cache for such a Pod while the process itself holds a large buffer pool.
11Cloud Disk Limits: Per-Volume, Per-Machine and Bursting
Performance in the cloud is a contract with several clauses. A bottleneck often hides in the one you did not read.
When you rent a network disk, the provider promises certain numbers. Those numbers come with rules, and the rules differ among providers and change over time, so always confirm them in the current documentation before you plan. The general shapes, however, repeat.
The three ceilings
The volume’s own limit
Each disk has a maximum IOPS and throughput. On some platforms this grows with size. On others you can buy performance independently of size.
The machine’s storage limit
Every virtual machine type can push only so much total storage traffic across all of its disks. Ten fast disks on a small machine are all squeezed through one narrow doorway.
The network share
On many platforms, storage traffic shares network paths with ordinary traffic, or has its own cap that depends on machine size.
Burst credits
Some disk types give a modest steady baseline and let you go faster for short periods by spending credits that build up when the disk is quiet. This is excellent for spiky workloads and treacherous for steady ones. A database that runs well for the first hour of a benchmark and then slows dramatically has probably used up its credits. Always run tests long enough to drain any burst allowance, and prefer disk types with a flat, documented baseline for production databases.
Size and performance linkage
On some platforms a bigger disk is a faster disk, because performance is tied to capacity. A team may therefore buy more space than it needs, just to gain speed. Other platforms decouple the two, so you pay for the exact IOPS and throughput you require. Understanding which model your provider uses saves money and prevents the confusing situation where doubling the disk doubles the speed.
Resizing and modifying volumes
Kubernetes supports growing a claim when the class allows expansion and the driver supports it. Many drivers can expand a volume while it is in use, and some can also change the performance settings in place, though capabilities vary. Check your driver’s feature list, and test an expansion in a non-production cluster. Shrinking is generally not supported.
| Question to ask the documentation | Why it matters |
|---|---|
| What are the baseline and maximum IOPS and throughput of this disk type? | Sets the first ceiling |
| Does performance depend on disk size? | Determines whether to buy size or performance |
| Is there a burst allowance, and how long does it last? | Explains tests that slow down after minutes |
| What is the storage bandwidth cap of my machine type? | Sets the second ceiling |
| How many disks can one machine attach? | Limits density per node |
| Can I change type or performance without downtime? | Decides how painful a mistake is |
12Ephemeral Storage, emptyDir and the Node Root Disk
Not all storage is persistent, and the temporary kind is where many surprises hide.
Every container needs somewhere to write that is not a persistent volume: a writable layer on top of its image, temporary directories, logs. This space is called ephemeral storage, and it lives on the node, usually on the same disk as the operating system and the container runtime. Everything on that disk competes for the same speed.
What shares the node disk
- Unpacked container images and their layers.
- The writable layer of every running container.
- emptyDir volumes of every Pod, unless memory-backed.
- Container logs written by the runtime.
- Kubelet working data and system logs.
A single Pod that writes heavily to its temporary space can slow down image pulls, log writing and the other Pods on that node. Because this is all one pool of I/O, it is also a hidden source of noisy-neighbour problems.
Memory-backed emptyDir
An emptyDir can be told to use memory instead of disk. Speed is superb, but the space counts against the container’s memory limit, and a full memory volume can cause the Pod to be killed. Use it for small, hot scratch data, such as sockets or tiny caches, and set a size limit.
Giving the container runtime its own disk
On busy nodes, a good pattern is to put the container runtime’s data directory on a separate disk from the operating system, and sometimes to give heavy temporary space a third. Then an image pull storm does not delay the kubelet, and a runaway log does not fill the system disk. Many managed services and node images allow you to choose the size and type of the node’s root volume, and tuning that choice is worthwhile.
Requests, limits and eviction
Pods may declare how much ephemeral storage they request and the most they may use. The kubelet watches disk space and inodes on the node, and when free space falls below its thresholds, it starts evicting Pods to protect the machine. Notice that these controls count space, not speed. They keep the disk from filling up, but they do not stop one Pod from using all the I/O bandwidth.
Assuming that CPU and memory limits protect you from disk contention. Kubernetes has no built-in per-Pod limit for disk IOPS or bandwidth. Isolation must come from separate disks, dedicated nodes, or features of your storage system.
13Know Your Workload: Access Patterns
You cannot tune what you have not described. Start by asking how the application actually touches its data.
Two workloads can both be “a database” and ask for opposite things. Before touching a setting, characterise the traffic using a few questions. How large are the requests? Are they random or in order? What share is reads versus writes? Do writes wait for confirmation? How much parallelism does the application create? How much data is the hot set that gets used repeatedly, compared with the cold bulk?
| Workload | Typical pattern | What it usually needs most |
|---|---|---|
| Transactional database | Small random reads and writes, frequent syncs on a write-ahead log | Low and steady latency, strong IOPS, fast sequential log writes |
| Analytics and reporting | Large sequential scans | High throughput, big block sizes |
| Message queue or log broker | Sequential appends, sequential reads of recent data | Throughput and steady write latency |
| Search index | Mix of random reads and bursty background merges | Good read IOPS and room for merge bursts |
| Key-value engine with compaction | Steady writes plus heavy background rewriting | Throughput headroom, since background work competes with foreground requests |
| Web content from a shared volume | Many small file reads | Metadata speed, caching, often better served by a CDN or object store |
| Machine-learning training | Big streaming reads, checkpoint writes in bursts | Throughput and parallel readers, local caching of datasets |
| CI build workers | Many small temporary files, bursts of writes | Fast local scratch, quick cleanup |
The write-ahead log
Many databases first write each change to a sequential journal called a write-ahead log and only later update the main data files. The log write must be confirmed as safe before the transaction is reported complete. That makes log latency the main limit on how many transactions per second a database can commit. The data files, by contrast, are written lazily in the background. This is why a favourite tuning move is to place the log on its own fast, low-latency volume.
Background work is part of the workload
Engines that organise data in sorted layers must periodically rewrite it, a process often called compaction or merging. It can use more I/O than the foreground requests. When a cluster looks fine and then stalls for a few minutes, the cause is frequently a background rewrite that saturated the disk. Include it when you size capacity.
Same product, different needs
A company ran two instances of the same relational database. One powered an online shop, with short transactions, and was latency-bound. The other fed a nightly report, scanning huge tables, and was throughput-bound. Putting both on the same disk class made the shop slow at night and the report slow by day. Giving each its own class, sized for its own pattern, cured both without buying anything larger.
14How to Benchmark Storage Properly
A measurement you can trust is worth more than any opinion. A misleading one is worse than none.
The most widely used tool for storage testing is fio, a flexible program that generates controlled read and write patterns and reports IOPS, throughput and latency percentiles. Other tools exist, including wrappers that run these tests inside a cluster against real volumes. Whichever you choose, the method matters more than the tool.
flowchart TD
A["Describe the real workload: block size, mix, parallelism"] --> B["Create a test volume from the exact StorageClass"]
B --> C["Run a Pod on a representative node with matching resources"]
C --> D["Warm up and run long enough to drain any burst allowance"]
D --> E["Record IOPS, throughput and latency percentiles"]
E --> F{"Do results meet the goal?"}
F -->|"Yes"| G["Write down the numbers as the baseline"]
F -->|"No"| H["Change one thing and repeat"]
H --> C
Rules for honest results
- Test the same path your application uses. A benchmark on the node’s local disk says nothing about a network volume mounted in a Pod.
- Match the access pattern. Use the block size, read and write mix, and parallelism that you measured in Chapter 13. Defaults are rarely realistic.
- Defeat the cache on purpose, or measure it on purpose. A file that fits in the page cache will report absurdly fast reads. Use a test file much larger than memory, or choose direct I/O when testing the device itself.
- Run long enough. Burst credits, thin provisioning and background garbage collection show up after minutes, not seconds.
- Test on a quiet and on a busy node. Shared resources change results when neighbours are active.
- Change one thing at a time. Otherwise you cannot tell which change helped.
- Repeat the test. Run it several times and at different hours to see the spread, not just a lucky number.
A standard set of tests
| Test | Pattern | What it reveals |
|---|---|---|
| Random read, small blocks | 4 KB, many in flight | Maximum read IOPS and latency under load |
| Random write, small blocks | 4 KB, many in flight | Maximum write IOPS and write latency |
| Sequential read, large blocks | 1 MB, a few in flight | Peak read throughput |
| Sequential write, large blocks | 1 MB, a few in flight | Peak write throughput |
| Single-depth sync write | One request at a time, waiting for each | Raw latency, the number a database log feels |
| Mixed read and write | For example 70 percent reads | How the volume behaves under a realistic blend |
Run the single-depth sync write test early. It takes seconds and tells you more about database suitability than any headline IOPS number.
15Reading the Results Without Fooling Yourself
Numbers do not speak for themselves. They need a careful reader.
Compare to the limits you know
Take the observed IOPS and throughput and compare them with the three ceilings from Chapter 11. If the result sits exactly at the volume’s published limit, you have found a ceiling and the cure is to raise it. If it sits far below every published limit, something else is in the way: queue depth, application behaviour, or a mount setting.
Watch the shape of latency
Healthy storage under rising load shows latency that stays flat, then climbs sharply once the device saturates. Plot latency against queue depth. The knee of that curve is the practical sweet spot. Running beyond it adds waiting and no extra work.
Common ways to be fooled
Testing a cold, empty disk
Some disks restored from snapshots load their blocks lazily from a remote store on first read. The first pass is slow, and later passes are fast. Know which phase you are measuring.
A test file that fits in memory
You are measuring the page cache, not the disk. Results look wonderful and mean nothing.
Too short a run
Burst credits mask the baseline. The honest figure appears only after they are spent.
One quiet hour
Shared systems behave differently at peak. A good result at 3 a.m. may not survive 3 p.m.
Writing down a baseline
When you have a trusted result, record it with the date, the class, the size, the node type, the kernel and driver versions and the exact test settings. This baseline becomes your reference. After any upgrade or change, rerun the same test. Storage regressions are common after driver or kernel updates, and without a baseline they are almost impossible to prove.
16Inside a CSI Driver and Its Effect on Speed
The driver sits between Kubernetes and the storage system. It does not touch your data path, but it controls everything around it.
A CSI driver is usually deployed as two cooperating parts. A controller part, running as ordinary Pods, handles cluster-wide actions such as creating disks, attaching them to machines, resizing and taking snapshots. A node part, running on every node, handles local actions such as formatting, mounting and reporting usage. Small helper containers called sidecars translate Kubernetes events into driver calls.
flowchart LR
subgraph CP["Controller side"]
P["External provisioner"] --> CD["Driver controller service"]
AT["External attacher"] --> CD
RS["External resizer"] --> CD
SN["External snapshotter"] --> CD
end
CD --> ST["Storage system API"]
subgraph ND["Each node"]
KL["Kubelet"] --> NP["Driver node service"]
NP --> MT["Format and mount on the node"]
end
ST -.-> MT
What this means for performance
- Data does not flow through the driver. Reads and writes go from the node straight to the device or storage service. The driver’s speed does not set your IOPS.
- The driver does set how quickly volumes appear. Slow provisioning, attaching or mounting delays Pod starts, rescheduling after failure and scaling events. Sidecars that are throttled by API rate limits make this worse in large clusters.
- The node part sets what happens at mount time. Formatting options, permission changes and checks happen there, as the next chapter explains.
- Capacity and attach limits are reported by the driver. The scheduler relies on them.
- Metrics come from the driver and the kubelet. They are your window into attach time, mount time and volume usage.
Give the controller sidecars enough CPU and memory, and keep them on stable nodes. A starved or restarting controller means volumes stop being created or attached across the whole cluster, which looks like a storage speed problem but is really a control-plane problem.
Keeping drivers current
Storage drivers evolve quickly. Newer releases bring faster attach, better limits handling, volume modification support and bug fixes. Treat them like any critical component: read the release notes, upgrade in a staging cluster first, and rerun your baseline benchmarks afterwards.
17Slow Pod Starts: Attach, Mount and Permission Delays
Storage performance is not only about steady speed. It is also about how long a Pod waits before it can begin.
A Pod that uses a persistent volume cannot start its containers until the volume is ready. In a healthy setup this takes a few seconds to a minute. In an unhealthy one it can take many minutes, and when a database restarts during an incident, those minutes are painful. Several distinct causes are worth knowing.
Detach from the old node
A block volume that allows one node at a time must be released by its previous node first. If that node crashed, the system has to decide that it is really gone. Controllers wait for a safety period before forcing a detach, commonly several minutes by default, to avoid corrupting data that a half-alive node is still writing.
Attach to the new node
The cloud or array must connect the disk to the new machine. This usually takes seconds, but can take longer when API rate limits or large numbers of simultaneous attaches are involved.
Format on first use, or check on later use
A brand-new volume is formatted once. Formatting a very large volume with some options can take a while. Later mounts run a quick consistency check where needed.
Fix ownership and permissions
If the Pod asks for a group ownership setting, the system may walk through every file on the volume and change its group. On a volume with millions of files, this can take many minutes before the container starts.
Security relabelling
On systems that use SELinux, files may need their security labels recursively set at mount time. This can add a similar delay on large volumes, and newer releases provide ways to apply the label at mount time instead.
The permission change trap
The group ownership step is the most common hidden start-up delay. Kubernetes offers a policy setting for it, fsGroupChangePolicy. With the value that checks the root directory only, the walk is skipped when the top-level ownership already matches. After the first run, restarts become fast. Many large stateful workloads gain minutes of start time from this single setting.
When a Pod with a large volume takes a long time to become ready, look at its events. They record when the volume was attached and when the mount finished. A long gap after the mount usually points to ownership or labelling work.
Reducing the failover window
- Use health checks and node monitoring that detect failure quickly.
- Know your cluster’s timing settings for unreachable nodes and volume detach.
- For databases with their own replicas, fail over at the application level rather than waiting for the old disk to move.
- Keep volumes lean. Millions of files make every volume-wide operation slower.
- Practice a node failure and record the time taken. The real figure often surprises teams.
18Snapshots, Clones and Restores
Backups are essential, but they have a speed footprint. Know it before the backup runs at noon.
A snapshot is a point-in-time picture of a volume. Many storage systems create snapshots almost instantly, using a technique that records only the changes after the picture was taken. A clone creates a new volume from an existing one or from a snapshot, which is a fast way to build test copies. Kubernetes exposes these through snapshot objects and through a data source field on new claims.
Imagine a notebook. A snapshot is like putting a bookmark at the current page and promising never to erase anything before it. New writing goes on fresh pages. This is quick, but later the notebook is messier, because old and new pages are interleaved and the librarian has to keep track of which is which.
How snapshots can affect speed
- Copy-on-write cost. In some systems, the first write to a block after a snapshot requires copying the old content aside. A heavily written volume with frequent snapshots may see added write latency.
- Many snapshots in a chain. Long chains can slow reads and make deletion expensive on certain platforms.
- Snapshot creation is not the same as a backup. On many clouds, a snapshot is copied to cheaper storage in the background, and that background copy can use bandwidth.
- Lazy loading after restore. A volume built from a snapshot may fetch blocks from the remote store on first access. The first read of each block is slow, and the workload may look broken until the volume is fully warmed. Some platforms offer ways to initialise the volume fully or in advance.
Application-consistent backups
A snapshot of a running database is only as good as the moment it was taken. Many databases need a short pause or a special flush for a snapshot to be consistent. Tools that coordinate this add a brief latency blip. Plan snapshots for quiet times, and test restores regularly, since a backup that cannot be restored is only a feeling of safety.
Time a full restore into a fresh volume and run your normal benchmarks on it. The speed of a just-restored volume can differ sharply from an ordinary one, and you need to know that before the day you depend on it.
19Noisy Neighbours and How to Isolate Storage Load
A shared node is a shared kitchen. One messy cook can slow everybody.
Since Kubernetes does not limit disk I/O per Pod by default, a single heavy Pod can consume most of a node’s disk bandwidth or most of the machine’s network storage bandwidth. Other Pods on the same node then see higher latency, even though their own volumes are separate disks. This is the classic noisy neighbour problem.
Dedicated nodes
Use node labels and taints so that storage-heavy workloads run on their own machines, away from latency-sensitive services.
Anti-affinity
Tell the scheduler that two heavy Pods must not share a node. They then never compete for the same machine-level cap.
Separate disks
Give heavy work a volume that is not the node root disk, so logs and images stay unaffected.
Throttling in the storage system
Some storage platforms and drivers can enforce per-volume or per-tenant performance limits. Where available, they provide true isolation.
Kernel-level controls
The operating system can limit disk bandwidth per group of processes. Using these controls inside Kubernetes usually needs node-level configuration or a specialised agent, and results depend on kernel version and cgroup setup. Test carefully.
Schedule heavy jobs for quiet hours
Backups, compaction and bulk loads can be timed so that they do not meet peak traffic.
Finding the culprit
The symptom is a latency rise across unrelated Pods on one node. On that node, look at disk utilisation, the queue length and the per-process I/O reports to see who is generating the load. Correlating the time of the slowdown with scheduled jobs often points straight at the cause.
The midnight log shipper
A team saw a nightly spike in database latency. Everything on the node looked normal until they found that a log-shipping agent started at midnight, reading yesterday’s logs from the shared root disk as fast as it could. Pointing the agent at a rate limit and moving heavy logs to a separate volume removed the spike with no change to the database at all.
20Operating System and Kernel Tuning on the Node
Some of the biggest levers sit just below the container, in the settings of the node’s operating system.
Most teams should change node-level settings carefully and only when measurements justify it, because these settings apply to everything on the machine. Managed Kubernetes services also restrict or reset some of them. Still, knowing what exists helps you read documentation and recommendations from database vendors.
| Setting area | What it does | When it might matter |
|---|---|---|
| I/O scheduler | Orders requests before sending them to the device | Fast flash devices usually need little or no reordering; spinning disks benefit from it |
| Queue depth and request limits | How many requests the operating system keeps in flight per device | Raising limits can help fast network disks that need parallelism |
| Read-ahead | Prefetches the following data when it sees sequential reads | Helps large scans; hurts random workloads on some devices |
| Dirty page limits | How much unwritten data may sit in memory before the system forces a write-out | Large values cause big, sudden write stalls; small values smooth the writes |
| Swappiness and memory pressure | How eagerly the system swaps or drops caches | Memory pressure turns into extra disk traffic |
| Transparent huge pages | Memory page size management | Some databases recommend disabling it to avoid latency spikes |
| File descriptor and event limits | How many files and events a process can have open | High-concurrency servers hitting hidden ceilings |
| Device multipath and driver options | How the node reaches network storage | Relevant for storage arrays and some network block systems |
The page cache is your friend, and also your trap
The kernel keeps recently used file data in memory. A node with plenty of free memory therefore serves many reads without touching the disk at all. But the cache also counts toward a container’s memory accounting in many setups, and memory limits that are too tight force the cache to shrink, which turns into more disk reads. When a Pod’s I/O rises after you lower its memory limit, you have probably squeezed its cache.
How to apply changes safely
- Change one setting on one test node.
- Repeat the benchmark from Chapter 14 and compare it with the baseline.
- Roll it out through your node image or configuration management so it survives replacement.
- Record why the setting exists, because six months later nobody will remember.
Copying a tuning list from the internet is risky. Recommendations are tied to specific kernels, devices and workloads. A setting that helped someone else’s flash array may harm your network disk. Measure first.
21Stateful Workloads: Databases, StatefulSets and Layout
Databases are the most demanding storage customers in a cluster. A few layout decisions give most of the gain.
A StatefulSet is the Kubernetes object designed for applications that need stable identity and their own storage. Each member gets its own claim, created from a template, and keeps it across restarts. This is the usual way to run databases, brokers and search nodes.
Separate volumes for separate jobs
Databases do different kinds of I/O, and giving each its own volume lets you tune each separately and prevents them from interfering.
| Volume | What it holds | Best characteristics |
|---|---|---|
| Data | Main table and index files | Large, good random IOPS, solid throughput |
| Write-ahead log | The sequential journal of changes | Small but very low and steady write latency |
| Temporary space | Sort and join spill files | Fast local scratch, loss acceptable |
| Backups and archives | Copies and shipped logs | Cheap, high-capacity, throughput-oriented |
Placement rules
- Spread members across nodes and zones with anti-affinity or topology spread rules.
- Reserve nodes for heavy databases with taints, so general workloads do not crowd them.
- Give the Pod guaranteed CPU and memory by setting equal requests and limits, so the database is not throttled or evicted during busy moments.
- Mind the memory settings of the engine itself. A buffer pool that is too small forces extra disk reads. One that is too large starves the operating system and the container limit.
Operators
An operator is a controller that encodes the operational knowledge for a particular database: creating members, handling failover, taking backups, resizing volumes. Mature operators expose storage options directly, such as separate volume sizes for data and log, and know how to expand claims safely. Using one reduces the chance of a mistake during a stressful night.
22Shared File Systems: NFS and Managed File Services
Sharing one volume across many Pods is convenient. It also changes the performance rules.
When many Pods on many nodes must read and write the same files, you need a file system that can be mounted in many places at once. Common examples include the Network File System protocol, managed file services from cloud providers, and distributed file systems. They offer the many-node access mode, but their strengths and weaknesses are different from a block disk.
Why metadata dominates
Every file operation involves two kinds of work: moving the data, and handling the metadata, which means names, sizes, permissions and timestamps. On a local disk, metadata is cheap. On a file share, each metadata action may be a round trip to the server. A job that lists a folder with ten thousand files, then opens each one, performs tens of thousands of network conversations before reading a single byte. This is why small-file workloads feel slow on shares even when throughput figures look high.
Common tuning levers
- Read and write sizes. Larger transfers reduce overhead for big files.
- Number of parallel connections. Modern clients can spread one mount across several connections, which helps throughput on fast links where the kernel and server support it.
- Attribute and directory caching. Longer caching reduces round trips but lets clients see stale information for a while. Choose according to how much consistency the application needs.
- Hard versus soft mounts. Hard mounts retry forever and protect data integrity. Soft mounts give up and return errors, which can corrupt applications that do not expect them.
- Server-side tier or performance mode. Managed services often offer modes trading cost for throughput or latency. Choose by workload.
- Server location. The file server in another zone adds latency and cost to every operation.
Where shares shine
- Shared content and media read by many Pods.
- Home directories and notebooks for teams.
- Large files read in big sequential streams.
- Legacy applications that expect a shared directory.
Where shares struggle
- Databases with many small synchronous writes.
- Build trees and package caches with huge numbers of tiny files.
- Applications relying on strict file locking behaviour.
- Workloads sensitive to tail latency.
If an application only needs shared data to be read, consider copying the data once to local scratch at start-up, or packaging it in object storage with a local cache. Local reads are many times quicker than repeated trips to a share.
23Distributed Storage Inside the Cluster
Running your own storage system on the cluster’s nodes gives flexibility and freedom. It also gives you its performance consequences.
Projects such as Rook with Ceph, Longhorn, OpenEBS variants and others turn the local disks of your nodes into a pooled, replicated storage service. Applications request volumes in the usual way, and the system places copies on several nodes. The benefit is that you keep durability and mobility on any infrastructure, including your own data centre.
Why writes cost more
To keep data safe, these systems write each block to several machines and wait for confirmation before saying the write is done. With three copies, a single application write becomes three disk writes and a set of network messages. This is called write amplification. Latency is set by the slowest of the required copies, plus network time.
sequenceDiagram
autonumber
participant App as Application Pod
participant Pri as Primary storage node
participant R1 as Replica node 1
participant R2 as Replica node 2
App->>Pri: Write one block
Pri->>R1: Copy the block
Pri->>R2: Copy the block
R1-->>Pri: Stored
R2-->>Pri: Stored
Pri-->>App: Write complete
What determines performance
The underlying devices
Flash devices give far lower latency than spinning disks. Using fast media for the journal or metadata of the system can dramatically improve write speed.
The network between storage nodes
Replication traffic is heavy. A fast, low-latency network, ideally dedicated or well-isolated from application traffic, is often the most valuable upgrade.
The replication or erasure coding choice
Keeping full copies is fast and costly in space. Erasure coding saves space but adds computing work and slower small writes. Choose per workload.
Data locality
Some systems can keep one copy on the same node as the Pod, so reads are local. This may improve read latency while writes still travel to the other copies.
Rebuild and rebalance work
When a node fails or is added, the system copies data around in the background. This heavy traffic can slow foreground requests unless limited. Tune recovery speed to balance safety and performance.
Running a replicated database on top of replicated storage multiplies the copies. Three database replicas, each on storage that makes three copies, means nine copies of every write. If the database already replicates, ask whether the storage layer should do so too.
24Observability: What to Measure and Where
Tuning without measurement is guessing with extra steps. Put the gauges in place before the incident.
Storage health can be seen from four viewpoints, and a good dashboard has something from each. Together they let you say which stop on the path from Chapter 3 is struggling.
The application
Query times, commit times, request latency percentiles, queue lengths and error rates. The application is the final judge of whether storage is good enough.
The volume
The kubelet reports capacity, used space and inode usage for each mounted volume. Alerts on space and inodes prevent the dullest outages.
The node
Per-device read and write rates, queue length, utilisation and average wait time, usually gathered by a node exporter. These show saturation and contention.
The storage system and cloud
Provider metrics for each disk, such as consumed IOPS, throughput, queue length, burst balance and throttling events, and the health metrics of any in-cluster storage system.
The metrics that tell the most
| Metric | What it tells you | Worry when |
|---|---|---|
| Device utilisation | Share of time the device is busy | Stays near the maximum while latency climbs |
| Average queue length | How many requests are waiting | Grows steadily; the device cannot keep up |
| Read and write latency percentiles | How long requests take | p99 diverges from the median |
| Throughput versus the limit | Closeness to the bandwidth ceiling | Flat at exactly the published limit |
| Provider throttling or burst balance | Whether credits or caps are in play | Balance trending to zero |
| Volume space and inodes | Distance to full | Above 80 percent, rising |
| Attach and mount durations | Pod start delays | Minutes instead of seconds |
| Page cache hit behaviour and memory pressure | Whether reads are served from memory | Cache shrinking while reads climb |
| Pending or failed volume events | Provisioning or attach errors | Any sustained growth |
Good alerts, not loud alerts
- Alert on user-visible symptoms first, such as commit latency, and on causes second.
- Alert on volume space with enough lead time to expand safely.
- Alert when a disk approaches its provisioned limits, before throttling begins.
- Alert on unusual attach and mount durations.
Put application latency, node disk wait and provider throttling on one dashboard with a shared time axis. The cause of a slowdown usually becomes obvious when you can see all three rise together.
25Troubleshooting: From Symptom to Cause
A calm, ordered path beats guessing. Work from the outside in, and from the cheap checks to the expensive ones.
flowchart TD
A["Application is slow"] --> B{"Is the slowness at start-up only?"}
B -->|"Yes"| C["Check attach, mount, ownership change and relabelling times in Pod events"]
B -->|"No"| D{"Is the node disk busy with a long queue?"}
D -->|"No"| E["Look at CPU, memory pressure, locks and the application itself"]
D -->|"Yes"| F{"Is throughput or IOPS flat at a published limit?"}
F -->|"Yes"| G["Raise the volume limit or machine storage cap, or spread the load"]
F -->|"No"| H{"Do other Pods on the node cause the load?"}
H -->|"Yes"| I["Isolate with dedicated nodes, separate disks or rate limits"]
H -->|"No"| J["Check queue depth, block size, filesystem options and the access pattern"]
Symptom table
| Symptom | Likely cause | First check | Typical cure |
|---|---|---|---|
| Pod stuck pending with a volume complaint | Disk in a different zone, or attach limit reached | Pod and claim events, node attach counts | Use wait-for-first-consumer binding; spread Pods |
| Pod takes many minutes to start | Recursive ownership or label change, slow detach | Time between attach and container start | Set ownership change policy to check the root only; trim file counts |
| Fast at first, slow after several minutes | Burst credits exhausted | Provider burst balance graph | Choose a type with a flat baseline or provision more |
| Latency spikes every few minutes | Background compaction, snapshot, or write-out stalls | Timing against engine and backup schedules | Separate volumes, throttle background work, tune dirty limits |
| Throughput stuck at an exact round number | Hitting a volume or machine cap | Compare with documented limits | Raise limits, larger machine type, add volumes |
| Writes fail though space is free | Out of inodes | Inode usage on the volume | Reformat with more inodes, reduce tiny files |
| Everything on a node slows together | Noisy neighbour or full root disk | Per-process I/O and root disk usage | Isolate, move logs, add disks |
| Shared volume slow for listing and builds | Metadata round trips | Operation counts versus data moved | Local scratch, caching, change tool or storage type |
| Large transfers hang but small ones work | Network packet size mismatch on the storage path | Network settings between node and storage | Align packet size settings |
| Speed dropped after an upgrade | Driver, kernel or option change | Rerun the baseline test | Roll back or adjust; report the regression |
Keep a small debugging Pod image with disk and network test tools ready. Being able to run a quick test on the same node and the same kind of volume in two minutes turns a long argument into a short answer.
26Patterns and Anti-Patterns
Learn from the mistakes other teams have already paid for.
Patterns that work
- Publish a small menu of well-benchmarked StorageClasses with clear names.
- Use wait-for-first-consumer binding for zonal and local storage.
- Give database logs their own low-latency volume.
- Benchmark with the real access pattern, long enough to drain bursts.
- Record baselines and repeat them after every upgrade.
- Reserve nodes for heavy storage users.
- Monitor application latency and provider throttling together.
- Rehearse node failure and restore, and time them.
Anti-patterns
- Buying the fastest disk everywhere without finding the real bottleneck.
- Benchmarking a file that fits in memory.
- Putting a database on a general-purpose shared file system.
- Letting every Pod write heavily to the node root disk.
- Stacking replication on replication without a reason.
- Disabling safety features to win a benchmark.
- Copying a kernel tuning list without measuring.
- Ignoring inode and space alerts until the volume is full.
Decision
Every production database gets a separate, low-latency volume for its write-ahead log, with its own StorageClass and its own alerts.
Reason
Commit latency is bounded by log write latency. Isolating the log from data file and background traffic keeps that latency steady and makes problems easier to diagnose.
Decision
All StorageClasses that use zonal or local storage must use wait-for-first-consumer binding.
Reason
Creating a disk before the Pod is placed leads to zone mismatches and pending Pods. Waiting costs nothing and removes the failure class.
A pattern in practice
A platform team publishes three classes called standard, fast-db and throughput. Each page of their internal documentation lists the benchmark results, the cost per month for a typical size, and the workloads that suit it. New teams choose from the menu in minutes, and the platform team reviews any request that needs a custom class. In a year, the number of storage-related incidents drops, and the storage bill becomes explainable.
27Balancing Cost and Performance
Speed costs money, and the most expensive disk is the one nobody needs.
Every performance tier has a price, and prices differ widely. The skill is to buy performance where it is felt and nowhere else. A few principles keep spending sensible.
Measure before upgrading
If the volume is nowhere near its limits, a faster tier changes nothing. The cheapest improvement is often in the application or the layout.
Split by need
Give the small, latency-critical log a premium disk and the large, cold data a cheaper one. Paying premium prices for terabytes of cold data is a classic waste.
Buy performance, not just size
Where your provider lets you provision performance independently, avoid buying empty space solely to gain speed.
Cache the hot part
A modest amount of memory or fast local scratch in front of slower storage often beats an expensive disk. Reading data twice from slow storage is the real waste.
Count the hidden costs
Cross-zone traffic, snapshot storage, API calls to object stores, idle volumes left behind by deleted workloads, and over-provisioned replicas all add up.
Review regularly
Workloads change. A quarterly look at utilisation versus provisioned limits finds disks that are far too big or far too small.
Orphaned volumes
When a claim is deleted but the reclaim policy keeps the disk, the volume remains and the bill continues. Likewise, volumes from retired test environments linger. A monthly report of volumes that are not attached to any Pod pays for itself surprisingly quickly.
28A Worked Example With Numbers
Walk through one problem from complaint to cure, using plain arithmetic.
The situation
A company runs a relational database for an order system in a Kubernetes cluster. The database uses one network block volume for everything, with published limits of 3,000 IOPS and 125 megabytes per second. At peak, orders feel sluggish, and every evening a reporting job makes things worse. The machine type running the database has a storage bandwidth cap well above the disk limit, so the volume itself is the first ceiling.
Measure the demand
| Item | Observation | Calculation |
|---|---|---|
| Peak order writes | 2,500 small writes per second, 8 KB each | 2,500 × 8 KB = 20,000 KB, about 20 MB per second |
| Peak read IOPS from queries | 1,200 per second, 8 KB each | 1,200 × 8 KB, about 10 MB per second |
| Combined IOPS | 2,500 + 1,200 | 3,700 per second, above the 3,000 limit |
| Evening report scan | Large sequential reads at full speed | Tries to read 120 MB per second |
| Combined throughput in the evening | 20 + 10 + 120 | 150 MB per second, above the 125 limit |
What the numbers say
- At peak, the IOPS demand of 3,700 is above the 3,000 limit by about 23 percent. Requests queue up and latency grows. The slowness is real, and it matches a ceiling, not an application bug.
- In the evening, throughput demand exceeds the bandwidth limit by 25 megabytes per second, so the report starves the orders, and the orders slow down the report.
- The log traffic and the data traffic share the same queue, so a burst of data reads delays log writes, which directly delays commits.
Applying queue depth arithmetic
The team measures a single-depth sync write latency of 2 milliseconds on the volume. Using the rule from Chapter 2, one request at a time gives at most about 500 operations per second (1 divided by 0.002). The database uses group commit, batching several transactions per log write, so it handles the 2,500 order writes with fewer log writes. Even so, the log’s latency sets the commit time, and 2 milliseconds is acceptable only if it stays steady. Sharing a queue with the report breaks that steadiness.
The plan
Separate the log
Move the write-ahead log to its own small volume provisioned for low latency. Its demand is small in size, so cost stays low.
Raise the data volume limit
Provision about 6,000 IOPS and 250 megabytes per second on the data volume, which gives roughly 60 percent headroom over the 3,700 IOPS peak and covers the evening throughput of 150.
Move the report off the primary
Run the report against a read replica, so its scan never meets order traffic. The replica has its own volume, sized for throughput.
Check the machine cap
Confirm that the combined bandwidth of the log and data volumes (about 250 plus a small log figure) is below the machine type’s storage cap. If not, move to a larger machine type.
Rerun the benchmark and alerts
Repeat the mixed test, confirm p99 commit latency, and set alerts at 70 percent of the new limits.
Nothing here required a heroic kernel tweak. Simple arithmetic found a ceiling, a clean layout removed interference, and a monitored limit protected the result.
29Real-World Scenarios
Typical situations where these ideas apply, described in general terms.
Black Friday database stalls
A shop’s order database slows during a sale because burst credits on its disk run out after the first hour of traffic. Switching to a type with a flat baseline and adding alerts for the credit balance solves it.
Transcoding farm on a shared file system
Workers read large source files from a share and write results back. Throughput is fine, but thousands of tiny status files overwhelm metadata. Moving status to a small database and using local scratch for temporary files fixes it.
Slow failover after a node crash
A payments database takes eight minutes to come back after a node loss. Time is lost waiting for the old disk to detach and then changing ownership on millions of files. Application-level failover to a standby and a root-only ownership check cut it to under a minute.
Analytics and operational data on one disk
A team runs nightly scans on the same volume as an operational service. Moving scans to a replica with a throughput-oriented class ends the daily complaints.
Training jobs waiting for data
GPUs sit idle because dataset reads are slow. Caching the dataset on local fast disks at the start of the job, and reading it in parallel, keeps the expensive accelerators busy.
Search cluster rebuilds slow the site
When a search node is replaced, rebuilding data saturates the network and disks. Limiting recovery speed and adding a dedicated storage network keeps queries responsive.
These are typical patterns, not reports about named companies. Check current provider limits and case studies before quoting figures in your own material.
30A Practical Tuning Checklist
A short, ordered list you can print and follow.
State the goal
Write one measurable sentence, such as a target p99 latency or a target restart time.
Describe the workload
Block size, read and write mix, parallelism, sync behaviour, hot data size and background work.
Know the ceilings
Volume limits, machine storage caps, network paths and burst rules from current provider documentation.
Choose the right kind of storage
Local, network block, shared file, distributed or object, based on durability, mobility and latency needs.
Set the class properly
Right disk type, wait-for-first-consumer binding, expansion allowed, sensible filesystem and mount options.
Lay out the volumes
Separate log, data and scratch. Dedicate nodes to heavy workloads. Spread replicas across zones.
Benchmark honestly
Same path, same pattern, long enough, repeated. Save the baseline with all settings.
Make start-up fast
Use the root-only ownership check, keep file counts sane, and rehearse failover.
Instrument and alert
Application latency, node disk wait, volume space and inodes, provider throttling and attach times.
Review on a schedule
Re-run baselines after upgrades, check utilisation against provisioned limits, and clean up orphaned volumes.
31Glossary of Terms
| Term | Meaning in plain words |
|---|---|
| IOPS | Input and output operations per second; how many separate requests storage handles each second |
| Throughput | How many bytes per second can flow to or from storage |
| Latency | The time one storage request takes from start to finish |
| Tail latency | The slow outliers, usually shown as p99 or p999 |
| Block size | The size of each individual read or write request |
| Queue depth | How many requests are in flight at the same moment |
| Sequential and random access | Reading in order versus jumping around |
| Sync write | A write that waits until data is safely stored before continuing |
| Page cache | Memory the operating system uses to keep recent file data |
| Write-ahead log | A sequential journal that a database writes first, to survive crashes |
| PersistentVolumeClaim | A request for storage made by an application |
| PersistentVolume | An actual piece of storage in the cluster |
| StorageClass | A named recipe describing how storage is created |
| CSI | Container Storage Interface; the standard plug-in model for storage drivers |
| Volume binding mode | The rule for when a disk is created and tied to a claim |
| Topology and zone | The physical grouping of machines; a zonal disk is usable only inside its zone |
| Access mode | How many nodes may use a volume at once and how |
| Ephemeral storage | Temporary space tied to a Pod’s life, on the node’s disk |
| emptyDir | A scratch directory that exists for the Pod’s lifetime |
| Local persistent volume | A disk attached to one node and declared as a Kubernetes volume |
| Burst credits | Extra speed allowed for a short time, earned while the disk is quiet |
| Inode | The record a filesystem keeps for each file |
| fsGroup | A Pod setting for group ownership of files on a volume |
| Snapshot | A point-in-time picture of a volume |
| Write amplification | The extra writes caused by copying data to several places |
| Noisy neighbour | A workload whose heavy use slows down others sharing the same resource |
| StatefulSet | A Kubernetes object for applications with stable identity and their own storage |
| Operator | A controller that automates running a particular application |
| Metadata | Information about files such as names, sizes and timestamps |
32Frequently Asked Questions
It depends on the workload. Transactional systems feel latency and IOPS. Scans, backups and streaming feel throughput. Describe your workload first, then pick the number that it is bound by.
Usually it has lower latency, because there is no network. But a poorly chosen local disk can be slower than a strong provisioned network disk, and local storage has durability and mobility costs. Benchmark both against your workload.
Not directly. The class of a bound claim is fixed. You normally create a new volume from the new class and migrate the data, or use a driver feature that modifies the existing volume where supported.
The test probably did not match the application’s pattern. Block size, queue depth, sync behaviour and the read and write mix all change results. Or the real limit is elsewhere, in locks, CPU or the network.
Not by default. Resource requests and limits cover CPU, memory and ephemeral storage space, not I/O speed. Isolation comes from separate disks, dedicated nodes or features in the storage system.
Check whether a recursive ownership change is happening, and set the ownership change policy so it runs only when the root directory does not already match. Then look at attach and detach delays.
No, not for data you care about. The gain is usually small compared with the risk of corruption after a crash or power loss.
Some platforms load blocks lazily from a remote store on first access, so early reads are slow until the volume warms. Initialise the volume or read through it before relying on it.
On some platforms performance is tied to size, and on others it is independent. Check your provider’s rules. Do not buy extra space just to buy speed if you can purchase performance directly.
Usually not. Databases need low, steady latency for small synchronous writes and reliable locking behaviour. Block volumes or local disks are normally a better fit, unless your vendor documents support for a specific file service.
Compare observed IOPS and throughput with the documented limits and look at the provider’s metrics for throttling or burst balance. Flat lines at an exact round number are the classic sign.
Not for applications that need a filesystem or low-latency random writes. It is excellent for backups, archives, logs and large datasets that are accessed through an API or loaded into local scratch.
After any change to the driver, kernel, node image, machine type or storage class, and on a regular calendar, such as quarterly, to catch drift.
They manage the control plane and often supply default storage classes, but the workload design, volume layout, class choice and monitoring remain your responsibility.
33Summary and Key Takeaways
What to remember
- Three numbers, one story. IOPS, throughput and latency describe different kinds of speed. Match the number that matters to your workload, and use percentiles instead of averages.
- The path has many stops. The application, mount, filesystem, block layer, network and storage system each add delay or limits. The narrowest stop sets the speed.
- Know the three ceilings. The volume, the machine’s storage cap and the network all limit the result. The smallest wins.
- Choose the kind of storage first. Local, network block, shared file, distributed and object storage have very different personalities and trade-offs.
- The class is the main dial. Use a small menu of benchmarked classes, with wait-for-first-consumer binding, and remember that existing volumes do not change when the class does.
- Layout beats brute force. Separate the log, data and scratch, isolate heavy workloads and spread replicas.
- Benchmark honestly. Use the real pattern, drain the burst allowance, repeat it, and keep a written baseline.
- Start-up is performance too. Attach, detach, ownership and relabelling steps can cost minutes. Prepare for them and rehearse failover.
- Watch from four viewpoints. Application, volume, node and provider metrics together reveal where the slow stop is.
- Spend where it is felt. Measure first, split by need, and clean up what nobody uses.