🦀 Caso 15 — Rust 1.83#
⬅️ Caso 15 · ⚖️ Comparativa de los 7 stacks · 🦀 Perfil de Rust · 🧬 Todos los perfiles
Stack Rust del caso 15. Cola sin capacidad contra cola acotada con polĂtica explĂcita.
El lĂmite está en el tipo, no en un parámetro#
let (tx, rx) = mpsc::channel::<Msg>(); // Sender<T> — sin capacidad
let (tx, rx) = mpsc::sync_channel::<Msg>(32); // SyncSender<T> — acotadoSon tipos distintos con mĂ©todos distintos. No se puede «olvidar» el lĂmite de un canal acotado ni pedirle backpressure a uno sin lĂmite: el compilador no deja escribir la confusiĂłn.
Comparar con Java es directo: allá ConcurrentLinkedQueue y ArrayBlockingQueue implementan la misma interfaz Queue, asĂ que cambiar una por otra es una lĂnea que compila y saca el freno del sistema. Acá eso no existe — el tipo del canal declara si hay freno o no.
El error de rechazo se lleva el mensaje adentro#
match tx.try_send(msg) {
Err(TrySendError::Full(msg)) => dlq.push(msg), // <- msg vuelve
...
}TrySendError::Full(T) devuelve la propiedad del valor rechazado. En Go o Java el mensaje descartado simplemente «sigue en tu mano» por convenciĂłn; acá el tipo garantiza que no se perdiĂł en el intento y que hay que decidir explĂcitamente quĂ© hacer con Ă©l.
Es exactamente lo que una dead letter queue necesita: el mensaje llega entero, sin clonarlo por las dudas antes de intentar el envĂo.
Primitivas nativas#
| Primitiva | Rol |
|---|---|
mpsc::sync_channel(N) → SyncSender<T> | La cola acotada. El lĂmite es parte del tipo. |
mpsc::channel() → Sender<T> | La versión sin capacidad, con otro tipo. |
TrySendError::Full(T) | El rechazo que devuelve el mensaje. |
AtomicI64::fetch_max | Profundidad pico y edad máxima sin lock. |
Rutas#
| Ruta | Qué muestra |
|---|---|
/health | liveness |
/produce-unbounded?messages=120&consume_ms=2 | queue_depth_peak = total producido; oldest_msg_age_ms_peak sin techo |
/produce-bounded?...&policy=block | profundidad acotada a capacity; producer_blocked_ms > 0, nada se pierde |
/produce-bounded?...&policy=drop_oldest | profundidad acotada; dropped > 0, el productor nunca se frena |
/produce-bounded?...&policy=dead_letter | profundidad acotada; dead_lettered > 0, revisable en /dlq |
/queue/state | profundidad pico, bytes ocupados y edad del mensaje más viejo |
/dlq?limit=20 | contenido de la dead letter queue |
/diagnostics/summary | acumulado por variante y polĂtica |
/reset-lab | limpia DLQ y contadores |
Parámetros: messages (1–2000), capacity (1–1000), consume_ms (0–100, cuánto tarda el consumidor por mensaje), policy (block · drop_oldest · dead_letter).
Hub#
docker compose -f compose.rust.yml up -d --build
curl "http://127.0.0.1:8700/15/produce-unbounded?messages=120&consume_ms=2"
curl "http://127.0.0.1:8700/15/produce-bounded?messages=120&capacity=32&policy=block&consume_ms=2"
curl "http://127.0.0.1:8700/15/queue/state"La lecciĂłn que ningĂşn stack cambia#
Las tres polĂticas pagan algo distinto y ninguna es gratis:
| PolĂtica | QuĂ© paga |
|---|---|
block | latencia: la lentitud viaja aguas arriba hasta el cliente |
drop_oldest | datos: se pierden mensajes, y en silencio salvo que se cuenten |
dead_letter | deuda operativa: alguien tiene que mirar esa cola (caso 20) |
Una cola sin lĂmite parece una cuarta opciĂłn sin costo. No lo es: solo difiere el pago hasta el OOM.