Zum Hauptinhalt springen

Zwei Bedeutungen von „Worker“

„Worker“ bezeichnet drei Dinge, die nichts miteinander zu tun haben. Sie teilen sich ein Wort und sonst nichts — deshalb lohnt es sich, sie zuerst auseinanderzuhalten.

Web Worker Ein zweiter JavaScript-Thread im Browser. Rechnet, damit die Oberfläche reaktionsfähig bleibt.
Queue-Worker Ein Konsument, der Arbeitseinheiten aus einer Queue zieht. Läuft auf dem Server.
Service Worker Ein Netzwerk-Proxy für eine PWA. Ohne Bezug zu den beiden oben — siehe das PWA-Showcase.

Die Probe: Frag, was es konsumiert. Ein Queue-Worker konsumiert aus einer Queue, ein Web Worker konsumiert Nachrichten, die du ihm schickst, ein Service Worker konsumiert Fetch-Events. Ein Job konsumiert nichts — er wacht per Timer auf und läuft.

Worker A — Browser-Thread

Beide Schaltflächen führen exakt dieselbe Funktion aus: Bild dekodieren, verkleinern und dann jedes Pixel durchlaufen, um ein Farbhistogramm zu bauen. Einzige Variable ist der Thread. Achte auf den Indikator, nicht auf die Palette.

Diesem Browser fehlen OffscreenCanvas oder createImageBitmap, deshalb läuft die Demo hier nicht.

Der Worker-Pfad überträgt den ArrayBuffer, statt ihn zu kopieren: postMessage(data, [buffer]) übergibt den Besitz und entwertet die Kopie des Senders. Bei einem großen Foto ist das der Unterschied zwischen einer Kopie und einer Zeigerübergabe — und der am häufigsten übersehene Teil der API.

Worker B — Queue-Worker

Ruhend

Dieses Deployment ist serverless (vercel): Kein Prozess überlebt die Antwort, der Worker kann also nicht dauerhaft laufen. Stattdessen leert ein täglicher Cron die Queue. Das Ruhen ist die Lektion, kein Defekt — genau deshalb ist das gesamte Job-System push-getrieben.

Der Benachrichtigungs-Outbox ist eine echte Queue-Tabelle, und dies ist ihr Konsument. Arbeit wird mit einer einzigen atomaren Anweisung übernommen: Zeilen werden gesperrt, markiert und gemeinsam zurückgegeben, sodass zwei Worker nie dieselbe Zeile nehmen können.

outbox.ts — claimDeliveries()
UPDATE notifications.notification_deliveries AS d
SET status = 'processing',
    attempts = d.attempts + 1,
    attempted_at = now()
FROM (
    SELECT id
    FROM notifications.notification_deliveries
    WHERE status = 'pending'
      AND next_attempt_at <= now()
    ORDER BY next_attempt_at, created_at
    LIMIT $1
    FOR UPDATE SKIP LOCKED
) AS c
WHERE d.id = c.id
RETURNING d.id, d.channel, d.attempts;
Stapelgröße 25
Max. Versuche 5
Wiederholungs-Backoff 30s → 2m → 8m → 32m
Claim-Laufzeit 5m

Aktuelle Queue-Tiefe

0 Ausstehend
0 In Bearbeitung
1 Gesendet
0 Fehlgeschlagen
0 Endgültig gescheitert

Der Versuchszähler dient zugleich als Fence-Token. Stirbt ein Worker mitten im Versand, stellt der Reaper die Zeile nach Ablauf der Claim-Laufzeit zurück in die Queue — und weil ein erneuter Claim den Zähler erhöht, passt der verspätete Schreibvorgang des toten Workers nicht mehr und wird verworfen, statt die Zeile zu beschädigen.

Keine Workers: Jobs

Die Hintergrundarbeit dieses Projekts besteht aus Jobs, nicht aus Workers. Der Unterschied ist keine Wortklauberei — er entscheidet, ob du überhaupt eine Queue brauchst.

Job Eine Arbeitseinheit. Etwas weckt sie und sagt ihr, was zu tun ist. Kostet im Leerlauf nichts, weil sie im Leerlauf nicht existiert.
Worker Ein Prozess, der bereits läuft und die Queue fragt, was ansteht. Kostet auch im Leerlauf Geld und liefert dafür Wiederholungen und schnelle Übernahme.

Alles Geplante hier wird gepusht statt gezogen: Ein Cron, ein Intervall oder ein Admin-Klick benennt die Arbeit direkt. Zum Jobs-Showcase.

Geht dieses Pattern noch besser? Sag uns, wie.

Feedback geben