Core-Metriken
Die Metrics-Extension stellt drei klassische Primitive bereit:
| Typ | Richtung | Wann |
|---|---|---|
| Counter | Monoton steigend | Gesamtevents, Gesamtwerte über die Zeit. |
| Gauge | Setzbar / inc / dec | Werte zu einem Zeitpunkt, die rauf und runter gehen. |
| Histogram | Verteilung von Beobachtungen | Latenz, Payload-Größe. |
import { ActorSystem, MetricsExtensionId } from 'actor-ts';
const metrics = system.extension(MetricsExtensionId).enable();
const requests = metrics.counter('http_requests_total', { route: '/orders' });const active = metrics.gauge('sessions_active');const latency = metrics.histogram('http_request_duration_ms', { route: '/orders' });
requests.inc();active.set(123);latency.observe(42);Bis enable() gerufen wird, ist die Registry eine Noop-Implementierung —
die Instrumentierung des Frameworks zeichnet also nichts auf. Genau eine
Sache schaltet sie für dich ein:
DevTools aktiviert die Registry,
solange es attached ist — die Übersicht braucht diese Zähler — und ruft
beim Detach disable(). Eine Registry, die du selbst eingeschaltet
hast, wird nie hinter deinem Rücken abgeschaltet.
Counter
Abschnitt betitelt „Counter“const c = metrics.counter('events_total', { source: 'web' });
c.inc(); // → +1c.inc(3); // → +3c.value; // → 4Monoton — geht nur nach oben. Negative Inkremente werfen einen Fehler. Reset beim Prozess-Neustart.
Für „Dinge, die du zählst”:
- Empfangene Requests insgesamt.
- Ausgelöste Fehler insgesamt.
- Cache-Hits / -Misses insgesamt.
Für Dinge, die runtergehen (aktive Sessions sinken), nutze einen Gauge, keinen Counter.
const g = metrics.gauge('sessions_active');
g.set(100); // → 100g.inc(); // → 101g.dec(5); // → 96g.value; // → 96Setzbar + bidirektional. Repräsentiert einen Wert zu einem Zeitpunkt.
Für „Dinge, die du gerade misst”:
- Aktive Sessions / Verbindungen.
- Mailbox-Tiefe.
- Queue-Größe.
- Verfügbarer Speicher.
Histogramme
Abschnitt betitelt „Histogramme“const h = metrics.histogram('http_request_duration_ms', { route: '/orders' }, { buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000],});
h.observe(42);h.observe(118);h.observe(7);Ein Histogram zählt, wie viele Beobachtungen in jeden Bucket fielen. Zur Export-Zeit siehst du:
http_request_duration_ms_bucket{route="/orders", le="10"} 1http_request_duration_ms_bucket{route="/orders", le="25"} 1http_request_duration_ms_bucket{route="/orders", le="50"} 2http_request_duration_ms_bucket{route="/orders", le="100"} 2http_request_duration_ms_bucket{route="/orders", le="250"} 3http_request_duration_ms_bucket{route="/orders", le="+Inf"} 3http_request_duration_ms_count{route="/orders"} 3http_request_duration_ms_sum{route="/orders"} 167Prometheus berechnet Perzentile (p50, p95, p99) zur
Query-Zeit aus diesen Buckets.
Buckets wählen:
- Wähle Buckets, die dein SLO abbilden. Für ein
HTTP-Latenz-Histogram mit 200 ms p95-Ziel inkludiere
100, 200, 500. - Zweier- oder Zehnerpotenzen sind übliche Defaults — neige zu weniger Buckets im Noise-Floor und mehr rund um dein Ziel.
- Default-Buckets (genutzt, wenn du keine spezifizierst):
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]— Sekundenskala. Überschreibe für ms-Skala.
Operationen timen
Abschnitt betitelt „Operationen timen“Es gibt kein eigenes Timer-Primitiv — miss eine Operation, indem du ihre verstrichene Dauer in ein Histogram beobachtest:
const timer = metrics.histogram('db_query_duration_ms', { table: 'users' });
const start = performance.now();await runQuery();timer.observe(performance.now() - start);performance.now() ist auf Bun, Node und Deno verfügbar. Wähle
Buckets, die zur beobachteten Einheit passen — hier Millisekunden.
metrics.counter('events_total', { source: 'web', env: 'prod' });Labels machen aus einer Metrik viele Zeitreihen. Zur Export-Zeit ist jede eindeutige Label-Kombination eine separate Serie:
events_total{source="web", env="prod"} 1234events_total{source="web", env="staging"} 56events_total{source="batch", env="prod"} 89In Prometheus / Grafana lesbar als Filter oder Group-by-Achsen.
Kardinalitätsdisziplin
Abschnitt betitelt „Kardinalitätsdisziplin“// ✗ HOCH-KARDINAL — TU DAS NICHTmetrics.counter('events_total', { requestId: req.id, // unique pro Request userId: req.user.id, // unique pro User});Jede eindeutige Label-Kombination erzeugt eine Serie. Unbeschränkte Labels (Request-ID, User-ID, Zeitstempel) produzieren unbeschränkte Serien — dein Monitoring-System geht der Speicher aus.
Nur beschränkte Labels:
- Route-Namen (
/orders,/users/:id). - Environment / Region.
- Status-Codes / Arten (ein paar Dutzend Werte).
- Pod-Namen, wenn die Pod-Anzahl beschränkt ist.
Ziel: < 100 Serien pro Metrik. Darüber Alarm schlagen.
bucketize — den Wert an der Quelle beschränken
Abschnitt betitelt „bucketize — den Wert an der Quelle beschränken“Wenn ein Label-Wert von außen kommt — ein URL-Pfad, ein Header, eine ID — bilde ihn auf eine feste Allow-List ab, bevor er die Registry erreicht:
import { bucketize } from 'actor-ts';
const ALLOWED_ROUTES = ['/orders', '/users/:id', '/health'] as const;
metrics.counter('http_requests_total', { route: bucketize(routeTemplateOf(request), ALLOWED_ROUTES),}).inc();bucketize(value, allowed) gibt value zurück, wenn es in
allowed steht, sonst 'other' — die Familie kann also nie mehr
als allowed.length + 1 Serien halten, egal was ein Angreifer
schickt.
Die Kardinalitätsgrenze
Abschnitt betitelt „Die Kardinalitätsgrenze“bucketize ist die eigentliche Lösung; die Grenze in der Registry
ist der Auffangnetz für die Labels, die niemand beschränkt hat.
Eine Familie hört auf, neue Serien anzulegen, sobald sie
maxSeriesPerFamily davon hält (Default 10 000), und faltet
alles darüber in eine einzige Overflow-Serie:
http_requests_total{route="/orders"} 1234http_requests_total{route="/health"} 56http_requests_total{route="__overflow__"} 8_912_004Die erste Familie, die die Grenze erreicht, loggt eine Warnung mit
Familienname und dem übergelaufenen Tupel — eine Warnung pro
Familie, nicht pro Aufruf. Der Marker steckt im Label-Wert,
nicht in einem Label-Namen: Prometheus reserviert Label-Namen mit
__-Präfix und entfernt sie beim Ingest, was die Overflow-Serie
still in eine echte Serie mischen würde.
Grenze beim Installieren der Registry anheben oder abschalten:
import { MetricsExtensionId, MetricsRegistryOptions } from 'actor-ts';
const metricsOptions = MetricsRegistryOptions.create().withMaxSeriesPerFamily(50_000);const metrics = system.extension(MetricsExtensionId).enable(metricsOptions);
// oder als einfaches Objekt: .enable({ maxSeriesPerFamily: 50_000 })0 schaltet die Grenze komplett ab — tu das nur, wenn jeder
Label-Wert nachweislich aus einer beschränkten Menge stammt.
(0, nicht Infinity: die Grenze ist eine Ganzzahl-Anzahl, und
Infinity ist keine Ganzzahl.)
Werte im Prozess auslesen
Abschnitt betitelt „Werte im Prozess auslesen“const counter = metrics.counter('events_total');counter.inc();counter.value; // → 1value ist der aktuelle Counter-/Gauge-Wert — eine
readonly-Property, keine Methode. Für Histogramme lies count,
sum, buckets und counts direkt:
const h = metrics.histogram('latency');h.observe(10);h.observe(20);h.count; // → 2h.sum; // → 30h.buckets; // → readonly Bucket-Grenzen, endend mit Infinityh.counts; // → kumulative Beobachtungsanzahl pro BucketNützlich für Tests und eigene Exporter.
Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Observability — Überblick — das größere Bild.
- Prometheus-Exporter —
/metricsfür Prometheus zum Scrapen freilegen. - Stock-Metriken — die automatisch aufgezeichneten Actor/Mailbox/Cluster-Metriken des Frameworks.
- prom-client-Adapter —
für Projekte, die schon
prom-clientnutzen.
Die MetricsExtension
API-Referenz deckt die volle Oberfläche ab.
