07 Probar, fallar y corregir
Notas del instructor para el módulo 07: tiempos, guion de presentación, fallos habituales y pasos de restablecimiento.
Material del facilitador correspondiente a la lección 07 Probar, fallar y corregir del participante.
Tiempos
Unos 20 minutos. Este es el laboratorio de incidentes; protege tiempo suficiente para llegar al diagnóstico antes del módulo 08 y el cierre. El laboratorio ejecuta el fallo 01 (diagnóstico de 5 a 15 min). Los fallos 02 (5 a 10 min) y 03 (10 a 20 min, y solo con el conjunto completo de aproximadamente 30 millones de filas) siguen disponibles como incidentes adicionales opcionales para un grupo rápido. Define una hora límite durante el ensayo para que el cierre no quede comprimido.
Guion de presentación
- Esta es la recompensa: usa todo lo construido hasta ahora para gestionar un incidente real.
- Describe el síntoma, no la causa, y deja que los agentes lleguen a una conclusión a partir de la telemetría.
- Considera mostrar en paralelo los diagnósticos de varios participantes en el proyector.
- El laboratorio ejecuta el fallo 01. Si hay tiempo para una ronda adicional, añade el fallo 02 (y el fallo 03 solo cuando se haya cargado previamente el conjunto de datos completo); entre fallos, usa el restablecimiento compartido para guardar cambios y cambiar de branch que aparece a continuación.
Solucionario
No compartas esta sección con los participantes; solo se encuentra en esta guía y nunca se
añade al repositorio de la aplicación. Cada fallo es un pequeño cambio en su propio branch, creado a partir de
build-workshop-v1;
la corrección consiste en revertirlo. Ejecuta un fallo cada vez. El incidente del laboratorio es el fallo 01 (5 a 15 min,
con una sorpresa: el back-end no tiene la culpa). Extras opcionales para un grupo rápido: fallo 02 (5 a 10 min,
calentamiento; el trace lo revela todo) y fallo 03 solo cuando esté disponible el conjunto completo de datos
(10 a 20 min, razonamiento más profundo sobre ClickHouse).
Restablecimiento compartido para todos los fallos (el stash conserva los cambios de los participantes y evita que se bloquee el cambio de branch):
git stash push --include-untracked -m "module-07-fix"
git switch build-workshop-v1
docker compose --env-file .env.workshop \
-f docker-compose.workshop.yml -f docker-compose.otel.yml up -d --buildFallo 02: las estadísticas de zona devuelven 500 (fault/02-zone-stats-500)
Mensaje del commit que ven los participantes: «backend: align zone stats column names with API params»
(solo modifica backend/app/query_builders.py y zone_stats_sql). Agrupa por
pickup_zone_id, una columna que no existe en taxi_trips, en lugar de
pickup_location_id.
- Síntoma: el mapa coroplético está vacío (se muestran los polígonos, pero todas las zonas quedan en el
intervalo más claro) y falta la leyenda «Query Nms»; aparecen ráfagas de errores 500 en
GET /api/metrics/zone_stats(React Query lo intenta tres veces). Las demás tarjetas funcionan correctamente. - Dónde está la señal: un span
clickhouse.queryde ClickStack conerror=True,error.category="query_failed"ydb.statementque contienepickup_zone_id AS zone_id; la excepción registrada es el Code 47UNKNOWN_IDENTIFIERde ClickHouse. Registro ERROR correspondiente: «ClickHouse query failed (category=query_failed, elapsed_ms=...): ... | sql=...». - Ruta de diagnóstico: encuentra el span con error 500 -> lee
db.statement->UNKNOWN_IDENTIFIERenpickup_zone_id->DESCRIBE taxi_trips(o compara constructores de consultas similares) -> la columna real espickup_location_id. - Corrección: cambia
pickup_zone_idde nuevo apickup_location_idenzone_stats_sql(o ejecutagit revertsobre el commit del fallo); vuelve a compilar el back-end. - Restablecimiento: usa el restablecimiento compartido anterior.
Fallo 03: panel lento (fault/03-slow-dashboard)
Mensaje del commit: «backend: window the trend series by bucket in HAVING» (modifica
timeseries_sql). Traslada el predicado de la ventana temporal de WHERE a un HAVING aplicado al
alias del intervalo, por lo que WHERE se convierte en 1. ClickHouse ya no puede descartar datos mediante la
clave primaria (car_type, pickup_datetime) y hace una lectura completa de taxi_trips con dos
estados quantileTDigest, lo que supera el max_execution_time de 5 s.
- Síntoma: solo falla la tarjeta de tendencia «What's happening now?»: gira y después muestra «504 Query timed out...». Las demás tarjetas siguen siendo rápidas.
- Dónde está la señal: un span
clickhouse.queryconerror.category="timeout",db.elapsed_mscercano a 5000 ydb.statementque muestraWHERE 1 ... GROUP BY ts HAVING ts >= ...; la excepción es el Code 159TIMEOUT_EXCEEDED. El contraste con los spans 200 rápidos de los otros endpoints es la pista decisiva. - Ruta de diagnóstico: una tarjeta lenta entre otras rápidas -> abre el span con timeout -> lee el
SQL -> el filtro temporal está en
HAVINGyWHEREno contiene ningún intervalo depickup_datetime-> un antipatrón que impide el descarte por clave primaria y el pushdown del predicado (las consultas similares colocan la ventana enWHERE). - Corrección: restaura la ventana temporal en
WHERE(revierte el commit del fallo); vuelve a compilar el back-end. - Restablecimiento: usa el restablecimiento compartido anterior.
- Advertencia para el instructor: la gravedad depende del volumen de datos y el tamaño del servicio. Con el conjunto completo
cargado (unos 30 millones de filas) en un servicio del nivel del taller (que puede estar reactivándose tras un periodo de inactividad), el
timeout de 5 s se produce de forma fiable; con la pequeña carga de muestra, la consulta solo es más lenta y no genera necesariamente un error 504.
Tanto
send_receive_timeouten el cliente comomax_execution_timeen el servidor son de 5 s, por lo que una carrera entre timeouts de socket puede aparecer ocasionalmente como un error 500 en vez del 504 limpio: es una propiedad de la configuración base, no del fallo.
Fallo 01: el mapa no se carga (fault/01-map-not-loading)
Mensaje del commit: «frontend: serve map geojson from /static asset path» (modifica la ruta de fetch
en frontend/src/ui/ZoneMap.tsx). Solicita /static/taxi_zones.geojson, que no
existe. Matiz importante: el mecanismo de reserva para SPA de nginx (try_files ... /index.html) devuelve HTTP 200
con el documento HTML, en lugar de 404, por lo que r.ok pasa y r.json() lanza un
SyntaxError como Unexpected token '<'.
- Síntoma: la tarjeta del mapa no funciona; aparecen las teselas base, pero no hay polígonos de Nueva York ni mapa coroplético, y se muestra un error rojo en la propia tarjeta. Todo lo demás funciona.
- Dónde está la señal: no aparece nada en ClickStack del back-end, porque el recurso nunca llega a
FastAPI. La consola del navegador muestra el error de análisis de JSON que menciona
/static/taxi_zones.geojson; la pestaña Network muestra que la solicitud devuelvetext/htmlcon estado 200; el registro de acceso de nginx muestra el mecanismo de reserva. - Ruta de diagnóstico: los traces del back-end están limpios -> cambia a la consola del navegador o a la pestaña Network
-> una solicitud
.geojsondevuelve 200text/html-> reconoce la trampa del mecanismo de reserva de la SPA -> el archivo está en realidad en la raíz web,/taxi_zones.geojson. - Corrección: revierte la ruta de fetch (dos líneas) o ejecuta
git revertsobre el commit del fallo; vuelve a compilar el front-end. - Restablecimiento: usa el restablecimiento compartido anterior.
- Idea didáctica: no todos los fallos aparecen en los traces del back-end, y un 404 puede disfrazarse de 200 tras el mecanismo de reserva de una SPA; por eso hay que leer el cuerpo real de la respuesta y el tipo de contenido, no solo el código de estado.
- Nota (SDK del navegador): con el SDK de navegador de HyperDX habilitado (superposición del módulo 05), el
front-end ahora refleja este problema en el propio ClickStack: el fallo de análisis de
ZoneMapse captura como un spanconsole.errorenServiceName=nyc-taxi-frontend, junto con el span de recurso del 200text/html. Así, el agente puede localizarlo mediante la telemetría sin salir de ClickStack; la consola del navegador o la pestaña Network son una alternativa, no la única ruta.
Ejemplo de respuesta del agente (fallo 01, mediante ClickStack MCP):

