Curso exprés · No. 18

Cuando una parte de un sistema necesita que otra haga algo, la forma sencilla es llamarla y esperar una respuesta. La forma poderosa es dejar un message en una queue y seguir adelante, dejando que el otro lado lo recoja cuando esté listo. Ese único cambio — de llamar y esperar a enviar un mensaje y continuar — es como los sistemas se mantienen rápidos, sobreviven a los fallos y escalan.

Solo lo esencial · Una imagen por idea · Aprende las palabras

§ 01

Para entender por qué existen las queues, primero hay que sentir el dolor de la alternativa sencilla: un servicio que llama a otro y lo espera. Funciona hasta el momento en que de verdad importa.

Una llamada directa ata dos servicios entre sí

Telefonear a alguien y quedarte en la línea hasta que termine la tarea — no puedes colgar, no puedes hacer nada más, y si no contesta, te quedas atascado.

La forma obvia de que el servicio A consiga que el servicio B haga algo es una synchronous call: A pregunta y luego espera, conteniéndolo todo hasta que B responde. Esto los acopla estrechamente — la velocidad de A queda ahora secuestrada por la velocidad de B, y A no puede continuar hasta que B termine. Para una respuesta rápida eso está bien. Para un trabajo lento o pesado, A pasa el tiempo congelado, esperando a otro.

Si el llamado está caído, el que llama se rompe

Llamar al almacén para confirmar un pedido y, como nadie contesta, negarte a aceptar cualquier pedido nuevo — una puerta cerrada bloquea toda la tienda.

El problema más profundo es el fallo. Si B está caído o sobrecargado cuando A llama, la petición de A también falla — el fallo se propaga directamente hacia arriba en la cadena. Un solo servicio lento o roto puede paralizar todo lo que depende de él, y un pico de tráfico hacia B arrastra a A consigo. El acoplamiento estrecho significa que la debilidad se extiende, y el sistema solo está tan disponible como su parte más frágil.

Cierto trabajo no debería bloquear al usuario

En una caja, pagas y te vas con tu recibo — no te quedas en la caja mientras empaquetan, envían y te mandan un correo. Las partes lentas ocurren después de que te marchas.

Mucho trabajo no necesita terminar antes de que el usuario reciba una respuesta — enviar el correo de confirmación, generar la factura, redimensionar la foto. Hacer que el usuario espere todo eso es lento e inútil. Quieres aceptar la petición, responder rápido y dejar que las partes lentas ocurran después. Una llamada directa no puede hacer eso; obliga a todos a esperar por todo.

Una llamada directa acopla dos servicios en el tiempo: el que llama espera y hereda la lentitud y los fallos del llamado. Para un trabajo lento o no urgente, eso es una trampa.

§ 02

La solución es dejar de llamar y empezar a enviar mensajes. Una queue es el búfer del medio que permite que un lado deje trabajo y el otro lo recoja — desacoplándolos en el tiempo.

Deja el mensaje y sigue adelante

Dejar un mensaje de voz en vez de esperar en espera: dices lo que necesitas, cuelgas y sigues con tu día — ellos se ocuparán cuando puedan.

En lugar de llamar a B y esperar, A escribe un message que describe el trabajo y lo deja en una queue — una fila donde los mensajes esperan a ser procesados. A queda ahora libre para continuar de inmediato; no le importa cuándo B llegue a él. El message es un pequeño paquete de «esto es lo que hay que hacer», entregado en vez de realizado en el acto. Esa entrega es toda la idea.

La queue es un búfer en el tiempo

Una bandeja de entrada en un escritorio: el trabajo se acumula en ella y se atiende de forma constante, así que una avalancha repentina de papeleo no abruma a la persona — solo hace la bandeja más alta por un rato.

