diff --git a/deploy/INBETRIEBNAHME.md b/deploy/INBETRIEBNAHME.md new file mode 100644 index 0000000..2f635e2 --- /dev/null +++ b/deploy/INBETRIEBNAHME.md @@ -0,0 +1,140 @@ +# Phase 2 auf Prod in Betrieb nehmen + +Reihenfolge ist verbindlich. Jeder Schritt hat eine Prüfung; schlägt sie +fehl, hilft der nächste Schritt nicht. + +Alles läuft als Benutzer `podman`, dessen Home `/home/podman` ist — `%h` in +den Units löst dorthin auf. Die Datenbankverbindung steht bereits in +`~/.config/wurzelwerk/database.env`. + +## 0. Migrationen — erledigt + +```sh +psql "$DATABASE_URL" -c 'select name, angewandt_am from schema_migrationen order by name' +``` + +Sechs Zeilen, `p2-000` bis `p2-005`. Fehlt eine, siehe README, Abschnitt +„Migrationen" — `p2-002` und `p2-003` brauchen einen Superuser. + +## 1. Abbild bauen + +```sh +cd /pfad/zum/repo && git pull +podman build -t wurzelwerk-p2 . +``` + +2,12 GB, davon 568 MB spaCy-Modell und 196 MB torch. Prüfung: + +```sh +podman run --rm --entrypoint ls localhost/wurzelwerk-p2 /app/lexikon/ +``` + +Zwölf Dateien. Fehlen `landtage.json`, `regierung.json` oder +`sondergesandte.json`, ist das Abbild älter als der Stand vom 08.09.2026. + +## 2. Lexikon in die Tabellen laden + +`akteure.py` liest aus `akteure` und `amtsinhaber`, nicht aus den +JSON-Dateien. Ohne diesen Schritt läuft Modul 4 durch und löst nichts auf, +**ohne zu scheitern** — der teuerste stille Fehler der ganzen Einrichtung. + +```sh +podman run --rm --network=pasta \ + --env-file ~/.config/wurzelwerk/database.env \ + localhost/wurzelwerk-p2 lexikon.py --laden +``` + +Erwartet: `person 5631`, `organisation 2319`, `partei 682`, `staat 195`, +`amt 29`, `amtsinhaber 418`. Die `Ueberschneidung:`-Zeilen sind die +Betriebssicht auf Doppelspitzen und Bremer Bürgermeister, kein Fehler. + +Läuft der Container hier in einen Verbindungsfehler, zeigt `DATABASE_URL` +vermutlich auf `127.0.0.1`. Aus dem Container heraus ist das der Container +selbst; nötig ist `host.containers.internal`. Dann in `p2.env` überschreiben +(Schritt 4), statt `database.env` anzufassen — die gehört Phase 1 mit. + +## 3. Modell-Volume anlegen und einmal füllen + +```sh +podman volume create wurzelwerk-modelle +``` + +Der erste Lauf braucht Netz und lädt 2,2 GB. Er muss **von Hand** laufen, +nicht aus dem Timer: der Erstbestand über den gesamten Korpus dauert etwa +17 Minuten je 5000 Items, und `TimeoutStartSec=1800` ist dafür knapp. + +```sh +podman run --rm --network=pasta --memory=8g --shm-size=1g \ + --volume wurzelwerk-modelle:/cache:U \ + --env-file ~/.config/wurzelwerk/database.env \ + --env OMP_NUM_THREADS=4 --env HF_HUB_OFFLINE=0 \ + localhost/wurzelwerk-p2 phase2.py --backfill +``` + +Prüfung — vier Ebenen, je eine `coder_version`, alle gleich hoch: + +```sh +psql "$DATABASE_URL" -c \ + 'select ebene, coder_version, count(*) from codings group by 1,2 order by 1' +psql "$DATABASE_URL" -c 'select count(*) from item_embeddings' +``` + +Stehen für eine Ebene zwei Versionen nebeneinander, ist der Lauf +unterbrochen worden und wurde mit geändertem Code fortgesetzt. Dann die +ältere löschen und den Backfill wiederholen. + +## 4. Umgebung für Phase 2 + +```sh +cp deploy/p2.env.beispiel ~/.config/wurzelwerk/p2.env +chmod 600 ~/.config/wurzelwerk/p2.env +$EDITOR ~/.config/wurzelwerk/p2.env # HF_HUB_OFFLINE=1 setzen +``` + +`HF_HUB_OFFLINE=1` erst jetzt, nach dem erfolgreichen Erstlauf. Danach geht +das Modell nie wieder ins Netz und lädt in zwei Sekunden aus dem Volume. + +## 5. Units einhängen + +```sh +cp deploy/wurzelwerk-p2.{service,timer} ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user start wurzelwerk-p2.service # ein Lauf von Hand +journalctl --user -u wurzelwerk-p2 -n 40 +``` + +Erst wenn dieser eine Lauf sauber durchgeht — er sollte `kein offener Slot` +melden, weil der Backfill alles kodiert hat —, den Timer scharfstellen: + +```sh +systemctl --user enable --now wurzelwerk-p2.timer +systemctl --user list-timers wurzelwerk-p2.timer +``` + +Feuert `*:03,18,33,48`, versetzt zum 15-Minuten-Raster von Phase 1. + +Damit die Units auch ohne angemeldete Sitzung laufen: + +```sh +loginctl enable-linger podman +``` + +## 6. Nach der ersten Stunde + +```sh +psql "$DATABASE_URL" -c \ + "select modul, coder_version, items_gesehen, items_kodiert, items_fehler, + begonnen_am + from p2_laeufe order by begonnen_am desc limit 12" +``` + +Vier Zeilen je Slot, `items_fehler = 0`. Ein Modul, das dauerhaft fehlt, +ist abgestürzt, ohne die anderen mitzureissen — genau dafür ist die +Schichtung da, aber es fällt dann eben auch nur hier auf. + +## Was diese Phase nicht tut + +`raw_items` wird nie geschrieben. Der gesamte Phase-2-Bestand — `codings`, +`item_embeddings`, `p2_laeufe` — ist verwerfbar und aus `raw_items` neu +herstellbar. Ein Rückbau ist deshalb `truncate`, kein Wiederherstellen aus +einem Backup. diff --git a/deploy/p2.env.beispiel b/deploy/p2.env.beispiel index 04eb75c..ba2a2f4 100644 --- a/deploy/p2.env.beispiel +++ b/deploy/p2.env.beispiel @@ -1,9 +1,12 @@ # Nach ~/.config/wurzelwerk/p2.env kopieren und ausfuellen. chmod 600. # Enthaelt Zugangsdaten und gehoert nicht ins Repo. -# host.containers.internal zeigt aus dem Container auf den Host, wo Postgres -# seinen Port veroeffentlicht. -DATABASE_URL=postgresql://wurzelwerk:PASSWORT@host.containers.internal:5432/wurzelwerk +# Die Datenbankverbindung steht auf Prod bereits in +# ~/.config/wurzelwerk/database.env; die Unit liest beide Dateien. Hier nur +# setzen, wenn Phase 2 eine andere braucht - etwa weil database.env auf +# 127.0.0.1 zeigt, was aus dem Container heraus ins Leere laeuft. +# +# DATABASE_URL=postgresql://wurzelwerk:PASSWORT@host.containers.internal:5432/wurzelwerk # Threadzahl. Geht in die coder_version ein - eine Aenderung erzeugt einen # neuen Vektorbestand. Prod hat 16 Threads, 4 laeuft ueberall. diff --git a/deploy/wurzelwerk-p2.service b/deploy/wurzelwerk-p2.service index 85874ce..ea8b7a8 100644 --- a/deploy/wurzelwerk-p2.service +++ b/deploy/wurzelwerk-p2.service @@ -15,7 +15,16 @@ After=postgres.service [Service] Type=oneshot -EnvironmentFile=%h/.config/wurzelwerk/p2.env +# Zwei Dateien, in dieser Reihenfolge. database.env haelt die +# Datenbankverbindung und gehoert nicht Phase 2 allein - sie steht auf Prod +# schon da und wird hier nur mitgelesen, nicht angefasst. p2.env traegt, was +# nur Phase 2 angeht; wird dort DATABASE_URL noch einmal gesetzt, gewinnt sie, +# weil systemd spaetere Dateien spaeter liest. +# +# Beide muessen aus dem Container heraus aufloesbar sein: der Rechnername in +# DATABASE_URL darf nicht 127.0.0.1 sein, sondern host.containers.internal. +EnvironmentFile=%h/.config/wurzelwerk/database.env +EnvironmentFile=-%h/.config/wurzelwerk/p2.env TimeoutStartSec=1800 ExecStart=/usr/bin/podman run --rm \ --name wurzelwerk-p2-lauf \