Prod fuehrt Phase 1 als Quadlet unter
~/.config/containers/systemd/wurzelwerk-ingest.container; Phase 2 legt
sich daneben. Damit faellt die eigene .service-Unit weg - zwei Wege,
dasselbe zu tun, sind einer zuviel.
Drei Dinge, die dabei nicht offensichtlich sind:
Type=oneshot Quadlet setzt sonst Type=notify, und die Unit
gaelte als gestartet, sobald der Container
laeuft, nicht wenn er durch ist.
kein RemainAfterExit Das Handbuch empfiehlt es fuer oneshot, aber bei
Timer-Aktivierung bliebe der Auftrag im Zustand
"started" und der Timer loeste ihn nie wieder
aus.
--env-file EnvironmentFile= im Abschnitt [Container] ist
nicht systemds EnvironmentFile=. Quadlet reicht
die Datei an podman weiter, und podman entfernt
keine Anfuehrungszeichen um Werte.
Network=bebop-net steht jetzt direkt in der Unit; P2_NETZ entfaellt.
Type=oneshot schaltet ausserdem die Startzeitgrenze ab, womit der Grund
fuer TimeoutStartSec=1800 wegfaellt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NupWEBPzsyohVPG47HsTz8
145 lines
4.7 KiB
Markdown
145 lines
4.7 KiB
Markdown
# 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=bebop-net \
|
|
--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.
|
|
|
|
Das Netz ist nicht schmückend: `DATABASE_URL` zeigt auf den Rechnernamen
|
|
`postgres`, und den löst nur Podmans DNS innerhalb von `bebop-net` auf.
|
|
Ohne `--network=bebop-net` scheitert die Namensauflösung, nicht die
|
|
Anmeldung — die Fehlermeldung nennt dann den Namen, nicht das Passwort.
|
|
|
|
## 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=bebop-net --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.container ~/.config/containers/systemd/
|
|
cp deploy/wurzelwerk-p2.timer ~/.config/systemd/user/
|
|
systemctl --user daemon-reload
|
|
|
|
# Was Quadlet daraus erzeugt hat, vor dem ersten Start ansehen:
|
|
/usr/lib/systemd/user-generators/podman-user-generator --dryrun
|
|
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.
|