Saltar al contenido

Deja el trabajo lento fuera del flujo en tiempo real

Una acción compartida no debería cargar con la latencia de cada función que activa.

Diseño de sistemas4 min de lectura

Ingeniero de software. Desarrollo extensiones de Chrome, aplicaciones web y herramientas de IA aplicada.

Contenido

Una acción del usuario puede activar bastante trabajo:

  • validar un comando;
  • guardar el estado compartido;
  • avisar a los clientes conectados;
  • llamar a un modelo de IA;
  • actualizar las analíticas;
  • enviar una notificación push;
  • escribir un registro de auditoría.

Si el producto espera a que todo termine antes de confirmar la acción, el efecto secundario más lento determina la latencia que percibe el usuario.

Define el evento principal

Pregúntate qué es lo primero que todos los participantes deben observar.

En ajedrez es la jugada aceptada; en un chat, el mensaje aceptado; en un editor colaborativo, la operación aceptada.

Clasifica todo lo demás en relación con ese hecho:

required before acceptance
required before broadcast
allowed after broadcast
optional

Es decir: necesario antes de aceptar, necesario antes de difundir, permitido después de difundir u opcional.

Esa clasificación sirve más que llamar «asíncrono» a todo.

Una jugada no debería esperar al comentario

Real-Time Chess genera un comentario breve después de cada jugada aceptada. La llamada al modelo puede fallar o tardar segundos.

Los eventos siguen este orden:

validate move
save room
emit move
generate commentary
save commentary
emit commentary

Primero se valida la jugada, se guarda la sala y se emite la jugada. Después se genera el comentario, se guarda y se emite.

El tablero se actualiza con el evento move. El panel de comentarios muestra su propio estado de carga y después consume commentary.

Si la generación falla, un generador local basado en reglas produce el texto. La partida no necesita saber qué generador terminó haciéndolo.

Dale a cada efecto secundario su propio estado

Separar los eventos es solo la mitad del trabajo. La interfaz tiene que representar cada etapa.

Para los comentarios:

move accepted → commentary pending → commentary ready
                                  ↘ fallback ready

La jugada se acepta, el comentario queda pendiente y después llega el comentario generado o el texto alternativo.

Para añadir información a un chat:

message accepted → link preview pending → preview ready | unavailable

El mensaje se acepta y la vista previa del enlace se carga por separado; puede estar lista o no disponible.

Para procesar archivos:

upload accepted → scan pending → usable | quarantined

La subida se acepta, el análisis queda pendiente y el archivo acaba utilizable o en cuarentena.

No hagas que un fallo secundario parezca un fallo de la acción principal.

Pon límites de tiempo

Toda llamada externa que forme parte de un efecto secundario necesita un plazo máximo.

La capa de comentarios del ajedrez usa:

  • un temporizador que cancela la petición al proveedor a los cuatro segundos;
  • un límite externo de ocho segundos que compite con la ejecución del generador;
  • un mensaje alternativo en el cliente si el comentario sigue sin llegar.

Las cifras concretas dependen del producto. El principio se mantiene: una rama pendiente debe acabar en éxito, en una alternativa o en un fallo. No puede quedarse pendiente para siempre.

¿Guardar antes o después de difundir?

Para la acción compartida principal, guardar antes de difundir suele ser la opción más segura:

validate → persist → broadcast

Primero valida, después persiste y luego difunde.

Si el servidor difunde primero y falla la escritura, todos los clientes pueden mostrar un estado que el sistema no puede recuperar.

Para un resultado secundario, la decisión depende de su valor:

  • guarda antes de difundir si los clientes esperan encontrarlo en futuras instantáneas;
  • difunde sin persistir los indicadores pasajeros de presencia o de escritura;
  • encola el trabajo de forma duradera si los reintentos deben sobrevivir a un fallo del proceso.

En el proyecto de ajedrez, tanto la jugada como el comentario final se guardan antes de emitir su evento correspondiente.

Lanzar y olvidarse no garantiza el trabajo en segundo plano

Llamar a una función asíncrona sin esperarla mantiene corto el flujo de la petición. No garantiza que termine.

Si el proceso se cae después de difundir la jugada, pero antes de guardar el comentario, ese comentario se pierde. Para una demo, es un compromiso aceptable. Para trabajo importante, usa una tarea duradera:

transaction commits primary state + outbox record
worker claims outbox record
worker performs side effect
worker records completion
server emits or clients poll the result

La transacción guarda el estado principal y un registro de salida; un worker toma ese registro, ejecuta el efecto secundario y guarda que terminó. El servidor emite el resultado o los clientes lo consultan.

El patrón outbox vincula la escritura principal y la intención de ejecutar trabajo en segundo plano dentro de una sola transacción.

Conserva la causalidad

Un efecto secundario necesita una referencia estable a la acción que lo causó.

El evento de comentario del ajedrez usa el valor SAN de la jugada. Es legible, pero no es único a nivel global. Un evento de producción llevaría:

{
  eventId: "01J...",
  causedBy: "move_01J...",
  roomId: "abc123",
  roomVersion: 18,
  text: "..."
}

Así, los registros, los reintentos, la deduplicación y la reconciliación de la interfaz comparten la misma cadena causal.

Decide qué pierde el usuario

La frontera entre el trabajo principal y el secundario queda clara cuando te preguntas qué significa un fallo.

Si se pierde la jugada, la partida queda en un estado incorrecto. Si se pierde el comentario, la partida sigue siendo jugable.

Esa diferencia debería determinar el orden, la persistencia, el esfuerzo dedicado a reintentos, las alertas y el lenguaje de la interfaz.

El patrón reutilizable

Para cualquier acción en tiempo real con procesamiento adicional:

  1. valida el comando principal;
  2. persiste el estado que el servidor acepta como válido;
  3. difunde el evento aceptado;
  4. muestra un estado pendiente para el procesamiento adicional;
  5. ejecuta el trabajo lento con un plazo máximo;
  6. persiste y emite el resultado;
  7. recurre a una alternativa sin deshacer la acción aceptada;
  8. pasa a una cola duradera cuando perder el efecto secundario deje de ser aceptable.

Los productos rápidos en tiempo real evitan que el usuario espere por trabajo que no pertenece al flujo principal.

Un pequeño voto de confianza

¿Valió la pena leer esta nota?