thermograph/deploy/db/init/20-tuning.sh
Emi Griffith dc4ee9a8db Scale DB tuning from DB_MEMORY; order Docker after the rclone mount (#226)
Two prod-readiness hardening changes:

DB tuning scales with the container budget. Replace the fixed 8 GB
20-tuning.sql with 20-tuning.sh, which derives shared_buffers (25%),
effective_cache_size (75%), work_mem, maintenance_work_mem and
duckdb.max_memory (50%) from the DB_MEMORY the compose db service now passes in.
The ratios reproduce the historical 8 GB tuning exactly and scale linearly, so
the 48 GB prod box (db_memory 16g) gets shared_buffers 4 GB / duckdb 8 GB with
no separate edit. Beta/local (8g default) are unchanged. Docs that told
operators to raise the tuning by hand are updated.

Boot ordering for the self-hosted archive. On an openmeteo host, install a
docker.service drop-in (Wants/After rclone-om.service) so Docker starts after
the object-storage mount is ready on every boot — the restart-policy containers
never bind an empty mount point. rclone-om is Type=notify, so After waits for
the mount to actually be ready. Cleaned up when openmeteo is toggled off.
2026-07-20 14:33:09 +00:00

64 lines
3.1 KiB
Bash
Executable file

#!/bin/bash
# Postgres memory / performance tuning, scaled to the container's DB_MEMORY budget so
# the same init serves every host (beta 8g; prod 16g on the 48 GB box) with no
# hardcoding. Runs once on a fresh data volume from /docker-entrypoint-initdb.d, after
# 10-parquet.sql enables pg_duckdb. Settings are written via ALTER SYSTEM (persisted to
# postgresql.auto.conf); the container's post-init restart brings restart-only settings
# (shared_buffers, …) into effect. Init scripts do NOT re-run on an existing volume — to
# re-tune later, set DB_MEMORY and run this by hand, then restart:
# docker compose exec -e DB_MEMORY=16g db bash /docker-entrypoint-initdb.d/20-tuning.sh
# docker compose restart db
set -euo pipefail
# Parse DB_MEMORY ("16g" / "8192m" / plain MB) into whole MB; default + floor at 8 GB.
budget="${DB_MEMORY:-8g}"
num="${budget//[!0-9]/}"
num="${num:-8}"
unit="$(printf '%s' "$budget" | tr -dc '[:alpha:]' | tr '[:upper:]' '[:lower:]')"
case "$unit" in
g | gb) mb=$((num * 1024)) ;;
m | mb | "") mb="$num" ;;
*) mb=8192 ;;
esac
if [ "$mb" -lt 1024 ]; then mb=8192; fi
# Derive settings from the budget. The ratios reproduce the historical 8 GB tuning
# (shared_buffers 2 GB, effective_cache_size 6 GB, duckdb 4 GB, work_mem 64 MB,
# maintenance_work_mem 512 MB) and scale linearly on a bigger box.
shared_buffers=$((mb / 4)) # 25% — the shared page cache
effective_cache=$((mb * 3 / 4)) # 75% — planner's view of total cache (PG + OS)
duckdb_mem=$((mb / 2)) # 50% — pg_duckdb ceiling for parquet processing
work_mem=$((mb / 128)) # ~64 MB at 8 GB (per-operation; kept modest)
if [ "$work_mem" -lt 16 ]; then work_mem=16; fi
maint_mem=$((mb / 16)) # 512 MB at 8 GB — index builds / VACUUM
echo "[tuning] DB_MEMORY=${budget} -> ${mb}MB: shared_buffers=${shared_buffers}MB" \
"effective_cache_size=${effective_cache}MB work_mem=${work_mem}MB" \
"maintenance_work_mem=${maint_mem}MB duckdb.max_memory=${duckdb_mem}MB"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<SQL
-- Caching: the shared page cache, and the planner's view of total cache (PG + OS).
ALTER SYSTEM SET shared_buffers = '${shared_buffers}MB';
ALTER SYSTEM SET effective_cache_size = '${effective_cache}MB';
-- Processing: per-operation sort/hash memory, and maintenance (index builds, VACUUM).
ALTER SYSTEM SET work_mem = '${work_mem}MB';
ALTER SYSTEM SET maintenance_work_mem = '${maint_mem}MB';
-- Write throughput: fewer, larger checkpoints.
ALTER SYSTEM SET wal_buffers = '16MB';
ALTER SYSTEM SET min_wal_size = '1GB';
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
-- SSD-friendly planner + IO concurrency.
ALTER SYSTEM SET random_page_cost = 1.1;
ALTER SYSTEM SET effective_io_concurrency = 200;
-- Room for parallel scans/aggregates on the bigger analytic queries.
ALTER SYSTEM SET max_parallel_workers_per_gather = 2;
-- DuckDB (pg_duckdb) memory ceiling for parquet processing. pg_duckdb is preloaded
-- (shared_preload_libraries), so this GUC exists at ALTER SYSTEM time.
ALTER SYSTEM SET duckdb.max_memory = '${duckdb_mem}MB';
SQL