Mohamed KEITA
Note #65 min read

Why PostgreSQL Struggles in Constrained Environments

PostgreSQL is one of the most admired relational database systems in the world. Its reliability, maturity, SQL compliance, and extensibility have made it a default choice for countless applications. But like every system, Postgres is shaped by deep architectural decisions—and some of these decisions make it poorly suited for environments with limited CPU, memory, storage bandwidth, or high network latency.

This note does not criticize Postgres as a whole. Instead, it explains why its architecture behaves the way it does, and why these behaviors can become bottlenecks in constrained or geographically distributed environments. Understanding these limitations is essential when evaluating whether Postgres is the right foundation for a workload or for designing alternative storage engines.

The B-Tree + VACUUM Architecture

Postgres stores most of its tables and indexes using B-Trees, a mature and well-understood structure optimized for fast point lookups and predictable read performance. But B-Trees rely on in-place updates, meaning updates must rewrite pages directly on disk. Because of this, Postgres uses a mechanism called MVCC (Multi-Version Concurrency Control), where old row versions are preserved until no transaction needs them.

Postgres never overwrites rows in place. Instead:

UPDATE → creates a new row version
DELETE → marks a row as dead
INSERT → adds a new version

Over time, these dead entries accumulate inside pages. To clean them, Postgres relies on VACUUM, a background process that reclaims space.

While MVCC provides excellent isolation guarantees, the combination of:

  • in-place B-Tree pages
  • multi-version rows
  • physical vacuuming

creates a maintenance-heavy system that performs best with abundant I/O and steady CPU availability.

Bloat: The Hidden Cost of MVCC

Bloat happens when page space is consumed by obsolete row versions. Because rows are never overwritten, each update produces more physical data.

A simplified view:


[Before UPDATE]        [After updates accumulate]
+---------+            +-------------------------+
| row A   |    --->    | row A (old versions)    |
| row B   |            | row A (current)         |
+---------+            | row B (old versions)    |
+------------------------------------------------+

As bloat increases:

  • queries read more pages,
  • indexes grow unnecessarily,
  • VACUUM becomes more frequent,
  • storage consumption inflates dramatically.

On powerful machines with large I/O bandwidth, VACUUM runs quietly in the background.
In constrained environments, it competes with your workload and can severely affect performance.

The Maintenance Burden: VACUUM and Autovacuum

VACUUM is not optional—Postgres depends on it for correctness. But VACUUM introduces challenges:

  • It is CPU- and I/O-intensive.
  • If autovacuum falls behind, bloat increases nonlinearly.
  • On slow disks, VACUUM can cause significant read/write contention.
  • In environments with limited memory, it fights the buffer cache.
  • In small deployments (edge devices, VMs, small cloud instances), it often becomes the dominant maintenance cost.

VACUUM also interacts poorly with high-churn workloads (frequent updates/deletes), where dead tuples accumulate faster than autovacuum can reclaim them.

Postgres was designed for servers—not for machines where maintenance must be minimal and predictable.

Scaling: Vertical, Not Horizontal

Postgres can be replicated, but its scaling model is fundamentally vertical:

  • Scaling reads uses physical read replicas.
  • Scaling writes is not natively supported.
  • Partitioning/sharding requires manual orchestration or third-party tools.
  • Replication requires stable, low-latency networks.

In distributed or African infrastructure contexts—where WAN latency, instability, or intermittent links are common—Postgres’ synchronous and asynchronous replication modes become fragile or impractical.

The engine assumes:

  • stable networks,
  • near-zero latency between nodes,
  • consistent throughput,
  • central datacenter availability.

These assumptions break down in constrained or local-first environments.

High Sensitivity to Network Latency

Postgres clients communicate using a chatty protocol: many queries require multiple round-trips. Even simple statements may involve:


Client → Parse
Server → ParseComplete
Client → Bind
Server → BindComplete
Client → Execute
Server → DataRow(s)

On a LAN, this is negligible.
On WAN links or unstable networks, latency multiplies dramatically.

If the round-trip time (RTT) is high:

  • throughput collapses,
  • transactions appear slow even if the server is idle,
  • connection pooling becomes mandatory but insufficient.

Postgres works best when the client and server live close to each other—architecturally and geographically.

Memory and CPU Consumption

Postgres keeps significant metadata, caches, and buffers in memory. While configurable, its memory architecture assumes:

  • hundreds of megabytes available for effective caching,
  • enough CPU to run Planner, Executor, VACUUM, and background workers concurrently.

On small machines, Postgres can run, but not efficiently. Its core algorithms were not designed with:

  • low-memory edge devices,
  • constrained VMs,
  • intermittent power conditions,
  • bandwidth-limited disks

in mind.

Many workloads in Africa, rural deployments, or embedded installations encounter exactly these constraints.

Conclusion

PostgreSQL remains one of the most powerful and reliable database systems available. But it was engineered for environments with:

  • steady CPU resources,
  • large memory pools,
  • predictable I/O throughput,
  • low-latency networks,
  • centralized deployments.

In constrained settings, its architectural assumptions start to fail.
Bloat grows faster, VACUUM becomes heavier, replication becomes unstable, and query latency becomes dominated by network round-trips.

Understanding these limitations is not a criticism—it is a recognition of the context in which Postgres excels, and the contexts where alternative architectures may be necessary.

Recommended References

  1. PostgreSQL Documentation — MVCC, VACUUM, and Storage Internals
  2. “The Anatomy of a PostgreSQL Update” — 2ndQuadrant
  3. H. Garcia-Molina et al. — Database Systems: The Complete Book
  4. PostgreSQL Wiki — Bloat and Autovacuum Tuning
  5. “PostgreSQL and High Latency Networks” — Postgres Professional