La mayoría del trabajo de backend se divide en dos tipos: el trabajo que un cliente espera en este momento y el trabajo que simplemente necesita suceder eventualmente. El modelo productor-consumidor es el patrón detrás de este segundo tipo. Una parte de un sistema produce unidades de trabajo y las entrega; otra parte las consume de forma independiente, según su propio horario; y un búfer duradero que se encuentra entre los dos absorbe la falta de coincidencia en su ritmo. Las colas, los trabajadores, BullMQ, SQS e incluso los trabajos activados por cron son implementaciones específicas de esta idea subyacente.
Esta página es el modelo mental detrás del resto de la sección: por qué es importante desacoplar el productor del consumidor, qué garantiza realmente un intermediario sobre la entrega (y qué no), y el vocabulario que las páginas prácticas asumen que ya conoces. Conceptos básicos de colas muestra este patrón en código funcional de Express y BullMQ; esta página trata sobre la forma subyacente.
Un productor entrega el trabajo a un intermediario duradero en lugar de hacerlo en línea, y uno o más consumidores (trabajadores) retiran ese trabajo de forma independiente, desacoplando el ritmo del manejo de solicitudes del ritmo de procesamiento.
Por qué es importante: Sin ese búfer, cada pieza de trabajo lenta o en ráfagas tiene que ocurrir dentro de la solicitud que la activó, lo que significa que la latencia orientada al usuario hereda el peor caso de lo que sea más lento en el flujo descendente.
Conceptos clave:productor, consumidor/trabajador, intermediario, contrapresión, garantía de entrega, cola de mensajes fallidos.
Cuándo usarlo: Trabajo que es lento en relación con el presupuesto de latencia de una solicitud, trabajo que debe sobrevivir a un fallo o despliegue, trabajo con patrones de llegada en ráfagas que de otro modo abrumarían los sistemas descendentes, y trabajo que legítimamente no necesita terminar antes de responder al llamador.
Limitaciones / Compensaciones: Se sacrifica la consistencia inmediata y la depuración simple por la resiliencia y el escalado independiente: un sistema en cola tiene más partes móviles, una finalización eventual en lugar de inmediata, y garantías de entrega que son más débiles de lo que parecen.
Temas relacionados: idempotencia en el consumidor, reintentos y retroceso exponencial, programación y cron, disyuntores.
Imagina la cocina de un restaurante con un riel de tickets entre el comedor y la línea de cocción. Un camarero toma un pedido, escribe un ticket y lo engancha al riel, luego regresa directamente a la siguiente mesa sin esperar a que se cocine la comida. Los cocineros retiran los tickets del riel cada vez que terminan su plato actual, los procesan aproximadamente en el orden en que llegaron, y nadie en el comedor se bloquea por el ritmo de un solo cocinero. El riel es el búfer que permite que la toma de pedidos y la cocina funcionen a dos velocidades completamente diferentes sin que una detenga a la otra.
Ese es el modelo productor-consumidor. El productor es la parte de tu sistema que descubre que se necesita hacer un trabajo (un manejador HTTP que acaba de aceptar una solicitud, por ejemplo) y su único trabajo es describir ese trabajo y entregarlo. El intermediario es el riel duradero en sí: una cola que contiene cada unidad de trabajo (un trabajo o mensaje) hasta que algo esté listo para procesarlo, sobreviviendo a un reinicio de cualquiera de las partes. El consumidor, a menudo llamado trabajador, es un proceso separado que retira trabajos del intermediario y realiza el trabajo real, según su propio horario y a menudo como un desplegable completamente diferente al productor.
El cambio importante es que el productor deja de esperar. Un manejador HTTP que encola un trabajo puede responder al cliente en milisegundos (típicamente con un 202 Accepted y un ID de trabajo como recibo) en lugar de bloquearse por el tiempo que tarde el trabajo real. El cliente obtiene una respuesta inmediata y honesta ("He aceptado esto"), y el procesamiento real ocurre en una línea de tiempo completamente desacoplada.
La principal diferencia mecánica con una llamada a función normal es que un productor no obtiene un valor de retorno, sino un recibo. Algo como esto es todo el contrato:
// Productor: entrega una descripción del trabajo, obtiene un ID de vuelta inmediatamenteconst job = await emailQueue.add("send-invite", { to, orgId });// job.id es un recibo, no un resultado; el trabajo aún no ha ocurrido
Lo que el intermediario promete sobre la entrega de ese trabajo es la parte que la gente suele entender mal. Las colas distribuidas ofrecen tres garantías teóricas: como máximo una vez (un trabajo podría desaparecer silenciosamente, pero nunca se ejecuta dos veces), al menos una vez (un trabajo está garantizado para ser intentado, pero podría ejecutarse más de una vez), y exactamente una vez (cada trabajo se ejecuta precisamente una vez, sin duplicados, sin pérdidas). Casi todos los intermediarios reales (BullMQ, SQS y la mayoría de los demás) por defecto son al menos una vez, porque exactamente una vez requiere coordinar el intermediario y los efectos secundarios del consumidor como una única transacción atómica, lo cual no es factible a través de una red en el caso general. Por eso Claves de Idempotencia existe como su propia página: la entrega al menos una vez traslada la responsabilidad de la corrección al consumidor, que tiene que tratar "procesar este trabajo dos veces" como un caso esperado, no como un caso excepcional.
El mecanismo que hace que "al menos una vez" funcione es un arrendamiento, a veces llamado tiempo de espera de visibilidad: cuando un trabajador toma un trabajo, el intermediario lo oculta de otros trabajadores durante un período limitado en lugar de eliminarlo directamente. Si el trabajador reconoce la finalización antes de que expire el arrendamiento, el trabajo se elimina definitivamente. Si no lo hace, porque el trabajador falló, o el proceso fue terminado a mitad del trabajo, o el despliegue reinició el pod, el arrendamiento expira y el intermediario hace que el trabajo sea visible nuevamente para que otro trabajador lo recoja. Esa es la mecánica detrás de "al menos una vez": el trabajo vuelve precisamente porque el intermediario no puede distinguir un trabajador lento de uno muerto.
La contrapresión es la otra cara del desacoplamiento. Una cola suaviza las ráfagas, pero no es infinita; la profundidad de la cola (cuántos trabajos están esperando) es la señal que te indica si los consumidores están siguiendo el ritmo de los productores. Una profundidad que crece constantemente significa que los consumidores se están quedando atrás, y a diferencia de un sistema síncrono donde eso se manifiesta inmediatamente como tiempos de espera, una cola puede enmascarar el problema por un tiempo al absorber la acumulación, que es exactamente por qué la profundidad de la cola debe estar en un panel de control junto con la concurrencia del trabajador, no dejarse que surja como un misterio horas después.
Debido a que el productor y el consumidor están desacoplados, escalan de forma independiente y a lo largo de diferentes ejes. Los productores, típicamente tu capa de API, escalan con el volumen de solicitudes; los consumidores escalan con el volumen y el costo del trabajo en sí, y un consumidor que consume mucha CPU (redimensionamiento de imágenes) a menudo necesita una forma de instancia completamente diferente a uno ligero (envío de un correo electrónico). Aquí es también donde el ordenamiento se vuelve sutil: una cola FIFO de una sola partición conserva un orden estricto pero limita el rendimiento a un trabajador a la vez para esa partición, mientras que una cola estándar (no FIFO) sacrifica el orden estricto por el consumo paralelo en muchos trabajadores. Las colas de prioridad se encuentran en la misma tensión: adelantar trabajos urgentes es fácil de añadir y fácil de abusar, y un flujo ilimitado de trabajo "urgente" dejará sin recursos a todo lo demás exactamente de la misma manera que lo haría un carril de prioridad no gestionado en la cocina.
Los trabajos que fallan repetidamente necesitan un lugar donde ir además de ser reintentados para siempre o eliminados silenciosamente: una cola de mensajes fallidos (DLQ) captura los mensajes que exceden su presupuesto de reintentos para que un humano pueda inspeccionar lo que realmente contenía un "mensaje venenoso", en lugar de perderlo o entrar en un bucle indefinidamente.
La programación merece una mención aquí porque es fácil pensar en ella como un concepto separado cuando en realidad es el mismo modelo con un disparador diferente: un trabajo activado por cron es un productor cuyo evento es "el reloj alcanzó esta hora" en lugar de "un usuario hizo esto". Programación y Cron cubre la complicación operativa que conlleva: sin un bloqueo de líder, cada réplica de un productor escalado dispara el mismo trabajo programado de forma redundante.
Estilo de intermediario
Fortaleza
Debilidad
Mejor ajuste
Basado en Redis (BullMQ)
Baja latencia, características de trabajo enriquecidas (prioridad, retardo, reintentos) de fábrica
Tú mismo ejecutas y operas Redis; no está diseñado para la durabilidad entre regiones
Trabajos en segundo plano a nivel de aplicación, rendimiento de pequeño a mediano
Cola en la nube gestionada (SQS)
Durabilidad y escalado totalmente gestionados, sin intermediario que operar
Conjunto de características más limitado; precios por solicitud y latencia de red adicional
Sistemas nativos de la nube ya en AWS, necesidades de alta durabilidad
Basado en registros (estilo Kafka)
Historial reproducible, muchos grupos de consumidores independientes sobre el mismo flujo
Mayor huella operativa; no está diseñado como una cola de tareas simple
Transmisión de eventos, registros de auditoría, múltiples equipos leyendo los mismos eventos
"Una cola garantiza el procesamiento exactamente una vez." Casi ninguna lo hace por defecto; la mayoría garantizan al menos una vez, lo que significa que tu consumidor debe ser idempotente, no el intermediario.
"Si la cola aceptó el trabajo, el trabajo está prácticamente hecho." La aceptación solo significa que el trabajo se registró de forma duradera; no dice nada sobre si un trabajador lo completa o cuándo.
"Que la profundidad de la cola crezca un poco está bien siempre que finalmente se vacíe." Una cola que nunca se vacía por completo durante el tráfico normal es un indicador principal de que los consumidores están infra dimensionados, no un problema que se corrige solo.
"El orden FIFO es el comportamiento predeterminado de una cola." Las colas estándar en la mayoría de los intermediarios sacrifican deliberadamente el orden estricto por el paralelismo; debes optar por la semántica FIFO y aceptar su límite de rendimiento.
"Los trabajadores tienen que vivir en el mismo proceso o contenedor que la API." El objetivo principal del modelo es que no lo hagan; el productor y el consumidor suelen ser desplegables separados escalados con diferentes señales.
¿Cuál es la diferencia entre una "cola" y un "intermediario"?
A menudo se usan indistintamente, pero estrictamente un intermediario es la pieza de infraestructura (Redis, SQS, Kafka) que gestiona el almacenamiento y la entrega de mensajes, mientras que una cola es un canal nombrado de trabajos dentro de ella; un solo intermediario puede alojar muchas colas separadas.
¿Por qué no simplemente procesar todo sincrónicamente y escalar la API en su lugar?
Escalar las réplicas de la API ayuda con el rendimiento, pero no soluciona la latencia de las operaciones lentas individuales, no sobrevive a un fallo a mitad de la operación y no te permite dimensionar la computación de manera diferente para solicitudes ligeras frente a trabajos pesados en segundo plano.
¿Qué significa realmente "entrega al menos una vez" en la práctica?
Significa que el intermediario garantiza que un trabajo se intentará al menos una vez, pero un fallo, un tiempo de espera o la expiración de un arrendamiento pueden hacer que se intente de nuevo, por lo que los efectos secundarios de tu trabajador deben ser seguros de repetir, no solo seguros de ejecutar una vez.
¿Cómo sabe un intermediario que un trabajador todavía está procesando un trabajo y no ha fallado?
No lo sabe directamente; se basa en un arrendamiento (tiempo de espera de visibilidad): el trabajador debe reconocer la finalización antes de que expire el arrendamiento, y si no lo hace, el intermediario asume lo peor y pone el trabajo a disposición de otro trabajador.
¿Es posible la entrega exactamente una vez?
No en el sentido distribuido general; lograrlo requeriría que el intermediario y el efecto secundario del consumidor se comprometieran como una operación atómica a través de una red, lo cual no está disponible en la práctica. Los sistemas que lo afirman casi siempre están haciendo entrega al menos una vez más deduplicación.
¿Qué debo observar realmente para saber si mis trabajadores están al día?
La profundidad de la cola a lo largo del tiempo y la antigüedad del trabajo (cuánto tiempo ha estado esperando el trabajo más antiguo); una profundidad plana o decreciente con una antigüedad de trabajo baja significa que los consumidores están al día; una profundidad que aumenta constantemente significa que no lo están.
¿Por qué los mensajes "venenosos" necesitan una cola de mensajes fallidos en lugar de simplemente reintentarse para siempre?
Un bucle de reintentos ilimitado en un trabajo que nunca puede tener éxito desperdicia la capacidad del trabajador indefinidamente y puede dejar sin recursos a los trabajos sanos que están detrás; una DLQ limita el presupuesto de reintentos y conserva el mensaje para su inspección en lugar de perderlo.
¿Es seguro confiar en las colas de prioridad?
Son útiles con moderación, pero cada trabajo marcado como "prioritario" está implícitamente despriorizando todo lo demás; si demasiado tráfico se marca como urgente, los trabajos de baja prioridad pueden quedarse sin recursos indefinidamente, lo que anula el propósito de tener prioridades.
¿Un trabajo cron es también un patrón productor-consumidor?
Sí, el programador actúa como el productor, disparándose por un disparador basado en el tiempo en lugar de un evento de usuario, y lo que ejecuta el trabajo sigue siendo un consumidor que extrae (o recibe) esa unidad de trabajo.
¿Por qué el procesamiento basado en colas cambia mi forma de pensar sobre los fallos?
Porque los fallos se vuelven explícitos e inspeccionables: un trabajo fallido se queda en algún lugar (reintentando o en una DLQ) en lugar de desaparecer en una respuesta 500 que el llamador tiene que interpretar y reintentar por sí mismo.
¿El productor y el consumidor necesitan estar escritos en el mismo lenguaje o framework?
No, solo necesitan acordar el protocolo del intermediario y la forma de la carga útil del trabajo, que es una de las razones por las que el modelo funciona bien para sistemas políglotas donde, por ejemplo, una API de Node encola el trabajo que consume un servicio de trabajador separado en otra pila.
¿Cuándo es una cola la herramienta equivocada?
Cuando el llamador realmente necesita el resultado antes de responder; la puesta en cola añade un viaje de ida y vuelta y una semántica de finalización eventual que tiene sentido para el trabajo en segundo plano, pero añade latencia y complejidad innecesarias a una solicitud que es inherentemente síncrona.