La queue se sitúa entre los dos lados como un buffer, conservando los mensajes hasta que se procesan. Esto los desacopla en el tiempo: el emisor y el receptor ya no tienen que ser rápidos, estar disponibles, ni siquiera estar en marcha en el mismo momento. El trabajo producido en una ráfaga puede consumirse de forma constante. La queue absorbe el desajuste entre la rapidez con que llega el trabajo y la rapidez con que puede hacerse.

Asynchronous significa no esperar el resultado

Enviar una carta por correo en lugar de mantener una conversación — la mandas y sigues adelante, confiando en que se leerá y se actuará más tarde, sin que tú estés ahí parado.

Esto es trabajo asynchronous: el emisor no espera el resultado. Dispara el message y continúa, y el desenlace ocurre más tarde, por fuera. Renuncias a la respuesta instantánea de una llamada directa y, a cambio, dejas de estar bloqueado. Para cualquier cosa que no necesite una respuesta inmediata, ese intercambio es la base de los sistemas rápidos y resilientes.

Una queue es un búfer entre emisor y receptor. Deja un message y sigue adelante — el trabajo queda desacoplado en el tiempo, hecho de forma asynchronous cuando el otro lado está listo.

§ 03

Tres roles hacen funcionar una queue, y nombrarlos despeja casi toda la jerga. Un lado crea mensajes, un lado los atiende, y algo en el medio los guarda a salvo.

Los producers crean trabajo; los consumers lo hacen

Una cocina: los camareros cuelgan las comandas, y los cocineros las van descolgando una a una para prepararlas. Los dos lados nunca hablan directamente — el riel de comandas es toda la interfaz.

El lado que crea los mensajes es el producer (o publisher); el lado que los procesa es el consumer (o worker). El producer deja una comanda; el consumer toma una, hace el trabajo y pasa a la siguiente. No se conocen ni se esperan — solo conocen la queue. Esa división limpia es lo que permite construir, escalar y cambiar cada lado por su cuenta.

El broker guarda los mensajes a salvo

Una oficina de correos entre emisor y destinatario: recoge tu carta, la mantiene a salvo y la conserva hasta que el destinatario la recoge — así no se pierde nada si está ausente.

En el medio se sitúa el broker — software como RabbitMQ o Apache Kafka que recibe mensajes, los almacena de forma fiable y los entrega a los consumers. Es la oficina de correos del sistema. Su fiabilidad es el punto clave: aunque todos los consumers estén caídos, el broker mantiene los mensajes a salvo hasta que alguien esté listo para procesarlos, de modo que el trabajo queda aparcado, no perdido.

Añade consumers para ir más rápido

Cuando las comandas se acumulan, pones más cocineros en la línea — cada uno toma la siguiente comanda, y el atasco se despeja más rápido sin cambiar cómo entran los pedidos.

Como los consumers simplemente toman el siguiente message, puedes ejecutar muchos en paralelo desde una sola queue. ¿Crece el atasco? Añade más workers, y se reparten la carga automáticamente — cada uno tomando un message distinto. Esta es una de las grandes fortalezas del patrón: escalas la parte lenta de forma independiente añadiendo consumers, sin tocar los producers ni la queue en sí.

Los producers crean mensajes, los consumers los procesan, y un broker los guarda a salvo en el medio. ¿Necesitas más throughput? Añade consumers — se reparten la queue automáticamente.

§ 04

Hay dos cosas muy distintas que un message puede ser: una orden de hacer algo, o un anuncio de que algo ocurrió. La segunda cambia sigilosamente cómo diseñas sistemas enteros.

Un command le dice a un worker que haga una tarea

Una orden de trabajo entregada a un departamento concreto: «redimensiona esta imagen». Nombra el trabajo, espera que exactamente un equipo lo haga, y ahí se acaba.

Un message de tipo command es una instrucción directa dirigida a un consumer: «envía este correo», «procesa este pago». Es la queue usada como lista de tareas — el producer sabe lo que debe ocurrir y entrega la tarea para que se haga una vez, por quien la tome. Este es el uso más simple de una queue: descargar trabajo concreto para hacerlo más tarde, exactamente una vez.

