Volver al inicio

Titanium: backend de mensajería en Erlang

El artículo disecciona la evolución del backend de Titanium para un mensajero personalizado: abandonando XMPP/FastAPI en favor de Erlang/OTP. Describe sincronización pts, sharding, benchmarks hasta 5k RPS. Para desarrolladores senior.

Cómo construir un backend de mensajería escalable en Erlang
Advertisement 728x90

# Titanium: Núcleo escalable de backend para mensajeros personalizados — De prototipo a producción

Los desarrolladores que crean mensajeros personalizados suelen empezar con XMPP/ejabberd o Matrix/Synapse. Estas plataformas ofrecen transporte listo, federación y cifrado E2E. Pero personalizarlas para una UX moderna choca con obstáculos.

XMPP con ejabberd en Erlang es compacto y resistente a cargas, pero:

  • Los stanzas XML añaden sobrecarga de parsing.
  • Las salas MUC no escalan linealmente: el CPU se dispara con el crecimiento de participantes.
  • Extensiones parciales de XEP provocan condiciones de carrera en la lógica de negocio.

Matrix es potente, pero su arquitectura en capas complica los cambios: Synapse en Python sufre con el GIL en tareas en tiempo real.

Google AdInline article slot

Configuraciones híbridas —como ejabberd para transporte + FastAPI para API/OTP/reacciones— causan estados desincronizados, conversiones XML<->JSON y colas extra. La única fuente de verdad se difumina entre componentes.

Conclusión: Es más simple construir un backend desde cero para tener control total sobre el estado y el rendimiento.

Titanium v1: FastAPI bajo carga

La primera versión de Titanium en FastAPI con WebSocket manejaba funciones básicas: chats, UIN, multimedia, pts-sync. El prototipo aguantó 500 RPS en un escenario de 1000 usuarios con 120 msg/s, 40 multimedia/min y 7% de desconexiones.

Google AdInline article slot

RPS se midió en rutas completas: envío -> entrega -> ack -> sync. Esto cubría:

  • Validación de derechos de acceso.
  • Actualizaciones de pts (puntos de mensaje para delta-sync).
  • Envíos en tiempo real por WebSocket.
  • Reintentos en redes inestables.
  • Logs de actualizaciones para clientes offline.

Problemas a escala:

  • GIL limitaba la concurrencia de WebSocket.
  • PostgreSQL se bloqueaba con alta contención en tablas pts.
  • Almacenamiento de multimedia necesitaba enlaces temporales, no permanentes.

Arquitectura de Titanium: Dónde vive la verdad

Titanium define reglas estrictas de gestión de estado:

Google AdInline article slot
  • Servidor como fuente de verdad: Nunca confiar en clientes; todos los cambios vía API.
  • Mecanismo pts: Delta-sync por puntos de mensaje, al estilo Telegram.
  • Log de actualizaciones: Log inmutable para replay de estado.
  • Estado distribuido: Sharding por UIN, sin sesiones sticky.
  • Multimedia: Firmas temporales estilo S3, TTL de 24h.

Componentes clave:

  • Gateway: WebSocket + HTTP/2, limitación de tasa, JWT.
  • Core: Lógica de negocio, pts, ACL (por UIN/grupos).
  • Almacenamiento: PostgreSQL (estados) + Redis (sesiones/colas) + S3 (multimedia).
  • Workers: Celery/RQ para tareas asíncronas (procesamiento multimedia, bots).

Ejemplo pts-sync (pseudocódigo):

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

Cambio a Erlang/OTP: Escalabilidad y resiliencia

Python no manejaba 10k WebSockets concurrentes. Erlang/OTP lo solucionó:

  • Modelo actor: Cada UIN tiene un árbol de supervisores con gen_server.
  • Recarga en caliente de VM sin downtime.
  • Mnesia/ETS integrado para almacenamiento de baja latencia.
  • Cowboy para escalar WebSocket a 1M+ conexiones por nodo.

Benchmarks:

| Escenario | FastAPI (Python) | Titanium (Erlang) |

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

| 1k usuarios, 120 msg/s | 500 RPS | 5k RPS |

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

| Multimedia 100/min | 2s latencia | 200ms |

Ejemplo gen_server de sesión:

-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.

Chats grupales y casos edge

Los grupos son un desafío. Abandonando MUC por:

  • Salas shardadas: Líder por hash(UIN_list), seguidores replican deltas.
  • Presencia: ETS para online/offline, pubsub al reconectar.
  • Redes malas: Envío optimista + ACK servidor, relleno de gaps desde logs.

Casos manejados:

  • Envíos duplicados (idempotencia por msg_id).
  • Reordenación de paquetes (timestamp + seq).
  • Pestañas sleep/wakeup (fallback long-poll).
  • Webhooks de bots con reintentos (cola dead letter).

Conclusiones clave

  • Titanium no es un clon de MTProto: es un stack ligero en tiempo real: pts, logs, sharding.
  • Erlang/OTP da 10x RPS con 1/4 CPU vs Python.
  • Verdad en servidor evita inconsistencias cliente.
  • Multimedia/bots offload asíncrono; core enfocado en entrega.
  • Escala: 3 nodos manejan 50k usuarios, 1k msg/s sin federación.

— Editorial Team

Advertisement 728x90

Leer después