Summary
I repeated Andres Freund's 2020 connection-memory measurement method on PostgreSQL 18.3 on Ubuntu 24.04, with both huge_pages = off and huge_pages = on.
Reference method: https://blog.anarazel.de/2020/10/07/measuring-the-memory-overhead-of-a-postgres-connection/
Short version: ps/top RSS is still the wrong metric. Using Pss_Anon from /proc/$pid/smaps_rollup plus VmPTE from /proc/$pid/status, a PG18 backend running a simple pgbench read-only workload on this host was:
| huge_pages |
shared_buffers |
median overhead |
range |
| off |
1GB |
2.64 MiB |
2.17–2.92 MiB |
| on |
1GB |
1.51 MiB |
1.51–1.52 MiB |
For reference, an earlier huge_pages = off run with shared_buffers = 2GB measured 2.33 MiB median across 10 sampled pgbench backends. The final comparison table above uses 1GB for both modes because this host could allocate only 594 huge pages (~1.16 GiB) without reboot/compaction.
Environment
Host: Linux openclaw-server 6.8.0-94-generic #96-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 9 20:36:55 UTC 2026 x86_64
OS: Ubuntu 24.04.3 LTS
PostgreSQL: PostgreSQL 18.3 (Ubuntu 18.3-1.pgdg24.04+1)
THP: always [madvise] never
Huge page size: 2048 kB
Cluster settings for the comparable runs:
max_connections = 1100
shared_buffers = 1GB
work_mem = 4MB
jit = on
For the huge_pages = on run, host huge pages were temporarily raised from 0 to 594 and then restored to 0 after the experiment.
Workload
Throwaway PG18 cluster under /tmp, Unix socket only.
pgbench -i -s 100 bench
pgbench -S -M prepared -c 1024 -j 16 -T 90 bench
At sampling time in both runs:
connected pgbench backends: 1024
pg_stat_activity rows: 1033
Measurement method
Same approximation as the blog post:
per-connection overhead ~= Pss_Anon + VmPTE
Where:
Pss_Anon comes from /proc/$pid/smaps_rollup
VmPTE comes from /proc/$pid/status
RssFile is ignored because it is mostly executable/shared library mappings
RssShmem / Pss_Shmem is not counted as private connection memory
Results: huge_pages = off
Cluster:
huge_pages = off
shared_buffers = 131072 8kB # 1GB
nr_hugepages = 0
Fresh sleeping connection before pgbench:
RssAnon: 3344 kB
RssFile: 8576 kB
RssShmem: 8448 kB
VmPTE: 236 kB
HugetlbPages: 0 kB
Pss: 8296 kB
Pss_Anon: 1510 kB
Pss_File: 3055 kB
Pss_Shmem: 3731 kB
Sampled pgbench backends:
| pid |
RssAnon kB |
RssFile kB |
RssShmem kB |
HugetlbPages kB |
Pss_Anon kB |
VmPTE kB |
approx overhead MiB |
| 177527 |
3216 |
9088 |
29184 |
0 |
1398 |
820 |
2.17 |
| 177528 |
3216 |
9088 |
31360 |
0 |
1398 |
868 |
2.21 |
| 177529 |
3344 |
8960 |
34560 |
0 |
1398 |
1064 |
2.40 |
| 177531 |
3216 |
9088 |
39424 |
0 |
1398 |
1132 |
2.47 |
| 177532 |
3344 |
9088 |
42112 |
0 |
1410 |
1236 |
2.58 |
| 177533 |
3344 |
9088 |
46464 |
0 |
1398 |
1360 |
2.69 |
| 177534 |
3472 |
9088 |
48128 |
0 |
1410 |
1372 |
2.72 |
| 177537 |
3472 |
9088 |
53504 |
0 |
1398 |
1504 |
2.83 |
| 177538 |
3344 |
8960 |
57984 |
0 |
1398 |
1580 |
2.91 |
| 177539 |
3344 |
9088 |
58240 |
0 |
1398 |
1596 |
2.92 |
Summary:
min: 2218 kB = 2.17 MiB
median: 2702 kB = 2.64 MiB
avg: 2653.6 kB = 2.59 MiB
max: 2994 kB = 2.92 MiB
pgbench:
number of transactions actually processed: 3176975
number of failed transactions: 0 (0.000%)
latency average = 28.249 ms
tps = 36249.676823 (without initial connection time)
Results: huge_pages = on
Cluster:
huge_pages = on
shared_buffers = 131072 8kB # 1GB
nr_hugepages = 594
HugePages_Total = 594
HugePages_Free at sampling = 108
Fresh sleeping connection before pgbench:
RssAnon: 3424 kB
RssFile: 8320 kB
RssShmem: 0 kB
VmPTE: 144 kB
HugetlbPages: 55296 kB
Pss: 4598 kB
Pss_Anon: 1508 kB
Pss_File: 2991 kB
Pss_Shmem: 99 kB
Sampled pgbench backends:
| pid |
RssAnon kB |
RssFile kB |
RssShmem kB |
HugetlbPages kB |
Pss_Anon kB |
VmPTE kB |
approx overhead MiB |
| 157621 |
3296 |
8960 |
0 |
516096 |
1398 |
144 |
1.51 |
| 157622 |
3424 |
9088 |
128 |
438272 |
1398 |
144 |
1.51 |
| 157626 |
3280 |
9088 |
0 |
561152 |
1398 |
144 |
1.51 |
| 157627 |
3424 |
9088 |
128 |
546816 |
1398 |
144 |
1.51 |
| 157628 |
3424 |
9088 |
128 |
743424 |
1410 |
144 |
1.52 |
| 157629 |
3424 |
9088 |
128 |
741376 |
1398 |
144 |
1.51 |
| 157634 |
3296 |
9088 |
0 |
722944 |
1398 |
144 |
1.51 |
| 157637 |
3424 |
9088 |
128 |
958464 |
1410 |
144 |
1.52 |
| 157641 |
3412 |
9088 |
128 |
776192 |
1410 |
144 |
1.52 |
| 157642 |
3424 |
9088 |
128 |
786432 |
1398 |
144 |
1.51 |
Summary:
min: 1542 kB = 1.51 MiB
median: 1542 kB = 1.51 MiB
avg: 1545.6 kB = 1.51 MiB
max: 1554 kB = 1.52 MiB
pgbench:
number of transactions actually processed: 4344743
number of failed transactions: 0 (0.000%)
latency average = 20.729 ms
tps = 49399.941112 (without initial connection time)
Notes
- Huge pages reduced
VmPTE from hundreds/thousands of kB down to a stable 144 kB in this run. That's the main difference, same as Andres described in 2020.
HugetlbPages looks huge per backend and should not be interpreted as private per-connection memory. The approximation intentionally uses Pss_Anon + VmPTE.
- RSS remains misleading: with
huge_pages = off, RssShmem grows as the backend touches shared buffers; with huge_pages = on, the same shared buffer footprint moves to HugetlbPages and page-table overhead collapses.
- On this host, PG18 connection overhead in this workload is roughly 2.6 MiB with huge pages off and 1.5 MiB with huge pages on.
Reproducer
PG=/usr/lib/postgresql/18/bin
BASE=/tmp/pg18-conn-overhead
DATA=$BASE/data
SOCK=$BASE/socket
PORT=55418
rm -rf "$BASE"
mkdir -p "$SOCK"
"$PG/initdb" -D "$DATA" --no-locale --encoding=UTF8
cat >> "$DATA/postgresql.conf" <<EOF
port = $PORT
listen_addresses = ''
unix_socket_directories = '$SOCK'
max_connections = 1100
shared_buffers = '1GB'
huge_pages = on # or off
fsync = off
synchronous_commit = off
full_page_writes = off
EOF
cat > "$DATA/pg_hba.conf" <<EOF
local all all trust
EOF
"$PG/pg_ctl" -D "$DATA" -l "$BASE/postgres.log" -w start
export PGHOST=$SOCK PGPORT=$PORT PGDATABASE=postgres PGUSER="$USER"
"$PG/createdb" bench
export PGDATABASE=bench
"$PG/pgbench" -i -s 100 bench
"$PG/pgbench" -S -M prepared -c 1024 -j 16 -T 90 bench
# During the pgbench run, pick backend PIDs from pg_stat_activity and inspect:
grep -E '^(Rss|HugetlbPages|VmPTE)' /proc/$pid/status
grep -E '^(Pss|Pss_Anon|Pss_File|Pss_Shmem):' /proc/$pid/smaps_rollup
Summary
I repeated Andres Freund's 2020 connection-memory measurement method on PostgreSQL 18.3 on Ubuntu 24.04, with both
huge_pages = offandhuge_pages = on.Reference method: https://blog.anarazel.de/2020/10/07/measuring-the-memory-overhead-of-a-postgres-connection/
Short version:
ps/topRSS is still the wrong metric. UsingPss_Anonfrom/proc/$pid/smaps_rollupplusVmPTEfrom/proc/$pid/status, a PG18 backend running a simple pgbench read-only workload on this host was:For reference, an earlier
huge_pages = offrun withshared_buffers = 2GBmeasured 2.33 MiB median across 10 sampled pgbench backends. The final comparison table above uses 1GB for both modes because this host could allocate only 594 huge pages (~1.16 GiB) without reboot/compaction.Environment
Cluster settings for the comparable runs:
For the
huge_pages = onrun, host huge pages were temporarily raised from 0 to 594 and then restored to 0 after the experiment.Workload
Throwaway PG18 cluster under
/tmp, Unix socket only.At sampling time in both runs:
Measurement method
Same approximation as the blog post:
Where:
Pss_Anoncomes from/proc/$pid/smaps_rollupVmPTEcomes from/proc/$pid/statusRssFileis ignored because it is mostly executable/shared library mappingsRssShmem/Pss_Shmemis not counted as private connection memoryResults: huge_pages = off
Cluster:
Fresh sleeping connection before pgbench:
Sampled pgbench backends:
Summary:
pgbench:
Results: huge_pages = on
Cluster:
Fresh sleeping connection before pgbench:
Sampled pgbench backends:
Summary:
pgbench:
Notes
VmPTEfrom hundreds/thousands of kB down to a stable 144 kB in this run. That's the main difference, same as Andres described in 2020.HugetlbPageslooks huge per backend and should not be interpreted as private per-connection memory. The approximation intentionally usesPss_Anon + VmPTE.huge_pages = off,RssShmemgrows as the backend touches shared buffers; withhuge_pages = on, the same shared buffer footprint moves toHugetlbPagesand page-table overhead collapses.Reproducer