Zurück zur Startseite

Titanium: Messenger-Backend auf Erlang

Der Artikel zerlegt die Evolution des Titanium-Backends für einen eigenen Messenger: Verzicht auf XMPP/FastAPI zugunsten von Erlang/OTP. Beschreibt pts-Synchronisation, Sharding, Benchmarks bis 5k RPS. Für Senior-Entwickler.

Wie baut man einen skalierbaren Messenger-Backend auf Erlang auf
Advertisement 728x90

Titanium: Skalierbarer Backend-Kern für Messenger – Vom Prototyp zur Produktion

Entwickler, die eigene Messenger bauen, greifen oft auf XMPP/ejabberd oder Matrix/Synapse zurück. Diese Plattformen bieten fertige Transportmechanismen, Föderation und E2E-Verschlüsselung. Doch bei der Anpassung an moderne UX stoßen sie schnell an Grenzen.

XMPP mit ejabberd auf Erlang ist kompakt und belastbar, aber:

  • XML-Stanzas verursachen Parsing-Overhead.
  • MUC-Räume skalieren nicht linear: CPU-Spitzen bei wachsender Teilnehmerzahl.
  • Partiell implementierte XEP-Erweiterungen führen zu Race Conditions in der Geschäftslogik.

Matrix ist mächtig, doch die geschichtete Architektur erschwert Änderungen: Synapse auf Python kämpft mit dem GIL bei Echtzeit-Aufgaben.

Google AdInline article slot

Hybride Setups – wie ejabberd für Transport + FastAPI für API/OTP/Reaktionen – verursachen Split-Brain-Zustände, XML<->JSON-Konvertierungen und zusätzliche Queues. Die einzige Wahrheit verschwimmt über Komponenten hinweg.

Fazit: Es ist einfacher, ein Backend von Grund auf zu bauen, um volle Kontrolle über Zustand und Performance zu haben.

Titanium v1: FastAPI unter Last

Die erste Titanium-Version auf FastAPI mit WebSocket beherrschte Kernfunktionen: Chats, UINs, Medien, pts-Sync. Der Prototyp hielt 500 RPS in einem Szenario mit 1000 Nutzern, 120 Nachr/s, 40 Medien/min und 7 % Abbrüchen stand.

Google AdInline article slot

RPS wurde über vollständige Pfade gemessen: Senden -> Zustellung -> Bestätigung -> Sync. Das umfasste:

  • Validierung von Zugriffsrechten.
  • pts-Updates (Nachrichtspunkte für Delta-Sync).
  • Echtzeit-WebSocket-Pushes.
  • Wiederholungen bei instabilen Netzen.
  • Update-Logs für Offline-Clients.

Probleme traten bei Skalierung auf:

  • GIL bremste WebSocket-Konkurrenz.
  • PostgreSQL blockierte bei hoher Konkurrenz in pts-Tabellen.
  • Medienspeicher brauchte temporäre Links, keine permanenten.

Titanium-Architektur: Wo die Wahrheit wohnt

Titanium definiert strenge Zustandsverwaltungsregeln:

Google AdInline article slot
  • Server als Quelle der Wahrheit: Clients nie vertrauen; alle Änderungen via API.
  • Pts-Mechanismus: Delta-Sync via Nachrichtspunkte, Telegram-Style.
  • Update-Log: Unveränderliches Log für Zustandsreplay.
  • Verteilter Zustand: Sharding nach UIN, keine sticky Sessions.
  • Medien: Temporäre S3-ähnliche Signaturen, 24h TTL.

Schlüsselkomponenten:

  • Gateway: WebSocket + HTTP/2, Rate Limiting, JWT.
  • Core: Geschäftslogik, pts, ACLs (nach UIN/Gruppen).
  • Storage: PostgreSQL (Zustände) + Redis (Sessions/Queues) + S3 (Medien).
  • Workers: Celery/RQ für asynchrone Tasks (Medienverarbeitung, Bots).

Pts-Sync-Beispiel (Pseudocode):

% Client fordert Delta an
handle_get_updates(ReqPts) ->
  Updates = db:fetch_updates_after(ReqPts, UserId),
  NewPts = lists:max([U#update.pts || U <- Updates]),
  {reply, #{updates => Updates, pts => NewPts}}.

Wechsel zu Erlang/OTP: Skalierbarkeit und Robustheit

Python schaffte keine 10k parallele WebSockets. Erlang/OTP löste das:

  • Actor-Modell: Jeder UIN bekommt einen Supervisor-Baum mit gen_server.
  • VM-Hot-Reload ohne Downtime.
  • Eingebaute Mnesia/ETS für latenzarme Speicherung.
  • Cowboy für WebSocket-Skalierung auf 1 Mio.+ Verbindungen pro Node.

Benchmarks:

| Szenario | FastAPI (Python) | Titanium (Erlang) |

|----------|-------------------|--------------------|

| 1k Nutzer, 120 Nachr/s | 500 RPS | 5k RPS |

| 10k WS | 20 % CPU | 5 % CPU |

| Medien 100/min | 2s Latenz | 200ms |

Gen_server-Session-Beispiel:

-module(session).
-export([start_link/1, handle_message/2]).

handle_call({send_message, Msg}, _From, State) ->
  case validate_rights(Msg) of
    ok ->
      NewPts = State#state.pts + 1,
      save_update(Msg#update{pts=NewPts}),
      broadcast_to_peers(Msg),
      {reply, ok, State#state{pts=NewPts}};
    {error, Reason} -> {reply, {error, Reason}, State}
  end.

Gruppenchats und Edge Cases

Gruppen sind ein Biest. MUC durch:

  • Geshardete Räume: Leader nach hash(UIN-Liste), Follower replizieren Deltas.
  • Presence: ETS für online/offline, pubsub bei Reconnect.
  • Schwache Netze: Optimistisches Senden + Server-ACK, Lückenfüllen aus Logs.

Abgehandelte Fälle:

  • Duplizierte Sends (Idempotenz nach msg_id).
  • Paket-Reihenfolge (Timestamp + Seq).
  • Schlaf-/Weck-Tabs (Long-Poll-Fallback).
  • Bot-Webhooks mit Retries (Dead-Letter-Queue).

Wichtige Erkenntnisse

  • Titanium ist kein MTProto-Klon – ein schlanker Echtzeit-Stack: pts, Logs, Sharding.
  • Erlang/OTP liefert 10x RPS bei 1/4 CPU vs. Python.
  • Server-Wahrheit verhindert Client-Inkonsistenzen.
  • Medien/Bots asynchron ausgelagert; Core fokussiert auf Zustellung.
  • Skalierung: 3 Nodes für 50k Nutzer, 1k Nachr/s ohne Föderation.

— Editorial Team

Advertisement 728x90

Weiterlesen