From 9e9d9b00352f808ff3c303c84769bc8db49f4611 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Wed, 12 Aug 2026 16:16:59 -0300 Subject: [PATCH] feat(postgres): expose PostgreSQL tunables as SHELLHUB_POSTGRES_* variables PostgreSQL runs on stock defaults on every deployment -- 128 MB of shared_buffers regardless of the machine underneath. On the largest managed instance that is a 84.76% buffer cache hit ratio against a 5.9 GB database on a 15.6 GB host. Sizing that per host needs a seam, and overriding `command:` from a second Compose file is the wrong one: Compose replaces `command:` rather than merging it, so an override has to restate io_method, wal_compression and shared_preload_libraries, and silently drops whatever this list grows next. Parameterise the flags instead. Every default is the value the deployment already runs with, so the rendered command line is unchanged and this carries no behaviour of its own: .env documents the knobs, and .env.override -- which bin/docker-compose already loads last -- is where a host sets them. Two Docker-side knobs come along because they bound the same thing. shm_size is where parallel-query segments are allocated from, not shared_buffers, so it has to track work_mem. mem_limit stays at today's unlimited, but is worth setting once shared_buffers grows on a host with no swap. Refs: shellhub-io/team#198 --- .env | 16 ++++++++++++++++ docker-compose.postgres.yml | 28 ++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.env b/.env index eec6bdae3ca..093c757f9f3 100644 --- a/.env +++ b/.env @@ -57,6 +57,22 @@ SHELLHUB_POSTGRES_DATABASE=main SHELLHUB_POSTGRES_LOG_LEVEL=INFO SHELLHUB_POSTGRES_LOG_VERBOSE=false +# PostgreSQL tuning. Defaults are PostgreSQL's own; size them to the host. +# NOTICE: Applied on restart, not reload. +SHELLHUB_POSTGRES_SHARED_BUFFERS=128MB +SHELLHUB_POSTGRES_EFFECTIVE_CACHE_SIZE=4GB +SHELLHUB_POSTGRES_WORK_MEM=4MB +SHELLHUB_POSTGRES_MAINTENANCE_WORK_MEM=64MB +SHELLHUB_POSTGRES_MAX_WAL_SIZE=1GB +SHELLHUB_POSTGRES_MIN_WAL_SIZE=80MB +SHELLHUB_POSTGRES_CHECKPOINT_TIMEOUT=5min +SHELLHUB_POSTGRES_RANDOM_PAGE_COST=4 +SHELLHUB_POSTGRES_EFFECTIVE_IO_CONCURRENCY=16 +SHELLHUB_POSTGRES_MAX_CONNECTIONS=100 +SHELLHUB_POSTGRES_WAL_COMPRESSION=lz4 +SHELLHUB_POSTGRES_SHM_SIZE=64m +SHELLHUB_POSTGRES_MEMORY_LIMIT=0 + # The domain of the server. # NOTICE: Required only if automatic HTTPS is enabled. # VALUES: A valid domain name diff --git a/docker-compose.postgres.yml b/docker-compose.postgres.yml index 6520ddc8c9e..d688ae9910c 100644 --- a/docker-compose.postgres.yml +++ b/docker-compose.postgres.yml @@ -1,13 +1,33 @@ services: postgres: image: postgres:18.0 - # lz4 compresses full-page images; safe to pin because the image tag above is pinned too, - # and a build without lz4 support would refuse to start. - # # shared_preload_libraries only takes effect on a restart, not a reload, so an existing # deployment picks pg_stat_statements up when the upgrade recreates the stack. A second # library would have to join this one as a quoted comma-separated list, not a second -c. - command: postgres -c io_method=worker -c wal_compression=lz4 -c shared_preload_libraries=pg_stat_statements + # + # Defaults are what this deployment already runs, so nothing changes out of the box. They + # let a host be sized from .env.override rather than by replacing `command:`, which Compose + # does wholesale. `:-` so an empty override still yields a value; lz4 is only safe as a + # default because the image tag is pinned. + command: > + postgres + -c io_method=worker + -c shared_preload_libraries=pg_stat_statements + -c wal_compression=${SHELLHUB_POSTGRES_WAL_COMPRESSION:-lz4} + -c shared_buffers=${SHELLHUB_POSTGRES_SHARED_BUFFERS:-128MB} + -c effective_cache_size=${SHELLHUB_POSTGRES_EFFECTIVE_CACHE_SIZE:-4GB} + -c work_mem=${SHELLHUB_POSTGRES_WORK_MEM:-4MB} + -c maintenance_work_mem=${SHELLHUB_POSTGRES_MAINTENANCE_WORK_MEM:-64MB} + -c max_wal_size=${SHELLHUB_POSTGRES_MAX_WAL_SIZE:-1GB} + -c min_wal_size=${SHELLHUB_POSTGRES_MIN_WAL_SIZE:-80MB} + -c checkpoint_timeout=${SHELLHUB_POSTGRES_CHECKPOINT_TIMEOUT:-5min} + -c random_page_cost=${SHELLHUB_POSTGRES_RANDOM_PAGE_COST:-4} + -c effective_io_concurrency=${SHELLHUB_POSTGRES_EFFECTIVE_IO_CONCURRENCY:-16} + -c max_connections=${SHELLHUB_POSTGRES_MAX_CONNECTIONS:-100} + # Not shared_buffers, but parallel-query segments — grows with work_mem. + shm_size: ${SHELLHUB_POSTGRES_SHM_SIZE:-64m} + # 0 is unlimited, as today. Worth setting once shared_buffers grows: no swap on these hosts. + mem_limit: ${SHELLHUB_POSTGRES_MEMORY_LIMIT:-0} restart: unless-stopped healthcheck: start_period: 90s