e6050a14c4
Manifest, wpięcie w kustomization i runbook krok po kroku. DECYZJE ZAPISANE W MANIFEŚCIE, ŻEBY NIE TRZEBA ICH BYŁO ODTWARZAĆ Z GŁOWY: local-path, NIE NFS. Postgres zakłada semantykę blokad i fsync, której NFS nie gwarantuje — to klasyczne źródło uszkodzenia bazy przy nagłym restarcie. Ceną jest przywiązanie do węzła; przy luście odtwarzalnym z Excela to akceptowalne. strategy: Recreate. Wolumen jest ReadWriteOnce, a dwa procesy Postgresa na jednym katalogu danych to uszkodzona baza — rolling próbowałby wstać z nowym podem, zanim stary zejdzie. PGDATA w PODKATALOGU wolumenu: katalog główny potrafi zawierać wpisy systemu plików, a initdb odmawia pracy w niepustym katalogu. Wersja PRZYPIĘTA i poza image-updaterem: podbicie majora wymaga migracji katalogu danych, więc nie może się zdarzyć samo, w nocy, przy okazji builda aplikacji. C.UTF-8 zamiast pl_PL.UTF-8: dopasowanie tekstu robimy przez unaccent i pg_trgm, nie przez collation, a pl_PL wymagałby obrazu z wygenerowanymi lokalizacjami. DATA_PROVIDER zostaje na `excel`. Postgres można wdrożyć i obejrzeć BEZ ryzyka dla działającego wyszukiwania; przełączenie to osobna, późniejsza decyzja. SPRAWDZONE PRZED ODDANIEM: `kubectl kustomize astrololo` składa komplet 21 zasobów bez błędu, YAML parsuje się poprawnie, audyt odwołań potwierdza, że jedynym brakującym sekretem jest astrololo-postgres (oba klucze), a DSN postgresql+psycopg:// jest rozpoznawany przez SQLAlchemy z psycopg 3. NIE SPRAWDZONE: skrypt inicjalizujący nie biegł przeciwko prawdziwemu Postgresowi — na maszynie, na której to powstawało, nie ma Dockera. Zapisane wprost w runbooku; krok 4 jest tam prawdziwym testem. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
6.1 KiB
YAML
151 lines
6.1 KiB
YAML
# Postgres — lustro baz Excela w SQL (DAN-28).
|
|
#
|
|
# DLACZEGO POSTGRES, A NIE SQLITE. Nie ze względu na skalę: dzisiejszy zbiór to
|
|
# ~54 tys. wierszy, przy których SQLite bywa szybszy. Powodem jest WYSZUKIWANIE
|
|
# w nieznormalizowanym, polskim tekście — `pg_trgm` (dopasowanie rozmyte, literówki)
|
|
# i `unaccent` (ogonki) nie mają w SQLite taniego zamiennika, a to jest główna
|
|
# funkcja programu, nie dodatek. Drugi powód: `pg_advisory_lock` sprawia, że model
|
|
# sesji z atomowym commitem przestaje zakładać „jedna replika na zawsze".
|
|
#
|
|
# TO LUSTRO, NIE ŹRÓDŁO PRAWDY. Źródłem są pliki Excela na NFS. Utrata tej bazy
|
|
# NIE JEST utratą danych — odbudowuje się z plików. Dlatego kopie zapasowe są tu
|
|
# OPCJONALNE, a wolumen może stać na local-path.
|
|
---
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: postgres-data
|
|
namespace: astrololo
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
# local-path (domyślny provisioner k3s), NIE NFS. Postgres zakłada semantykę
|
|
# blokad i fsync, której NFS nie gwarantuje — to klasyczne źródło uszkodzenia
|
|
# bazy przy nagłym restarcie. Ceną jest przywiązanie do węzła; przy luście
|
|
# odtwarzalnym z Excela to akceptowalne.
|
|
storageClassName: local-path
|
|
resources:
|
|
requests:
|
|
storage: 5Gi
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: postgres-init
|
|
namespace: astrololo
|
|
data:
|
|
# UWAGA: skrypty z /docker-entrypoint-initdb.d/ uruchamiają się WYŁĄCZNIE przy
|
|
# pierwszej inicjalizacji, na pustym katalogu danych. Dopisanie tu rozszerzenia
|
|
# PO utworzeniu bazy nic nie zrobi — trzeba je wtedy założyć ręcznie (patrz
|
|
# README-postgres.md, sekcja „Dodanie rozszerzenia do istniejącej bazy").
|
|
01-extensions.sql: |
|
|
-- Dopasowanie rozmyte: similarity(), operator %, indeksy GIN po trigramach.
|
|
-- To jest powód, dla którego stoi tu Postgres, a nie SQLite.
|
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
-- Zdejmowanie ogonków: „różdżka" ma się znaleźć na „rozdzka".
|
|
CREATE EXTENSION IF NOT EXISTS unaccent;
|
|
|
|
-- Schemat na lustro. Osobny, żeby purge i odbudowa nie musiały ruszać
|
|
-- niczego w `public` i żeby `DROP SCHEMA mirror CASCADE` był bezpiecznym
|
|
-- „wyczyść wszystko i wczytaj od nowa".
|
|
CREATE SCHEMA IF NOT EXISTS mirror;
|
|
|
|
-- Rejestr luster: co, z jakiego pliku, o jakim skrócie i kiedy wczytane.
|
|
-- Zgodność pliku z tabelą poznajemy po sha256 — nazwa pliku nie wystarcza,
|
|
-- bo treść potrafi się zmienić bez zmiany nazwy.
|
|
CREATE TABLE IF NOT EXISTS mirror.registry (
|
|
table_name text PRIMARY KEY,
|
|
source_path text NOT NULL UNIQUE,
|
|
sha256 text NOT NULL,
|
|
row_count integer NOT NULL DEFAULT 0,
|
|
loaded_at timestamptz NOT NULL DEFAULT now(),
|
|
active boolean NOT NULL DEFAULT false
|
|
);
|
|
CREATE INDEX IF NOT EXISTS registry_active_idx ON mirror.registry (active);
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: postgres
|
|
namespace: astrololo
|
|
spec:
|
|
replicas: 1
|
|
# Recreate, NIE RollingUpdate: wolumen jest ReadWriteOnce, a dwa procesy
|
|
# Postgresa na jednym katalogu danych to uszkodzona baza. Rolling próbowałby
|
|
# wstać z nowym podem, zanim stary zejdzie.
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels: { app: postgres }
|
|
template:
|
|
metadata:
|
|
labels: { app: postgres }
|
|
spec:
|
|
containers:
|
|
- name: postgres
|
|
# Wersja PRZYPIĘTA i celowo poza image-updaterem: podbicie majora
|
|
# Postgresa wymaga migracji katalogu danych, więc nie może się zdarzyć
|
|
# samo, w nocy, przy okazji builda aplikacji.
|
|
image: postgres:17
|
|
ports: [{ containerPort: 5432, name: postgres }]
|
|
env:
|
|
- name: POSTGRES_DB
|
|
value: astrololo
|
|
- name: POSTGRES_USER
|
|
value: astrololo
|
|
- name: POSTGRES_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef: { name: astrololo-postgres, key: POSTGRES_PASSWORD }
|
|
# PGDATA w PODKATALOGU montowanego wolumenu. Katalog główny wolumenu
|
|
# potrafi zawierać wpisy systemu plików (np. lost+found), a initdb
|
|
# odmawia inicjalizacji w niepustym katalogu.
|
|
- name: PGDATA
|
|
value: /var/lib/postgresql/data/pgdata
|
|
# C.UTF-8 zamiast pl_PL.UTF-8: dopasowanie tekstu robimy przez
|
|
# unaccent i pg_trgm, nie przez collation, a locale pl_PL wymagałoby
|
|
# obrazu z wygenerowanymi lokalizacjami. Gdyby kiedyś potrzebne było
|
|
# polskie SORTOWANIE, dokłada się collation ICU na konkretnej kolumnie.
|
|
- name: POSTGRES_INITDB_ARGS
|
|
value: "--encoding=UTF8 --locale=C.UTF-8"
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /var/lib/postgresql/data
|
|
- name: init
|
|
mountPath: /docker-entrypoint-initdb.d
|
|
readOnly: true
|
|
# pg_isready, nie zwykły TCP: gniazdo słucha, zanim baza przyjmuje
|
|
# zapytania, więc sonda po porcie przepuściłaby ruch za wcześnie.
|
|
readinessProbe:
|
|
exec:
|
|
command: ["pg_isready", "-U", "astrololo", "-d", "astrololo", "-q"]
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
livenessProbe:
|
|
exec:
|
|
command: ["pg_isready", "-U", "astrololo", "-d", "astrololo", "-q"]
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 20
|
|
failureThreshold: 6
|
|
resources:
|
|
requests: { cpu: "100m", memory: "256Mi" }
|
|
limits: { cpu: "1000m", memory: "1Gi" }
|
|
volumes:
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: postgres-data
|
|
- name: init
|
|
configMap:
|
|
name: postgres-init
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: postgres
|
|
namespace: astrololo
|
|
spec:
|
|
# ClusterIP i tylko ClusterIP. Baza nie ma żadnego powodu być widoczna poza
|
|
# klastrem; dostęp z zewnątrz robi się przez `kubectl port-forward` na czas
|
|
# jednej sesji (patrz README-postgres.md).
|
|
type: ClusterIP
|
|
selector: { app: postgres }
|
|
ports: [{ port: 5432, targetPort: 5432 }]
|