#!/usr/bin/env bash # 010-tune-postgres - Memory & stability tuning for the 1GB e2-micro host # (B-tier of the 2026-08 tuning review). Settings land as a conf.d drop-in; # postgresql.conf itself stays pristine (include_dir = 'conf.d' is stock). # # max_connections 100 -> 30 each backend costs MBs; real # concurrency comes from PgBouncer # (013). 30 = pool 20 + admin headroom # effective_cache_size 4GB -> 768MB planner hint; box has 1GB, the 4GB # default overrates cache -> bad plans # work_mem 4MB (explicit, equals default; kept for determinism) # statement_timeout 0 -> 30s runaway-query guard; long migrations: # SET statement_timeout = 0; in-session # idle_in_transaction_session_timeout 0 -> 60s leaked transactions block # vacuum and hold locks; kill them # temp_file_limit -1 -> 2GB per-backend sort-spill cap so one bad # query cannot fill the 20G data disk # up: write drop-in; restart only if max_connections changes, else reload # down: remove drop-in; restart (restoring max_connections needs it) set -euo pipefail PGBIN=/etc/postgresql/18/main CONF=$PGBIN/conf.d/010-tune-postgres.conf CLUSTER=$(pg_lsclusters --no-header | awk 'NR==1{print $1"/"$2}') # desired state as "guc value" pairs; SHOW renders exactly these strings WANT=( 'max_connections 30' 'effective_cache_size 768MB' 'work_mem 4MB' 'statement_timeout 30s' 'idle_in_transaction_session_timeout 60s' 'temp_file_limit 2GB' ) pg() { runuser -u postgres -- psql -d postgres -v ON_ERROR_STOP=1 -tAc "$1"; } write_conf() { install -d -m 755 "$PGBIN/conf.d" cat > "$CONF" <<'EOF' # managed by sysmig 010-tune-postgres - memory & stability for 1GB e2-micro max_connections = 30 effective_cache_size = 768MB work_mem = 4MB statement_timeout = 30s idle_in_transaction_session_timeout = 60s temp_file_limit = 2GB EOF chown root:postgres "$CONF" chmod 644 "$CONF" } case "${1:-}" in up) write_conf if [[ $(pg 'SHOW max_connections') != 30 ]]; then pg_ctlcluster "$CLUSTER" restart pg_isready -q echo " cluster restarted (max_connections requires it)" else pg_ctlcluster "$CLUSTER" reload echo " cluster reloaded" fi for kv in "${WANT[@]}"; do read -r guc want <<<"$kv" got=$(pg "SHOW $guc") [[ $got == "$want" ]] || { echo "FATAL: $guc is '$got', expected '$want'" >&2; exit 1; } done echo " ${#WANT[@]} memory/stability settings active" ;; status) if [[ $EUID -eq 0 ]]; then q() { runuser -u postgres -- psql -d postgres -v ON_ERROR_STOP=1 -tAc "$1"; } elif psql -d postgres -tAc 'SELECT 1' >/dev/null 2>&1; then q() { psql -d postgres -v ON_ERROR_STOP=1 -tAc "$1"; } else echo "? needs root or peer DB access to verify" exit 2 fi [[ -f $CONF ]] || { echo "DRIFT drop-in $CONF missing"; exit 1; } bad=() for kv in "${WANT[@]}"; do read -r guc want <<<"$kv" got=$(q "SHOW $guc" 2>/dev/null) [[ $got == "$want" ]] || bad+=("$guc=$got(want $want)") done if ((${#bad[@]})); then echo "DRIFT ${bad[*]}"; exit 1; fi echo "OK ${#WANT[@]} settings active (max_connections=30, timeouts, temp_file_limit)" ;; down) rm -f "$CONF" if [[ $(pg 'SHOW max_connections') == 30 ]]; then pg_ctlcluster "$CLUSTER" restart echo " drop-in removed, cluster restarted (defaults restored)" else pg_ctlcluster "$CLUSTER" reload echo " drop-in removed, cluster reloaded" fi ;; esac