Un event anuncia que algo ocurrió

Un anuncio para toda la empresa en un tablón: «acaba de registrarse un nuevo cliente». No le dice a nadie qué hacer — quien le interese reacciona a su manera.

Un event es distinto: declara un hecho del pasado — «pedido realizado», «usuario registrado» — sin decir quién debe hacer qué. El producer simplemente lo anuncia y no sabe ni le importa quién está escuchando. Quizá nadie reacciona; quizá lo hacen cinco servicios. Esto invierte la relación: en lugar de ordenar a un worker concreto, difundes que algo es cierto y dejas que las partes interesadas decidan.

Pub/sub: un event, muchas reacciones

Un periódico impreso una vez y entregado a cada suscriptor, cada uno de los cuales lo lee por sus propios motivos — aficionado al deporte, inversor, crucigramista — a partir del mismo único ejemplar.

Este modelo de difusión es publish/subscribe (pub/sub): un producer publishes un event en un topic, y cada consumer interesado se subscribes y recibe su propia copia. Un solo event «pedido realizado» puede activar a la vez el servicio de correo, el de analítica y el de envíos — esto es fan-out. El publisher ni siquiera sabe que existen. Pueden añadirse nuevas reacciones más tarde sin tocar el publisher en absoluto.

Los eventos desacoplan quién conoce a quién

Añadir un nuevo suscriptor al periódico no cambia nada de cómo se escribe el diario — el publisher nunca necesita saber quién lo está leyendo.

Este es el poder profundo del diseño event-driven: el producer de un event está totalmente desacoplado de todos los que reaccionan a él. Para añadir una nueva función que responda a «pedido realizado», basta con suscribir un nuevo consumer — sin cambiar el servicio de pedidos. Los sistemas construidos así crecen añadiendo oyentes, no editando aquello que se escucha. Así es como los sistemas grandes evolucionan sin que todo dependa de todo.

Un command le dice a un consumer que haga una tarea. Un event anuncia un hecho y deja que cualquiera reaccione — y pub/sub reparte un event hacia muchos, desacoplando quién conoce a quién.

§ 05

Las queues añaden piezas en movimiento, así que tienen que ganárselo. Lo hacen, al aportar tres cosas difíciles de conseguir de otra forma: resiliencia, manejo suave de los picos y escalado independiente.

Resiliencia: un consumer caído solo retrasa el trabajo

Si la cocina cierra una hora, las comandas simplemente esperan en el riel — cuando reabre, los cocineros despejan el atasco. No se pierde ningún pedido.

Como el broker conserva los mensajes, que un consumer se caiga no pierde trabajo — los mensajes esperan hasta que vuelve y entonces se procesan. Compáralo con una llamada directa, donde un servicio caído significa un fallo total. La queue convierte «el servicio está caído» de un error duro en un retraso temporal. Esta resiliencia — el trabajo sobrevive a un fallo y se reanuda — suele ser la mayor razón por la que los equipos recurren a una queue.

Nivelado de carga: absorbe los picos

Un embalse entre una riada repentina y un pueblo: la avalancha se vierte en el embalse, que libera el agua río abajo a un ritmo constante y seguro.

Cuando llega de golpe una avalancha de peticiones — una rebaja, un momento viral — una queue te permite nivelar la carga: el pico llena la queue, y los consumers la vacían a su propio ritmo constante. El sistema lento de río abajo nunca ve el pico, solo un flujo manejable y uniforme. La presión que ejerce el atasco se llama backpressure. Sin una queue, el mismo pico golpearía el sistema directamente y probablemente lo derribaría.

Escala y evoluciona cada lado por su cuenta

Puedes contratar más cocineros, cambiar el equipamiento de la cocina o añadir una nueva estación — todo sin cambiar cómo toman los pedidos los camareros, porque ellos solo tocan el riel de comandas.

