Files
sysmig/migrations/010-tune-postgres.sh
T
wan ff3f6732e9 010: fix verification - compare pg_settings raw values, not SHOW output
SHOW() pretty-renders units (60s -> '1min'), which broke the post-up
check after a successful apply. Compare pg_settings.setting (base-unit
raw values) instead - rendering-proof.
2026-08-31 22:24:38 +09:00

106 lines
4.2 KiB
Bash

#!/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 raw_value" pairs, compared against pg_settings.setting
# (each GUC's base unit). Immune to SHOW()'s pretty unit rendering -
# SHOW renders 60000ms as '1min', 2097152kB as '2GB', etc.
# max_connections - effective_cache_size 8kB
# work_mem kB *_timeout ms
# temp_file_limit kB
WANT=(
'max_connections 30'
'effective_cache_size 98304' # 768MB
'work_mem 4096' # 4MB
'statement_timeout 30000' # 30s
'idle_in_transaction_session_timeout 60000' # 60s
'temp_file_limit 2097152' # 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 "SELECT setting FROM pg_settings WHERE name='$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 "SELECT setting FROM pg_settings WHERE name='$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