Zum Inhalt springen
Deutsch

Observability — Überblick

Ein produktives Actor-System braucht drei Dinge, damit es von außen beobachtbar ist:

SäuleWas sie beantwortetModul
Metriken„Wie hoch ist die Rate / Anzahl / Latenz gerade?”MetricsExtension
Tracing„Was hat dieser einzelne Request gemacht?”TracingExtension
Management„Läuft das System und ist es gesund?”managementRoutes

Alle drei sind Extensions — sie laufen nicht, bis du danach greifst. Eine App, die Observability ignoriert, hat keinen Overhead durch ungenutzte Metrik-Puffer oder nicht gestartete Trace-Exporter.

import { ActorSystem, MetricsExtensionId } from 'actor-ts';
const system = ActorSystem.create('my-app');
const metrics = system.extension(MetricsExtensionId);
const requests = metrics.counter('http.requests.total', { route: '/orders' });
requests.inc();
const latency = metrics.histogram('http.requests.duration_ms', { route: '/orders' });
latency.observe(42);
const active = metrics.gauge('sessions.active');
active.set(123);

Vier Metrik-Typen:

  • Counter — monoton steigend. Gesamtanzahl der Requests, Gesamtfehler.
  • Gauge — Wert zu einem Zeitpunkt. Aktive Sessions, aktuelle Speichernutzung.
  • Histogram — gesampelte Verteilung. Request-Latenz, Payload- Größe. Erlaubt es, p50/p95/p99 zur Scrape-Zeit zu berechnen.
  • Timertimer.start() gibt eine Stop-Funktion zurück; baut auf Histogram auf für timing-spezifische Ergonomie.

Jede Metrik hat einen Namen + Labels (Key-Value-Paare). Labels erlauben es dir, dieselbe Metrik nach Dimensionen zu zerlegen — http.requests.total nach route oder status.

Die Metriken selbst sind framework-intern; sie zu einem Metrik-Backend zu bringen, geschieht über einen Exporter:

ExporterBackend
PrometheusExporterStellt einen /metrics-Endpoint bereit, den Prometheus scrapt.
PromClientAdapterSchiebt in die prom-client-Bibliothek, falls du sie schon nutzt.

Siehe Prometheus-Exporter für den Deep-Dive zu jedem.

Das Framework zeichnet automatisch eine Basislinie an Metriken auf, sobald die Extension gestartet wird:

  • Actor-Metriken — ein Counter der an onReceive zugestellten Nachrichten (actor_messages_delivered_total) und ein Handler-Dauer-Histogramm (actor_message_handler_seconds).
  • Mailbox-Metriken — ein Counter der Nachrichten, die von der Overflow-Policy einer Bounded Mailbox verworfen werden (actor_mailbox_dropped_total).
  • Cluster-Metriken — ein Gauge der aktuell im Up-Zustand befindlichen Mitglieder (cluster_members_up) und ein Counter der Gossip-Runden (cluster_gossip_rounds_total).

Siehe Stock-Metriken für die vollständige Liste. Damit bekommst du „Verarbeiten meine Actor Nachrichten?” Out-of-the-Box, ohne einen einzigen Metrik-Code zu schreiben.

import * as otel from '@opentelemetry/api';
import { ActorSystem, TracingExtensionId, otelTracer, OtelAdapterOptions } from 'actor-ts';
const system = ActorSystem.create('my-app');
const otelAdapterOptions = OtelAdapterOptions.create().withApi(otel);
system.extension(TracingExtensionId).enable(otelTracer(otelAdapterOptions));

Mit aktiviertem Tracing bekommt jede Actor-Nachricht ihren eigenen Span. Der Span trägt:

  • Den Pfad des Actors.
  • Die Klasse / Art der Nachricht.
  • Den Parent-Span-Kontext (aus dem aktiven Span des Senders).
  • Die Dauer von onReceive.

Spans verketten sich über Tells hinweg — ein Actor, der einen Request verarbeitet und einem anderen Actor etwas tellt, gibt den aktuellen Span-Kontext via Envelope weiter; der Span des zweiten Actors verlinkt zurück zum ersten.

HTTP-Request

actor /user/api

empfängt Request

actor /user/db

verarbeitet Query — verlinkt zurück

Postgres-Span

OTel-Auto-Instrumentation

Das Endergebnis: ein Trace pro logischem Request, selbst wenn er durch 4-5 Actor hüpft.

Der Tracer ist eine Brücke zu OpenTelemetry. Nutze otelTracer in Produktion; ein RecordingTracer existiert für Tests.

import { managementRoutes, ActorSystem } from 'actor-ts';
const system = ActorSystem.create('my-app');
// cluster is optional — pass null to skip the /cluster/* endpoints
const { routes, health } = managementRoutes(system, cluster);
await system.http(8558).bind(routes);

Das startet einen kleinen HTTP-Server (getrennt vom HTTP-Server deiner App), der Endpunkte für den Betrieb bereitstellt:

EndpunktWas
GET /healthLiveness — läuft der Prozess?
GET /readyReadiness — bereit für Traffic (Cluster up + Checks)?
GET /cluster/membersListe der Cluster-Mitglieder (wenn ein Cluster übergeben wird).
GET /cluster/shards?type=<name>Shard-Verteilung für einen sharded Typ.
GET /metricsPrometheus-Exposition (Opt-in via enableMetricsEndpoint).

Nützlich für K8s-Probes (Liveness + Readiness) und Ad-hoc- Operations-Debugging. Siehe HTTP-Endpunkte für die volle Oberfläche.

health.addReadiness(async () => {
const ok = await db.ping();
return { name: 'db', status: ok, detail: ok ? undefined : 'db unreachable' };
});

Eigene Checks hängen sich in /ready ein — ein fehlschlagender Check lässt den Endpunkt 503 zurückgeben, was K8s als „nicht zu diesem Pod routen” liest.

Siehe Health Checks für die Konfiguration.

Für ein neues Produktivsystem:

  1. Metriken — zumindest die Stock-Metriken, mit einem Prometheus-Exporter. Counter- und Gauge-Dashboards geben dir „was macht das System gerade?”.
  2. Health Checks — Liveness + Readiness für K8s. Selbst wenn dein Workload keine fancy Probes braucht, will K8s diese Endpunkte.
  3. Tracing — zuletzt. Tracing ist aufwendiger (Exporter-Konfiguration, Sampling, Kosten) und bringt abnehmenden Grenznutzen für einfache Apps. Füge es hinzu, wenn du Multi-Actor-Requests hast und Ende-zu-Ende-Latenz sehen musst.

Für ein Dev-/Staging-Umfeld ist nichts davon Pflicht — Console-Logs decken die Basics ab.

Die drei Säulen oben dienen dazu, ein System von außen und über die Zeit zu beobachten. Willst du stattdessen gerade jetzt hineinschauen — in den Actor-Baum, die Mailboxes, das, was ein einzelner Actor tut —, hänge die DevTools-UI an. Sie ist ein Debugger, kein Monitoring-Stack: standardmäßig Loopback und ohne Auth, und aus, bis du danach greifst.