Beliebig viele Tenant-Server-Replikas hinter einem Load Balancer betreiben — ohne Einschränkung "muss auf demselben Pod laufen". Jede Dienstkomponente ist zustandslos konzipiert — geteilter Zustand liegt in Postgres und Redis.
TENANT_BASE_URL auf Ihren Load Balancer zeigen lassen. Alle Replikas teilen dasselbe Postgres und Redis. Keine Sticky Sessions für Voice erforderlich. Der Customization-Agent-Worker kann jede Replik erreichen.
Der Tenant-Server hält keinen In-Memory-Zustand, der einen Round-Robin zu einer anderen Replik überstehen muss. JWT-Signierungsschlüssel liegen in Postgres (im livekit_server-Namespace) und in Ihrem Deployment-Zeit-passwords.yaml. Jede Replik liest aus derselben Quelle, sodass jeder Pod für jeden Benutzer einen LiveKit-JWT ausstellen kann.
Der Customization-Agent (Voice/Avatar-Worker) ruft /internal/customization-agent/*-Routen zurück. Diese Routen suchen den Organisations-Benutzer in Postgres, validieren den Tenant und fahren fort — kein Pro-Replik-In-Memory-Zustand wird herangezogen. TENANT_BASE_URL auf den Load Balancer zeigen und jede Replik kann antworten.
Überall dort, wo Replikas unter Load Balancing in Konflikt geraten könnten, koordiniert YOffice über Redis. Jeder der folgenden Bereiche wird einem verteilten Primitiv zugeordnet, das alle Replikas synchron hält:
| Koordinationsbereich | Warum es bei mehreren Replikas relevant ist | Wie YOffice es löst |
|---|---|---|
| Periodische Sweeps in server.dart | Ohne Koordination würde jede Replik jeden Sweep ausführen → N× die Arbeit und doppelte Auslöser | LeaderOnlyTimer.periodic(taskName: …) — ein Redis-gewählter Leader führt den Sweep durch — ein Redis-gewählter Leader führt den Sweep durch |
| Anfrage-Rate-Limiting pro Organisation | Ein In-Memory-Zähler würde jeder Replik erlauben, das Limit auf ihrem eigenen Anteil durchzusetzen, sodass N Replikas das N-fache Limit erlauben | DistributedRateLimiter — atomares Redis INCRBY pro Tenant + Zeitfenster |
| IP-Rate-Limit für öffentliche Formulare | Anfragen könnten das Limit umgehen, indem sie verschiedene Replikas treffen | DistributedRateLimiter mit IP + Bucket als Schlüssel |
| Embedding-Parallelität | Ein Pro-Replik-Semaphor würde sich mit N Replikas multiplizieren und Provider-Kontingente überschreiten | DistributedSemaphore mit clusterweiter Kapazität |
| Direktivencache-Invalidierung | Eine nur lokale Invalidierung würde andere Replikas bis zu 5 Minuten veraltete Direktiven liefern lassen | Redis-Generationszähler — die Invalidierung einer Replik erhöht ihn; alle Replikas sehen die Änderung |
| Echtzeit-Pub/Sub-Fanout | postMessage ohne erreicht nur die Abonnenten der veröffentlichenden Replikglobal: true reaches only the publishing replica's subscribers | Alle 137 Aufrufstellen verwenden ClusterPubsub.broadcast (global fest verdrahtet) |
Alle Primitive befinden sich in command_center_tenant_server/lib/src/scaling/horizontal_scaling.dart. Sie benötigen nur einen gemeinsamen Redis-Cluster — keinen separaten Koordinationsdienst.
Redis SET NX PX Advisory Lock. Dient zur Koordinierung gegenseitiger Ausschlüsse über Replikas hinweg. Läuft nach einem konfigurierbaren TTL automatisch ab, damit ein abgestürzter Eigentümer den Cluster nicht dauerhaft blockiert. Langfristig haltende Sperren müssen renew() innerhalb des TTL-Fensters aufrufen.
Ein Drop-in-Ersatz für Timer.periodic, der ein Pro-Task Redis-Leader-Lease verwendet. Nur die gewählte Leader-Replik führt den Body aus; alle anderen Replikas bleiben inaktiv. Jede Task wählt ihren eigenen Leader unabhängig, was die Last natürlich über Pods verteilt.
Ein zählender Semaphor, der die clusterweite Parallelität mit Redis-Slot-Keys begrenzt. Ersetzt Pro-Isolate-Semaphore, die Limits nur lokal durchsetzen — wichtig für Massen-Knowledge-Folder-Embeddings und ähnliche Provider-kontingentsensitive Operationen.
Atomares INCRBY in Redis mit Tenant + Zeitfenster als Schlüssel. Erzwingt Pro-Org-Anfrageobergrenzen über alle Replikas gleichzeitig. Pro-Org-Überschreibungen werden über den Admin-Rate-Limit-Einstellungsbildschirm geschrieben und innerhalb von ~30 Sekunden an Redis übertragen.
Ein schlanker Wrapper um Serverpod session.messages, der global: true übergibt, damit Pub/Sub-Ereignisse Abonnenten auf jeder Replik erreichen, nicht nur der veröffentlichenden. Alle Echtzeit-Chat-, Präsenz- und Workflow-Stream-Ereignisse verwenden diesen Wrapper.
Ein typisches Produktions-Setup sieht so aus:
Clients (Flutter / browser)
│ HTTPS / WSS
Load balancer (nginx / Cloudflare / ALB)
│ │
tenant-server pod tenant-server pod … × N
│ │
├── Postgres (shared)
├── Redis (shared)
├── LiveKit Cloud
└── MinIO / Supabase Storage
customization-agent worker(s)
└── HTTP callbacks → TENANT_BASE_URL/internal/…
(any pod can answer)TENANT_BASE_URL auf den Hostnamen Ihres Load Balancers setzen (z. B. https://tenant.ihre-domain). Jeder Callback des Customization-Agent-Workers — /internal/customization-agent/* — enthält tenantId, organizationUserId und einen Secret-Header. Jede Replik kann antworten; kein In-Memory-Zustand der JWT-ausstellenden Replik wird benötigt.
Alle Replikas müssen von derselben Postgres-Instanz und demselben Redis-Cluster lesen. Die verteilten Primitive (Sperren, Leader-Wahlen, Semaphore, Rate-Limits, Pub/Sub) funktionieren nur korrekt, wenn alle Pods einen einzigen Redis-Keyspace teilen.
Sticky Sessions sind für Voice nicht erforderlich — LiveKit Cloud verbindet Clients nach der JWT-Ausstellung direkt mit seiner eigenen Media-Plane. Sticky Sessions sind jedoch HILFREICH für den Serverpod-WebSocket, damit Chat-Clients beim Neustart eines Pods nicht dauernd neu verbinden. ip_hash auf nginx oder eine Session-Affinität-Richtlinie auf einem verwalteten Load Balancer verwenden.
Jeder Pod leitet eine stabile Identität aus CC_INSTANCE_ID → HOSTNAME → Plattform-Hostname → Zufallsfallback ab. Das explizite Setzen von CC_INSTANCE_ID im Deployment-Manifest gibt Ihnen konsistente Pod-Namen in Logs, Distributed-Lock-Owner-Tokens und der Debug-Route /internal/instance-info.
LiveKit Cloud verbindet Clients nach der JWT-Ausstellung durch den Tenant-Server direkt mit seiner eigenen WebRTC-Media-Plane. Der Tenant-Server ist nach der Token-Ausstellung nicht mehr im Medienpfad, daher beeinflusst Session-Affinität die Sprachqualität nicht.
Der Customization-Agent-Worker registriert sich bei LiveKit Cloud mit dem Bezeichner agent_name=customization. Er sendet Callbacks an TENANT_BASE_URL — Ihren Load Balancer — und jede Replik kann sie verarbeiten. Der Worker selbst kann ebenfalls als mehrere Instanzen laufen; jede Instanz registriert sich unabhängig bei LiveKit.
Organisations-Admins können das Callback-Rate-Limit pro Organisation unter Einstellungen → Rate-Limits anpassen. Der neue Wert wird in Postgres geschrieben und innerhalb von ~30 Sekunden an Redis übertragen, sodass alle Replikas ihn ohne Neustart übernehmen. Der Standardwert beträgt 240 Anfragen pro Minute.Settings → Rate Limits
Die verteilten Primitive (Sperren, Leader-Wahlen, Rate-Limits, Pub/Sub) setzen Redis voraus. Ist Redis nicht verfügbar, fällt der Server auf prozessinterne Verarbeitung zurück, was für Einzelreplik-Bereitstellungen sicher ist, aber unter Load Balancing die oben beschriebenen Probleme zeigt.