Dispatcher-Tuning
Der Dispatcher entscheidet, wann Actor-Nachrichten auf der JavaScript-Event-Loop laufen. Drei Formen werden ausgeliefert:
| Dispatcher | Schedult via | Passt zu |
|---|---|---|
MicrotaskDispatcher | queueMicrotask | CPU-tight; kein I/O. |
ImmediateDispatcher (Default) | setImmediate / setTimeout(0) | HTTP-Server + gemischtes I/O. |
ThroughputDispatcher | setImmediate mit N-dann-yield | Batch-Verarbeitung. |
Der Default — ImmediateDispatcher — passt für die meisten Apps.
Diese Seite behandelt, wann er nicht passt und wie du eine bessere
Wahl triffst.
Default-Verhalten
Abschnitt betitelt „Default-Verhalten“const system = ActorSystem.create('my-app');// ↑ nutzt per Default ImmediateDispatcherActor-Nachrichten laufen via setImmediate, das zwischen jeder
Nachricht yieldet — damit I/O-Callbacks (HTTP-Handler,
Broker-Nachrichten, Timer-Feuer) natürlich verschränken.
Für HTTP-Server + Broker-Actor-Cluster (der häufige Fall) ergibt das gute HTTP-Latenz auf Kosten von etwas höherem Per-Message-Overhead.
Symptom: hohe HTTP-Latenz unter Actor-Last
Abschnitt betitelt „Symptom: hohe HTTP-Latenz unter Actor-Last“P99-HTTP-Response-Time ist 200ms; Actors verarbeiten ZehntausendeNachrichten/sec. Die Actor-Arbeit ist nicht das Problem — sonderndass HTTP-Requests nicht drankommen.Ursache: Ein Actor (oder eine Gruppe Actors) verarbeitet Nachrichten so schnell, dass HTTP-Handler auf ihren Turn warten.
Fix: Beim ImmediateDispatcher (Default) bleiben und die
beschäftigten Actors mit einem Throughput-Dispatcher pro Actor
bounden:
import { ActorOptions, ThroughputDispatcher } from 'actor-ts';
const heavyOptions = ActorOptions.create().withDispatcher(new ThroughputDispatcher(100));
const heavyActor = system.spawnAnonymous(HeavyWorker, heavyOptions);Der schwere Actor verarbeitet 100 Nachrichten, yieldet, lässt HTTP aufholen, verarbeitet 100 weitere. HTTP-Latenz sinkt; Durchsatz beim schweren Actor wird kaum beeinträchtigt.
Symptom: niedriger Actor-Durchsatz, niedrige CPU-Nutzung
Abschnitt betitelt „Symptom: niedriger Actor-Durchsatz, niedrige CPU-Nutzung“Das Actor-System macht 1000 msg/sec auf einer idle CPU. Profilzeigt Zeit in setImmediate verbracht.Ursache: ImmediateDispatcher hat Per-Message-Overhead durch
das Yield zur Event Loop bei jeder Nachricht. Für Tight-Loop-CPU-Arbeit
ohne I/O ist das Verschwendung.
Fix: MicrotaskDispatcher nutzen:
import { ActorOptions, MicrotaskDispatcher } from 'actor-ts';
const cpuOptions = ActorOptions.create().withDispatcher(new MicrotaskDispatcher());
const cpuActor = system.spawnAnonymous(CpuIntensive, cpuOptions);Microtasks umgehen die Event Loop, ~50× schnelleres Scheduling. Caveat: Ein CPU-tighter Actor auf Microtask kann I/O aushungern (Netzwerk-Reads, Timer). Nur nutzen, wenn:
- Der Actor das System nicht mit HTTP-Traffic teilt (Compute-Only-Worker).
- Der Actor selbst nicht
awaitauf I/O macht (rein CPU).
ThroughputDispatcher-Optionen
Abschnitt betitelt „ThroughputDispatcher-Optionen“new ThroughputDispatcher(100); // Nachrichten pro BatchDer Konstruktor ist positional — throughput zuerst, eine
optionale Dispatcher-id als zweites:
throughput— Nachrichten, die pro Actor vor dem Yield verarbeitet werden. Höher = mehr Durchsatz, schlechtere I/O-Verschränkung. Übliche Werte: 10-1000. Default ist 16.- Zwischen Batches yieldet er immer via
setImmediate(mit Fallback aufsetTimeout(0), wosetImmediatenicht verfügbar ist), damit I/O und Timer verschränken. Das ist nicht konfigurierbar.
Für einen Batch-Prozessor, der Broker-Nachrichten verarbeitet:
throughput: 200 ist ein vernünftiger Startpunkt.
Per-Actor-Dispatcher
Abschnitt betitelt „Per-Actor-Dispatcher“import { ActorOptions } from 'actor-ts';
const bulkOptions = ActorOptions.create().withDispatcher(new ThroughputDispatcher(500));
const heavy = system.spawnAnonymous(BulkProcessor, bulkOptions);
const httpHandler = system.spawn( HttpHandler, // → nutzt den ImmediateDispatcher-Default des Systems);Frei mischen. Schwere Actors bekommen ihren eigenen throughput-getuneten Dispatcher; HTTP-Handler bleiben auf dem Default. Das ist das empfohlene Produktionsmuster.
Systemweiter Dispatcher
Abschnitt betitelt „Systemweiter Dispatcher“const actorSystemOptions = ActorSystemOptions.create().withDispatcher(new ThroughputDispatcher(100));const system = ActorSystem.create('my-app', actorSystemOptions);Override den Default für jeden Actor, der nichts anderes spezifiziert. Nützlich für Batch-only-Systeme ohne HTTP-Traffic.
Nutze die
Stock-Metrik
actor_message_duration_ms:
P50 ≈ ArbeitszeitP99 - P50 ≈ Dispatcher-Latenz (Queueing)Wenn P99 deutlich höher als P50 mit kleiner Varianz der Arbeitszeit ist, hilft Dispatcher-Tuning.
Das Gauge
actor_mailbox_size unter Last zeigt, ob Actors mitkommen.
Dauerhaft wachsende Tiefe = entweder ein langsamer Handler oder
ein fehlkonfigurierter Dispatcher.
Heuristiken
Abschnitt betitelt „Heuristiken“HTTP-Server + Actors → ImmediateDispatcher (Default)Compute-heavy + kein HTTP → MicrotaskDispatcherBatch-Verarbeitung → ThroughputDispatcher (throughput 100-500)Gemischt: schwerer Actor + HTTP → ImmediateDispatcher-Default + ThroughputDispatcher pro schwerem ActorWohin als nächstes
Abschnitt betitelt „Wohin als nächstes“- Dispatcher — die konzeptuelle Referenz.
- Mailbox-Sizing — der komplementäre Knopf.
- Stock-Metriken — die Per-Actor-Performance-Metriken.
