# 🏥 /health Endpoint Enhancement
**Issue:** [P3.2](#)
**Prioridad:** Media
**Estimación:** 6-8 horas
---
## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago?
El endpoint `/health` es minimal — solo `{"status": "ok"}`. En producción, necesitamos visibilidad de cada dependencia, SLOs y capacidad para diagnosticar degradación.
**What:** Expandir `/health` con 4 secciones estructuradas (services, SLOs, capacity, metadata).
**How:**
1. Probar latencia de cada dependencia (Qdrant, Anthropic, embeddings)
2. Calcular SLOs en tiempo real (availability, latency p99, cache hit rate)
3. Reportar utilización de recursos (memory, cache entries, knowledge entries)
**Why:** Kubernetes readinessProbe/livenessProbe necesita detail; alerting basado en `/health` sin detail es ciego.
---
## 📊 Response Schema
```json
{
"status": "healthy",
"timestamp": "2026-09-09T12:34:56.789Z",
"version": "1.2.0",
"uptime_seconds": 86400,
"services": {
"qdrant_cache": {
"status": "UP",
"latency_ms": 45,
"last_check": "2026-09-09T12:34:50Z",
"reachable": true
},
"qdrant_knowledge": {
"status": "UP",
"latency_ms": 52,
"last_check": "2026-09-09T12:34:50Z",
"reachable": true
},
"anthropic": {
"status": "UP",
"latency_ms": 250,
"last_check": "2026-09-09T12:34:55Z",
"reachable": true,
"rate_limit_remaining": 4950
},
"embeddings": {
"status": "UP",
"model": "nomic-embed-text-v1.5",
"inference_latency_ms": 120
}
},
"slos": {
"availability_pct": 99.82,
"availability_status": "PASS",
"latency_p99_ms": 1850,
"latency_status": "PASS",
"cache_hit_rate_pct": 72.3,
"cache_hit_status": "PASS"
},
"capacity": {
"memory_mb": 234,
"memory_limit_mb": 512,
"memory_utilization_pct": 45.7,
"cache_entries": 1200,
"knowledge_entries": 450,
"log_buffer_size": 2340
},
"checks_performed": {
"qdrant_connectivity": "PASS",
"anthropic_auth": "PASS",
"embeddings_model_loaded": "PASS",
"disk_space": "PASS",
"certificate_expiry": "PASS"
}
}
```
---
## 🟢 Status Codes
| Status Code | Significado | Caso |
| --- | --- | --- |
| **200** | Healthy | Todas las dependencias UP, SLOs pass |
| **200 + warn** | Degraded pero operable | 1 dependencia lenta, SLO amarillo |
| **503** | Unhealthy | Dependencia DOWN, SLO rojo |
---
## 📝 Latencia de Health Check
- `GET /health` debe responder en <100ms (sin esperar a Qdrant)
- Latencies de dependencias calculadas en background (async cache)
- Fallback: si check reciente existe (<5s), usarlo; sino, skip ese service
**Implementación:**
```python
@app.get("/health")
async def health() -> dict:
"""Health check rápido con datos cacheados."""
cached = await get_health_cache() # <1ms (redis/memory)
# Información que siempre tenemos (no requiere I/O)
return {
"status": cached.get("overall_status", "healthy"),
"services": cached.get("services", {}),
"slos": cached.get("slos", {}),
"capacity": get_current_capacity(), # instant (memory.usage())
}
```
**Background task (cada 5 segundos):**
```python
async def update_health_cache():
"""Actualizar cache de health en background."""
while True:
services = {}
services["qdrant_cache"] = await check_qdrant_cache() # async
services["qdrant_knowledge"] = await check_qdrant_knowledge()
services["anthropic"] = await check_anthropic()
cache_health = {
"services": services,
"slos": calculate_slos(),
"timestamp": datetime.utcnow().isoformat(),
}
await set_health_cache(cache_health)
await asyncio.sleep(5)
```
---
## 🧪 Test Cases
| Caso | Escenario | Expected | Status |
| --- | --- | --- | --- |
| TC1 | All services UP | status=healthy, 200 OK | ✅ |
| TC2 | Qdrant DOWN | status=degraded, 503 | ✅ |
| TC3 | Anthropic rate limited | rate_limit_remaining=0, warn | ✅ |
| TC4 | Response <100ms | latency <100ms | ✅ |
| TC5 | Memory at 90% | capacity.utilization_pct=90 | ✅ |
| TC6 | SLO threshold violated | slos.status=FAIL | ✅ |
| TC7 | No requests since startup | availability_pct=100 | ✅ |
| TC8 | Schema validation | JSON schema pass | ✅ |
| TC9 | Certificate expiry check | checks_performed.certificate_expiry | ✅ |
| TC10 | Cache entries accurate | matches actual_cache.size | ✅ |
| TC11 | Concurrent health requests | All get cache, no contention | ✅ |
| TC12 | Health after dependency recovery | Status goes UP | ✅ |
---
## 🔗 Kubernetes Integration
```yaml
# deployment.yaml
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 2
timeoutSeconds: 3
# Ready si status code 200 y status != "DOWN"
```
---
## 📊 Monitoreo en Prometheus
```promql
# Latency de Qdrant
probe_latency_ms{service="qdrant_cache"}
# Tasa de aciertos de caché
slo_cache_hit_rate_pct
# Disponibilidad observada
slo_availability_pct
# Utilización de memoria
health_memory_utilization_pct
```
---
## ✅ Criterios de Aceptación
- ✅ `/health` expone 4 secciones (services, SLOs, capacity, checks)
- ✅ Responde en <100ms siempre
- ✅ Detección automática de degradación
- ✅ Latencies reales para cada dependencia
- ✅ Tests: 12/12 pass
- ✅ Schema JSON validable
---
## 📝 Próximos Pasos
1. Crear rama: `GH-XXX-health-enhancement`
2. Implementar background cache updater
3. Expandir `/health` endpoint
4. Escribir 12 tests
5. Integrar con Kubernetes probes
6. PR → merge
7. Deploy y validar latencias en staging