Shard-Introspektion
Eine Region zu starten gibt dir genau eine Ref — die der Region — und
das war bisher die ganze Geschichte. Du konntest Nachrichten durch
das Sharding schicken, aber es nichts fragen: nicht “welche Shards gibt
es”, nicht “wo liegt Shard 7”, nicht “gib mir einen Handle auf Entity
user-42”.
Drei APIs auf ClusterSharding schließen diese Lücke, dazu ein
Cluster-Event für die Push-Seite.
Die Shards auflisten
Abschnitt betitelt „Die Shards auflisten“shards(typeName) antwortet clusterweit, von jedem Node aus:
const shards = await cluster.sharding.shards<CounterCommand>('counter');
for (const shard of shards) { console.log( `shard ${shard.shardId} on ${shard.node}`, `— ${shard.entityCount} entities`, shard.local ? '(here)' : '', );}Jeder Eintrag ist ein ShardInfo:
type ShardInfo<TMessage = unknown> = { readonly shardId: number; readonly node: NodeAddress; // node currently hosting the shard readonly regionPath: string; // region on that node readonly entityCount: number; // live entities when its region answered readonly resident: boolean; // shard actor materialised at that moment readonly local: boolean; // hosted by the node that asked readonly ref: ActorRef<ShardMessage<TMessage>>;};resident unterscheidet einen Shard, der läuft und gerade leer ist, von
einem, der
leer passiviert wurde
— entityCount: 0 meldet beides. Auf die Erreichbarkeit hat es keinen
Einfluss: ref funktioniert so oder so. Lies es, wenn du
shardPassivationIdleMs einstellst oder zählen willst, wie viele Actors
ein Node tatsächlich hält.
Der Eintrag trägt eine lebende Ref, nicht nur Platzierungsdaten —
eine Abfrage beantwortet also sowohl “was gibt es da draußen” als auch
“wie rede ich damit”. Der Preis: ShardInfo ist nicht
JSON-serialisierbar. Wenn du eine serialisierbare Sicht brauchst,
liefert der Management-Endpunkt
GET /cluster/shards
stattdessen eine reine Datenform.
Shards ohne Zuhause fehlen in der Liste. Niemand hat nach ihnen gefragt, also hatte der Koordinator keinen Anlass, sie zu platzieren: ein Typ mit 64 Shards, der drei Entity-IDs gesehen hat, meldet ein bis drei Shards, nicht 64.
Was es kostet
Abschnitt betitelt „Was es kostet“Der Koordinator besitzt die Shard-Karte, aber nicht die Entity-Zahlen — die kennt nur die Region, die den Shard hostet. Der Aufruf fächert deshalb an alle registrierten Regions aus und joint die Antworten gegen die Allokationskarte. Also ein zusätzlicher Round-Trip.
Die Fan-out-Frist ist eine Teilantwort-Frist: eine Region, die zu
langsam ist oder zwischen Karten-Schnappschuss und Fan-out gegangen ist,
steuert entityCount: 0 bei, statt den ganzen Aufruf scheitern zu
lassen. Du bekommst eine vollständige Shard-Liste mit einer
konservativen Zahl, keine Exception.
Eine Ref auf einen Shard
Abschnitt betitelt „Eine Ref auf einen Shard“const shard = await cluster.sharding.shardRefFor<CounterCommand>('counter', 7);Hat Shard 7 noch kein Zuhause, platziert dieser Aufruf ihn — genau das, was eine erste Nachricht an ihn auch getan hätte — und antwortet, sobald der Koordinator entschieden hat.
Ein Shard, der
leer passiviert wurde,
braucht keine Sonderbehandlung: die Ref bleibt über die Lücke hinweg
gültig, und der Shard wird neu erstellt, sobald etwas für ihn eintrifft.
Lokal passiert das schon beim Fragen, denn ein Handle auf einen nicht
laufenden Actor wäre ein Handle auf nichts; bei einem Shard auf einem
anderen Node stellt die Ref über dessen Region zu, die den Shard vor dem
Weiterleiten materialisiert. shardRefFor() weckt einen lokalen Shard
also sofort, einen entfernten das erste tell.
Die Ref ist das echte Ding. Ein Shard ist ein Actor
(/system/cluster/sharding/region-counter/shard-7), und die Ref trägt diesen Pfad als
Identität, egal von welchem Node du gefragt hast — was sich unterscheidet,
ist der Weg: direkt zum Actor, wenn dieser Node ihn hostet, über die
besitzende Region, wenn nicht. tell funktioniert damit von überall:
// Bring an entity up without sending it a message.shard.tell({ kind: 'sharding.StartEntity', entityId: 'user-42' });
// Address one entity through the shard.shard.tell({ kind: 'sharding.EntityEnvelope', entityId: 'user-42', message: { id: 'user-42', kind: 'increment' },});Und um zu fragen, was ein Shard hält:
const stats = await shard.ask<ShardStats>({ kind: 'sharding.GetShardStats' });// { shardId: 7, entityCount: 2, entityIds: ['user-42', 'user-99'] }Eine Ref auf eine Entity
Abschnitt betitelt „Eine Ref auf eine Entity“entityRefFor ist das Gegenstück zur Region-Ref — ein Handle auf eine
einzelne Entity, wo immer sie gerade liegt:
const entity = cluster.sharding.entityRefFor<CounterCommand>('counter', 'user-42');
entity.tell({ kind: 'increment', by: 1 });const value = await entity.ask<number>({ kind: 'get' });Zwei Dinge sind anders als beim Reden mit der Region:
- Die Nachricht trägt ihren Routing-Key nicht mehr selbst. An eine
Region zu senden heißt, dass
extractEntityIddie ID wieder aus deiner Nachricht herausgraben muss — jeder Nachrichtentyp braucht also eine. Der Handle benennt seine Entity direkt,extractEntityIdwird nie befragt. - Er ist synchron zu bekommen. Der Shard ist
hash(entityId) % numShards, es muss also nichts nachgeschlagen werden. Eine Nachricht für einen Shard, dessen Zuhause noch unbekannt ist, puffert die Region — genau wie bisher.
Location-transparent ist er, weil er über die lokale Region routet, und
die weiß bereits, wie sie jeden Node erreicht. Eine Proxy-Region
(startProxy) reicht aus, um einen auszugeben — der Node muss selbst
nichts hosten.
ask funktioniert auf einer Entity-Ref unabhängig davon, wo die Entity
liegt: die Region reicht deinen Sender an die Entity durch, die Antwort
kommt also direkt zu dir zurück.
Der Karte folgen
Abschnitt betitelt „Der Karte folgen“Für die Push-Seite abonnierst du ShardMapChanged:
import { match, P } from 'ts-pattern';import { ShardMapChanged } from 'actor-ts';
cluster.subscribe((event) => match(event) .with(P.instanceOf(ShardMapChanged), (e) => onShardMapChanged(e)) .otherwise(() => onOtherEvent()));
function onShardMapChanged(event: ShardMapChanged): void { // event.type — the sharded type name // event.shards — ReadonlyMap<shardId, regionKey> // event.regions — the region table, with per-region shard counts // event.version — increments once per broadcast}Das Event feuert auf jedem Node, nicht nur auf dem Leader: der Koordinator broadcastet an jede Region, und die Region publiziert lokal. Genau das macht es für einen Anwendungs-Listener oder ein Per-Node-Dashboard überhaupt brauchbar.
Broadcasts werden zusammengefasst. Allokationsänderungen kommen
einzeln pro Shard, und ein frischer Cluster platziert alle Shards auf
einmal — version zählt deshalb Broadcasts, nicht einzelne Zuweisungen.
Lies sie nicht als “wie viele Shards sind umgezogen”.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Sharding — Überblick — die Region-, Shard- und Entity-Actors, auf die diese APIs dir Refs geben.
- Rebalance — warum Platzierung sich bewegt und was dabei mit einer Shard-Ref passiert.
- HTTP-Management-Endpunkte —
GET /cluster/shards, die serialisierbare Sicht auf dieselbe Karte.
