AI SREClickHouse Workshops

07 Probar, fallar y corregir

Notas del instructor para el módulo 07: tiempos, guion de presentación, fallos habituales y pasos de restablecimiento.

Tu equipo
Terminal de macOS: Ejecuta los comandos del taller en Terminal con zsh o bash.

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 --build

Fallo 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.query de ClickStack con error=True, error.category="query_failed" y db.statement que contiene pickup_zone_id AS zone_id; la excepción registrada es el Code 47 UNKNOWN_IDENTIFIER de 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_IDENTIFIER en pickup_zone_id -> DESCRIBE taxi_trips (o compara constructores de consultas similares) -> la columna real es pickup_location_id.
  • Corrección: cambia pickup_zone_id de nuevo a pickup_location_id en zone_stats_sql (o ejecuta git revert sobre 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.query con error.category="timeout", db.elapsed_ms cercano a 5000 y db.statement que muestra WHERE 1 ... GROUP BY ts HAVING ts >= ...; la excepción es el Code 159 TIMEOUT_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 HAVING y WHERE no contiene ningún intervalo de pickup_datetime -> un antipatrón que impide el descarte por clave primaria y el pushdown del predicado (las consultas similares colocan la ventana en WHERE).
  • 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_timeout en el cliente como max_execution_time en 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 devuelve text/html con 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 .geojson devuelve 200 text/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 revert sobre 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 ZoneMap se captura como un span console.error en ServiceName=nyc-taxi-frontend, junto con el span de recurso del 200 text/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):

Análisis de la causa raíz del fallo 01 por parte del agente: identifica que falta /static/taxi_zones.geojson y que se devuelve el index.html de la SPA como 200 text/html, relaciona el error de análisis JSON de ZoneMap capturado como un span console.error de nyc-taxi-frontend, lo contrasta con los spans correctos de /api y del back-end, descarta como ruido los errores de autocomprobación del SDK y propone publicar el recurso o proteger el análisis

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/html mediante 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 respuesta text/html.
  • Se olvida --build despué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-dashboard y 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.

En esta página

ES