문제 발견
Contents Hub 프로젝트에서 /health 엔드포인트가 단순히 {"status": "ok"}만 반환하고 있었다. 서버 프로세스는 떠있지만 DB 연결이 끊기거나 Redis가 죽어도 health check는 "ok"를 반환했다.
외부 모니터링(UptimeRobot 같은)에서는 200 응답만 보고 "정상"으로 판단하지만, 실제로는:
- DB lock으로 크롤러가 멈춰있거나
- Redis 장애로 캐시가 불가능한 상태
이런 "서버는 살았지만 서비스는 불가"한 상태를 감지하지 못했다.
핵심 인사이트
Health check는 서버 프로세스가 아니라 "서비스가 제공 가능한지"를 검증해야 한다.
얕은 Health Check의 문제
# Before: 서버만 확인
@app.get("/health")
async def health():
return {"status": "ok"}
이 방식의 문제:
- FastAPI가 HTTP 요청을 받을 수 있는지만 확인
- DB/Redis 연결 여부는 검증 안 함
- 모니터링 시스템이 "정상"이라고 오판하게 만듦
개선된 Health Check
# After: 의존성까지 확인
@app.get("/health")
async def health():
checks = {"server": "ok"}
# DB 연결 확인
try:
async with AsyncSessionLocal() as session:
await session.execute(text("SELECT 1"))
checks["db"] = "ok"
except Exception as e:
checks["db"] = f"error: {type(e).__name__}"
# Redis 연결 확인
try:
redis_client = aioredis.from_url(settings.redis_url)
await redis_client.ping()
checks["redis"] = "ok"
except Exception as e:
checks["redis"] = f"error: {type(e).__name__}"
status = "ok" if all(v == "ok" for v in checks.values()) else "degraded"
return {"status": status, "checks": checks}
Degraded 상태 패턴
이진 상태(ok/error)만으로는 부분 장애를 표현할 수 없다.
예: Redis는 죽었지만 DB는 살아있으면 읽기 전용 모드로 제한적 서비스 제공이 가능하다.
3단계 상태 모델:
ok: 모든 의존성 정상degraded: 일부 기능 불가, 서비스는 제공 가능error: 서비스 불가
{
"status": "degraded",
"checks": {
"server": "ok",
"db": "ok",
"redis": "error: ConnectionError"
}
}
운영자가 어느 부분이 문제인지 즉시 파악 가능하다.
보너스: 의존성 자동 시작
수동 절차("서버 시작 전에 Docker 컨테이너를 먼저 띄워야 한다")는 반드시 누락된다. 특히 드물게 실행하는 절차(서버 재시작)일수록 자동화가 필수다.
# start_server.sh
check_docker() {
local db_running=$(docker ps --filter "name=db" --filter "status=running" -q)
if [ -z "$db_running" ]; then
echo "Starting docker-compose..."
docker-compose up -d
sleep 3
fi
}
start_server() {
check_docker # 의존성 자동 확인/시작
# ... 서버 시작
}
"사람은 실수하지만 스크립트는 일관적"
요약
- 얕은 health check는 장애를 숨긴다 - "응답한다 ≠ 제대로 작동한다"
- 의존성을 개별적으로 검증하라 - DB는
SELECT 1, Redis는PING - Degraded 상태로 부분 장애를 표현하라 - 불필요한 전체 서비스 중단 방지
- 의존성 시작을 자동화하라 - 수동 절차는 반드시 누락됨