## 📌 개요 <!-- "무엇을 만드는가"보다 "왜 만드는가"를 중심으로 작성해주세요. 관련 요구사항 ID를 함께 적으면 SA 문서와 연결이 쉬워집니다. 예) U-09 요구사항에 따라, ADMIN 승인 없이 HOST 권한이 부여되는 것을 방지하기 위해 HostRequest 플로우를 구현합니다. --> 대기열 폴링 응답에 서버가 권장하는 다음 폴링 간격(`retryAfterMs`)을 포함하여 **Server-driven Adaptive Polling** 을 구현한다. 현재 클라이언트는 고정 3초 간격으로 폴링하고 있어 사용자 순번과 관계없이 동일한 부하를 발생시킨다. 부하 테스트 결과 1000 동시 사용자 기준 333 TPS 의 폴링 요청이 발생하며, 대부분 "아직 차례 아님" 응답인 비효율 구조다. 순번에 따라 폴링 간격을 차등 적용하여: - 임박한 사용자(≤100명): 빠른 알림 유지 (1초) - 멀리 있는 사용자: 폴링 간격 확대 (5~30초) 전체 폴링 부하를 약 40~50% 감소시키고, Thundering Herd 방지를 위해 jitter 를 함께 적용한다. ## 🎯 완료 기준 <!-- 이 이슈가 "닫혀도 되는 상태"를 구체적으로 정의해주세요. 모호한 완료 기준은 리뷰 시 불필요한 논쟁을 만듭니다. 예) X: 호스트 요청 기능 구현 완료 O: POST /api/v1/host-requests 호출 시 DB에 PENDING 상태로 저장되고, ADMIN PATCH 요청으로 APPROVED / REJECTED 전이가 정상 동작한다. --> - [ ] `GET /api/v1/queues/programs/{programId}` 응답 DTO 에 `retryAfterMs` 필드 추가 - [ ] 사용자 순번에 따라 동적 계산된 `retryAfterMs` 값 반환 - position ≤ 100: 1000ms (jitter 없음) - position ≤ 1000: 5000ms (jitter ±300ms) - position ≤ 10000: 15000ms (jitter ±500ms) - position > 10000: 30000ms (jitter ±4000ms) - [ ] ADMITTED 등 큐에서 빠진 상태(`position = null`)는 `retryAfterMs = null` 반환 - [ ] `@JsonInclude(NON_NULL)` 로 null 값은 응답에서 제외됨 확인 - [ ] nGrinder 부하 테스트 스크립트가 응답값 따라 동적 sleep 하도록 수정 - [ ] Before/After 부하 테스트 비교 데이터 확보 ## 🧩 작업 범위 <!-- 작업을 최대한 작은 단위로 쪼개주세요. 하나의 이슈가 너무 크면 PR 리뷰 비용이 급격히 올라갑니다. 타 서비스(Kafka 이벤트, API 계약 등)에 영향을 주는 항목은 ⚠️ 표시해주세요. --> - [ ] `QueueTokenResponse` DTO 에 `retryAfterMs` 필드 추가 - [ ] 순번 기반 폴링 간격 동적 계산 로직 구현 - [ ] Jitter 적용 (`ThreadLocalRandom`) - [ ] nGrinder 폴링 스크립트 수정 (응답 파싱 → 동적 sleep) - [ ] Before/After 측정 및 비교 ## 🏗️ 설계 요약 <!-- 전체 설계를 다 적을 필요는 없습니다. "이 기능의 핵심 흐름이 뭔지" 한눈에 보일 정도면 충분합니다. 복잡한 설계는 Notion SA 문서 링크로 대체해도 좋습니다. --> ### API 응답 변경 **Before** ```json { "tokenId": "...", "status": "WAITING", "issuedAt": "2026-05-19T...", "position": 234 } ``` **After** ```json { "tokenId": "...", "status": "WAITING", "issuedAt": "2026-05-19T...", "position": 234, "retryAfterMs": 5300 } ``` ### 핵심 로직 `QueueTokenResponse.from(QueueTokenResult result)` 에서 `result.position()` 기반으로 `retryAfterMs` 동적 계산: ```java private static Integer calculateRetryAfterMs(Long position) { if (position == null) return null; int interval; int jitter; if (position <= 100) { interval = 1000; jitter = 0; } else if (position <= 1000) { interval = 5000; jitter = 300; } else if (position <= 10000) { interval = 15000; jitter = 500; } else { interval = 30000; jitter = 4000; } int jitterValue = jitter > 0 ? ThreadLocalRandom.current().nextInt(-jitter, jitter + 1) : 0; return interval + jitterValue; } ``` ### 변경 범위 - **수정**: `presentation/dto/QueueTokenResponse.java` (필드 추가 + 계산 로직) ## 🔗 참고 자료 - 참고 자료 혹은 이미지 삽입 등이 필요한 경우, 이곳에 작성해주세요.
Activity
feat: Server-driven Adaptive Polling 도입
test: queue-token-issue 의 retryAfterMs 를 required 로 변경
[feat][queue-service] Server-driven Adaptive Polling 도입 (#35)