# 설계서: 백엔드 - Health/모니터링 (#277) > **상태**: Approved > **작성**: [AI] Architect · **최종수정**: 2026-06-15 > **추적성** — Redmine: #277 · 관련 ADR: 없음 > · 구현 파일: `backend-java/src/main/java/com/tasteby/controller/HealthController.java` · 테스트: TBD (현재 없음) ## 1. 목적 (Why) PM2(dev) / Kubernetes (OKE prod) / Nginx Ingress 가 백엔드 프로세스가 살아있는지 판단할 수 있는 경량 HTTP 엔드포인트를 제공해, 장애 시 재기동·트래픽 차단을 자동화한다. ## 2. 범위 (Scope) - **포함**: - `GET /api/health` — 항상 `200 OK` 와 `{"status":"ok"}` 를 반환하는 liveness 체크 엔드포인트. - 인증 없이 호출 가능(공개). - **제외 (out of scope)**: - DB/Redis/외부 API 의존성 상태를 포함하는 deep health(readiness) 체크. - Spring Boot Actuator (`/actuator/health`) 활성화. - 메트릭(Micrometer/Prometheus) 노출. - 분산 트레이싱·로그 수집. - 알림(Slack/Email/PagerDuty) 통합. ## 3. 인수조건 (Acceptance Criteria) - [x] `GET /api/health` 호출 시 HTTP 200 과 본문 `{"status":"ok"}` 를 반환한다. - [x] 인증 없이 누구나 호출 가능하다(`WebConfig` CORS 허용 범위 내). - [x] 응답 본문은 `Content-Type: application/json` 으로 직렬화된다(Spring 기본). - [x] 외부 의존성(DB/Redis/LLM)이 다운되어도 본 엔드포인트는 영향을 받지 않는다(순수 인메모리 응답). ## 4. 컨텍스트 & 제약 - **의존성**: - Spring Web (`@RestController`, `@GetMapping`). - 외부 의존성 없음 — 컨트롤러 자체가 무상태 상수 응답. - 호출자: PM2 헬스(현재는 미사용), Kubernetes liveness/readiness probe(향후), Nginx upstream check(현재는 미사용), Uptime 모니터링. - **제약**: - 응답 시간 < 50ms 가정. 어떤 비용 있는 작업도 포함하지 않아야 함. - 본 엔드포인트가 200 을 반환한다고 해서 "서비스 정상" 을 의미하지 않음(프로세스 생존만 보장). - **가정**: - 프로세스가 응답할 수 있으면 JVM/Tomcat 이 살아있다는 신호로 충분. - 인증 미들웨어(Spring Security/필터)는 `/api/health` 를 통과시킨다(현재 인증 강제 없음). ## 5. 아키텍처 개요 - **모듈**: - `HealthController` — 단일 `@RestController` 클래스, 메서드 1개. - **경계**: - I/O: HTTP 입출력만. DB/Redis/LLM 호출 없음. - 순수 로직: 상수 `Map` 반환 — 테스트 자체가 거의 불필요한 수준. ``` Probe/Monitor ──HTTP GET /api/health──▶ Spring DispatcherServlet │ ▼ HealthController.health() │ ▼ Map.of("status","ok") → JSON 200 ``` ## 6. 데이터 모델 - 단순 응답 객체: `Map` 의 불변 단일 엔트리. ```json { "status": "ok" } ``` - 입력 파라미터/바디 없음. - 상태 코드: 항상 200. 다른 코드 없음. - 경계 검증: 해당 없음. ## 7. 함수 명세 (Function Specs) | 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? | |------|-----------|----------------|------|------|-----------|-------| | `HealthController.health` | liveness 응답 반환 | `Map health()` | 없음 | `{"status":"ok"}` | 이론상 없음(JVM/Tomcat 죽으면 연결 실패) | 단순 | ## 8. 흐름 / 알고리즘 1. 클라이언트(K8s probe / 모니터)가 `GET /api/health` 호출. 2. Spring DispatcherServlet 라우팅 → `HealthController.health()` 호출. 3. 메서드는 `Map.of("status","ok")` 상수 반환. 4. Jackson 이 JSON 직렬화 → HTTP 200 응답. cron 표현/주기: 본 컨트롤러 자체는 스케줄러가 아님(외부 probe 가 주기적으로 호출). 운영 권장 주기: K8s liveness 10s, readiness 5s. ## 9. 엣지케이스 & 에러 처리 - **JVM 데드락/OOM**: 응답 없음 → probe 가 타임아웃 → 비정상 판정 → 재기동(외부 메커니즘이 처리, 본 엔드포인트가 의도한 시나리오). - **요청 폭주(DDoS)**: 본 메서드는 매우 가볍지만 인증/레이트리밋이 없음. Nginx Ingress 단에서 처리 가정. - **DB/Redis 다운**: 본 엔드포인트는 영향 없음(의도된 동작). 단, 운영자가 "서비스 정상"으로 오인할 수 있는 한계 — 별도 readiness 필요(11장 참조). - **잘못된 HTTP 메서드**: `POST /api/health` 등은 405 (Spring 기본). - **CORS**: `WebConfig` 의 허용 origin 에 따라 브라우저 접근 제한될 수 있음(서버-서버 probe 에는 영향 없음). ## 10. 테스트 계획 현재 자동 테스트 없음(TBD). 권장 케이스: - **Unit/Slice (`@WebMvcTest(HealthController.class)`)**: - `GET /api/health` → 200 + 본문 `{"status":"ok"}`. - `POST /api/health` → 405. - **Smoke**: - 운영/dev 배포 후 `curl -sf https://www.tasteby.net/api/health` 가 0 종료 코드. - **모킹**: 불필요 — 의존성 없음. ## 11. 리스크 & 대안 검토 - **선택**: 자체 컨트롤러 1개 + 상수 응답. - 장점: 의존성 0, 인증/필터 우회 명확, K8s probe 와 1:1 매핑 쉬움. - 단점: deep health(DB/Redis 연결 확인) 부재 → "프로세스는 살아있지만 서비스 불가" 상황 감지 못 함. - **대안**: - **Spring Boot Actuator (`/actuator/health`)** — DB/Redis HealthIndicator 자동 구성, 표준화. 보안 노출 범위 제어 필요. → readiness 용도로 향후 도입 후보. - **K8s readinessProbe 별도 엔드포인트** (`/api/health/ready`) — DB ping, Redis ping 결과 합산. 본 설계의 자연스러운 확장. - **외부 모니터링(UptimeRobot/Healthchecks.io)** — HTTP 200 만으로 충분히 활용 가능. - **트레이드오프**: 현재는 운영자 1인 / 트래픽 적음 → 최소 구현이 합리적. Prod 안정화 단계에서 Actuator + readiness 분리 권장. - **되돌리기 어려운 결정 없음**. ## 12. 미해결 질문 (Open Questions) - DB/Redis 연결을 검사하는 readiness 엔드포인트(`/api/health/ready`)를 언제 도입할 것인가? - Spring Boot Actuator 를 켜고 노출 범위(어떤 엔드포인트, 어떤 인증)를 어떻게 제어할 것인가? - Prometheus 메트릭/Grafana 대시보드를 운영할 것인가(현재는 PM2/kubectl 로그 의존)? - K8s probe 구성(initialDelay, periodSeconds, failureThreshold) 표준값은? - 헬스 응답에 빌드 버전/커밋 SHA를 포함할 것인가(`/api/version` 분리 vs 통합)? - 본 엔드포인트도 인증/IP 제한 대상으로 둘 필요가 있는가(공개 권장)?