La presencia es una señal en tiempo real que indica si un usuario está online, ausente u offline, mantenida fresca por conexiones y heartbeats. El punto verde es una interfaz engañosamente simple sobre un problema distribuido genuinamente difícil: el servidor debe inferir la ausencia — los dispositivos rara vez anuncian su propia muerte — y luego difundir cada inferencia a todos los que observan, a escala de lista de contactos. Estandarizado mucho antes del chat moderno, en las stanzas de presencia de XMPP, el patrón tiene fundamentos que no han cambiado desde entonces.
Puntos clave
| Pregunta | Respuesta |
|---|---|
| Los estados | online · ausente/inactivo · offline — más overrides definidos por el usuario (no molestar) |
| El mecanismo | Escrituras de heartbeat + expiración por TTL; eventos de conexión como camino rápido |
| Los números | Heartbeat de ~30 s · timeout de offline en 2–3× el intervalo · 30–60 s de gracia contra el parpadeo |
| El compañero | La última conexión (last seen) — el timestamp durable detrás del punto efímero |
| La parte difícil | Fan-out: N observadores × M cambios de estado, multiplicados por tu éxito |
El contrato del heartbeat
El patrón entero cabe en un intercambio:
cliente cada 30 s: save { status: "online", lastActiveAt: now }
servidor cada 60 s: barrido — ¿lastActiveAt más viejo que 90 s? → status: "offline"
(TTL = 2–3 × intervalo del heartbeat)
observador status = "online" → punto verde
status = "offline" → "visto a las 12:41" ← lastActiveAt, la parte durable
El mismo contrato como código de SDK — heartbeat hacia afuera, suscripción hacia adentro:
// JavaScript / Node.js — Back4app JS SDK
// Presence: heartbeat your own status, subscribe to your contacts'
const heartbeat = () =>
myPresence.save({ status: 'online', lastActiveAt: new Date() });
await heartbeat();
setInterval(heartbeat, 30_000); // server sweeps offline at 2–3× this
const contacts = new Parse.Query('Presence');
contacts.containedIn('user', myContactIds);
const sub = await contacts.subscribe();
sub.on('update', (p) => setStatus(p.get('user'), p.get('status'))); // Flutter / Dart — Back4app Flutter SDK
// Presence: heartbeat your own status, subscribe to your contacts'
Future<void> heartbeat() async {
myPresence
..set('status', 'online')
..set('lastActiveAt', DateTime.now());
await myPresence.save();
}
Timer.periodic(const Duration(seconds: 30), (_) => heartbeat());
final contacts = QueryBuilder<ParseObject>(ParseObject('Presence'))
..whereContainedIn('user', myContactIds);
final sub = await LiveQuery().client.subscribe(contacts);
sub.on(LiveQueryEvent.update, (p) => setStatus(p.get('user'), p.get('status'))); // iOS / Swift — Back4app Swift SDK
// Presence: heartbeat your own status, subscribe to your contacts'
func heartbeat() async throws {
myPresence.status = "online"
myPresence.lastActiveAt = Date()
_ = try await myPresence.save()
}
// Fire every 30 s — the server sweeps offline at 2–3× this
let contacts = Presence.query(containedIn(key: "user", array: myContactIds))
let sub = try await contacts.subscribe()
sub.handleEvent { _, e in
if case .updated(let p) = e { setStatus(p.user, p.status) }
} // Android / Kotlin — Back4app Android SDK
// Presence: heartbeat your own status, subscribe to your contacts'
fun heartbeat() {
myPresence.put("status", "online")
myPresence.put("lastActiveAt", Date())
myPresence.saveInBackground()
}
timer.scheduleAtFixedRate(0, 30_000) { heartbeat() } // sweep at 2–3× this
val contacts = ParseQuery.getQuery<ParseObject>("Presence")
contacts.whereContainedIn("user", myContactIds)
val sub = ParseLiveQueryClient.Factory.getClient().subscribe(contacts)
sub.handleEvent(SubscriptionHandling.Event.UPDATE) { _, p ->
setStatus(p.getString("user"), p.getString("status"))
} Eventos de conexión vs. TTL del heartbeat
Los dos modelos de detección, comparados con honestidad — ninguna página de rankings hace esto en una sola tabla:
| Ciclo de vida de la conexión | Heartbeat + TTL | |
|---|---|---|
| Señal de online | Socket abierto | Heartbeat fresco dentro de la ventana |
| Señal de offline | Evento/frame de cierre | Expiración del TTL — sin heartbeat por 2–3 intervalos |
| Velocidad de detección | Instantánea en un cierre limpio | Retraso acotado (hasta el timeout) |
| Fallas silenciosas | Se le escapan — un corte de energía no envía frame de cierre | Las atrapa — el silencio es la señal |
| Estado en el servidor | Seguimiento por conexión | Escrituras sin estado a un store con TTL |
| Cómo falla | Usuarios “online” zombis | Breves falsos “offline” por latidos perdidos |
El propio protocolo WebSocket enseña la lección: incluye frames de control ping/pong precisamente porque TCP no revela a un par desaparecido, y su taxonomía de close codes reserva el 1006 para “abnormal closure — no close frame received”, el caso zombi. La presencia de producción, por lo tanto, apila ambos modelos: eventos de conexión para transiciones instantáneas, expiración de heartbeat como la verdad que atrapa lo que los eventos dejan pasar.
La máquina de estados
Dos refinamientos distinguen a las implementaciones pulidas. Ausente se detecta en el cliente: el servidor no puede ver una pestaña en segundo plano, pero el navegador sí — la Page Visibility API marca las pestañas ocultas, los listeners de entrada marcan la inactividad, y una señal de offline de mejor esfuerzo al cerrar la pestaña viaja por la Beacon API. Offline lleva debounce: el estado de gracia existe porque las conexiones móviles oscilan — publica “offline” en cada túnel y ascensor, y la lista de contactos de cada observador parpadea en solidaridad. Retrasa el anuncio, cancela en la reconexión, y la oscilación colapsa en quietud. Una distinción más que vale la pena robarle al protocolo que formalizó la presencia: presencia computada (online, por la conexión) versus estado definido por el usuario (no molestar) — el segundo siempre gana el merge.
Efímera por diseño
La presencia es una caché de la realidad, no un registro: un estado que llega tarde es peor que ninguno — el “online” de ayer es una mentira hoy. Ese principio guía las decisiones de implementación: los estados viven en stores rápidos con TTL en lugar de tablas durables, nunca se encolan para entrega offline y son el primer dato que se descarta bajo backpressure. Solo lastActiveAt merece persistencia. Los indicadores de escritura son el principio llevado al extremo — micro-presencia acotada a una conversación, que expira en segundos, con debounce en el emisor y descartada en lugar de reintentada; si “Ada está escribiendo…” no puede llegar ahora, no debería llegar nunca.
El problema del fan-out
El muro de escala de la presencia es la multiplicación, no el almacenamiento. Un ejemplo con números: 100.000 usuarios concurrentes, cada uno visible para 50 contactos, cada uno cambiando de estado unas modestas 20 veces por hora — eso da 100.000 × 50 × 20 = 100 millones de eventos de presencia por hora para entregar, desde una feature que guarda una fila pequeña por usuario. Las palancas, en el orden en que se tiran: suscríbete de forma estrecha — observa a los usuarios en pantalla (la lista de conversaciones abierta), no el grafo completo de contactos; consulta, no empujes, la cola larga — busca la última conexión cuando se abre un perfil en lugar de transmitirla en stream; limita la difusión en grupos — pasados unos cientos de miembros, muestra presencia en la interacción, no para toda la lista; y agrupa las transiciones para que un usuario que oscila cueste un evento con debounce, no treinta. El fan-out en sí corre sobre la maquinaria estándar de pub/sub — la presencia es el caso extremo at-most-once de ese patrón, donde descartar un evento viejo es una feature.
Multi-dispositivo y privacidad
Un usuario, tres dispositivos, un punto — la presencia por usuario es un merge sobre la presencia por dispositivo. Rastrea el heartbeat de cada dispositivo por separado; el usuario está online mientras cualquier dispositivo lo esté, y la política de merge es “gana el más disponible”: online en el teléfono le gana a inactivo en el escritorio, y el no-molestar de cualquier dispositivo se impone sobre el resto. La última conexión reporta el dispositivo más reciente. La privacidad es la otra mitad del diseño, y la mitad que los blogs de ingeniería omiten pese a ser lo que los usuarios más preguntan: reglas de visibilidad (todos / contactos / nadie), reciprocidad (oculta la tuya, pierde de vista la de los demás) y última conexión gruesa (“recientemente” en lugar de horarios exactos). Aplica las reglas en los permisos de la capa de datos — la presencia es dato de comportamiento, y un “online a las 3 a. m.” filtrado es una divulgación real.
Casos de uso comunes
- Chat y mensajería — el punto verde canónico, la última conexión y los indicadores de escritura.
- Herramientas de colaboración — quién está en el documento, presencia de cursor, barras laterales de “activos ahora”.
- Soporte y marketplaces — enrutamiento por disponibilidad de agentes, señales de confianza tipo “el vendedor está online”.
- Multijugador y social — lobbies, listas de amigos, atajos para unirte a tu amigo.
- Herramientas de fuerza laboral — estados de disponibilidad alimentando enrutamiento y dashboards de estado.
¿Cómo deberías construir presencia? Matriz de decisión
| Feature | Mecanismo |
|---|---|
| Punto verde en los contactos | Heartbeat + store con TTL, suscripción de live query sobre los usuarios visibles |
| Transiciones instantáneas en un chat abierto | Eventos de conexión como camino rápido sobre el socket |
| Última conexión | Timestamp en cada heartbeat; consulta a demanda |
| Detección de ausencia | En el cliente: listeners de visibilidad e inactividad, reportados en el heartbeat |
| Indicador de escritura | Micro-eventos por conversación, TTL de segundos, nunca persistidos |
| Presencia en un grupo de 5.000 miembros | No difundas — muestra en la interacción, limita la lista |
Limitaciones y trade-offs
- La presencia es probabilística. Entre heartbeats, el punto es una conjetura; los sistemas honestos abrazan la obsolescencia acotada en lugar de fingir verdad instantánea.
- La frescura cuesta escrituras. Reducir el intervalo de heartbeat a la mitad duplica la carga de escritura de toda tu población online — la velocidad de detección se compra en throughput.
- El fan-out escala con el éxito. La feature es barata con 1.000 usuarios y un proyecto de arquitectura con 10 millones; diseña el alcance de las suscripciones antes de que el crecimiento lo fuerce.
- La oscilación es inherente. Las redes móviles garantizan churn de reconexión; sin debounce, la presencia amplifica el ruido de red en ruido de interfaz.
- Tiene forma de vigilancia. Los patrones de conexión revelan sueño, trabajo y hábitos; los controles de visibilidad y su aplicación en la capa de permisos son requisitos, no mejoras.
Presencia en Back4app
Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. Los bloques de arriba mapean uno a uno: una clase Presence guarda status y lastActiveAt; el heartbeat es un save periódico (las pestañas de código); los observadores mantienen una suscripción de Live Query sobre los contactos que están en pantalla, recibiendo actualizaciones por push sobre la flota gestionada de WebSockets de la plataforma; un job programado de Cloud Code es el barrido de TTL, pasando a offline las filas viejas y disparando el debounce; y los permisos a nivel de clase más las ACLs implementan las reglas de visibilidad en la capa de datos, donde deben vivir. Nada a medida que operar — la presencia se vuelve un ejercicio de modelado de datos sobre infraestructura que el backend ya corre.
Preguntas frecuentes
¿Qué es un sistema de presencia?
Un servicio que rastrea si cada usuario está online, ausente u offline y transmite los cambios, en tiempo real, a los usuarios con permiso para verlos. Bajo el capó: conexiones persistentes o heartbeats periódicos que escriben en un store rápido en memoria, con los cambios de estado repartidos por pub/sub.
¿Cómo funciona la "última conexión" (last seen)?
El servidor registra un timestamp en cada heartbeat y en cada desconexión. Mientras estás online, se sirve el estado en vivo; cuando pasas a offline, responde en su lugar el último timestamp registrado. Es la única pieza durable de un sistema por lo demás efímero — el estado expira, la última conexión persiste.
¿Qué intervalo de heartbeat debería usar la presencia?
Treinta segundos es el default común en producción, con el timeout de offline en dos a tres veces el intervalo (60–90 segundos). Los intervalos más rápidos encogen el retraso de detección pero multiplican la carga de escritura linealmente con tu población online; el intervalo también debe quedar por debajo de los timeouts de inactividad de la infraestructura, o los proxies matan primero las conexiones calladas.
¿Detección por conexión o por heartbeat — cuál es mejor?
Ambas, para fallas distintas. Los eventos de conexión (abrir/cerrar) dan transiciones instantáneas pero se pierden las muertes silenciosas — un dispositivo que se queda sin batería no envía frame de cierre y deja una conexión zombi. La expiración del heartbeat acota esa obsolescencia al costo de un retraso de detección. Los sistemas de producción usan los eventos de conexión como camino rápido y el TTL del heartbeat como fuente de la verdad.
¿Cómo evitas que el status online parpadee?
Haz debounce de la transición a offline: en la desconexión, arranca un temporizador de gracia — comúnmente de 30 a 60 segundos — y cancélalo si el usuario reconecta, publicando "offline" solo cuando el temporizador dispara. Los usuarios en ascensores y túneles de tren reconectan constantemente; sin el período de gracia, cada lista de contactos en la que aparecen parpadea con ellos.
¿Cómo escala la presencia a millones de usuarios?
Respetando la matemática del fan-out: cada cambio de estado debe llegar a cada observador, así que N contactos por M transiciones explota rápido. Las palancas son un store en memoria con TTL para el estado, suscribirse solo a los usuarios visibles (la lista de chats abierta, no la agenda completa), consultar la última conexión a demanda en lugar de empujarla, y limitar la difusión de estado en grupos grandes.
¿Cómo funciona la presencia multi-dispositivo?
Rastrea una conexión o un heartbeat por dispositivo y fusiona: el usuario está online mientras cualquier dispositivo lo esté, y offline solo cuando el último se calla. La política de merge habitual es "gana el más disponible" — online en el teléfono le gana a inactivo en el escritorio — con la última conexión tomada del dispositivo más reciente.
¿Pueden los usuarios ocultar su status online?
Deberían poder — la visibilidad es una feature de producto, no una ocurrencia tardía: reglas como todos, solo contactos o nadie, normalmente con reciprocidad (oculta el tuyo y dejas de ver el de los demás). Los datos de presencia son datos de comportamiento; define quién puede observar a quién en el modelo de permisos, no en la interfaz.