PostgreSQL on a Low-Memory Linux Server: Configuration Changes That Prevent Swap Thrash

PostgreSQL’s default configuration assumes a machine with far more RAM than a small server provides. On a 1 GB or 2 GB instance, out-of-the-box settings can push the database into swap within a day of moderate traffic, and swap thrashing on a shared virtual disk turns a 5 ms query into a 2-second one. This is a practical tuning walkthrough for memory-constrained PostgreSQL deployments.

Baseline: What Each Setting Costs

SettingDefaultMemory behaviourSensible small-server value
shared_buffers128 MBFixed allocation at startup25% of RAM, e.g. 256 MB on 1 GB
work_mem4 MBPer sort/hash operation, can multiply4-8 MB; keep low on shared boxes
maintenance_work_mem64 MBPer maintenance connection64-128 MB
effective_cache_size4 GBPlanner hint only, no allocation50-75% of RAM
max_connections100Each backend ~5-10 MB20-40, with a connection pooler
wal_buffers-1 (auto)Small fixed bufferLeave default or 16 MB

The dangerous settings are the per-operation ones. work_mem is allocated per sort or hash node, per connection. With work_mem = 64MB and 20 concurrent sort-heavy queries, PostgreSQL will attempt 1.28 GB of work memory on a 1 GB box. That is the classic path to swap death, and it happens gradually enough that people blame the provider instead of the config.

The Tuning Sequence

  1. Measure current usage. Run free -m and ps aux --sort=-rss | head to see actual PostgreSQL RSS. Check vmstat 1 10 — a non-zero si/so column during normal traffic means you are already swapping.
  2. Check the log for spills. Set log_temp_files = 0 to log every temp file larger than 0 bytes. Frequent temp files mean work_mem is too small for your queries; one or two large ones mean a single bad query.
  3. Set shared_buffers to 25% of RAM. Edit postgresql.conf, restart, and confirm with SHOW shared_buffers;.
  4. Set max_connections to 20-40 and deploy a pooler. PgBouncer in transaction mode lets 200 application clients share 20 database backends — the single largest memory saving available.
  5. Cap work_mem conservatively. Multiply expected concurrent sort operations by work_mem and confirm the product stays under 20% of RAM.
  6. Set effective_cache_size to 60% of RAM. This is planner guidance only — it costs nothing and improves join planning.
  7. Restart and re-measure. Compare vmstat swap activity and query latency before and after.

Change one group of settings at a time. If you alter all seven at once and performance improves, you learn nothing about which change mattered, and you cannot safely tune the next server.

Sizing Guide by Instance RAM

Instance RAMshared_bufferswork_memmax_connectionseffective_cache_size
1 GB256 MB4 MB20512 MB
2 GB512 MB6 MB301 GB
4 GB1 GB8 MB402.5 GB
8 GB2 GB16 MB605 GB

Kernel and OS Level Adjustments

  • Swap on SSD, not a spinning or network volume. If your provider gives you swapon --show output with a slow device, keep swap minimal rather than large.
  • Lower vm.swappiness to 10 so the kernel keeps the database in RAM instead of eagerly swapping idle pages.
  • Consider vm.overcommit_memory = 2 with a modest overcommit ratio on dedicated boxes — it prevents the OOM killer from choosing random victims, though it can cause allocation failures instead.
  • Install pg_stat_statements before tuning further; it adds small overhead but is the only reliable way to find the queries causing temp spills.
  • Review autovacuum — a table bloated by stale rows reads more pages and burns more buffer cache than a freshly vacuumed one.

When Tuning Is Not Enough

If pg_stat_statements shows a handful of queries causing multi-gigabyte sorts, the fix is an index or a rewritten query, not more RAM. Add the index, confirm the temp file count drops, and only then reconsider your plan size. Many deployments chase a plan upgrade when an index would have solved the problem for zero dollars.

If you do conclude the instance is genuinely too small, size the upgrade against measured peak memory rather than guessing. You can compare budget VPS plans on our comparison table to see which tiers give you the RAM headroom you need, and our guide to RAM headroom on a VPS explains how much spare memory to keep on a database server. For swap-specific diagnostics, our analysis of swap versus RAM covers how to tell a quiet memory shortage from a real one.

Run the tuning loop once a quarter, or after any traffic step-change. Configuration is not a one-time task — it is a habit that keeps a small database server fast without buying more hardware than you need.

Monitoring: Catching Swap Thrash Before Users Do

Tuning is static; monitoring is what keeps it working. Three cheap signals tell you a small PostgreSQL server is under memory pressure before queries slow down visibly. First, watch si and so in vmstat — sustained non-zero values mean active swapping, not just a loaded swap file. Second, track the count of temp files logged by log_temp_files; a rising count means queries are spilling to disk. Third, watch the OS page cache size with free -m — if the cache is being squeezed to near zero, the database has outgrown the instance.

Set a simple alert on any of these and you get days of warning instead of a sudden outage. A cron job writing vmstat and pg_stat_activity summaries to a log, plus a weekly glance at the trend, is enough for most small deployments. The goal is to make the memory ceiling visible long before the OOM killer makes it obvious.

A Realistic Before-and-After

A typical 1 GB instance running PostgreSQL with default settings might show 200 MB of swap in use during peak traffic and a p99 query latency near 800 ms. After capping work_mem at 4 MB, adding PgBouncer, and setting shared_buffers to 256 MB, the same workload often drops to near-zero swap and a p99 in the low tens of milliseconds — on identical hardware, for no additional money. That gap is why tuning before upgrading is almost always the correct order of operations.

Affordable-Vps-Server-Author
Affordable-Vps-Server-Author
Articles: 352

Leave a Reply