Postgres Isn't Slow — Your Storage Is

Before blaming Postgres, check your storage — cloud IOPS limits are often the real culprit.
This article challenges the reflex to "fix the SQL" when performance degrades, arguing that in cloud-native environments, Postgres slowdowns typically originate in the storage layer — not the database engine. Cloud block storage abstracts away its remote network I/O nature, leading engineers astray when they see low CPU but slow responses. The article recommends diagnosing via OS-level metrics like iowait, disk queue depth, and cache hit rates, then prioritizing memory increases, tuning `shared_buffers`, and upgrading storage over blind query rewrites.
When the Database Gets Blamed Unfairly: Where the Real Bottleneck Lives
A debate that keeps resurfacing in tech circles: when application performance degrades, Postgres is often the first to stand trial. Developers suspect poorly written queries, missing indexes, or simply conclude that "relational databases are just slow." But a growing body of real-world experience points to a long-overlooked truth — most of the time, the database itself isn't the problem. What's actually dragging everything down is the underlying storage layer.
This seems like a simple observation, yet it exposes a critical blind spot in modern data architecture. A database engine's execution plans, locking mechanisms, and buffer management all matter — but they ultimately bottleneck at the physical layer of disk I/O. When storage latency becomes the constraint, even the most sophisticated query optimizer can't save you.

Why Storage Performance Gets Overlooked
Storage performance is so often ignored largely because of its "invisibility." In cloud environments, developers interact with abstracted block storage services, where the underlying IOPS limits, latency characteristics, and throughput ceilings are buried beneath thick layers of abstraction. What you see is a "disk" — but in reality, it may be remote storage connected over a network, where every read and write incurs additional network hops.
This architecture delivers elasticity and reliability, but it also introduces non-trivial latency. Random read/write latency on local NVMe SSDs can be measured in microseconds, while some cloud block storage solutions run an order of magnitude slower. When a database needs to perform frequent random I/O — such as handling large volumes of point lookups or operations requiring heavy disk access — that gap compounds quickly.
The Hidden Cost of Cloud Storage
Cloud providers typically tier their storage offerings by IOPS and throughput, with pricing to match. Many teams opt for lower-tier configurations early on to control costs, only to discover later that storage has become the ceiling once traffic picks up. What makes this especially tricky is that these problems often surface in monitoring dashboards as "low database CPU but slow response times" — a symptom that easily sends engineers down the wrong diagnostic path.
IOPS (Input/Output Operations Per Second) is one of the core metrics for measuring storage performance. Take AWS EBS as an example: general-purpose gp2 volumes provide a baseline of 3 IOPS per GB with a maximum of 16,000 IOPS, while io2 Block Express can reach 256,000 IOPS — at a dramatically different price point. For a database like Postgres that frequently performs small, random I/O operations, the IOPS ceiling tends to be hit before the throughput bandwidth limit.
When storage hits its IOPS limit, I/O requests queue up. Every physical read or write behind a SELECT or UPDATE has to wait in line, which directly manifests as a spike in query latency — while CPU and network monitors still show "everything is fine." This is precisely what makes storage bottlenecks so deceptive. Cloud providers typically allow storage to "burst" to higher IOPS for short periods, but once burst credits are exhausted, performance drops to baseline levels. This burst behavior is especially prone to triggering unexpected performance cliffs during traffic peaks.
How to Properly Diagnose a Storage Bottleneck
Diagnosing a storage bottleneck requires stepping outside the database's own perspective. Common signals include: low database CPU utilization alongside persistently high query latency, elevated I/O wait time (iowait), and a consistently growing disk queue depth. These indicators converge on a single conclusion — the system is waiting on storage, not computing.
A practical approach is to investigate in layers: start with OS-level tools like iostat and iotop to observe actual disk load, then cross-reference Postgres's own statistics views to analyze query I/O patterns. If you find that a significant portion of time is spent on physical reads with a low cache hit rate, the root cause likely isn't the SQL itself — it's the storage configuration, or insufficient memory causing frequent page evictions.
Prioritizing Your Optimization Approach
Once a storage bottleneck is confirmed, the optimization path is usually more straightforward than rewriting queries: add memory so more data stays in shared buffers and physical I/O decreases; upgrade to a higher-performance storage tier or local SSDs; and tune Postgres parameters like shared_buffers and effective_cache_size to match the actual hardware. Compared to blindly adding indexes or refactoring queries, these measures typically deliver far more dramatic performance gains.
shared_buffers is Postgres's memory pool for caching data pages. Its default value is typically only 128MB — far below what's recommended for production (generally 25% of total system memory). effective_cache_size is an estimation parameter that tells the query planner approximately how much memory is available in the OS page cache for data caching. It doesn't actually allocate memory, but it influences whether the planner favors index scans over sequential scans — setting it too low pushes the planner toward sequential scans, while a higher value encourages index usage.
The two work in tandem: shared_buffers determines how much hot data Postgres can cache internally, while effective_cache_size helps the planner make more realistic execution decisions. In cloud environments where storage I/O is expensive, thoughtfully increasing both parameters can significantly reduce physical disk access — making it one of the lowest-cost optimizations available.
Implications for Architectural Decisions
The deeper value of this discussion is a reminder to engineering teams: performance optimization must be grounded in accurate attribution. Treating the database as the default scapegoat for performance problems can lead teams to invest significant effort optimizing parts of the system that aren't actually the bottleneck, while the real issue — storage configuration — goes unaddressed.
For teams currently working through technology selection or capacity planning, this means treating storage layer characteristics as a first-class concern when evaluating database performance. Whether you're choosing a cloud instance type, designing a data tiering strategy, or setting a cost budget, storage latency and throughput capacity should be explicitly built into the decision model.
Ultimately, the claim that "Postgres isn't slow" isn't a defense of the database — it's an argument for a more systematic and fundamental approach to performance analysis: before drawing conclusions, figure out exactly which layer the bottleneck lives in.
Related articles

AI Visibility Metrics: Definitions Matter More Than Dashboards
AI visibility measurement tools are proliferating, but the lack of unified definitions makes data incomparable. This article analyzes the mismatch and explores why repeatable methodology matters more than dashboards, and how a shared vocabulary can make AI search measurement truly credible.

Liquid AI Releases LFM2.5-24B-A2B: A 2B Active-Parameter MoE Built for On-Device Agents
Liquid AI releases LFM2.5-24B-A2B, an open-weights MoE model with 24B total and 2B active parameters for on-device agents, scoring 48.09 on LiveClaw and hitting 184 tokens/s on Apple M5 Max.

WorkBuddy Full Walkthrough: From Chat to AI Agent-Powered Smart Office Work
Using batch Word-to-PDF archiving as an example, this guide compares Doubao and WorkBuddy. WorkBuddy, as an AI Agent, reads local files, performs batch operations, and delivers results—beginner-friendly, with credit data and scenario analysis.