feat(dbserver): migrate to stock postgres image + bind-mounted autotune#10
Merged
Conversation
The custom iplweb/bpp_dbserver image is discontinued (its only delta over
stock postgres was the autotune step). Switch dbserver to the official
Debian-based postgres:<MAJOR.MINOR> image with the autotune scripts
bind-mounted from dbserver/ (copied verbatim from iplweb/bpp-dbserver).
docker-compose.database.yml:
- image -> postgres:${DJANGO_BPP_POSTGRESQL_VERSION...} (two-tier fallback kept)
- autotune entrypoint + command: postgres; scripts bind-mounted read-only
- pin PGDATA=/var/lib/postgresql/data (stock postgres:18+ defaults elsewhere
-> would ignore the existing volume and re-init blank)
- POSTGRES_INITDB_ARGS ICU pl-PL for fresh installs (never re-collates existing)
- pg_isready healthcheck (stock postgres has none; appserver/authserver
depend on service_healthy)
- service-level env_file (the include-level env_file is interpolation-only and
is not injected into the container) so autotune POSTGRESQL_* knobs are honoured
upgrade-postgres.sh / test-upgrade-postgres.sh:
- NEW_DBSERVER_IMAGE -> postgres:<ver>; drop the "wait for image publish" step
- test harness uses the stock multi-arch image (drop platform: +
DOCKER_DEFAULT_PLATFORM -> runs native on Apple Silicon) and mirrors prod
(autotune mounts, PGDATA pin, healthcheck)
init-configs.sh / check-image-versions.sh: message/comment updates only
(version-var logic and old-name migrations unchanged).
Docs: postgresql.md, limity-zasobow.md (autotune reads the cgroup limit),
CLAUDE.md, plus the design spec under docs/superpowers/specs/.
Verified: compose config, autotune --test, shellcheck, mkdocs --strict, and a
live boot of postgres:16.13 (healthy; shared_buffers sized to the cgroup limit;
ICU pl-PL collation; PGDATA pinned).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…collation
Clusters created by the discontinued iplweb/bpp_dbserver image bake
lc_messages/lc_monetary/lc_numeric/lc_time and datcollate/datctype =
pl_PL.utf-8, so they cannot start on the stock postgres image (which lacks the
pl_PL.UTF-8 libc locale). Migrating such a cluster therefore requires
dump -> strip the pl_PL collation -> restore into a fresh stock cluster
(ICU pl-PL), not an in-place image swap.
Adds a 3-step toolset (+ shared lib, make targets, runbook):
- pg-collation-migrate-1-dump.sh : pg_dump -Fd of the live (old-image) cluster
- pg-collation-migrate-2-fix.sh : pg_restore -f - | strip CREATE/ALTER/COMMENT
COLLATION pl_PL + COLLATE "pl_PL" -> .sql.gz
- pg-collation-migrate-3-load.sh : dropdb/createdb (ICU) + psql -f into psql 18
- mk/database.mk: migrate-collation-{dump,fix,load} targets
- docs/eksploatacja/migracja-collation-stock-pg.md: runbook
Pairs with bpp migration 0443_drop_pl_PL_collation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e detour Krok 1 zrzucał format katalogowy (-Fd) + tar, co zmuszało krok 2 do konwersji `pg_restore -f -` (binarny toc.dat) → SQL, tylko po to żeby go zesed-ować. Format binarny nic tu nie dawał: kolację trzeba wyciąć z TEKSTU definicji widoków (COLLATE "pl_PL"), a load i tak idzie psql-em (jednowątkowo), więc równoległość pg_restore -j była martwa. Teraz plain SQL przez cały pipeline: - krok 1: `pg_dump -Fp | gzip` → db-backup-<TS>.sql.gz (in-container dump, host-side gzip; trap czyści .partial gdy pg_dump padnie; gzip -t) - krok 2: `gunzip | sed | gzip` — bez docker run, bez pg_restore, bez obrazu postgres, bez tar; guard odrzuca stary .tar.gz; gzip -t na wejściu - krok 3: bez zmian Usunięto PG_TARGET_IMAGE z lib (był tylko do konwersji w kroku 2). Makefile: migrate-collation-fix TARBALL= → DUMPGZ=. Runbook zaktualizowany. Zweryfikowane na realnym prod-dumpie: 7 ref. kolacji → 0, ostrzeżenie plpython działa, guard .tar.gz odrzuca stary format. shellcheck Passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd-to-end Bug z produkcji: load padał na "could not create locale pl_PL.utf8", bo realna kolacja nazywa sie `public.pl_pl` (male litery, bez cudzyslowu, locale='pl_PL.utf8'), a sed wycinal tylko case-sensitive `pl_PL`. To samo tlumaczy czemu migracja bpp 0443 (DROP COLLATION ... "pl_PL") jej nie ruszyla. Teraz wzorzec uzywa klas znakowych [pP][lL]_[pP][lL] + opcjonalny public./cudzyslow -> lapie KAZDY wariant (pl_pl, pl_PL, "pl_PL", public.*). Dodatkowo (prosba usera): caly pipeline bez gzipa, plain .sql end-to-end: - krok 1: pg_dump -Fp -> db-backup-<TS>.sql (bez gzipa; kompletnosc przez marker 'PostgreSQL database dump complete' zamiast gzip -t; pv zostaje) - krok 2: sed in->out (bez gunzip/gzip); weryfikacja plain grep; usuwa tez naglowkowe komentarze '-- Name: pl_PL...; Type: COLLATION' - krok 3: psql < file (bez gunzip), opcjonalny pasek pv; weryfikacja kolacji liczona case-insensitive w schemacie public - Makefile: DUMPGZ->DUMPSQL, SQLGZ->SQL; guardy odrzucaja .gz/.tar.gz SC2034 (NOINPUT uzywane w confirm() z lib) wyciszone tez w kroku 3 — przy commicie lib nie jest stage'owana, wiec shellcheck nie sledzi source. Zweryfikowane na fixturze z realnym ksztaltem dumpu: 6 ref. kolacji (pl_pl/"pl_PL"/COLLATE oba) -> 0, string '0443_drop_pl_PL_collation' w django_migrations zachowany. shellcheck Passed (standalone, per plik). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Realny prod-dump ma nazwe kolacji `public."pl_PL.utf8"` — identyfikator z kropka i `.utf8` W SRODKU cudzyslowu (musi byc cytowany). Poprzedni wzorzec `"?pl_PL"?` wymagal spacji/cudzyslowu zaraz po `pl_PL`, wiec `.utf8` go omijal -> CREATE/ALTER COLLATION przechodzily, load padal. Wzorzec nazwy = (public.)? ( "pl_PL<dowolny-sufiks>" | goly pl_pl ): QNAME='"[pP][lL]_[pP][lL][^"]*"' # "pl_PL.utf8" / "pl_PL.UTF-8" / "pl_PL" BNAME='[pP][lL]_[pP][lL]' # pl_pl (niecytowany ident nie ma kropek) Oddzielne galezie quoted/unquoted chronia przed zjedzeniem przecinka w `COLLATE pl_pl, y` (goly ident konczy sie po 5 znakach, nie lyka `,`). Zweryfikowane na fixturze: "pl_PL.utf8"/"pl_PL"/pl_pl + 3 formy COLLATE -> 0; przecinek w `(x COLLATE public.pl_pl), y` zachowany; '0443_drop_pl_PL_collation' w django_migrations i string-mention 'pl_PL' nietkniete. shellcheck Passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ys collation-only Load do swiezego klastra padal na "role bpp does not exist": per-bazowy pg_dump nie zrzuca rol (sa cluster-level), a swiezy psql 18 ma tylko superusera POSTGRES_USER (np. postgres), nie ma `bpp` z OWNER TO. Rozwiazanie strukturalne i bezpieczne: krok 1 robi teraz `pg_dump --no-owner --no-privileges` — zrzut bez OWNER TO / GRANT / REVOKE, wiec laduje sie do dowolnego klastra (obiekty obejmuje user ladujacy). Pojedynczy pg_dump i tak nie tworzy userow (CREATE ROLE to pg_dumpall). Krok 2 NIE strippuje ownerow/grantow sed-em: rozwazane, ale `^GRANT`/ `^OWNER TO` potrafi wystapic w ciele funkcji (dollar-quote) i line-based sed by je uszkodzil. Owner-stripping nalezy do dump-time (--no-owner), nie do post-processingu. Krok 2 zostaje czysto kolacyjny. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…--pick restore.sh dzialal tylko na parach db+media w formacie -Fd (.tar.gz) i globowal *.tar.gz, wiec plain .sql/.sql.gz nie dalo sie ani podac, ani wybrac przez --pick. Nowosci: - detekcja formatu zrzutu DB (auto): -Fd tar.gz (toc.dat) -> pg_restore -Fd; -Fc (magic PGDMP), tez gzipowany -> pg_restore; plain .sql / .sql.gz -> psql. pg_restore zawsze --no-owner --no-privileges; plain wczytywany psql-em. - --db-file=PATH: pojedynczy plik zrzutu (dowolny format, tez spoza katalogu backupow), tryb DB-only (media nietkniete). plain/.gz/-Fc ladowane przez stdin (dziala dla pliku spoza /backup); -Fd kopiowany do /backup gdy trzeba, nazwa katalogu top-level czytana z tarballa. - --db-format=auto|directory|custom|plain: reczny override detekcji. - --pick pokazuje TERAZ ZAROWNO pary db+media, JAK I pojedyncze zrzuty DB (*.sql/*.sql.gz/*.dump/*.custom). Wybor zwraca pair:<TS> albo file:<PATH>. - Makefile: make restore DBFILE=... [DBFORMAT=...]. Zweryfikowane: detekcja 5 formatow na fixturach (directory/custom/custom-gz/ plain/plain-gz), listing pickera (pary + pliki, bez podwojnego listowania .tar.gz), ekstrakcja tagu, shellcheck Passed, -h OK. Sciezki dockerowe (pg_restore/psql/tar w kontenerze) do sprawdzenia na zywym stacku. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sanity-check plpython w kroku 2 dawal falszywy alarm: `grep plpython3u| LANGUAGE plpython` lapal slowo "plpython" w NAZWACH migracji w danych django_migrations (0440_port_plpython_to_plpgsql, 0442_drop_plpython3u) — czyli ostrzegal nawet na zrzutach JUZ po usunieciu plpythona. Teraz szukamy REALNEGO uzycia, ktore faktycznie wywroci load na stocku: ^CREATE EXTENSION ... plpython | LANGUAGE plpython Nazwy migracji (bez "CREATE EXTENSION"/"LANGUAGE " przed plpython) nie pasuja. Zweryfikowane: post-0442 (tylko nazwy migracji) -> brak ostrzezenia; pre-0442 (CREATE EXTENSION plpython3u + LANGUAGE plpython3u) -> ostrzega. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st make migrate Appserver przy starcie sam przepuszcza migracje; 'make migrate' wymaga juz dzialajacego appservera (robi docker compose exec appserver ...). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pisarze Ostrzezenie/prompt 'aplikacja NIE jest zatrzymywana' bylo bezwarunkowe — mylace, gdy stack stoi a recznie wstal tylko dbserver. Teraz skrypt sprawdza, ktore z serwisow-pisarzy (appserver, workerserver, celerybeat, denorm-queue) faktycznie chodza: pyta tylko o dzialajace, a gdy zaden nie dziala — leci bez promptu (zrzut i tak spojny). Pisarzami sa tez celery/denorm-queue, nie tylko appserver. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make migrate robi 'docker compose exec appserver ...', wiec wymaga juz dzialajacego appservera. Po kroku 2 appserver jest zatrzymany, a krok 7 wstawia tylko dbserver — wiec dotychczasowa kolejnosc 'make migrate' -> 'make up' wywalala sie z "service appserver is not running". make up jest teraz pierwsze (appserver przy starcie sam przepuszcza migracje), make migrate po nim to bezpieczne, jawne powtorzenie. Bug przezyl bo test-upgrade-postgres.sh jawnie pomija krok 10. Zaktualizowano tez opisy kroku 10 (naglowek skryptu, komunikaty, komentarz testu, docs/konfiguracja/postgresql.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pg_database_size to rozmiar FIZYCZNY (z indeksami, TOAST skompresowany). Plain-SQL dump nie zrzuca danych indeksow -> realnie bywa ~40% pg_database_size, przez co goly 'pv -s pg_database_size' konczyl pasek na ~40%. Zamiast zgadywac funkcja katalogu (pg_table_size tez nie odda kodowania COPY ani indeksow), kalibrujemy: po zrzucie zapisujemy ratio = rozmiar_zrzutu / pg_database_size do $HOST_BACKUP_DIR/.pg-dump-size-ratio, a nastepny run mnozy nim estymate. Ratio walidowane do zakresu 0.1..20. Kierunkowo-agnostyczne (dziala gdy dump < lub > bazy). awk wymusza LC_ALL=C, bo pl_PL formatuje ulamek przecinkiem (0,400), co psuloby zapis i walidacje ratio. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ie istnieje 'docker volume rm' na nieistniejacym wolumenie zwraca blad i przy set -e zatrzymywal caly skrypt — a BRAK wolumenu to dokladnie stan docelowy (swiezy cluster i tak powstanie). Usuwamy tylko gdy 'docker volume inspect' potwierdzi istnienie; prawdziwy blad usuwania (np. volume in use) nadal zatrzyma skrypt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o-prune po make up; default PG 18.4 - backup-runner uzywa domyslnie tego SAMEGO obrazu co dbserver (postgres:<MAJOR.MINOR>, Debian) zamiast osobnego -alpine -> wspoldzieli 100% warstw, ~0 MB netto zamiast +~350 MB na dysku. Tryb external nadal alpine przez BPP_BACKUP_PG_IMAGE (ustawiany w init-configs, dosypywany do starych .env przez ensure-config-files). Blok command wykrywa apk vs apt-get, wiec dziala na obu obrazach. - make up (a wiec i make run) sprzata Dockera (docker system prune -af) po udanym starcie (--wait) i PRZED pullem html2docx; wypisuje tylko ile GB zwolniono. Bez --volumes -> nazwane wolumeny danych sa bezpieczne. - Domyslna wersja PostgreSQL dla NOWYCH instalacji: 16.13 -> 18.4 (init-configs prompt + fresh .env). Safety-nety w compose (:-16.13) oraz migracja dla .env bez zmiennej CELOWO zostaja na 16.13 - kompatybilnosc wsteczna, zaden istniejacy klaster nie dostaje cichego upgrade'u majora. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Co i dlaczego
Obraz
iplweb/bpp_dbserverjest wycofany — jego jedynym dodatkiem ponad stockowego postgresa był autotune. Ten PR przenosidbserverna oficjalny obrazpostgres:<MAJOR.MINOR>(Debian) z dwoma skryptami autotune bind-mountowanymi z repo (dbserver/, skopiowane verbatim ziplweb/bpp-dbserver). Brak buildu, brakpython3.Decyzje (uzgodnione): ICU
pl-PLdla świeżych instalacji + pełny zakres (swap obrazu oraz uproszczenie flow upgrade'u majora).Zmiany
docker-compose.database.ymlimage→postgres:${DJANGO_BPP_POSTGRESQL_VERSION:-${DJANGO_BPP_DBSERVER_PG_VERSION:-16.13}}(dwuwarstwowy fallback zachowany)entrypoint+command: postgres; skrypty bind-mount read-onlyPGDATA=/var/lib/postgresql/data— stockpostgres:18+domyślnie używa innej ścieżki → bez pinu zignorowałby istniejący wolumen i zainicjował pustą bazęPOSTGRES_INITDB_ARGSICUpl-PL— tylko świeże instalacje, nigdy nie re-kolacjonuje istniejących klastrówpg_isreadyhealthcheck — stock postgres nie ma wbudowanego, aappserver/authservermajądepends_on: dbserver: service_healthyenv_file— include-levelenv_filesłuży tylko interpolacji compose i nie trafia do kontenera (knobyPOSTGRESQL_*autotune'a)scripts/upgrade-postgres.sh/test-upgrade-postgres.shNEW_DBSERVER_IMAGE→postgres:<ver>; usunięty wymóg "obraz musi być wcześniej opublikowany"platform:+DOCKER_DEFAULT_PLATFORM→ leci natywnie na Apple Silicon), mirror produkcji (mounty autotune, pin PGDATA, healthcheck)init-configs.sh/check-image-versions.sh: tylko komunikaty/komentarze (logika zmiennych wersji i migracje starych nazw bez zmian).Docs:
docs/konfiguracja/postgresql.md,limity-zasobow.md(autotune czyta limit cgroup),CLAUDE.md+ spec projektowy.Kompatybilność wsteczna
.env—DJANGO_BPP_POSTGRESQL_VERSION(np.16.13) mapuje się wprost na tagpostgres:16.13.pl-PL; istniejące wolumeny zachowują swoją kolację.Weryfikacja
docker compose config(database.yml + pełny stack + wygenerowany test compose)bash dbserver/autotune.sh --test(parity self-test)bash -n+ pre-commit (shellcheck, check-yaml, trufflehog)mkdocs build --strict(brak zepsutych linków/nav)postgres:16.13:healthy;shared_buffers = 498072kB(~486 MB z limitu 2 GiB cgroup, nie domyślne 128 MB); kolacjabpp | i | en_US.utf8 | pl-PL;data_directory = /var/lib/postgresql/dataSpec:
docs/superpowers/specs/2026-06-13-dbserver-stock-postgres-autotune-design.md(dwukrotnie zrecenzowany; review wyłapało defekt include-levelenv_file, naprawiony).🤖 Generated with Claude Code