Una cola de mensajes permite aceptar trabajo y completarlo después, a un ritmo que los consumidores puedan sostener. Introducirla en un diseño exige explicar qué se guarda, cuándo se considera aceptado el trabajo y cómo se recupera una operación que quedó a medias.

En una entrevista, el nombre del broker importa menos que ese contrato. La cola debe responder a una necesidad concreta: desacoplar tiempos de ejecución, absorber un pico acotado o coordinar procesamiento en segundo plano.

Qué resuelve una cola

El desacoplamiento reduce la dependencia temporal entre productor y consumidor. El productor puede persistir trabajo aunque un consumidor esté detenido, dentro de los límites de disponibilidad, capacidad y retención del sistema. Las dependencias de esquema y significado permanecen: ambos deben interpretar el mensaje de forma compatible.

La asincronía permite sacar del camino de una petición tareas como generar reportes o preparar notificaciones. La respuesta puede comunicar que una solicitud fue aceptada, mientras el resultado se consulta después. El trabajo y sus errores siguen existiendo; ahora necesitan un estado observable y una política de recuperación.

El buffer absorbe ráfagas cuando la llegada supera temporalmente al procesamiento. Su tamaño y duración son finitos. La cola compra tiempo para procesar el excedente y añade espera al resultado final.

La fiabilidad depende de persistencia, replicación, confirmaciones y recuperación. También importan los mensajes que expiran y los resultados inciertos de una publicación. Una cola ayuda a conservar trabajo pendiente bajo su contrato; no garantiza por sí sola ausencia de pérdidas ni ejecución única de los efectos.

Un contrato de procesamiento

Para generar un reporte, la API puede registrar un jobID, el estado pendiente y un evento en una outbox dentro de la misma transacción local. Después responde HTTP 202 Accepted con un identificador para consultar el estado. Esa respuesta confirma la aceptación durable de la solicitud, todavía pendiente de publicación o procesamiento.

Un relay publica el trabajo y un worker genera el reporte. El resultado debe registrarse de forma idempotente para ese jobID, con estados que permitan distinguir pendiente, en ejecución, completado y fallido. Si hay efectos fuera de la base de datos, su recuperación también forma parte del diseño.

---
config:
  look: handDrawn
---
flowchart TD
    client["Cliente"] --> api["API<br/>Aceptar solicitud"]
    api --> database[("Transacción local<br/>Job pendiente + outbox")]
    database -->|"Solicitud persistida"| accepted["HTTP 202<br/>Consultar estado por jobID"]
    accepted --> client
    database --> relay["Relay de outbox"]
    relay --> queue[("Cola<br/>Buffer y retención finitos")]
    queue --> workers["Workers<br/>Procesamiento idempotente"]
    workers -->|"Resultado registrado"| result["Job completado<br/>Reporte disponible"]
    workers -->|"Recuperación agotada"| dlq[("DLQ<br/>Diagnóstico y recuperación")]
    workers -.->|"Limitar admisión si crece la espera"| api

La figura muestra el flujo de trabajo y una señal de control de capacidad. Las confirmaciones de publicación y consumo deben completarlo según el broker utilizado. La outbox transaccional evita separar la aceptación del cambio local y el registro del evento pendiente, pero su publicación todavía puede repetirse.

Capacidad y backpressure

Si la tasa de entrada supera de forma sostenida la tasa útil de finalización, crecen el backlog y la antigüedad del trabajo pendiente. La capacidad relevante incluye los efectos que deben completarse, no solo la cantidad de mensajes que un worker logra leer.

Con tasas aproximadamente constantes, durante un intervalo en el que la entrada supera al procesamiento:

aumentoDeBacklog ≈ (entradaPorSegundo - finalizaciónPorSegundo) * duración

Por ejemplo, una ráfaga de 3,000 mensajes por segundo durante 60 segundos, con capacidad de finalizar 2,000 por segundo, añade aproximadamente 60,000 mensajes pendientes. Si después la entrada baja a 1,000 por segundo y la capacidad sigue en 2,000, el excedente se drena en unos 60 segundos. Son supuestos de cálculo; los reintentos y las tareas de distinto coste pueden cambiar esa capacidad efectiva.

El backpressure limita cuánto trabajo entra o permanece en ejecución cuando la capacidad está comprometida. Puede reducir el ritmo de producción, acotar solicitudes concurrentes o rechazar nuevas operaciones con una respuesta que comunique la condición de admisión. La política debe considerar el tiempo de espera tolerable y la equidad entre clientes.

El autoscaling ayuda mientras haya paralelismo y capacidad en las dependencias. Añadir workers contra una base de datos saturada puede empeorar el problema. Los límites de concurrencia y de mensajes en vuelo protegen esas dependencias, mientras la antigüedad de la cola y el lag ayudan a decidir cuándo escalar o limitar admisión.

Particiones, claves y orden