Desacoplar significa que cada lado puede cambiar de forma independiente. Escala los consumers hacia arriba o hacia abajo para ajustarte a la demanda; reescribe un consumer en otro lenguaje; añade un consumer completamente nuevo que reaccione a eventos existentes — todo sin tocar los producers. La queue es una costura estable entre partes del sistema, y las costuras estables son las que permiten que un sistema grande crezca y cambie por piezas en lugar de todo a la vez.

Las queues aportan resiliencia (el trabajo sobrevive a una caída), nivelado de carga (los picos se absorben) y escalado independiente — beneficios que una llamada directa sencillamente no puede darte.

§ 06

El mensajería asynchronous resuelve problemas reales y crea otros nuevos. Ninguno es un impedimento, pero fingir que no existen es como los sistemas basados en queues acaban con bugs sutiles y exasperantes.

Los mensajes pueden llegar dos veces

La oficina de correos, por precaución, a veces entrega una copia de una carta de la que no está segura que llegó — mejor dos veces que nunca, pero ahora podrías actuar dos veces sobre la misma instrucción.

La mayoría de los brokers garantizan entrega at-least-once: prefieren enviar un message dos veces antes que arriesgarse a perderlo, así que un consumer puede recibir ocasionalmente el mismo message más de una vez. Si «cobra al cliente» corre dos veces, eso es un problema real. La solución es la idempotency — diseñar el trabajo de modo que hacerlo dos veces tenga el mismo efecto que hacerlo una vez (comprobar primero «¿ya cobrado?»). Da por hecho los duplicados; hazlos inofensivos.

El orden no está garantizado

Dos cartas enviadas en secuencia pueden llegar en cualquier orden — así que si el paso dos aparece antes que el paso uno, el destinatario se confunde.

Con muchos mensajes y muchos consumers en paralelo, los mensajes no se procesan necesariamente en el orden en que se enviaron. Si «actualizar dirección» y «eliminar cuenta» llegan en desorden, obtienes un sinsentido. Algunos sistemas preservan el orden dentro de una categoría a cierto coste; a menudo sale más barato diseñar un trabajo que no dependa de un orden estricto. En cualquier caso, nunca des por hecho el orden a menos que lo hayas dispuesto específicamente.

El resultado es eventually consistent

Un anuncio que se propaga por una oficina: durante unos instantes algunos lo saben y otros no, hasta que la noticia llega a todos y vuelven a estar de acuerdo.

Como las reacciones ocurren de forma asynchronous, el sistema es eventually consistent: justo después de un event, distintas partes pueden discrepar brevemente — el pedido existe pero la analítica aún no lo ha contabilizado. Se pone al día en instantes, pero «consistente al instante en todas partes» desaparece. Diseñas en torno a esa brecha, mostrando a los usuarios estados intermedios sensatos, en vez de suponer que cada parte se actualiza en el mismo instante.

Un mensaje envenenado necesita un sitio adonde ir

Una carta sobre la que nadie puede actuar — emborronada, imposible — no puede simplemente reintentarse para siempre, atascando la línea. Se aparta en una bandeja especial para que alguien la examine.

Algunos mensajes nunca pueden procesarse con éxito — mal formados, o para datos que ya no existen. Si se dejan estar, un consumer los reintenta para siempre y bloquea la queue. La respuesta estándar es una dead-letter queue: tras unos cuantos intentos fallidos, el message se aparta a una queue separada para inspección, de modo que deja de envenenar el flujo. Planifica para los mensajes que fallan, o un solo message defectuoso paraliza todo lo que tiene detrás.

El mensajería asynchronous trae duplicados (gestiónalos con idempotency), ningún orden garantizado, eventual consistency y mensajes que fallan (usa una dead-letter queue). Planifica para los cuatro.

§ 07

Una queue es una herramienta potente, no algo por defecto. La habilidad está en recurrir a ella cuando el desacoplamiento de verdad compensa, y conservar la llamada directa cuando la simplicidad vale más.

Usa una queue cuando esperar es el problema

