# search das komplette serverjonas search - `crawler.py` – PostgreSQL-Crawler, baut den Suchindex auf - `launcher.py` – startet/überwacht den Crawler und rollt Updates aus (siehe unten) - `search/` – Suchmaschine darüber (CLI + HTTP-API), siehe [search/README.md](search/README.md) - `web/` – Statistik-Webseite - `fix_pages_search_vector_trigger.sql` – einmalig laufen lassen: kappelt den Text, der im DB-Trigger `pages_search_vector_update()` in `to_tsvector()` einfließt (Postgres hartes Limit: 1.048.575 Bytes pro `tsvector`) ## Sehr große Seiten / tsvector-Limit PostgreSQL begrenzt einen einzelnen `tsvector`-Wert hart auf 1.048.575 Bytes (nicht konfigurierbar). Der Trigger `pages_search_vector_update()` baut diesen aus der `content`-Spalte – Seiten mit sehr viel Text (beobachtet: ~2 MB, z. B. `pkg.go.dev/...?tab=importedby`) haben den Insert dadurch mit `ProgramLimitExceeded` abgebrochen und alle Worker crashen lassen. Dagegen wirken drei Maßnahmen zusammen: 1. **DB-Trigger (empfohlen, einmalig einspielen):** `fix_pages_search_vector_trigger.sql` ersetzt die Trigger-Funktion so, dass nur die ersten 200.000 Zeichen in `to_tsvector()` einfließen. Der volle Text bleibt in `content` gespeichert, nur der Suchindex ist gekürzt. Vor dem Einspielen die eigene Funktions-Version prüfen: `SELECT prosrc FROM pg_proc WHERE proname = 'pages_search_vector_update';` 2. **Im Crawler:** `save_page()` kappelt den Inhalt vor dem Insert zusätzlich auf `CONTENT_INDEX_MAX_CHARS` (200.000 Zeichen) – wirkt sofort, auch ohne Trigger-Fix. Überlange Seiten werden dann normal gespeichert. 3. **Fehlerbehandlung:** Sollte der Insert trotzdem an einem Datenbank-Limit scheitern, fängt der Worker `ProgramLimitExceeded` ab, loggt die URL als Warnung, rollt die Transaktion zurück, markiert den Queue-Eintrag als `failed` (kein endloses Wiederholen) und läuft weiter, statt zu sterben. Zusätzlich werden bekannt wertlose URLs (`pkg.go.dev/...?tab=importedby` – Megabyte-große reine Rückverweis-Listen) über `SKIP_URL_PATTERNS` schon vor dem Fetch übersprungen und als `done` markiert. ## Rolling Updates (Generation-Mechanismus) Zentrale Tabelle (eine Zeile, `id = 1`): ```sql CREATE TABLE IF NOT EXISTS deployment_state ( id INTEGER PRIMARY KEY, generation BIGINT NOT NULL, required_version INTEGER NOT NULL ); ``` **Ablauf:** 1. Einmalig initialisieren: `python3 crawler.py --init-deployment` 2. Launcher starten: `python3 launcher.py` (startet + überwacht den Crawler) 3. Neue Version verteilen: - Neue `crawler.py` mit erhöhter `JONASSEARCH_CRAWLER_VERSION` bereitstellen (Update-Quelle im `.env` setzen oder Datei nach `releases//crawler.py` legen) - `python3 launcher.py --check-update` → neue Version wird gestaget → geprüft (`--self-update`) → aktiviert → **erst dann** wird die Generation in PostgreSQL erhöht (`--update-deployment`) 4. Alle laufenden alten Crawler merken das beim nächsten sicheren Punkt (vor jedem relevanten Commit bzw. spätestens nach `JONASSEARCH_UPDATE_CHECK_SECONDS` im Hintergrund-Check), rollen ihre offene Transaktion sauber zurück und beenden sich mit **Exit-Code 42** – nie mitten in einer Transaktion, kein hartes Killen. 5. Der jeweilige Launcher erkennt 42 und startet die bereits bereitgestellte neue Version, die die neue Generation beim Start liest. Exit-Codes des Crawlers: `0` = normal beendet, `1` = Fehler/Crash, `42` = ausschließlich „Update erforderlich". Nützliche Befehle: ```bash python3 crawler.py --version # installierte Version python3 launcher.py --status # Generation + Versionen anzeigen python3 crawler.py --self-update 2 # gestagte Release prüfen (ohne zu aktivieren) python3 crawler.py --update-deployment 2 # Generation+1 und required_version=2 setzen ```