Diagnóstico esperado: el agente relaciona el fallo con la solicitud de geojson que devuelve 200 text/html (mecanismo de reserva de la SPA), el error de JSON.parse resultante capturado en nyc-taxi-frontend, y recomienda publicar el recurso en /static/ o proteger la llamada .json().
Fallos habituales
- No hay suficiente telemetría de referencia para que el agente localice el problema; el módulo 05 debe haberse ejecutado pronto.
- Los participantes saltan a la corrección antes de confirmar la causa raíz con pruebas.
- El síntoma del fallo todavía no es visible porque la pila acaba de iniciarse o, para el fallo 03, porque se han cargado pocos datos históricos. Medición del ensayo: con la carga predeterminada de un mes (unos 3,17 millones de filas), el fallo por la diferencia entre WHERE y HAVING no se aprecia: la lectura completa tarda unos 300-560 ms, casi lo mismo que la referencia. Carga varios meses antes de demostrar el fallo 03, o utiliza el fallo 01 (que se reproduce al instante) y presenta el fallo 03 como una ilustración a escala.
- El fallo 01 no genera ningún trace en el back-end, y la solicitud incorrecta devuelve 200
text/htmlmediante el mecanismo de reserva de la SPA en lugar de 404; los participantes pueden buscar en los traces indefinidamente. Indícales que abran la consola del navegador o la pestaña Network, donde aparecen el error de análisis de JSON y la respuestatext/html. - Se olvida
--builddespués de cambiar a un branch de fallo, por lo que sigue ejecutándose la imagen anterior.
Pasos de restablecimiento
- El restablecimiento compartido para guardar cambios y cambiar de branch recupera la aplicación completa sin descartar el intento de corrección del participante.
- Vuelve a inyectar un fallo cambiando a
fault/01-map-not-loading/fault/02-zone-stats-500/fault/03-slow-dashboardy recompilando con los archivos de Compose del taller y de otel. - Los síntomas, las señales y las correcciones de cada fallo se encuentran en la sección Solucionario. Conserva el solucionario solo en esta guía; nunca se añade al repositorio de la aplicación.