CrestVPS

postgresql vps

Databases and in-memory workloads

Enough memory that the disk stops mattering.

What we would deploy

€125.30/mo

€17930% annual

Past a certain working-set size the only optimisation that counts is fitting the whole thing in RAM. FORGE gives up to sixteen gigabytes per core of registered ECC memory with hugepages preconfigured and NUMA-aware placement, which is what PostgreSQL, Redis, ClickHouse and large JVM heaps want. Underneath it is Gen5 NVMe in RAID10, so the writes that do hit disk are not the bottleneck either.

Why this configuration

FORGE with single-NUMA affinity keeps memory access local to the socket doing the work. Add nightly backups with thirty restore points, because the failure mode you will actually hit is a bad migration, not a dead drive.

What we would deploy

PlanFORGE F2
vCPU8 (dedicated)
RAM128 GB
Storage800 GB
ImageDebian 13 Trixie

Sizing

Size RAM to the working set, not the database. The working set is hot index pages plus rows actually touched, often 10-20% of a 2 TB OLTP dataset. Set the InnoDB buffer pool at 70-75% of RAM, or Postgres shared_buffers at 25% and let the page cache hold the rest. FORGE reaches 16 GB per core, so a 512 GB buffer pool sits on about 700 GB of RAM and at least 44 cores. If reads miss cache more than 1% of the time, buy RAM.

Hugepages, and why THP is not the same thing

A 256 GB buffer pool mapped in 4 KB pages needs about 64 million page table entries. The TLB cannot hold a meaningful fraction of that, so random access pays a page walk on most lookups. Static 2 MB hugepages cut the entry count by 512 and the walks with it — expect a measurable drop in CPU time per query on large pools. Allocate them at boot via vm.nr_hugepages, size for the pool plus roughly 10%, and give the database user the memlock limit. Transparent hugepages achieve the same mapping but do it opportunistically, with compaction stalls. Disable THP and allocate explicitly.

Working set, measured rather than guessed

Do not estimate the working set from the data directory size. Measure it. In Postgres, pg_buffercache tells you which relations occupy shared_buffers, and the ratio of heap_blks_hit to heap_blks_read in pg_statio_user_tables gives the cache hit rate per table. In MySQL, Innodb_buffer_pool_reads against Innodb_buffer_pool_read_requests gives the same signal. Above 99% you are in memory and adding RAM buys little. Between 95% and 99% you are on the cliff edge, and a single unindexed query can push you over. Below 95% every latency number you have is really a disk latency number wearing a disguise.

What goes wrong

  • Leaving transparent hugepages enabled. THP defragmentation stalls the process for milliseconds at unpredictable moments, which shows up as p99 latency nobody can explain. Set it to never and allocate static hugepages instead.
  • Ignoring NUMA on a two-socket FORGE. The buffer pool lands on one node, half the cores read across the interconnect, and MySQL may swap despite free memory. Use numactl --interleave=all or bind explicitly.
  • Sizing IOPS from average throughput. Checkpoints, vacuum and backups are the peaks that matter. A pool that handles the steady state fine will stall the whole instance during a checkpoint flush.

Tune the machine

  • CPU schedulingSingle-NUMA-node affinity · €14
  • BackupsDaily backup, 30 restore points · €11
  • Everything elseSecond-resolution metrics + alerting · €5
  • Storage and encryptionZFS with hourly snapshots · €7