B2B Engineering Insights & Architectural Teardowns

Tracing im Actor-Modell ohne Degradation durch Envelope

In Actor-Systemen gibt es keinen eingebauten Kanal für den Trace-Kontext. Discord hat dies ohne Architekturwechsel und ohne Produktionsunterbrechung gelöst.

Das Problem tritt an der Grenze des Modells auf. Im HTTP wird der Trace-Kontext in den Headern übergeben. In der Elixir Actor-System sind Nachrichten beliebige Strukturen ohne Metadaten. Der Standard OpenTelemetry funktioniert innerhalb des Dienstes, verliert jedoch die Kohärenz zwischen Prozessen. Im Maßstab von Discord bedeutet dies blinde Zonen: Millionen konkurrierender Benutzer, Fanout auf Tausende von Empfängern und das Fehlen einer End-to-End-Trace. Bei dem Versuch, Tracing naiv hinzuzufügen, stößt das System auf CPU- und Datenvolumenprobleme.

Die Lösung besteht darin, das Modell nicht zu brechen, sondern es zu umhüllen. Das Team führte das Primitive Envelope ein: Nachricht + serialisierter Trace-Kontext. Die Transportbibliothek ersetzt die Aufrufe von GenServer (call/cast) und fügt automatisch den Kontext hinzu. Am Empfänger verarbeitet ein einheitlicher Normalisierungspunkt sowohl „alte“ Nachrichten als auch neue mit Envelope. Dies ist ein entscheidender Kompromiss: minimales Eindringen in den Code gegen die Notwendigkeit, während der Migration ein doppeltes Format zu unterstützen. Im Gegenzug – die Möglichkeit, Änderungen ohne Neustarts und synchrone Cluster-Updates durchzuführen.

Die Implementierung stieß auf zwei Engpässe: Fanout und Kosten (De)Serialisierung. Bei der Verteilung in Gilden mit Tausenden von Empfängern erzeugt eine Anfrage eine Lawine von Spans. Das Team führte dynamisches Sampling basierend auf der Größe des Fanouts ein: 100 % für Einzelnachrichten, 10 % bei ~100 Empfängern, 0,1 % bei 10k+. Darüber hinaus – aggressive CPU-Einsparungen:
– Der Kontext wird nur für gesampelte Operationen übergeben. Nicht gesampelte Nachrichten gehen ohne Trace-Kontext.
– Der Sitzungsdienst initiiert keine neuen Traces beim Fanout, sondern setzt nur bestehende fort. Dies reduzierte die CPU-Auslastung von etwa 55 % auf 45 %.
– In der gRPC-Verbindung mit Python gingen 75 % der Zeit für das Entpacken des Kontexts drauf. Ein schneller Filter wurde hinzugefügt: Lesen des Sampling-Flags ohne vollständige Deserialisierung und Verzicht auf die Übertragung des Kontexts, wenn dieser nicht benötigt wird.

Das Ergebnis – Beobachtbarkeit (Observability), die mit der Last skaliert. Traces sind nun für reale Vorfälle geeignet: Beispielsweise wurde eine Verzögerung bei der Verbindung von Benutzern von bis zu 16 Minuten und ein Kaskadeneffekt auf die Verfügbarkeit der Gilde festgestellt – etwas, das in Metriken und Logs nicht sichtbar war. Konkrete Verbesserungen der SLA werden nicht angegeben, aber qualitativ hat das System ein diagnostisches Signal erhalten, wo zuvor Stille herrschte. Der Ansatz erscheint pragmatisch: die Vorteile des Actor-Modells zu bewahren und das Tracing durch eine Hülle und strenge Kostenkontrolle hinzuzufügen.

Mehr lesen – InfoQ

×

🚀 Deploy the Blocks

Controls: ← → to move, ↑ to rotate, ↓ to drop.
Mobile: use buttons below.