En Kafka, un topic puede dividirse en particiones y un grupo de consumo convencional reparte esas particiones entre consumidores. Cada partición se asigna a un consumidor del grupo a la vez. Si hay más consumidores que particiones, algunos pueden quedar sin asignación; esto limita el paralelismo útil de ese modelo, pero no prohíbe iniciar más consumidores ni describe todas las colas de mensajes.

La clave de partición determina dónde se colocan mensajes relacionados bajo la estrategia elegida. Un account_id puede mantener los registros de una cuenta en una misma partición mientras la configuración de particionado conserve esa asignación. Si una cuenta concentra gran parte del tráfico, también puede producir una partición caliente: conservar esa secuencia y repartir su carga exige resolver un compromiso explícito.

El orden del log es por partición. Para preservar el orden de los efectos, el consumidor debe procesar la secuencia de manera compatible y manejar reintentos, rebalanceos y operaciones antiguas. Un handler que lanza tareas concurrentes puede finalizar el segundo evento antes que el primero aunque los haya leído en orden.

La clave tampoco establece qué mensaje debe publicarse primero. El productor debe preservar la secuencia relevante para el negocio; el broker conserva el orden en que los registros se incorporan a la partición.

Por ejemplo, una cuenta con saldo inicial de $0 tiene dos operaciones pendientes de aplicar, sin permitir sobregiros. Ambos mensajes usan account_id = 42:

Secuencia requeridaOperación pendienteSaldo después de aplicarla
1Depositar $100.$100.
2Retirar $50.$50.

Si el depósito se publica primero y el consumidor aplica las operaciones en ese orden, el saldo final es $50. Aplicar el retiro antes del depósito puede provocar un rechazo por fondos insuficientes. Dirigir ambos mensajes a una misma partición permite conservar su secuencia, pero todavía exige que el productor y el consumidor mantengan ese orden.

Una secuencia por cuenta asignada por el servicio que controla las operaciones permite detectar que la operación 2 tiene un predecesor pendiente. El consumidor debe esperar o reconciliar ese caso antes de aplicarla, y deduplicar cada operación mediante una identidad estable. El cambio del saldo y sus condiciones se validan atómicamente en el sistema autoritativo. Este ejemplo trata solicitudes pendientes; si los mensajes describen movimientos ya confirmados, el consumidor reconstruye una proyección de esos movimientos.

El alcance de la regla de negocio también importa. Mantener eventos de una cuenta en orden no hace atómica una transferencia entre dos cuentas. Esa operación requiere una coordinación que preserve sus invariantes.

En otros modelos, el escalado y el orden se expresan mediante workers competidores, varias colas o grupos de mensajes. La regla de un consumidor por partición, dentro de un mismo grupo, debe aplicarse al modelo que realmente la ofrece.

Garantías de entrega y recuperación

Las garantías de entrega describen qué puede ocurrir con un mensaje durante su transmisión y recuperación. Para interpretarlas debe establecerse el alcance: publicación, entrega al consumidor o resultado confirmado del procesamiento. Recibir un mensaje y completar su efecto de negocio son hechos distintos.

ModeloContrato dentro del alcance definidoConsecuencia para la aplicación
At least once — al menos una vezUna o más entregas bajo las condiciones de recuperación acordadas.Debe tolerar duplicados y coordinar efectos idempotentes.
At most once — como máximo una vezCero o una entrega; se admite que el trabajo no llegue.Debe aceptar la pérdida y evitar repeticiones dentro de ese alcance.
Exactly once — exactamente una vezUn único resultado observable confirmado por registro dentro de una frontera definida.Requiere coordinar estado, progreso y efectos cubiertos por la garantía.

La entrega al menos una vez requiere confirmaciones, reintentos y almacenamiento compatibles con los fallos y plazos definidos. Los consumidores deben asumir que un mensaje puede volver a procesarse. El caso central ocurre cuando se completa el efecto y se pierde la confirmación: RabbitMQ puede volver a entregar un mensaje sin ack; en Kafka, un grupo puede releer registros cuyo progreso no quedó confirmado.

Confirmar antes de completar el efecto puede dejar trabajo sin realizar después de un fallo. El modelo como máximo una vez puede encajar con algunas métricas o telemetría cuando se acepta explícitamente esa pérdida. Un envío fire and forget describe una interacción sin esperar confirmación; por sí solo no define todas las garantías del broker. En Kafka, confirmar un offset no elimina el registro: el grupo puede avanzar sin completar el trabajo aunque el dato siga disponible para replay.

Un consumidor idempotente puede repetir la misma operación sin añadir un efecto de negocio adicional. Una identidad estable permite reconocerla. Si el efecto está en una base de datos, el registro de deduplicación y el cambio local deben coordinarse atómicamente, por ejemplo en una misma transacción. Un cobro o una publicación externa necesita además una estrategia para consultar resultados inciertos y repetir la operación bajo las condiciones y vigencia de su contrato de idempotencia.

La garantía exactamente una vez no implica necesariamente ejecutar el handler una sola vez. Puede haber varios intentos y un único resultado confirmado. Kafka permite coordinar transaccionalmente registros producidos y offsets consumidos, con lectores read_committed que ven resultados confirmados. Esa frontera no incluye automáticamente una escritura en una base externa ni un cobro; esos efectos necesitan su propia coordinación.

