Poll-Takt von 15 Minuten auf 2 Stunden senken

Reduziert den Traffic gegen die Quellen. slot_von() rundet jetzt generisch
auf TAKT-Grenzen statt fest auf 15-Minuten-Slots, die Stillstands-Schwelle
in quellen_stillstand steigt von 6h auf 24h, damit der Watchdog nicht schon
nach drei verpassten Slots anschlaegt. GDELT-Vergleich aus den Kommentaren
entfernt, da die Slots nicht mehr mit dessen 15-Minuten-Paketen zusammenfallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013vHZCwcCJnz4LKNgvT4bxv
This commit is contained in:
irrlicht
2026-09-04 20:34:22 +02:00
co-authored by Claude Sonnet 5
parent c43d8496bf
commit 8a1ab8a146
6 changed files with 17 additions and 13 deletions
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
+1 -1
View File
@@ -9,6 +9,6 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY poller.py sources.toml ./ COPY poller.py sources.toml ./
USER ingest USER ingest
# Ohne Argumente: Dauerbetrieb im 15-Minuten-Takt, Senke aus $DATABASE_URL. # Ohne Argumente: Dauerbetrieb im 2-Stunden-Takt, Senke aus $DATABASE_URL.
# -u, damit die Logs unmittelbar im journal landen und nicht im Puffer haengen. # -u, damit die Logs unmittelbar im journal landen und nicht im Puffer haengen.
ENTRYPOINT ["python", "-u", "/app/poller.py"] ENTRYPOINT ["python", "-u", "/app/poller.py"]
+2 -2
View File
@@ -101,8 +101,8 @@ ueber die gesamte Historie, ohne dass ein Feed erneut abgerufen wird.
## Verhalten ## Verhalten
- **15-Minuten-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz), - **2-Stunden-Takt**, an den Slotgrenzen ausgerichtet (+30 s Versatz), um
identisch zu GDELTs Paketgrenzen — damit bleiben die Slots vergleichbar. unnoetigen Traffic bei den Quellen zu vermeiden.
- **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im - **Bedingter GET** ueber ETag/Last-Modified, im Register gespeichert. Im
Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304. Test antworteten 4 von 12 Quellen im zweiten Durchlauf mit 304.
- **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise - **Dedup** ueber `unique (quelle, item_key)`; `item_key` ist guid, ersatzweise
+7 -6
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Ingest-Poller fuer den deutschen Ereignisdatensatz. """Ingest-Poller fuer den deutschen Ereignisdatensatz.
Liest die Quellen aus sources.toml, holt alle 15 Minuten die Feeds und legt Liest die Quellen aus sources.toml, holt alle 2 Stunden die Feeds und legt
jedes neue Item roh ab. Bewusst ohne Kodierung: was hier landet, soll sich jedes neue Item roh ab. Bewusst ohne Kodierung: was hier landet, soll sich
beliebig oft neu kodieren lassen. beliebig oft neu kodieren lassen.
@@ -10,7 +10,7 @@ Zwei Senken:
--sink jsonl:/pfad/dir Schatten-Ingest als gzip-JSONL pro Slot --sink jsonl:/pfad/dir Schatten-Ingest als gzip-JSONL pro Slot
Aufrufe: Aufrufe:
poller.py --sink "$DATABASE_URL" # Dauerbetrieb, 15-Minuten-Takt poller.py --sink "$DATABASE_URL" # Dauerbetrieb, 2-Stunden-Takt
poller.py --sink "$DATABASE_URL" --once # ein Durchlauf poller.py --sink "$DATABASE_URL" --once # ein Durchlauf
poller.py --check-robots # Lizenzstatus nachpruefen poller.py --check-robots # Lizenzstatus nachpruefen
""" """
@@ -40,7 +40,7 @@ USER_AGENT = os.environ.get(
"INGEST_USER_AGENT", "INGEST_USER_AGENT",
"wurzelwerk-ingest/0.1 (+https://stinkwurzpresse.de/wurzelwerk; kontakt@stinkwurzpresse.de)", "wurzelwerk-ingest/0.1 (+https://stinkwurzpresse.de/wurzelwerk; kontakt@stinkwurzpresse.de)",
) )
TAKT = 15 * 60 # Slotlaenge in Sekunden, wie bei GDELT TAKT = 2 * 60 * 60 # Slotlaenge in Sekunden
VERSATZ = 30 # Sekunden nach der Slotgrenze, nicht exakt darauf VERSATZ = 30 # Sekunden nach der Slotgrenze, nicht exakt darauf
NS = { NS = {
"a": "http://www.w3.org/2005/Atom", "a": "http://www.w3.org/2005/Atom",
@@ -66,9 +66,10 @@ def _jetzt():
def slot_von(ts): def slot_von(ts):
"""Auf 15 Minuten abrunden, wie GDELTs Paketgrenzen.""" """Auf TAKT-Sekunden seit Epoch abrunden (bei 2 h also volle gerade Stunden)."""
return ts.replace(second=0, microsecond=0, sekunden = int(ts.timestamp())
minute=(ts.minute // 15) * 15) return dt.datetime.fromtimestamp(
(sekunden // TAKT) * TAKT, tz=dt.timezone.utc)
def norm_titel(t): def norm_titel(t):
+4 -3
View File
@@ -48,7 +48,7 @@ create table if not exists raw_items (
autor text, autor text,
pubdate timestamptz, -- Angabe der Quelle, kann fehlen/luegen pubdate timestamptz, -- Angabe der Quelle, kann fehlen/luegen
abgerufen_am timestamptz not null default now(), abgerufen_am timestamptz not null default now(),
slot timestamptz not null, -- auf 15 min abgerundet, GDELT-kompatibel slot timestamptz not null, -- auf den Poll-Takt abgerundet
sprache text not null default 'de', sprache text not null default 'de',
roh jsonb not null, -- alle geparsten Feldwerte, unveraendert roh jsonb not null, -- alle geparsten Feldwerte, unveraendert
@@ -113,7 +113,8 @@ create table if not exists poll_laeufe (
create index if not exists poll_laeufe_slot_idx on poll_laeufe (slot desc); create index if not exists poll_laeufe_slot_idx on poll_laeufe (slot desc);
create index if not exists poll_laeufe_quelle_idx on poll_laeufe (quelle, begonnen_am desc); create index if not exists poll_laeufe_quelle_idx on poll_laeufe (quelle, begonnen_am desc);
-- Welche aktive Quelle hat seit ueber sechs Stunden nichts Neues geliefert? -- Welche aktive Quelle hat seit ueber 24 Stunden (12 verpasste Slots bei
-- 2-Stunden-Takt) nichts Neues geliefert?
create or replace view quellen_stillstand as create or replace view quellen_stillstand as
select q.id, q.name, max(r.abgerufen_am) as letztes_item, select q.id, q.name, max(r.abgerufen_am) as letztes_item,
now() - max(r.abgerufen_am) as stille now() - max(r.abgerufen_am) as stille
@@ -122,5 +123,5 @@ left join raw_items r on r.quelle = q.id
where q.aktiv where q.aktiv
group by q.id, q.name group by q.id, q.name
having max(r.abgerufen_am) is null having max(r.abgerufen_am) is null
or now() - max(r.abgerufen_am) > interval '6 hours' or now() - max(r.abgerufen_am) > interval '24 hours'
order by stille desc nulls first; order by stille desc nulls first;
+1 -1
View File
@@ -2,7 +2,7 @@
# Ablegen unter ~/.config/containers/systemd/wurzelwerk-ingest.container, # Ablegen unter ~/.config/containers/systemd/wurzelwerk-ingest.container,
# danach: systemctl --user daemon-reload && systemctl --user start wurzelwerk-ingest # danach: systemctl --user daemon-reload && systemctl --user start wurzelwerk-ingest
# #
# Der Poller laeuft dauerhaft und taktet sich selbst auf die 15-Minuten-Grenzen. # Der Poller laeuft dauerhaft und taktet sich selbst auf die 2-Stunden-Grenzen.
# Deshalb kein systemd-Timer: ein Timer wuerde den bedingten GET-Zustand # Deshalb kein systemd-Timer: ein Timer wuerde den bedingten GET-Zustand
# (ETag/Last-Modified) bei jedem Start neu aufbauen muessen. # (ETag/Last-Modified) bei jedem Start neu aufbauen muessen.