Skip to content

Commit 3627e04

Browse files
authored
Merge pull request #183 from Team-StackUp/fix/rabbitmq-health-path
fix(backend): 헬스체크에서 RabbitMQ 상태가 항상 UNKNOWN 이던 문제
2 parents 93a551f + f662d57 commit 3627e04

3 files changed

Lines changed: 53 additions & 4 deletions

File tree

backend/src/main/java/com/stackup/stackup/system/application/SystemHealthService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
@Service
1616
public class SystemHealthService {
1717

18+
// 응답 키(name)와 Actuator 컴포넌트 키(actuatorPath)는 다르다.
19+
// Actuator 키는 Spring 이 등록하는 빈 이름에서 접미사를 뗀 값이다 —
20+
// DataSourceHealthContributor→"db", rabbitHealthContributor→"rabbit".
21+
// "rabbitmq" 로 조회하면 항상 null 이라 rabbitmq 컴포넌트가 영구 UNKNOWN 이 된다.
1822
private static final ComponentSpec DATABASE = new ComponentSpec("database", "db");
19-
private static final ComponentSpec RABBITMQ = new ComponentSpec("rabbitmq", "rabbitmq");
23+
private static final ComponentSpec RABBITMQ = new ComponentSpec("rabbitmq", "rabbit");
2024
private static final ComponentSpec S3 = new ComponentSpec("s3", "s3");
2125
private static final ComponentSpec AI_SERVER = new ComponentSpec("aiServer", "aiServer");
2226

backend/src/test/java/com/stackup/stackup/system/application/SystemHealthServiceTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void live_returnsUpStatus() {
3838
void ready_usesDatabaseAndRabbitmqIndicators() {
3939
HealthEndpoint healthEndpoint = healthEndpoint(Map.of(
4040
"db", indicator(Status.UP, Map.of("connections", 12)),
41-
"rabbitmq", indicator(Status.UP, Map.of("host", "localhost"))
41+
"rabbit", indicator(Status.UP, Map.of("host", "localhost")) // Actuator 실제 키
4242
));
4343
SystemHealthService systemHealthService = new SystemHealthService(healthEndpoint);
4444

@@ -54,7 +54,7 @@ void ready_usesDatabaseAndRabbitmqIndicators() {
5454
void health_includesDatabaseRabbitmqS3AndAiServer() {
5555
HealthEndpoint healthEndpoint = healthEndpoint(Map.of(
5656
"db", indicator(Status.UP, Map.of()),
57-
"rabbitmq", indicator(Status.DOWN, Map.of("reachable", false))
57+
"rabbit", indicator(Status.DOWN, Map.of("reachable", false)) // Actuator 실제 키
5858
));
5959
SystemHealthService systemHealthService = new SystemHealthService(healthEndpoint);
6060

@@ -66,6 +66,44 @@ void health_includesDatabaseRabbitmqS3AndAiServer() {
6666
assertThat(response.components().get("aiServer").status()).isEqualTo(Status.UNKNOWN.getCode());
6767
}
6868

69+
/**
70+
* 응답 키와 Actuator 컴포넌트 키가 다르다는 사실 자체를 못 박는다.
71+
*
72+
* <p>운영에서 rabbitmq 가 영구 UNKNOWN 이었던 원인이 여기였다 — Actuator 는
73+
* {@code rabbitHealthContributor} 빈을 "rabbit" 으로 등록하는데 "rabbitmq" 로 조회했다.
74+
* 기존 테스트는 픽스처를 "rabbitmq" 로 등록해서 같은 실수를 그대로 재현하고 있었다.
75+
*/
76+
@Test
77+
void health_readsRabbitFromActuatorKeyNotResponseKey() {
78+
HealthEndpoint healthEndpoint = healthEndpoint(Map.of(
79+
"db", indicator(Status.UP, Map.of()),
80+
"rabbit", indicator(Status.UP, Map.of("version", "3.13"))
81+
));
82+
SystemHealthService systemHealthService = new SystemHealthService(healthEndpoint);
83+
84+
var response = systemHealthService.ready();
85+
86+
// 응답 키는 그대로 rabbitmq — 공개 계약은 바뀌지 않는다.
87+
assertThat(response.components().get("rabbitmq").status()).isEqualTo(Status.UP.getCode());
88+
assertThat(response.components().get("rabbitmq").details()).containsEntry("version", "3.13");
89+
assertThat(response.status()).isEqualTo(Status.UP.getCode());
90+
}
91+
92+
// Actuator 에 없는 이름으로 조회하면 UNKNOWN 이 된다 — 회귀 시 이 테스트가 아니라
93+
// 위 테스트가 깨지도록, 여기서는 '없을 때의 동작'만 확인한다.
94+
@Test
95+
void health_reportsUnknownWhenActuatorHasNoSuchComponent() {
96+
HealthEndpoint healthEndpoint = healthEndpoint(Map.of(
97+
"db", indicator(Status.UP, Map.of())
98+
));
99+
SystemHealthService systemHealthService = new SystemHealthService(healthEndpoint);
100+
101+
var response = systemHealthService.ready();
102+
103+
assertThat(response.components().get("rabbitmq").status()).isEqualTo(Status.UNKNOWN.getCode());
104+
assertThat(response.status()).isEqualTo(Status.UNKNOWN.getCode());
105+
}
106+
69107
private static HealthEndpoint healthEndpoint(Map<String, HealthIndicator> indicators) {
70108
HealthContributorRegistry registry = new DefaultHealthContributorRegistry();
71109
indicators.forEach(registry::registerContributor);

docs/observability.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,16 @@ GET /api/system/health
166166
}
167167
```
168168

169-
- Spring Boot Actuator + 커스텀 indicator
169+
- Spring Boot Actuator 의 컴포넌트를 이름으로 조회해 재구성한다(`SystemHealthService`).
170+
- **응답 키와 Actuator 컴포넌트 키는 다르다.** Actuator 키는 Spring 이 등록하는 빈 이름에서
171+
접미사를 뗀 값이라 `database``db`, `rabbitmq`**`rabbit`** 이다. 여기를 틀리면 조회가
172+
null 을 돌려줘 그 컴포넌트가 **영구 UNKNOWN** 이 된다(에러가 아니라 조용한 무응답).
173+
- `s3`·`aiServer` 는 아직 커스텀 indicator 가 없어 UNKNOWN 이다 → `/health` 의 종합 status 도
174+
UNKNOWN 으로 고정된다. 종합 판단이 필요하면 현재는 `/ready`(database·rabbitmq)를 쓴다.
170175
- K8s liveness: 단순 200 응답 (`/api/system/live`)
171176
- K8s readiness: 의존성 포함 (`/api/system/ready`)
177+
- 컨테이너 healthcheck 는 Spring 자체 `/actuator/health` 를 쓴다(docker-compose) — 이쪽은
178+
Actuator 종합이라 RabbitMQ 장애를 정상적으로 잡는다.
172179

173180
---
174181

0 commit comments

Comments
 (0)