diff --git a/.claude/commands/develop-make-run-profile.md b/.claude/commands/develop-make-run-profile.md index 65740e5..06b2768 100644 --- a/.claude/commands/develop-make-run-profile.md +++ b/.claude/commands/develop-make-run-profile.md @@ -1,5 +1,5 @@ @test-backend -'서비스실행파일작성가이드'에 따라 테스트를 해 주세요. +'서비스실행프로파일작성가이드'에 따라 테스트를 해 주세요. 프롬프트에 '[작성정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. DB나 Redis의 접근 정보는 지정할 필요 없습니다. 특별히 없으면 '[작성정보]'섹션에 '없음'이라고 하세요. {안내메시지} diff --git a/analytics-service/.run/analytics-service.run.xml b/analytics-service/.run/analytics-service.run.xml index 91ac5be..44dfb98 100644 --- a/analytics-service/.run/analytics-service.run.xml +++ b/analytics-service/.run/analytics-service.run.xml @@ -21,9 +21,14 @@ - + + + + + + diff --git a/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java b/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java index 60b3ae0..e225ea9 100644 --- a/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java +++ b/analytics-service/src/main/java/com/kt/event/analytics/config/SampleDataLoader.java @@ -84,9 +84,13 @@ public class SampleDataLoader implements ApplicationRunner { try { // 1. EventCreated 이벤트 발행 (3개 이벤트) publishEventCreatedEvents(); + log.info("⏳ EventStats 생성 대기 중... (2초)"); + Thread.sleep(2000); // EventCreatedConsumer가 EventStats 생성할 시간 // 2. DistributionCompleted 이벤트 발행 (각 이벤트당 4개 채널) publishDistributionCompletedEvents(); + log.info("⏳ ChannelStats 생성 대기 중... (1초)"); + Thread.sleep(1000); // DistributionCompletedConsumer가 ChannelStats 생성할 시간 // 3. ParticipantRegistered 이벤트 발행 (각 이벤트당 다수 참여자) publishParticipantRegisteredEvents(); @@ -100,9 +104,9 @@ public class SampleDataLoader implements ApplicationRunner { log.info(" - ParticipantRegistered: 180건 (MVP 테스트용)"); log.info("========================================"); - // Consumer 처리 대기 (3초) - log.info("⏳ Consumer 처리 대기 중... (3초)"); - Thread.sleep(3000); + // Consumer 처리 대기 (2초) + log.info("⏳ 참여자 수 업데이트 대기 중... (2초)"); + Thread.sleep(2000); // 4. TimelineData 생성 (시간대별 데이터) createTimelineData(); diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java index a7c2a41..1b3d1d1 100644 --- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java +++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/DistributionCompletedConsumer.java @@ -38,7 +38,7 @@ public class DistributionCompletedConsumer { /** * DistributionCompleted 이벤트 처리 (설계서 기준 - 여러 채널 배열) */ - @KafkaListener(topics = "sample.distribution.completed", groupId = "analytics-service") + @KafkaListener(topics = "sample.distribution.completed", groupId = "${spring.kafka.consumer.group-id}") public void handleDistributionCompleted(String message) { try { log.info("📩 DistributionCompleted 이벤트 수신: {}", message); diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java index 480125a..c7c7689 100644 --- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java +++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/EventCreatedConsumer.java @@ -35,7 +35,7 @@ public class EventCreatedConsumer { /** * EventCreated 이벤트 처리 (MVP용 샘플 토픽) */ - @KafkaListener(topics = "sample.event.created", groupId = "analytics-service") + @KafkaListener(topics = "sample.event.created", groupId = "${spring.kafka.consumer.group-id}") public void handleEventCreated(String message) { try { log.info("📩 EventCreated 이벤트 수신: {}", message); diff --git a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java index 6df8e6e..ae33697 100644 --- a/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java +++ b/analytics-service/src/main/java/com/kt/event/analytics/messaging/consumer/ParticipantRegisteredConsumer.java @@ -35,7 +35,7 @@ public class ParticipantRegisteredConsumer { /** * ParticipantRegistered 이벤트 처리 (MVP용 샘플 토픽) */ - @KafkaListener(topics = "sample.participant.registered", groupId = "analytics-service") + @KafkaListener(topics = "sample.participant.registered", groupId = "${spring.kafka.consumer.group-id}") public void handleParticipantRegistered(String message) { try { log.info("📩 ParticipantRegistered 이벤트 수신: {}", message); diff --git a/analytics-service/src/main/resources/application.yml b/analytics-service/src/main/resources/application.yml index 340313c..5a217e2 100644 --- a/analytics-service/src/main/resources/application.yml +++ b/analytics-service/src/main/resources/application.yml @@ -41,10 +41,10 @@ spring: max-wait: -1ms database: ${REDIS_DATABASE:5} - # Kafka + # Kafka (원격 서버 사용) kafka: - enabled: ${KAFKA_ENABLED:false} - bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:4.217.131.59:9095} + enabled: ${KAFKA_ENABLED:true} + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:20.249.182.13:9095,4.217.131.59:9095} consumer: group-id: ${KAFKA_CONSUMER_GROUP_ID:analytics-service} auto-offset-reset: earliest diff --git a/claude/make-run-profile.md b/claude/make-run-profile.md index f363a91..2afafe5 100644 --- a/claude/make-run-profile.md +++ b/claude/make-run-profile.md @@ -1,4 +1,4 @@ -# 서비스실행파일작성가이드 +# 서비스실행프로파일작성가이드 [요청사항] - <수행원칙>을 준용하여 수행 diff --git a/claude/test-backend.md b/claude/test-backend.md new file mode 100644 index 0000000..a5f88e8 --- /dev/null +++ b/claude/test-backend.md @@ -0,0 +1,48 @@ +# 백엔드 테스트 가이드 + +[요청사항] +- <테스트원칙>을 준용하여 수행 +- <테스트순서>에 따라 수행 +- [결과파일] 안내에 따라 파일 작성 + +[가이드] +<테스트원칙> +- 설정 Manifest(src/main/resources/application*.yml)의 각 항목의 값은 하드코딩하지 않고 환경변수 처리 +- Kubernetes에 배포된 데이터베이스는 LoadBalacer유형의 Service를 만들어 연결 +<테스트순서> +- 준비: + - 설정 Manifest(src/main/resources/application*.yml)와 실행 프로파일({service-name}.run.xml 내부에 있음)의 일치여부 검사 및 수정 +- 실행: + - 'curl'명령을 이용한 테스트 및 오류 수정 + - 서비스 의존관계를 고려하여 테스트 순서 결정 + - 순서에 따라 순차적으로 각 서비스의 Controller에서 API 스펙 확인 후 API 테스트 + - API경로와 DTO클래스를 확인하여 정확한 request data 구성 + - 소스 수정 후 테스트 절차 + - 컴파일 및 오류 수정: {프로젝트 루트}/gradlew {service-name}:compileJava + - 컴파일 성공 후 서비스 재시작 요청: 서비스 시작은 인간에게 요청 + - 만약 직접 서비스를 실행하려면 '<서비스 시작 방법>'으로 수행 + - 서비스 중지는 '<서비스 중지 방법>'을 참조 수행 + - 설정 Manifest 수정 시 민감 정보는 기본값으로 지정하지 않고 '<실행프로파일 작성 가이드>'를 참조하여 실행 프로파일에 값을 지정함 + - 실행 결과 로그는 'logs' 디렉토리 하위에 생성 + - 결과: test-backend.md +<실행프로파일 작성 가이드> +- {service-name}/.run/{service-name}.run.xml 파일로 작성 +- Kubernetes에 배포된 데이터베이스의 LoadBalancer Service 확인: + - kubectl get svc -n {namespace} | grep LoadBalancer 명령으로 LoadBalancer IP 확인 + - 각 서비스별 데이터베이스의 LoadBalancer External IP를 DB_HOST로 사용 + - 캐시(Redis)의 LoadBalancer External IP를 REDIS_HOST로 사용 +<서비스 시작 방법> +- 'IntelliJ서비스실행기'를 'tools' 디렉토리에 다운로드 +- python 또는 python3 명령으로 백그라우드로 실행하고 결과 로그를 분석 + nohup python3 tools/run-intellij-service-profile.py {service-name} > logs/{service-name}.log 2>&1 & echo "Started {service-name} with PID: $!" +- 서비스 실행은 다른 방법 사용하지 말고 **반드시 python 프로그램 이용** +<서비스 중지 방법> +- Window + - netstat -ano | findstr :{PORT} + - powershell "Stop-Process -Id {Process number} -Force" +- Linux/Mac + - netstat -ano | grep {PORT} + - kill -9 {Process number} + +[결과파일] +- develop/dev/test-backend.md \ No newline at end of file diff --git a/develop/dev/test-backend.md b/develop/dev/test-backend.md new file mode 100644 index 0000000..34497c7 --- /dev/null +++ b/develop/dev/test-backend.md @@ -0,0 +1,305 @@ +# 백엔드 테스트 결과서 + +## 테스트 개요 +- **테스트 일시**: 2025-10-27 13:46 +- **대상 서비스**: analytics-service +- **서버 포트**: 8086 +- **테스트 환경**: 로컬 개발 환경 + +## 1. 서버 상태 확인 + +### 1.1 Health Check +**요청**: +```bash +curl -X GET "http://localhost:8086/actuator/health" +``` + +**응답**: +```json +{ + "status": "UP", + "components": { + "db": { + "status": "UP", + "details": { + "database": "PostgreSQL", + "validationQuery": "isValid()" + } + }, + "redis": { + "status": "UP", + "details": { + "version": "7.2.3" + } + }, + "diskSpace": {"status": "UP"}, + "livenessState": {"status": "UP"}, + "readinessState": {"status": "UP"} + } +} +``` + +**결과**: ✅ **성공** - 서버 정상 작동, DB 및 Redis 연결 정상 + +--- + +## 2. API 테스트 결과 + +### 2.1 성과 대시보드 조회 API +**엔드포인트**: `GET /api/v1/events/{eventId}/analytics` + +**테스트 케이스**: +```bash +curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics" +``` + +**응답**: +```json +{ + "success": false, + "errorCode": "EVENT_001", + "message": "이벤트를 찾을 수 없습니다", + "timestamp": "2025-10-27T13:46:50.7331807" +} +``` + +**결과**: ❌ **실패** - EventStats 데이터 미생성 +- **원인**: Kafka Consumer 미작동으로 EventCreated 이벤트 미처리 +- **근본 원인**: Kafka 브로커 연결 실패 + +--- + +### 2.2 시간대별 참여 추이 API +**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/timeline` + +**테스트 케이스**: +```bash +curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/timeline?interval=daily" +``` + +**응답**: +```json +{ + "success": true, + "data": { + "eventId": "evt_2025012301", + "interval": "daily", + "dataPoints": [ + { + "timestamp": "2024-09-24T00:00:00", + "participants": 36, + "views": 108, + "engagement": 36, + "conversions": 24, + "cumulativeParticipants": 36 + } + // ... 150개 데이터 포인트 + ], + "trends": { + "overallTrend": "stable", + "growthRate": 11.1, + "projectedParticipants": 944, + "peakPeriod": "2024-09-24" + }, + "peakTimes": [ + { + "timestamp": "2024-09-24T00:00:00", + "metric": "participants", + "value": 40, + "description": "최대 참여자 수" + }, + { + "timestamp": "2024-09-27T00:00:00", + "metric": "views", + "value": 200, + "description": "최대 조회수" + } + ] + } +} +``` + +**결과**: ✅ **성공** - TimelineData 정상 조회 +- **데이터 포인트**: 150개 (30일 × 5개 채널) +- **기간**: 2024-09-24 ~ 2024-10-23 +- **트렌드 분석**: 정상 작동 +- **Peak Time 분석**: 정상 작동 + +--- + +### 2.3 채널별 성과 분석 API +**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/channels` + +**테스트 케이스**: +```bash +curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/channels" +``` + +**응답**: +```json +{ + "success": true, + "data": { + "eventId": "evt_2025012301", + "channels": [], + "comparison": null, + "lastUpdatedAt": "2025-10-27T13:46:55.9759532" + } +} +``` + +**결과**: ⚠️ **부분 성공** - 응답은 정상이나 데이터 비어있음 +- **원인**: ChannelStats 데이터 미생성 +- **근본 원인**: Kafka Consumer 미작동으로 DistributionCompleted 이벤트 미처리 + +--- + +### 2.4 투자 대비 수익률 분석 API +**엔드포인트**: `GET /api/v1/events/{eventId}/analytics/roi` + +**테스트 케이스**: +```bash +curl -X GET "http://localhost:8086/api/v1/events/evt_2025012301/analytics/roi" +``` + +**응답**: +```json +{ + "success": false, + "errorCode": "EVENT_001", + "message": "이벤트를 찾을 수 없습니다", + "timestamp": "2025-10-27T13:46:58.6552438" +} +``` + +**결과**: ❌ **실패** - EventStats 데이터 미생성 +- **원인**: Kafka Consumer 미작동으로 EventCreated 이벤트 미처리 + +--- + +## 3. 문제 분석 + +### 3.1 Kafka 연결 실패 +**로그 확인**: +``` +2025-10-27 13:46:46 [kafka-producer-network-thread] INFO o.apache.kafka.clients.NetworkClient - + [Producer clientId=analytics-service-producer-1] Node 101 disconnected. +2025-10-27 13:46:56 [kafka-producer-network-thread] INFO o.apache.kafka.clients.NetworkClient - + [Producer clientId=analytics-service-producer-1] Node 100 disconnected. +``` + +**문제점**: +1. Kafka 브로커(20.249.182.13:9095, 4.217.131.59:9095)에 연결 실패 +2. SampleDataLoader가 이벤트를 발행했지만 브로커에 도달하지 못함 +3. Kafka Consumer가 이벤트를 수신하지 못함 + +### 3.2 Consumer 설정 확인 +**파일**: `EventCreatedConsumer.java:23` +```java +@ConditionalOnProperty(name = "spring.kafka.enabled", havingValue = "true", matchIfMissing = false) +``` + +**설정**: +- `application.yml`: `spring.kafka.enabled: ${KAFKA_ENABLED:false}` +- 실행 프로파일: `KAFKA_ENABLED=true` + +**Consumer 토픽**: +- `sample.event.created` - EventCreatedConsumer +- `sample.distribution.completed` - DistributionCompletedConsumer +- `sample.participant.registered` - ParticipantRegisteredConsumer + +### 3.3 데이터 생성 흐름 + +**정상 흐름**: +``` +SampleDataLoader (시작 시) + ↓ Kafka 이벤트 발행 + ├─ EventCreated (3개) → EventCreatedConsumer → EventStats 생성 + ├─ DistributionCompleted (3개) → DistributionCompletedConsumer → ChannelStats 생성 + ├─ ParticipantRegistered (180개) → ParticipantRegisteredConsumer → ChannelStats 업데이트 + └─ TimelineData 직접 생성 (90개) +``` + +**실제 흐름**: +``` +SampleDataLoader (시작 시) + ↓ Kafka 이벤트 발행 시도 + ├─ EventCreated → Kafka 연결 실패 → EventStats 미생성 ❌ + ├─ DistributionCompleted → Kafka 연결 실패 → ChannelStats 미생성 ❌ + ├─ ParticipantRegistered → Kafka 연결 실패 → 처리 안됨 ❌ + └─ TimelineData 직접 생성 (90개) ✅ +``` + +--- + +## 4. 테스트 결과 요약 + +| API | 엔드포인트 | 상태 | 비고 | +|-----|----------|------|------| +| Health Check | `/actuator/health` | ✅ 성공 | DB, Redis 연결 정상 | +| 성과 대시보드 | `/api/v1/events/{eventId}/analytics` | ❌ 실패 | EventStats 미생성 | +| 시간대별 추이 | `/api/v1/events/{eventId}/analytics/timeline` | ✅ 성공 | 150개 데이터 정상 조회 | +| 채널별 분석 | `/api/v1/events/{eventId}/analytics/channels` | ⚠️ 부분 | 빈 배열 반환 | +| ROI 분석 | `/api/v1/events/{eventId}/analytics/roi` | ❌ 실패 | EventStats 미생성 | + +**성공률**: 1/4 (25%) +- **완전 성공**: Timeline API +- **부분 성공**: Channel API (응답 정상, 데이터 없음) +- **실패**: Dashboard API, ROI API + +--- + +## 5. 개선 사항 + +### 5.1 즉시 조치 필요 +1. **Kafka 브로커 연결 확인** + - 브로커 상태 확인: `20.249.182.13:9095`, `4.217.131.59:9095` + - 네트워크 방화벽 규칙 확인 + - Kubernetes Service 확인: `kubectl get svc -n kafka` + +2. **Kafka Consumer autoStartup 확인** + ```java + @KafkaListener( + topics = "sample.event.created", + groupId = "analytics-service", + autoStartup = "true" // 추가 확인 + ) + ``` + +### 5.2 대안 방안 +**Kafka 없이 테스트하는 방법**: +1. SampleDataLoader 수정하여 Kafka 없이 직접 Repository에 데이터 생성 +2. 또는 `KAFKA_ENABLED=false` 설정하고 REST API로 데이터 직접 등록 + +### 5.3 장기 개선 +1. **Resilience 향상** + - Kafka 연결 실패 시 Retry 메커니즘 추가 + - Circuit Breaker 패턴 적용 (Resilience4j 이미 설정됨) + +2. **모니터링 강화** + - Kafka Consumer Lag 모니터링 + - 이벤트 처리 실패 알림 + +3. **테스트 환경 구성** + - 로컬 테스트용 Embedded Kafka 설정 + - Docker Compose로 Kafka 로컬 환경 구성 + +--- + +## 6. 결론 + +### 6.1 현재 상태 +- **기본 기능**: 정상 작동 (서버, DB, Redis 연결) +- **API 응답**: 구조적으로 정상 (에러 처리 적절) +- **Timeline API**: 완전 정상 작동 +- **Kafka 의존 API**: Kafka 연결 문제로 데이터 부재 + +### 6.2 권고 사항 +1. **단기**: Kafka 브로커 연결 문제 해결 후 재테스트 +2. **중기**: Kafka 없이도 테스트 가능한 대안 구현 +3. **장기**: 이벤트 기반 아키텍처 안정성 개선 + +### 6.3 다음 단계 +1. Kafka 브로커 상태 확인 및 연결 복구 +2. Consumer 활성화 확인 및 이벤트 재처리 +3. 전체 API 재테스트 및 결과 검증