Tomas un número y te vas a por un servicio lento que recogerás luego — pero para un sí-o-no rápido simplemente preguntas en el mostrador, porque tomar un ticket sería más absurdo que esperar.

Recurre a una queue cuando el trabajo sea lento, irregular o no urgente, cuando quieras que el que llama siga siendo rápido, o cuando varios servicios deban reaccionar a una sola cosa. Conserva una llamada directa cuando necesites una respuesta inmediata para continuar — una consulta de precio, una comprobación de permiso — porque ahí la simplicidad y el resultado instantáneo valen más que el desacoplamiento. Ajusta la herramienta a si esperar es de verdad el problema.

No recurras a ella por reflejo

Instalar toda una sala de correo y un sistema de clasificación para pasar una nota a la persona del escritorio de al lado — la maquinaria cuesta ahora más que el problema que resuelve.

Las queues añaden complejidad real: un broker que mantener, duplicados y orden que gestionar, eventual consistency en torno a la cual diseñar, y más dificultad para rastrear una petición que ahora salta entre mensajes. Para un sistema simple con una llamada rápida entre dos partes, esa sobrecarga no vale la pena. Añade una queue cuando un beneficio concreto — resiliencia, escala, desacoplamiento — justifique el coste, no porque «event-driven» suene avanzado.

Antes de añadir una queue
  • ¿Es esperar el problema — el trabajo es lo bastante lento, irregular o no urgente como para desacoplarlo? - ¿Command o event — un worker haciendo una tarea, o muchos servicios reaccionando a un hecho? - ¿Es el trabajo idempotent — seguro de ejecutar dos veces, ya que los mensajes pueden llegar dos veces? - ¿Da por hecho el orden, y lo he dispuesto o lo he diseñado para evitarlo? - ¿Puede el sistema tolerar una breve eventual inconsistency? - ¿Adónde van los mensajes fallidos — hay una dead-letter queue?
Las palabras que ahora dominas
  • synchronous / asynchronous — llamar y esperar, frente a enviar un mensaje y continuar. - coupling / decoupling — atados en el tiempo, frente a libres el uno del otro. - queue / message / broker — el búfer, la unidad de trabajo, el software que los guarda. - producer / consumer — el lado que crea los mensajes, el lado que los procesa. - command / event — haz esta tarea, frente a esto ocurrió. - pub/sub / topic / fan-out — difundir un event a muchos suscriptores. - at-least-once / idempotency / dead-letter queue / eventual consistency — los peligros del async y sus soluciones.
Señales de que usas bien las queues
  • Pones en queue el trabajo lento, irregular, no urgente y conservas las llamadas directas para respuestas instantáneas. - Tus consumers son idempotent, así que un message duplicado no hace daño. - No das por hecho el orden a menos que lo hayas dispuesto explícitamente. - Diseñas para la eventual consistency en lugar de esperar acuerdo instantáneo. - Los mensajes fallidos aterrizan en una dead-letter queue en lugar de atascar la línea.

Las queues son deliberadas: recurre a una cuando desacoplar en el tiempo aporte resiliencia, escala o fan-out — y conserva la llamada directa cuando una respuesta instantánea importe más que todo lo demás.

Fin del curso exprés · 7 capítulos · aprende las palabras

Después viene la práctica: toma una cosa lenta que tu app hace en línea — enviar un correo, generar un informe — y muévela detrás de una queue, para que el usuario reciba una respuesta instantánea y un worker haga la parte lenta después. Luego haz que ese worker sea seguro de ejecutar dos veces. El patrón encaja en el momento en que ves que la respuesta se vuelve rápida y el trabajo aun así se hace. Pero mantén una idea por encima del resto: el cambio es de llamar y esperar a dejar un mensaje y continuar. Desacopla el trabajo en el tiempo y tu sistema se vuelve más rápido, más resistente y más fácil de hacer crecer — al precio de pensar en eventos en lugar de comandos.