# PIHA-specific overrides for kb-postgres (KB spine). # # WHY PIHA: the KB store must answer queries 24/7. SOLARIA (GPU/compute) is # powered down intermittently; PIHA (Raspberry Pi 5, always-on, mains power) is # the right home for an always-available spine. Embeddings/models still run on # SOLARIA's GPU — only the Postgres+pgvector store lives here. # # IMAGE / ARCH: pgvector/pgvector:pg16 is multi-arch and publishes a linux/arm64 # manifest, so it runs natively on the Pi 5 (arm64) — no emulation. We do NOT # change the pg16 tag; arch is handled by the manifest list, not the tag. # # RESOURCE CONTEXT: PIHA has 8 GB RAM, but ~6 GB is already resident # (Home Assistant, Immich, monitoring). Only ~2 GB is free (+4 GB swap). This is # a RAM-bound box shared with Home Assistant — Postgres MUST NOT starve HA. # Everything below is sized to keep kb-postgres's resident set near ~0.5–0.8 GB # under normal load, with a hard 1 GB ceiling. services: kb-postgres: # Hard cgroup ceiling. ~1 GB (not the 4 GB used on SOLARIA). If Postgres ever # exceeds this, the cgroup OOM killer restarts the container via Docker — # Postgres recovers cleanly via crash recovery — instead of letting the host # OOM killer pick a victim (which could be Home Assistant). 1 GB comfortably # covers the worst-case allocation below. mem_limit: 1g # Soft floor for the scheduler: reserve enough that shared_buffers (256 MB) # plus connection/backend overhead is not constantly contended under memory # pressure, without hard-pinning a full GB away from HA. mem_reservation: 512m # Postgres tuning for a tight, shared RAM budget. Passed as server args so we # need no mounted postgresql.conf. Defaults (shared_buffers 128 MB, work_mem # 4 MB, max_connections 100) assume a dedicated box — far too loose here. # # shared_buffers=256MB Postgres's own page cache. ~25% of the 1 GB # ceiling — the standard rule of thumb. Bigger # would crowd HA; smaller hurts cache hit rate # for the envelope + pgvector working set. # effective_cache_size=512MB Planner hint only (allocates nothing). Tells # the planner how much OS+PG cache it can assume # for this DB's share of the box, so it favours # index scans appropriately. Conservative given # the page cache is shared with HA/Immich. # work_mem=8MB Per-sort/hash node. With max_connections=30 the # worst case is bounded (~30 * a few nodes * 8MB); # keeps a runaway analytic query from blowing the # budget. Small enough for a Pi, big enough for # typical KB lookups. # maintenance_work_mem=64MB For VACUUM / CREATE INDEX (incl. building the # pgvector ivfflat/hnsw index). One-at-a-time and # transient, so a larger value than work_mem is # safe and speeds index builds. # max_connections=30 KB clients are a handful of agents/jobs, not a # web fleet. Capping at 30 bounds per-backend RAM # (each backend ~5–10 MB) and the work_mem blast # radius. Raise only if a real client count needs it. # # Sanity check on the ceiling: 256 MB shared_buffers + ~30 backends * ~10 MB # overhead (~300 MB) + bounded work_mem spikes stays well under mem_limit=1g. command: - "postgres" - "-c" - "shared_buffers=256MB" - "-c" - "effective_cache_size=512MB" - "-c" - "work_mem=8MB" - "-c" - "maintenance_work_mem=64MB" - "-c" - "max_connections=30" # --------------------------------------------------------------------------- # DATA PLACEMENT — must land on the NVMe (/home, ~170 GB free), NEVER the SD card. # # The base compose uses the Docker-managed named volume `kb_postgres_data`, which # physically lives under Docker's data-root. On PIHA, Immich already stores its # (large) photo library in Docker volumes here, which is only possible if the # data-root sits on the NVMe — so the plain named volume should already land on # NVMe and is the convention used by other PIHA services (e.g. vikunja). # # BEFORE FIRST DEPLOY, verify on PIHA: # docker info -f '{{.DockerRootDir}}' # expect a path on the NVMe # df -h "$(docker info -f '{{.DockerRootDir}}')" # confirm it's the NVMe fs # # If (and only if) the data-root is NOT on the NVMe, pin the volume explicitly to # an NVMe path by uncommenting the block below. The official postgres entrypoint # runs as root and chowns PGDATA to the in-container postgres user (uid 999) on # startup, so PIHA's host-uid 1004-vs-1000 skew does not apply to PGDATA itself — # but the bind *device* directory must pre-exist (Docker will not create it): # sudo mkdir -p /home/oskar/homelab-data/kb-postgres # sudo chown 1004:1004 /home/oskar/homelab-data/kb-postgres # host owner; PG re-chowns PGDATA to 999 inside # # volumes: # kb_postgres_data: # name: kb_postgres_data # driver: local # driver_opts: # type: none # o: bind # device: /home/oskar/homelab-data/kb-postgres # ---------------------------------------------------------------------------