🧪 Problem-Driven Systems Lab
🌊 Caso 15 · Resiliencia

Backpressure en colas de mensajes

Productores mas rapidos que consumidores: la cola sin limite crece hasta el OOM y la acotada obliga a elegir entre frenar, perder o mudar el problema.

OPERATIVO 🐘 PHP🐍 Python🟢 Node.js☕ Java🔵 .NET🐹 Go🦀 Rust

📈 Que cambia para el negocio

Evita caidas por memoria y perdidas silenciosas de mensajes, y obliga a decidir explicitamente que se sacrifica cuando el consumidor no da abasto.

💼 Que demuestra tecnicamente

Muestra que el throughput se ve perfecto hasta el segundo antes del OOM, y que las tres formas de acotar una cola pagan cosas distintas: latencia, datos o deuda operativa.

✅ Evidencia que deja

  • Contrasta /produce-unbounded y /produce-bounded con las tres politicas sobre la misma carga.
  • Hace visible queue_depth_peak, oldest_msg_age_ms_peak y queue_bytes_peak: profundidad, latencia real y memoria.
  • Expone messages_dropped_total y dlq_depth para que ninguna perdida quede sin contar.

👀 Que mirar al ejecutarlo

  • Si queue_depth_peak sin limite iguala al total producido: la cola absorbio todo.
  • Si oldest_msg_age_ms_peak es 3 o 4 veces mayor sin limite que con limite, con el mismo throughput.
  • Si cada politica acotada paga algo distinto: producer_blocked_ms en block, dropped en drop_oldest, dlq_depth en dead_letter.
Honestidad: La cola vive dentro del proceso y los mensajes son objetos en memoria, no un broker real. El tiempo de consumo es una espera, que es el modelo fiel: un consumidor se demora esperando I/O, no quemando CPU. En PHP el productor y el consumidor son pasos del mismo bucle porque el lenguaje no tiene concurrencia dentro del proceso.

Como esta resuelto en cada stack

Cola acotada con la primitiva de cada runtime: el freno vive en listen.backlog de FPM en PHP, queue.Queue(maxsize=N) con la politica en la firma de put() en Python, stream.Writable con highWaterMark y el evento drain en Node, ArrayBlockingQueue con put/offer/offer(timeout) en Java, Channel.CreateBounded con BoundedChannelFullMode como enum del constructor en .NET, canal bufferizado con select y default en Go, y mpsc::sync_channel en Rust — donde el limite esta en el tipo y TrySendError::Full devuelve el mensaje rechazado adentro

StackHealth check localComposeDetalle
🐘 PHP 8.3http://localhost:8100/15/healthcompose.root.ymlREADME del stack
🐍 Python 3.12http://localhost:8200/15/healthcompose.python.ymlREADME del stack
🟢 Node.js 22http://localhost:8300/15/healthcompose.nodejs.ymlREADME del stack
☕ Java 21http://localhost:8400/15/healthcompose.java.ymlREADME del stack
🔵 .NET 8http://localhost:8500/15/healthcompose.dotnet.ymlREADME del stack
🐹 Go 1.23http://localhost:8600/15/healthcompose.go.ymlREADME del stack
🦀 Rust 1.83http://localhost:8700/15/healthcompose.rust.ymlREADME del stack

El expediente completo

El caso no empieza en el codigo: empieza en el sintoma y termina en el postmortem.

← Caso 14Caso 16 →