Die Unit las bisher nur p2.env. Auf Prod steht die Datenbankverbindung
schon in ~/.config/wurzelwerk/database.env und gehoert Phase 1 mit; die
Unit liest jetzt beide, database.env zuerst. p2.env kann DATABASE_URL
ueberschreiben, falls database.env auf 127.0.0.1 zeigt - aus dem
Container heraus laeuft das ins Leere, dort muss
host.containers.internal stehen.
deploy/INBETRIEBNAHME.md haelt die Reihenfolge fest. Zwei Schritte darin
sind nicht offensichtlich und beide teuer, wenn man sie auslaesst:
lexikon.py --laden akteure.py liest aus den Tabellen, nicht aus den
JSON-Dateien. Ohne diesen Schritt laeuft Modul 4
durch und loest nichts auf, ohne zu scheitern.
Erstbestand von Hand Der erste Lauf laedt 2,2 GB Modellgewichte und
kodiert den gesamten Korpus, ~17 min je 5000
Items. Aus dem Timer heraus liefe er in
TimeoutStartSec=1800.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NupWEBPzsyohVPG47HsTz8
4.5 KiB
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
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
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:
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.
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
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.
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:
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
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
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:
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:
loginctl enable-linger podman
6. Nach der ersten Stunde
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.