Cuando recuperar trabajo es un requisito, at least once con efectos idempotentes suele ser una opción sólida para defender en una entrevista. Permite explicar cómo se recupera una confirmación perdida y cómo se evita repetir el efecto. La elección debe seguir los requisitos de pérdida tolerable, recuperación y alcance de los efectos.

Las reservas de entrega y los tiempos de visibilidad también requieren atención. Un plazo que vence puede permitir otro intento mientras el worker anterior sigue ejecutándose. Dimensionar o renovar ese plazo ayuda a reducir concurrencia accidental; la corrección sigue dependiendo de validar el estado y de coordinar los efectos repetidos.

DLQ y mensajes que fallan

Un mensaje venenoso, o poison message, provoca fallos repetidos por su contenido o por la forma en que lo procesa el consumidor. Puede tener un payload malformado, un esquema incompatible o activar un defecto del handler. Reintentarlo indefinidamente consume capacidad y puede detener el progreso. Una caída de infraestructura, en cambio, puede afectar mensajes válidos y requiere diagnosticar su causa.

Los errores temporales pueden recuperarse con reintentos acotados, espera creciente y jitter. La política define un límite de intentos, aclara si incluye el intento inicial y conserva su historial durante la recuperación. Al agotar ese presupuesto, el mensaje se deriva a una Dead Letter Queue (DLQ). Un error permanente, como un payload inválido que no puede procesarse, puede derivarse directamente sin repetir intentos que producirían el mismo fallo.

La DLQ aísla el mensaje para que pueda continuar trabajo independiente y permite inspeccionar la causa. Debe conservar identidad, contenido original, error e historial de intentos, junto con alertas y un procedimiento de recuperación. Esos datos permiten distinguir un fallo del mensaje de un problema del consumidor.

La transferencia hacia ese destino debe preservar el contrato de entrega. Cuando publicar en la DLQ y confirmar el original son pasos separados, se confirma el destino antes de dar el original por resuelto; un fallo entre ambas acciones todavía puede duplicar la derivación. En Kafka, la DLQ suele ser otro topic y necesita lógica explícita de publicación y avance del offset, que pueden coordinarse mediante una transacción Kafka dentro de su alcance.

Un administrador o una herramienta asistida por IA puede clasificar los fallos y proponer correcciones a partir de esos datos. Antes del replay se valida la causa y la corrección, se conserva la trazabilidad y se controla el ritmo de reentrada. Los intentos de la misma operación mantienen su identidad para seguir siendo idempotentes. Si la corrección cambia el significado de un hecho ya confirmado, debe representarse como una nueva operación vinculada al original.

Separar un mensaje fallido permite continuar trabajo independiente. Si los eventos posteriores dependen de ese mensaje, saltarlo puede romper la secuencia de negocio. En ese caso debe detenerse la secuencia afectada o definirse una reconciliación antes de continuar. La DLQ conserva el problema para resolverlo; no completa automáticamente la operación original.

Elegir el modelo y la tecnología

La elección debe partir de si se distribuyen tareas dirigidas o se conserva un flujo para lectores independientes. Después se concretan enrutamiento, retención, recuperación, orden y carga.

TecnologíaModelo que puede resultar útilDecisiones que permanecen
KafkaLog particionado, grupos independientes y replay de datos retenidos.Claves, particiones, offsets, retención y orden de procesamiento.
SQSColas gestionadas para trabajo asíncrono.Modalidad Standard o FIFO, visibilidad, duplicados y orden por grupo cuando aplica.
RabbitMQEnrutamiento mediante exchanges y colas de tareas; Streams ofrece otro modelo.Tipo de cola, confirmaciones, concurrencia y recuperación de mensajes fallidos.

Un servicio gestionado reduce parte de la operación de infraestructura. El contrato de la aplicación sigue necesitando límites de capacidad, idempotencia y tratamiento de los fallos. Ninguna tecnología sustituye esas decisiones.

Qué defender en una entrevista

El diseño debe permitir recorrer una solicitud desde que se acepta hasta que termina, y después repetir el recorrido introduciendo un fallo. Las decisiones que conviene justificar son:

  • Dónde queda persistido el trabajo antes de responder y cómo se consulta su resultado.
  • Cuánto backlog produce una ráfaga y qué tiempo de espera puede admitirse.
  • Qué operaciones deben mantener orden y cómo se reparte su carga.
  • Qué ocurre si se completa un efecto y se pierde su confirmación.
  • Cuándo se limita admisión, se reintenta o se deriva un mensaje a la DLQ.

Las métricas útiles son antigüedad del trabajo pendiente, profundidad de cola, mensajes en vuelo, lag por grupo o partición, latencia de extremo a extremo y efectos duplicados. También deben observarse los mensajes pendientes de recuperación y los consumidores detenidos: una cola vacía puede coexistir con trabajo que terminó en una DLQ sin resolverse.