From 9a4266ae7edde8cd2d1b8921a9c54fd3acd8f43e Mon Sep 17 00:00:00 2001 From: Sascha Wolf Date: Tue, 11 Aug 2026 11:32:49 +0200 Subject: [PATCH] fix(scanner): Suchintervall ist ein Takt, keine Pause MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `last_scan_at` hielt das ENDE des Laufs fest. Der Scheduler rechnet die Fälligkeit aber von diesem Wert aus, also kam die Laufzeit von drei bis vier Minuten jedes Mal obendrauf: bei Einstellung 15 Minuten lagen die Läufe 19 Minuten auseinander. Gemessen heute früh, unsere Instanz gegen das Original: wir: 07:00 07:19 07:38 07:57 08:16 → 19 Min Original: 07:00 07:16 07:31 07:47 08:03 → 15-16 Min Über das Fenster 07-22 Uhr sind das rund 47 statt 58 Durchgänge pro Tag. Die Trefferliste der Börsen zeigt nur die neuesten Projekte, jeder ausgelassene Durchgang kostet deshalb echte Funde. Jetzt wird der Startzeitpunkt gespeichert. Das `finally` bleibt, damit sich ein gescheiterter Lauf nicht sofort wiederholt; dauert ein Lauf länger als das Intervall, ist der nächste sofort fällig - die Sperre aus demselben Commit verhindert die Überlappung. Kein Rückschritt zum vorherigen Fehler (e58b94f): der Zeitstempel liegt weiterhin in der Datenbank und trägt den Zeitplan über Neustarts. Co-Authored-By: Claude Opus 5 (1M context) --- docs/03-scanner.md | 7 ++++++- docs/09-quellcode.md | 6 ++++-- server/src/scanner/services/pipeline.js | 15 +++++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/03-scanner.md b/docs/03-scanner.md index 9eea638..1b79400 100644 --- a/docs/03-scanner.md +++ b/docs/03-scanner.md @@ -63,9 +63,14 @@ flowchart TD S --> F{"Intervall seit
letztem Lauf um?"} F -->|nein| STOP5["nichts tun"] F -->|ja| G["Pipeline starten"] - G --> W["last_scan_at schreiben"] + G --> W["last_scan_at = Beginn des Laufs"] ``` +Geschrieben wird der **Beginn** des Laufs, nicht sein Ende. Das Intervall ist ein +Takt: mit dem Endzeitpunkt kam die Laufzeit von drei bis vier Minuten jedes Mal +obendrauf, aus 15 Minuten wurden 19 - rund ein Fuenftel weniger Durchgaenge pro +Tag, und die Trefferliste der Boersen zeigt nur die neuesten Projekte. + Die **Sperrfrist** ist die Antwort auf ein konkretes Problem: beide Instanzen benutzen dieselben Portal-Cookies. Beim ersten Start ziehen sonst beide gleichzeitig los, was Rate-Limits und CAPTCHAs provoziert - und zwei Chromium diff --git a/docs/09-quellcode.md b/docs/09-quellcode.md index d0a9f5b..a171e3e 100644 --- a/docs/09-quellcode.md +++ b/docs/09-quellcode.md @@ -168,8 +168,10 @@ Zusagen, auf die sich der Rest verlässt: * `runMatchingPipeline()` läuft **nie zweimal gleichzeitig** und gibt `false` zurück, wenn schon einer läuft. `isPipelineRunning()` fragt den Zustand ab. -* Der Zeitpunkt landet im `finally` in `settings.last_scan_at` - auch nach einem - Abbruch, damit sich ein gescheiterter Lauf nicht sofort wiederholt. +* Der **Beginn** des Laufs landet im `finally` in `settings.last_scan_at` - im + `finally`, damit sich ein gescheiterter Lauf nicht sofort wiederholt, aber mit + dem Startzeitpunkt, weil das Intervall ein Takt ist und keine Pause zwischen + zwei Läufen. Mit dem Endzeitpunkt wurden aus 15 Minuten 19. * `cleanup.js` benutzt `sequelize.col('created_at')` statt des Attributnamens: `Project` benennt die Spalte um, und in `where` greift die Zuordnung nicht. diff --git a/server/src/scanner/services/pipeline.js b/server/src/scanner/services/pipeline.js index 949e956..f008f2b 100644 --- a/server/src/scanner/services/pipeline.js +++ b/server/src/scanner/services/pipeline.js @@ -93,6 +93,16 @@ export const runMatchingPipeline = async () => { return false; } pipelineRunning = true; + + /* + * Der BEGINN des Laufs, nicht sein Ende. Das Suchintervall ist ein Takt und + * keine Pause: mit dem Endzeitpunkt kam die Laufzeit von drei bis vier Minuten + * jedes Mal obendrauf, aus 15 Minuten wurden 19 und damit rund ein Fuenftel + * weniger Durchgaenge pro Tag. Bei einer Trefferliste, die nur die neuesten + * Projekte zeigt, kostet jeder ausgelassene Durchgang echte Funde. + */ + const startedAt = new Date().toISOString(); + try { return await executePipeline(); } finally { @@ -103,9 +113,10 @@ export const runMatchingPipeline = async () => { * Scan zaehlt: er hat die Boersen ja gerade abgefragt. * * Im finally, also auch nach einem Abbruch: ein gescheiterter Lauf soll sich - * nicht sofort wiederholen. + * nicht sofort wiederholen. Dauert ein Lauf laenger als das Intervall, ist + * der naechste sofort faellig - die Sperre oben verhindert die Ueberlappung. */ - await Setting.setVal('last_scan_at', new Date().toISOString()).catch(() => {}); + await Setting.setVal('last_scan_at', startedAt).catch(() => {}); } };