🧪 Problem-Driven Systems Lab
🔎 Caso 19 · Observabilidad

Deriva del indice de busqueda y CDC roto

La busqueda responde 200 y lo que devuelve esta mal: el indice se desincroniza de a un documento por vez y nada dispara una alerta.

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

📈 Que cambia para el negocio

Elimina la categoria entera de 'el producto existe pero no aparece', que en un catalogo son productos que no se pueden comprar.

💼 Que demuestra tecnicamente

Muestra un modo de falla que no rompe nada: 98,95% de recall no se ve como un incidente, se ve como una busqueda que anda. Y ordena a los siete stacks por que hace cada lenguaje cuando el programador no mira el resultado de una escritura.

✅ Evidencia que deja

  • Contrasta /search-drifted y /search-reconciled con recall y precision medidos con consultas reales.
  • Separa las tres caras de la deriva: missing no se encuentra, stale se encuentra mal, orphan es fantasma.
  • Expone drift_age_ms, la metrica que dice si algo va a reparar la deriva solo.

👀 Que mirar al ejecutarlo

  • Si drift_count es mayor que cero en el dual-write y exactamente cero con outbox mas barrido.
  • Si recall y precision bajan lo suficiente para doler y poco para que nadie mire (98,95% y 98,02%).
  • Si el checkpoint se frena en el cambio que no entra en vez de saltearlo.
Honestidad: El indice de busqueda es un diccionario en memoria (un archivo JSON bajo flock en PHP), no Elasticsearch. Lo que define el caso no es el motor: es que la base y el indice son dos sistemas sin transaccion comun, y eso es igual de cierto con un dict. La falla de escritura es deterministica —multiplicador primo sobre el indice— para que los 7 stacks den resultados identicos y lo unico comparable sea como se escribe. drift_age_ms sale en milisegundos por la escala del laboratorio; en produccion se mide en minutos y horas.

Como esta resuelto en cada stack

Dual-write sin transaccion comun contra outbox + checkpoint durable + barrido de reconciliacion, con las tres caras de la deriva separadas (missing, stale, orphan). El caso ordena por una dimension que ningun otro usa: que hace el lenguaje cuando el programador no mira. Rust con #[must_use] y deny(unused_must_use) — el bug original NO compila —, Go con el _ = auditable y errcheck, .NET con Except/Join tipados y la pereza de LINQ como trampa, Python con el algebra de conjuntos mas corta del lab y el except desnudo mas corto tambien, PHP con checkpoint durable por obligacion del modelo share-nothing, Java con ConcurrentSkipListMap.tailMap y @Transactional que sugiere una atomicidad que no alcanza al indice, y Node donde el bug se produce por NO escribir await

StackHealth check localComposeDetalle
🐘 PHP 8.3http://localhost:8100/19/healthcompose.root.ymlREADME del stack
🐍 Python 3.12http://localhost:8200/19/healthcompose.python.ymlREADME del stack
🟢 Node.js 22http://localhost:8300/19/healthcompose.nodejs.ymlREADME del stack
☕ Java 21http://localhost:8400/19/healthcompose.java.ymlREADME del stack
🔵 .NET 8http://localhost:8500/19/healthcompose.dotnet.ymlREADME del stack
🐹 Go 1.23http://localhost:8600/19/healthcompose.go.ymlREADME del stack
🦀 Rust 1.83http://localhost:8700/19/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 18Caso 20 →