"서버 프로세스는 떠 있는데, DB 커넥션이 끊겨서 에러만 뱉고 있습니다. 로드 밸런서는 이걸 아는지 모르는지 계속 요청을 보내네요."
운영 중인 서비스의 상태를 모니터링하기 위해 /health 같은 API를 만들어두곤 합니다. 하지만 이 API가 단순히 **"나 살아있어(200 OK)"**만 외쳐야 할까요, 아니면 **"DB도 연결됐고, 캐시도 정상이고, 디스크 공간도 충분해"**라고 보고해야 할까요?
이 목적에 따라 헬스 체크는 엄격하게 두 가지로 나뉩니다.
1. 방식 1: 라이브니스 체크 (Liveness Probe) - "생존 신고"
**"프로세스가 살아있는가?"**를 확인합니다. 이 체크가 실패하면 시스템(K8s)은 애플리케이션을 재시작(Restart) 시킵니다.
특징
- Shallow Check (얕은 검사): 외부 의존성(DB, Redis 등)을 검사하지 않습니다. 오직 '나 자신'이 응답 가능한 상태인지만 봅니다.
- 목적: 교착 상태(Deadlock)에 빠졌거나, 메모리 누수로 멈춰버린 프로세스를 감지하여 껐다 켜기 위함입니다.
코드 예시 (Spring Boot)
@RestController
public class HealthController {
// Liveness: 정말 간단하게 200만 리턴
@GetMapping("/health/liveness")
public ResponseEntity<String> liveness() {
return ResponseEntity.ok("ALIVE");
}
}
주의할 점
여기서 DB 연결을 검사하면 절대 안 됩니다.
만약 DB가 일시적으로 느려졌는데 라이브니스 체크가 실패한다면? 쿠버네티스는 멀쩡한 웹 서버를 죽이고 재시작합니다. 재시작된 서버가 다시 DB에 붙으려고 하면서 **DB 부하는 더 심해지고, 전체 서비스가 다운(Cascading Failure)**됩니다.
2. 방식 2: 레디니스 체크 (Readiness Probe) - "업무 준비 완료"
**"지금 요청을 처리할 수 있는가?"**를 확인합니다. 이 체크가 실패하면 시스템은 재시작하는 게 아니라, **로드 밸런서에서 이 서버를 제외(Traffic Cut-off)**시킵니다.
특징
- Deep Check (깊은 검사): DB, Redis, 외부 API 등 서비스 처리에 필수적인 의존성들이 모두 정상인지 확인합니다.
- 목적: 아직 부팅 중이거나(Warm-up), DB 연결이 끊겨서 에러를 낼 수밖에 없는 서버에는 트래픽을 보내지 않기 위함입니다.
코드 예시
@RestController
@RequiredArgsConstructor
public class HealthController {
private final DataSource dataSource;
// Readiness: DB 연결까지 확인
@GetMapping("/health/readiness")
public ResponseEntity<String> readiness() {
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(1)) {
return ResponseEntity.ok("READY");
}
} catch (Exception e) {
// DB 연결 안 되면 503 Service Unavailable 반환
// -> 로드 밸런서가 트래픽을 차단함 (재시작 X)
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("NOT_READY");
}
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("NOT_READY");
}
}
동작 시나리오
DB 점검으로 인해 연결이 끊기면 Readiness가 실패합니다. 로드 밸런서는 해당 서버로 가는 트래픽을 막습니다. 서버는 살아있지만 "휴식 중" 상태가 됩니다. DB 점검이 끝나고 연결이 복구되면 다시 200 OK가 나가고, 로드 밸런서는 다시 트래픽을 투입합니다.
3. 실무 예시: Spring Boot Actuator
스프링 부트는 이 패턴을 완벽하게 지원하는 Actuator 라이브러리를 제공합니다.
- 설정 (application.yml):
management:
endpoint:
health:
probes:
enabled: true # 쿠버네티스용 프로브 활성화
- 자동 생성되는 URL:
- /actuator/health/liveness: 애플리케이션 상태만 확인 (OK/BROKEN)
- /actuator/health/readiness: DB, Disk, Ping 등 모든 통합 상태 확인 (OK/OUT_OF_SERVICE)
요약: 재시작할 것인가, 제외할 것인가?
| 구분 | Liveness Probe (생존 확인) | Readiness Probe (준비 확인) |
| 질문 내용 | "너 숨 쉬고 있니?" (Process Running?) | "일할 준비 됐니?" (Dependencies OK?) |
| 실패 시 동작 | 컨테이너 재시작 (Restart) | 트래픽 유입 차단 (Remove from LB) |
| 검사 범위 | 애플리케이션 자체 (Deadlock 감지) | DB, Cache, 외부 API 등 연동 상태 |
| 구현 방식 | 즉시 200 OK 반환 (가볍게) | 모든 의존성 연결 확인 (무겁게) |
| 안티 패턴 | 여기서 DB를 검사하면 무한 재부팅 발생 | 여기서 검사를 안 하면 에러 응답이 유저에게 감 |
결론
헬스 체크를 하나만 만들어서(GET /health) 퉁치지 마세요.
- Liveness: "프로세스가 멈췄을 때만" 실패하게 만드세요.
- Readiness: "DB나 외부 요인 때문에 에러가 날 것 같을 때" 실패하게 만드세요.
이 두 가지를 분리하는 것이 무중단 배포와 안정적인 서비스 운영의 첫걸음입니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 서비스 디스커버리 패턴(Service Discovery) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 재시도 패턴(Retry Pattern) 완벽 정리 (0) | 2025.12.07 |
| 처리율 제한 패턴(Rate Limiter Pattern) 완벽 정리 (0) | 2025.12.07 |
| 트랜잭셔널 아웃박스 패턴(Transactional Outbox Pattern) 완벽 정리 (0) | 2025.12.07 |
| CQRS 패턴(CQRS Pattern) 완벽 정리 (0) | 2025.12.07 |