2 min read

Using PostgreSQL Tablespaces to Unlock Hidden Performance

Using PostgreSQL Tablespaces to Unlock Hidden Performance
Photo by Tobias Fischer / Unsplash

The Problem

A customer approached me after migrating their PostgreSQL database to new infrastructure. On paper the setup looked strong: plenty of CPU, ample memory, and a clean migration to a larger instance. Yet after a few weeks in production, users were reporting that queries were lagging. Resource graphs didn’t show obvious bottlenecks. CPU was idle, memory was healthy, connections were fine. Still, performance felt off.

The clue came from the storage layout. The host included a small but extremely fast NVMe disk. The database, however, was much larger than that disk could hold, so the team had placed the entire cluster on a large remote volume instead. That solved the capacity problem but quietly introduced a new one: the local NVMe was idle, while all of the database work was stuck on slower networked storage.

The Investigation

We ran a simple benchmark to compare the two storage types. The results were stark.

  • Local NVMe: hundreds of thousands of IOPS and gigabytes per second of throughput
  • Remote volume: only a fraction of that, roughly three times slower for reads and nearly five times slower for writes

The picture was clear. The database wasn’t “slow” in the abstract—it was being throttled by the choice of storage. The natural question became: could the system take advantage of both?

The Solution

PostgreSQL includes a feature many teams overlook: tablespaces. By default, everything is stored in one data directory, but tablespaces let you define additional storage locations and move specific objects there. Tables, indexes, even entire databases can live on different disks, and PostgreSQL manages them seamlessly.

We created a new tablespace on the NVMe disk and moved the most performance-sensitive objects there. The indexes were the obvious candidates. They are relatively small, but they are accessed constantly and benefit enormously from low-latency storage. We also relocated a handful of smaller, heavily read tables. The larger bulk data remained on the remote volume where capacity was plentiful.

The Results

The impact was immediate.

  • Query latency dropped by nearly half
  • Transactions per second almost doubled
  • The system felt noticeably more responsive under load

Write performance still depended on the slower volume because the WAL remained there, but even that saw indirect benefits as checkpoints cleared more quickly. The customer had unlocked the power of their fast local storage without sacrificing capacity or rewriting application code.

The Takeaway

This case highlights a broader principle. Databases are not monolithic. Different parts of a PostgreSQL cluster stress storage in different ways. Indexes, WAL, and large tables each have unique access patterns. Treating all of them the same leaves performance on the table.

With tablespaces, you don’t have to choose between fast-but-small and large-but-slow disks. You can combine them. Place the right objects on the right storage, and suddenly the hardware you already have works much harder for you.

For this customer, the answer was as simple as letting PostgreSQL use both the NVMe and the remote volume. For anyone running PostgreSQL in production, the question is worth asking: are you really making the most of the storage sitting inside your server?