70 KiB
크리에이터 랭킹 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development또는superpowers:executing-plans로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.
Goal: 홈 내부 랭킹 탭에서 GET /api/v2/home/rankings/creators로 KST 기준 지난 주 크리에이터 랭킹 상위 20명을 조회한다.
Architecture: 공개 endpoint는 home 하위 URL을 사용하고, 클라이언트 API 표면(Controller, API 조합 Facade, DTO)은 기존 홈 API 관례에 맞춰 kr.co.vividnext.sodalive.v2.api.home 하위에 둔다. 랭킹 기능 본체(domain/application/port/persistence/scheduler)는 추천 기능과 분리된 kr.co.vividnext.sodalive.v2.ranking 하위에 둔다. 주간 스냅샷 생성 작업이 KST 기간을 UTC DB 조회 조건으로 변환해 원천 데이터를 집계하고, 조회 API는 최신 완료 주차 스냅샷을 우선 읽어 응답을 조립한다. 단, 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 제한적 원천 데이터 fallback 집계를 시도할 수 있다.
Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring Data JPA, QueryDSL 또는 native SQL, JUnit 5, Gradle Wrapper
0. 구현 전 확정 사항
- API endpoint:
GET /api/v2/home/rankings/creators - 랭킹 기능 본체 패키지:
kr.co.vividnext.sodalive.v2.ranking - 홈 공개 API 조립 패키지:
kr.co.vividnext.sodalive.v2.api.home - 집계 기간: 조회/스냅샷 생성 시점 기준 KST 지난 주 월요일 00:00:00 이상, 이번 주 월요일 00:00:00 미만
- DB 조회 기간: KST 집계 기간을 UTC 기준
LocalDateTime또는 프로젝트 표준 시간 타입으로 변환한 기간 - 스냅샷 생성 스케줄 후보: 매주 월요일 KST 07:30,
@Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul") - 다중 서버 인스턴스에서 스냅샷 스케줄러가 중복 실행되지 않도록 기존 Redisson 기반 분산 lock을 사용한다.
- 랭킹 스냅샷 lock key는
lock:creator-ranking-snapshot-refresh로 고정하고, lock 획득 실패 인스턴스는 정상 skip한다. - 조회 API는 스냅샷 기반 응답을 기본으로 하며, 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 제한적 원천 데이터 fallback 집계를 시도할 수 있다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 기존 최신 완료 주차 스냅샷 기준 응답을 유지한다.
- 스냅샷 테이블이 완전히 비어 있는 cold-start fallback 성공 시 조회 API는 fallback 응답을 반환하고, 같은 집계 기간의 스냅샷 생성은 조회 서비스가 직접 저장하지 않고
CreatorRankingSnapshotJobService/CreatorRankingSnapshotRefreshService책임으로 위임한다. - cold-start fallback 스냅샷 생성 트리거는 운영 배포 직후 내부 테스트 등 초기 검증 보강책이며, 동일 집계 기간에 대해 한 번만 실행되도록 기간 기반 Redisson lock을 사용한다.
- 스냅샷 생성 직전 집계 시작/종료 시각을 포함한 job 이력을 생성하고, 스케줄 실행과 관리자 수동 생성 모두 성공/실패 상태를 기록한다.
- 관리자는 날짜 범위를 직접 선택해 스냅샷 생성 job을 만들 수 있으며, 실패한 job은 관리자 전용 재시도 API로 대기 상태로 되돌려 재처리할 수 있어야 한다.
- 스냅샷은 현재 누적 저장하며, 보존 기간/정리 배치는 운영 데이터 규모 확인 후 별도 결정한다.
- API 응답은
showRankChange,items[].rank,items[].rankChange,items[].isNew,items[].creatorId,items[].nickname,items[].profileImageUrl만 포함한다. - API 응답에는 집계 기간 날짜와
finalScore를 포함하지 않는다. - raw value 방식으로 계산하며 0~100 정규화는 하지 않는다.
- 스냅샷 저장 대상은 20위 점수보다 높은 후보와 20위 점수에 동점인 후보 전체로 제한한다.
- 동점자는 조회 시 랜덤 정렬로 상위 20명을 추출하고, 별도
randomTieBreaker는 저장하지 않는다. - 직전 완료 주차 스냅샷이 없으면
showRankChange=false,rankChange=null,isNew=false로 응답한다. - 비활성 및 탈퇴 크리에이터는 랭킹에 노출하지 않는다.
- 차단 관계가 있으면 row는 유지하되
creatorId=0,nickname="",profileImageUrl=기본 이미지 URL로 마스킹한다. - 신규 팔로우 수는
CreatorFollowing.createdAt기준, 언팔로우 수는CreatorFollowing.isActive == false및CreatorFollowing.updatedAt기준으로 계산한다.
1. 파일 구조 계획
신규 ranking domain/application
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingPeriodPolicy.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScorePolicy.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScoreSpec.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingSnapshotCandidate.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingItem.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingAggregationPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingBlockPort.kt
신규 홈 API 조립 계층
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/ranking/CreatorRankingResponse.kt
신규 scheduler / persistence
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshot.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingBlockRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotJob.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotJobRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotJobRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotJobPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobService.kt
신규 관리자 API
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobController.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobResponse.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobRequest.kt
문서 산출물
- Create:
docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - Modify:
docs/20260608_크리에이터_랭킹/plan-task.md
테스트
- Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingPeriodPolicyTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScorePolicyTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt
Phase 1: 기간/점수 도메인 정책
-
Task 1.1: KST 주간 기간 산출과 UTC 조회 기간 변환 정책 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingPeriodPolicy.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingPeriodPolicyTest.kt
- Create:
- RED: 월요일 KST 기준 지난 주 기간, 월/연도 경계, 서버 timezone UTC와 무관한 기간 산출, KST 2026-06-01 00:00:00
2026-06-08 00:00:00이 UTC 2026-05-31 15:00:002026-06-07 15:00:00으로 변환되는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicyTest - GREEN:
CreatorRankingPeriodPolicy.resolveLastCompletedWeek(now: ZonedDateTime)와toUtcRange(period)를 구현한다. - REFACTOR: 기간 경계는 종료 미만(
< end) 조건으로 사용할 수 있도록startInclusiveUtc,endExclusiveUtc명칭을 유지한다. - 기대 결과: KST 기준 기간 산출과 UTC 변환이 테스트로 고정된다.
- Files:
-
Task 1.2: raw value 기반 점수 정책 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScoreSpec.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScorePolicy.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingScorePolicyTest.kt
- Create:
- RED: 콘텐츠/라이브 점수, 참여 반응 점수, 응원 점수, 팬 충성도 점수, 최종 점수 산식 테스트를 작성한다. 0~100 정규화 없이 캔/건수/팔로우 원천값이 그대로 가중합되는지 검증한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScorePolicyTest - GREEN: 가중치 상수와
calculateContentLiveScore,calculateEngagementScore,calculateSupportScore,calculateFanLoyaltyScore,calculateFinalScore를 구현한다. - REFACTOR: 소수 계산 비교는
assertEquals(expected, actual, 0.0001)기준을 사용한다. - 기대 결과: PRD의 raw value 정책과 음수 팔로우 증가 반영이 테스트로 고정된다.
- Files:
-
Task 1.3: 스냅샷 후보/응답 내부 모델 작성
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingSnapshotCandidate.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/domain/CreatorRankingItem.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Create:
- RED:
rankChange양수/음수/null과isNew를 담을 수 있는 내부 item 모델이 없으면 컴파일 실패하는 테스트 골격을 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: 스냅샷 후보와 조회 item 내부 모델을 작성한다.
- REFACTOR: API DTO와 domain model을 분리해 Controller가 persistence entity에 의존하지 않도록 한다.
- 기대 결과: 이후 service/controller task가 같은 타입을 재사용할 수 있다.
- Files:
Phase 2: 스냅샷 저장소와 DDL
-
Task 2.1: 랭킹 스냅샷 엔티티/리포지토리 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshot.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotPort.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt
- Create:
- RED: 같은 집계 기간의 스냅샷 replace, 최신 완료 주차 조회, 직전 완료 주차 조회, 20위 경계 동점 후보 저장 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest - GREEN: 스냅샷 엔티티에
aggregationStartAtUtc,aggregationEndAtUtc,creatorId,finalScore, 카테고리별 점수, 원천 지표,createdAt을 저장한다. 저장 전 같은 기간 row를 삭제하고 새 후보를 저장한다. - REFACTOR: 스냅샷 조회 port는 domain model만 반환하고 JPA entity를 application 계층으로 노출하지 않는다.
- 기대 결과: 같은 기간 재생성 시 중복 노출되지 않고 최신/직전 주차를 구분해 조회할 수 있다.
- Files:
-
Task 2.2: 운영 DB 반영용 스냅샷 DDL 문서 작성
- Files:
- Create:
docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - Modify:
docs/20260608_크리에이터_랭킹/plan-task.md
- Create:
- RED: 테스트 작성 예외.
TDD 예외 사유: SQL 운영 반영 문서 작성 task로, 실행 대상 DB가 현재 workspace에 없다. - 대체 검증 방법:
rg -n "creator_ranking_snapshot|aggregation_start_at_utc|creator_id|final_score" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - GREEN:
creator_ranking_snapshot테이블 생성 SQL, 기간/점수 조회용 index, 같은 기간 재생성 시 삭제 기준을 문서에 작성한다. - REFACTOR: 컬럼명은 JPA entity와 1:1로 대응하도록 정리한다.
- 기대 결과: 운영 배포 전 DB 테이블 생성 SQL을 검토할 수 있다.
- Files:
Phase 3: 원천 지표 집계 repository
-
Task 3.1: 콘텐츠/라이브 캔 집계 구현
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingAggregationPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt
- Create:
- RED:
CanUsage.DONATION,LIVE,SPIN_ROULETTE는 라이브 계열 캔으로,ORDER_CONTENT는 콘텐츠 구매 캔으로 집계되고 환불 row가 제외되는 repository 통합 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest - GREEN: KST에서 변환한 UTC 기간으로
UseCan계열 데이터를 조회하고 크리에이터별 캔 합계를 반환한다. - REFACTOR: can usage 조건은 private 함수 또는 enum set으로 분리해 산식과 조회 조건이 섞이지 않도록 한다.
- 기대 결과: 콘텐츠/라이브 카테고리의 원천 지표가 정확히 집계된다.
- Files:
-
Task 3.2: 콘텐츠 좋아요/댓글 반응 집계 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt
- Modify:
- RED: 활성 콘텐츠 좋아요 수, 댓글+대댓글 수, 크리에이터 본인 댓글/대댓글 제외, 비활성/삭제 정책 제외를 검증하는 repository 통합 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest - GREEN:
AudioContentLike,AudioContentComment,AudioContent를 기준으로 크리에이터별 좋아요/댓글 원천 지표를 반환한다. - REFACTOR: 댓글 작성자가 콘텐츠 소유 크리에이터와 같은 경우 제외하는 조건을 테스트 fixture 이름에 드러나게 정리한다.
- 기대 결과: 참여 반응 점수 입력값이 PRD 조건과 일치한다.
- Files:
-
Task 3.3: 채널 후원/팬 Talk 응원 집계 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt
- Modify:
- RED:
CanUsage.CHANNEL_DONATION캔 합계와 건수, 환불 제외,CreatorCheers최상위 row만 팬 Talk로 집계하고 답글은 제외하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest - GREEN: 채널 후원 원천 지표와 팬 Talk 원천 지표를 크리에이터별로 반환한다.
- REFACTOR: 팬 Talk 답글 제외 조건은
parent is null또는 기존 엔티티 구조에 맞는 조건으로 명확히 둔다. - 기대 결과: 응원 점수 입력값이 캔/건수/최상위 팬 Talk 기준으로 집계된다.
- Files:
-
Task 3.4: 팔로우 최종 수/증가 수 집계 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt
- Modify:
- RED: 최종 팔로우 수는 기간 종료 시점 활성 row, 신규 팔로우 수는
createdAt기간 내, 언팔로우 수는isActive=false및updatedAt기간 내, 기간 내 재팔로우는 신규/언팔로우 이벤트로 별도 복원하지 않는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest - GREEN:
CreatorFollowing기준 최종 팔로우 수와 팔로우 증가 수를 반환한다. - REFACTOR: 현재 row만으로 계산하는 정책 한계를 테스트명과 주석 한 줄로 남긴다.
- 기대 결과: 팬 충성도 점수 입력값이 PRD의
createdAt/updatedAt정책과 일치한다.
- Files:
-
Task 3.5: 랭킹 후보 통합 집계와 비활성/탈퇴 제외 구현
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingAggregationRepositoryTest.kt
- Modify:
- RED: 여러 원천 지표를 크리에이터별로 합쳐 후보를 만들고, 비활성/탈퇴 크리에이터와 최종 점수 1점 미만 후보가 제외되는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest - GREEN: 원천 지표 aggregate를 크리에이터 id 기준으로 합쳐
CreatorRankingSnapshotCandidate를 반환한다. - REFACTOR: 복잡한 집계가 QueryDSL로 과도해지면 native SQL을 사용하되, 테스트로 H2 호환성을 고정한다.
- 기대 결과: 스냅샷 생성 서비스가 별도 원천 조회를 여러 번 조합하지 않고 후보 목록을 받을 수 있다.
- Files:
Phase 4: 스냅샷 생성 서비스와 스케줄러
-
Task 4.1: 주간 스냅샷 생성 서비스 구현
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt
- Create:
- RED: KST 기간 산출, UTC 조회 기간 전달, raw value 점수 계산, 20위 점수 경계 동점 후보 전체 저장, 같은 기간 replace를 검증하는 service 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest - GREEN: aggregation port에서 후보를 조회하고 score policy로 점수를 계산한 뒤 저장 대상 후보만 snapshot port에 저장한다.
- REFACTOR: service는 계산 흐름만 담당하고 DB 조회 조건/저장 구현은 port 뒤로 숨긴다.
- 기대 결과: 스냅샷 저장 대상이 “20위 초과 점수 + 20위 동점 전체” 규칙을 만족한다.
- Files:
-
Task 4.2: 매주 월요일 07:30 KST 스케줄러 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt
- Create:
- RED: scheduler method에
@Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul")가 선언되어 있는지 reflection 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest - GREEN: 스케줄러가
CreatorRankingSnapshotRefreshService.refreshLastCompletedWeek()를 호출하도록 구현한다. - REFACTOR: 스케줄러에는 기간/점수/DB 로직을 두지 않는다.
- 기대 결과: 주간 스냅샷 생성 트리거가 KST 기준으로 고정된다.
- Files:
-
Task 4.3: 주간 스냅샷 스케줄러 Redisson lock 적용
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt
- Modify:
- RED: Redisson lock 획득 성공 시
CreatorRankingSnapshotRefreshService.refreshLastCompletedWeek()를 1회 호출하고, 획득 실패 시 호출하지 않는 테스트를 작성한다. lock key가lock:creator-ranking-snapshot-refresh인지도 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest - GREEN: 기존
RedissonClientbean을 스케줄러에 주입하고tryLock으로 lock을 획득한 인스턴스만 refresh service를 호출한다. lock 획득 실패는 정상 skip으로 처리한다. - REFACTOR: DB 기반 scheduler lock 테이블은 추가하지 않고, 기존
AudioContentReleaseScheduledTask의 Redisson lock 패턴을 참고하되 스케줄러에는 lock 획득/해제와 service 호출만 둔다. - 기대 결과: 여러 서버 인스턴스에서 같은 cron이 동시에 실행돼도 클러스터 전체에서 한 인스턴스만 주간 랭킹 스냅샷을 생성한다.
- Files:
Phase 5: 조회 서비스, 순위 변화, 차단 마스킹
-
Task 5.1: 최신/직전 스냅샷 기반 조회 서비스 구현
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Create:
- RED: 최신 완료 주차 스냅샷 없음 빈 결과, 직전 주차 없음
showRankChange=false, 직전 주차 있음rankChange양수/음수/null 및isNew계산 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: 최신 스냅샷 후보를 최종 점수 내림차순과 동점 랜덤 정렬로 최대 20명 선정하고, 직전 스냅샷 순위와 비교해 순위 변화를 계산한다.
- REFACTOR: 동점 랜덤으로 인해 같은 동점 구간의 순위 변화가 조회마다 달라질 수 있음을 테스트에서 허용 범위로 표현한다.
- 기대 결과: 홈 API Facade가 사용할
showRankChange와 item 목록이 ranking application service에서 완성된다.
- Files:
-
Task 5.2: 차단 관계 마스킹 port 구현
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingBlockPort.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingBlockRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Create:
- RED: 조회자와 랭킹 크리에이터 사이에 차단 관계가 있으면 row는 유지되고
creatorId=0,nickname="",profileImageUrl=기본 이미지 URL로 마스킹되는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: block port로 차단 대상 creator id를 조회하고, service에서 응답 item을 마스킹한다.
- REFACTOR: 기본 이미지 URL은 기존 프로젝트 상수/설정이 있으면 재사용하고, 없으면 ranking service 내부 상수로 분리한다.
- 기대 결과: 차단 관계가 있어도 순위 row 수는 유지되고 개인 식별 정보만 가려진다.
- Files:
Phase 6: 홈 API endpoint, Facade, DTO
-
Task 6.1: 랭킹 조회 DTO, 홈 API Facade, Controller 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/ranking/CreatorRankingResponse.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt
- Create:
- RED:
GET /api/v2/home/rankings/creators가showRankChange,items[].rank,rankChange,isNew,creatorId,nickname,profileImageUrl만 반환하고 날짜와finalScore를 반환하지 않는 controller 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest - GREEN: controller, API Facade, response DTO를 구현하고 Facade가
CreatorRankingQueryService를 호출해 홈 API 응답으로 변환한다. - REFACTOR: URL과 클라이언트 API 표면은
v2.api.home하위에 두고, 랭킹 DTO는v2.api.home.dto.ranking하위에 둔다. 랭킹 계산/조회 본체는v2.ranking에 유지한다. - 기대 결과: 클라이언트 홈 랭킹 탭에서 사용할 공개 API 계약이 테스트로 고정된다.
- Files:
-
Task 6.2: 인증/비인증 조회와 차단 마스킹 연결
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/adapter/in/web/CreatorRankingController.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeCreatorRankingFacade.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt
- Modify:
- RED: 비회원 조회는 기본 랭킹을 반환하고, 인증 회원 조회는 차단 관계 마스킹을 적용하는 controller 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest - GREEN: 기존 인증 주입 패턴을 확인해 member nullable 흐름을 service에 전달한다.
- REFACTOR: 기존 API 응답 wrapper 관례와 상태 코드를 맞춘다.
- 기대 결과: 인증 여부에 따라 차단 마스킹만 달라지고 endpoint 계약은 동일하다.
- Files:
Phase 7: 관측/문서/회귀 검증
-
Task 7.1: 스냅샷 생성/조회 로그 추가
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Modify:
- RED: 스냅샷 생성 성공/실패, 후보 수, 저장 수, 조회 성공/실패 로그가 남는지 output capture 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: 기존 프로젝트 관례대로
LoggerFactory기반 구조화 로그를 추가한다. - REFACTOR: 로그에 개인정보를 직접 남기지 않고 creator id/count/period만 남긴다.
- 기대 결과: PRD metrics 확인에 필요한 최소 로그가 남는다.
- Files:
-
Task 7.2: 전체 ranking 테스트와 포맷 검증
- Files:
- Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/** - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/** - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/** - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/** - Modify:
docs/20260608_크리에이터_랭킹/plan-task.md
- Verify:
- RED: 테스트 작성 예외.
TDD 예외 사유: 구현 완료 후 회귀 검증 task다. - 대체 검증 방법:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'./gradlew ktlintCheck./gradlew test
- GREEN: 실패하는 테스트가 있으면 해당 phase task로 돌아가 수정하고, 모든 명령을 통과시킨다.
- REFACTOR: plan-task 하단 검증 기록에 실행 명령, 목적, 결과를 누적한다.
- 기대 결과: ranking 기능 본체와 홈 API 조립 계층 테스트, 포맷, 전체 회귀 테스트가 통과한다.
- Files:
Phase 8: 스냅샷 job 이력과 스케줄 기록
-
Task 8.1: 스냅샷 job 이력 모델/DDL 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotJob.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotJobRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotJobRepository.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotJobPort.kt - Modify:
docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotJobRepositoryTest.kt
- Create:
- RED: 집계 시작/종료 시각, 실행 트리거, 상태(
PENDING,PROCESSING,DONE,FAILED), 실패 사유, 처리 시작/완료 시각을 저장하고 조회할 수 있는 repository 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest - GREEN: 기존
charge_event_job관례를 참고해 스냅샷 job entity/repository/port와 운영 반영용 DDL을 작성한다. - REFACTOR: 컬럼명은 관리자 목록과 worker 처리에 필요한 최소 필드로 제한하고 공개 API DTO와 분리한다.
- 기대 결과: 스냅샷 생성 이력이 기간/상태 기준으로 추적 가능해진다.
- Files:
-
Task 8.2: 스케줄 실행 전 job 생성과 성공/실패 기록 연결
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobServiceTest.kt
- Create:
- RED: 스케줄러가 스냅샷 생성 직전 집계 기간을 포함한
SCHEDULEDjob을 만들고, refresh 성공 시DONE, 예외 발생 시FAILED와 실패 사유를 기록하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN: 스케줄러는 lock 획득 후 job service를 통해 job 생성/실행/상태 기록을 위임하고, refresh service는 기존 스냅샷 생성 책임을 유지한다.
- REFACTOR: lock 획득 실패는 job 실패로 기록하지 않고 기존 정상 skip 정책을 유지한다.
- 기대 결과: 매주 스케줄 실행 여부와 성공/실패가 관리자에서 추적 가능한 job 이력으로 남는다.
- Files:
Phase 9: 관리자 수동 생성과 실패 job 재시도 API
-
Task 9.1: 관리자 날짜 범위 수동 생성 API 추가
- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobController.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobService.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobRequest.kt - Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobResponse.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobControllerTest.kt
- Create:
- RED:
POST /admin/rankings/creators/snapshot-jobs가 관리자 권한에서 날짜 범위를 받아MANUALjob을 생성하고, 비관리자 요청은 거부되는 controller/service 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.admin.ranking.creator.AdminCreatorRankingSnapshotJobControllerTest - GREEN: 기존 관리자 API 관례대로
@PreAuthorize("hasRole('ADMIN')")와ApiResponse.ok(...)를 사용해 수동 생성 job id와 상태를 반환한다. - REFACTOR: 날짜 범위 validation은 KST 주차/UTC 변환 정책과 중복되지 않도록 application service에 모은다.
- 기대 결과: 운영자가 별도 DB 확인 없이 필요한 날짜 범위의 스냅샷 생성을 요청할 수 있다.
- Files:
-
Task 9.2: 관리자 job 목록/실패 job 재시도 API 추가
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobController.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/admin/ranking/creator/AdminCreatorRankingSnapshotJobControllerTest.kt
- Modify:
- RED:
GET /admin/rankings/creators/snapshot-jobs가 날짜 범위/상태/실패 사유/재시도 가능 여부를 반환하고,POST /admin/rankings/creators/snapshot-jobs/{jobId}/retry가FAILEDjob만PENDING으로 되돌리는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.admin.ranking.creator.AdminCreatorRankingSnapshotJobControllerTest - GREEN: 기존
AdminChargeEventJobController/AdminChargeEventJobService패턴을 참고해 관리자 목록과 재시도 API를 구현한다. - REFACTOR:
PENDING,PROCESSING,DONE상태 job은 재시도 대상으로 변경하지 않고 명확한 실패 응답을 반환한다. - 기대 결과: 실패한 스냅샷 job을 관리자 버튼/API로 재시도할 수 있다.
- Files:
Phase 10: 스냅샷 완전 공백 fallback
-
Task 10.1: 스냅샷 테이블 완전 공백 여부 조회 port 추가
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotRepository.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt
- Modify:
- RED: 스냅샷 row가 하나도 없을 때만 true를 반환하고, 과거 주차 스냅샷이 하나라도 있으면 false를 반환하는 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest - GREEN: snapshot port에
isSnapshotTableEmpty()또는 동등한 메서드를 추가해 조회 서비스가 fallback 조건을 판단할 수 있게 한다. - REFACTOR: “최신 주차 스냅샷 없음”과 “테이블 완전 공백”을 서로 다른 조건으로 유지한다.
- 기대 결과: cold-start fallback이 과거 스냅샷 존재 시 실행되지 않도록 조건이 고정된다.
- Files:
-
Task 10.2: 조회 API cold-start fallback 연결
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Modify:
- RED: 최신 스냅샷이 없고 스냅샷 테이블이 완전히 비어 있을 때만 fallback 집계를 시도하고, 과거 스냅샷이 있으면 fallback을 시도하지 않는 테스트를 작성한다. 공개 응답 스키마가
showRankChange와items로 유지되는지도 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: query service가 snapshot-first 흐름을 유지하면서 완전 공백 상태에서만 제한적 fallback 집계를 호출하고 결과를 기존 ranking result로 변환한다.
- REFACTOR: fallback은 장기 실시간 랭킹 경로가 아니라 초기 스냅샷 부재 안전장치임을 service 경계와 테스트명에 드러낸다.
- 기대 결과: 초기 운영 상태에서는 빈 화면을 줄이고, 운영 중에는 기존 스냅샷 기반 정책을 유지한다.
- Files:
-
Task 10.3: fallback/job 관측 로그와 회귀 검증
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobServiceTest.kt
- Modify:
- RED: fallback 시도/성공/실패와 job 상태 변경 로그가 남는지 output capture 테스트를 작성한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN: 개인정보 없이 period, jobId, trigger, status, count, elapsedMs 중심의 구조화 로그를 추가한다.
- REFACTOR: 기존 Phase 7 로그와 이벤트명 충돌이 없도록 prefix를 정리한다.
- 기대 결과: 관리자 job과 cold-start fallback 상태를 운영 로그/메트릭으로 추적할 수 있다.
- Files:
Phase 11: cold-start fallback 스냅샷 생성 트리거
-
Task 11.1: cold-start fallback 전용 기간 기반 lock 실행 경계 추가
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobServiceTest.kt
- Modify:
- RED: 스냅샷 테이블이 완전히 비어 있는 초기 상태에서 같은 KST 지난 주 기간에 대해 lock을 획득한 경우에만 refresh 책임을 실행하고, lock 획득 실패 시 refresh를 호출하지 않는 테스트를 작성한다. lock key는 집계 시작/종료 UTC 시각을 포함한
lock:creator-ranking-snapshot-refresh:{start}:{end}형식으로 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN:
CreatorRankingSnapshotJobService에ensureLastCompletedWeekSnapshotForColdStart()또는 동등한 메서드를 추가한다. 이 메서드는CreatorRankingPeriodPolicy로 기간을 산출하고, Redisson lock을tryLock(0, -1, TimeUnit.SECONDS)로 획득한 경우에만 기존 refresh service를 호출한다. - REFACTOR: 조회 API가 직접
creator_ranking_snapshot을 저장하지 않도록 하고, lock 획득/해제와 refresh 위임 책임은 job service에 둔다. 스케줄러의 고정 lock key 정책은 유지하고, cold-start 전용 메서드에서만 기간 기반 lock key를 사용한다. - 기대 결과: 운영 배포 직후 내부 테스트 등 초기 cold-start 상황에서 같은 기간 스냅샷 생성이 중복 실행되지 않는다.
- Files:
-
Task 11.2: fallback 성공 후 스냅샷 생성 책임 위임 연결
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryService.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobServiceTest.kt
- Modify:
- RED:
getCreatorRankings()가 최신 스냅샷 없음 + 스냅샷 테이블 완전 공백 상태에서 fallback 결과를 응답하면서 cold-start 스냅샷 생성 위임 메서드를 호출하는 테스트를 작성한다. 과거 스냅샷이 있거나 fallback 후보가 없으면 cold-start 생성 위임을 호출하지 않는 테스트도 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN: query service는 fallback 응답 조립 후 job service에 스냅샷 생성 책임을 위임한다. 위임 실패는 공개 API 응답을 깨지 않도록 catch 후 구조화 로그로 남기고, fallback 응답 스키마는
showRankChange와items그대로 유지한다. - REFACTOR: fallback은 장기 실시간 랭킹 경로가 아니라 초기 상태 보강책임을 테스트명과 로그 이벤트명에 드러낸다.
- 기대 결과: 첫 내부 조회에서 fallback 응답을 내려주면서 이후 조회가 스냅샷 기반으로 전환될 수 있다.
- Files:
-
Task 11.3: cold-start 스냅샷 생성 트리거 회귀 검증
- Files:
- Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/** - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/** - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/CreatorRankingControllerTest.kt - Modify:
docs/20260608_크리에이터_랭킹/plan-task.md
- Verify:
- RED: 테스트 작성 예외.
TDD 예외 사유: 구현 완료 후 회귀 검증 task다. - 대체 검증 방법:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'./gradlew ktlintCheck
- GREEN: cold-start fallback, 스케줄러, 관리자 job, 차단 마스킹, CDN profile image 응답 테스트가 모두 통과해야 한다.
- REFACTOR: 검증 기록에 실행 명령, 목적, 결과를 누적한다.
- 기대 결과: cold-start 스냅샷 생성 보강이 기존 스케줄/관리자/조회 경로를 깨지 않는다.
- Files:
2. PRD 요구사항 추적
- Feature A: Task 1.1, Task 4.1에서 KST 기간 산출과 UTC DB 조회 변환을 검증한다.
- Feature B: Task 1.2, Task 3.1, Task 4.1에서 콘텐츠/라이브 raw can 산식을 검증한다.
- Feature C: Task 1.2, Task 3.2, Task 4.1에서 좋아요/댓글/대댓글 및 크리에이터 본인 댓글 제외를 검증한다.
- Feature D: Task 1.2, Task 3.3, Task 4.1에서 채널 후원 캔/건수와 최상위 팬 Talk 집계를 검증한다.
- Feature E: Task 1.2, Task 3.4, Task 4.1에서 최종 팔로우 수와
createdAt/updatedAt기반 팔로우 증가 수를 검증한다. - Feature F: Task 1.2, Task 4.1, Task 5.1에서 raw value 최종 점수, 1점 미만 제외, 20위 동점 후보 저장, 동점 랜덤 조회를 검증한다.
- Feature G: Task 5.1, Task 5.2에서 ranking 조회 결과와 차단 마스킹을 검증하고, Task 6.1, Task 6.2에서 홈 API endpoint, 응답 스키마, 인증/비인증 연결을 검증한다. Task 10.1, Task 10.2에서 스냅샷 테이블 완전 공백 상태의 제한적 fallback과 공개 응답 스키마 유지를 검증하고, Task 11.2에서 fallback 성공 후 응답을 깨지 않고 스냅샷 생성 책임을 위임하는 흐름을 검증한다.
- Feature H: Task 2.1, Task 2.2, Task 4.1, Task 4.2, Task 4.3에서 주간 스냅샷 저장, 스케줄, 클러스터 단일 실행 lock을 검증한다. Task 8.1, Task 8.2에서 스케줄 job 이력과 성공/실패 기록을 검증하고, Task 9.1, Task 9.2에서 관리자 날짜 범위 수동 생성과 실패 job 재시도 API를 검증한다. Task 11.1, Task 11.2에서 cold-start fallback 성공 후 기간 기반 lock으로 동일 기간 스냅샷 생성 중복을 방지하는 보강책을 검증한다.
- Feature I: Phase 5의 ranking 기능 본체는
v2.ranking패키지 경계를 유지하고, Phase 6의 클라이언트 API 표면은v2.api.home하위에 둔다. Phase 8~10의 관리자/job/fallback 기능도 공개 API 응답 DTO를 변경하지 않는다.
3. 검증 기록
-
2026-06-08: PRD 기준 구현 계획/TASK 문서를 작성했다. 구현 시작 전 문서 산출물이므로 코드 테스트는 실행하지 않았고, 문서 규칙에 따라
./gradlew tasks --all로 Gradle 명령 유효성을 확인한다. -
2026-06-08:
rg -n "TBD|TODO|작성 예정|fill in|placeholder|similar|위와 동일|적절한|나중" docs/20260608_크리에이터_랭킹/plan-task.md로 placeholder 문구가 없음을 확인했다. -
2026-06-08:
./gradlew tasks --all은 sandbox 기본 권한에서~/.gradlewrapper lock 파일 접근 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 778ms를 확인했다. -
2026-06-08: Phase 1 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicyTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScorePolicyTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 시 신규 ranking domain 타입 미정의로compileTestKotlin실패를 확인했다. -
2026-06-08: Phase 1 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScorePolicyTest와./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest는BUILD SUCCESSFUL을 확인했다. 병렬 실행한 period 단일 테스트 1건은 Kotlin/kapt cache 경합으로 실패해 후속 통합 검증에서 재확인한다. -
2026-06-08: Phase 2 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest는 production persistence 추가 전 실행했으나 Kotlin daemon heap 오류로 컴파일 단계에서 중단됐다. 당시 테스트가 참조하는CreatorRankingSnapshotRepository등 production 타입은 미구현 상태였다. -
2026-06-08: Phase 2 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest재실행 결과BUILD SUCCESSFUL in 1m 49s를 확인했다. -
2026-06-08: DDL 대체 검증:
rg -n "creator_ranking_snapshot|aggregation_start_at_utc|creator_id|final_score" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 테이블명, 기간 컬럼, 크리에이터 id, 최종 점수 컬럼 및 index 문구를 확인했다. -
2026-06-08: Phase 1~2 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'재실행 결과BUILD SUCCESSFUL in 22s를 확인했다. -
2026-06-08: 포맷 검증:
./gradlew ktlintCheck는 최초 신규 테스트 긴 줄로 실패했고, 줄바꿈 수정 후 재실행해BUILD SUCCESSFUL in 10s를 확인했다. -
2026-06-08: 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 16s를 확인했다. -
2026-06-08: Phase 3 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest실행 결과DefaultCreatorRankingAggregationRepository미구현으로compileTestKotlin실패를 확인했다. -
2026-06-08: Phase 3 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest재실행 결과BUILD SUCCESSFUL in 13s를 확인했다. -
2026-06-08: Phase 3 focused 재검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest재실행 결과BUILD SUCCESSFUL in 14s를 확인했다. -
2026-06-08: Phase 3 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 12s를 확인했다. -
2026-06-08: Phase 3 포맷 검증:
./gradlew ktlintCheck는 최초 신규 테스트 긴 줄로 실패했고, 줄바꿈 수정 후 재실행해BUILD SUCCESSFUL in 5s를 확인했다. -
2026-06-08: Phase 4 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest실행 결과CreatorRankingSnapshotRefreshService,CreatorRankingSnapshotScheduler미구현으로compileTestKotlin실패를 확인했다. -
2026-06-08: Phase 4 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest재실행 결과BUILD SUCCESSFUL in 3s를 확인했다. -
2026-06-08: Phase 4 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 15s를 확인했다. -
2026-06-08: Phase 4 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 22s를 확인했다. -
2026-06-08: Phase 4 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 56s를 확인했다. -
2026-06-08: Phase 4 reviewer gate: 스냅샷 생성 서비스/스케줄러/테스트/문서 변경에 대해 strict review를 수행했고
PASS판정을 확인했다. -
2026-06-08: Task 4.3 및 07:30 스케줄 변경 focused 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest실행 결과BUILD SUCCESSFUL in 16s를 확인했다. -
2026-06-08: Task 4.3 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 18s를 확인했다. -
2026-06-08: Task 4.3 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 26s를 확인했다. -
2026-06-08: Phase 5 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 결과CreatorRankingBlockPort,CreatorRankingQueryService미구현으로compileTestKotlin실패를 확인했다. -
2026-06-08: Phase 5 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest재실행 결과BUILD SUCCESSFUL in 29s를 확인했다. -
2026-06-08: Phase 5 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 18s를 확인했다. -
2026-06-08: Phase 5 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 25s를 확인했다. -
2026-06-08: Phase 5 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 1s를 확인했다. -
2026-06-08: Phase 5 reviewer gate: 조회 서비스/차단 마스킹/테스트/문서 변경에 대해 strict review를 수행했고
PASS판정을 확인했다. -
2026-06-08: Phase 6 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest실행 결과 신규 endpoint/permit rule 미구현으로 비회원 요청 401, 인증 요청 404 등 신규 controller 테스트 3건 실패를 확인했다. -
2026-06-08: Phase 6 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.CreatorRankingControllerTest재실행 결과BUILD SUCCESSFUL in 33s를 확인했다. -
2026-06-08: Phase 6 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 36s를 확인했다. -
2026-06-08: Phase 6 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 19s를 확인했다. -
2026-06-08: Phase 6 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 10s를 확인했다. -
2026-06-08: Phase 7 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 결과 신규 로그 assertion 4건이 이벤트 로그 부재로 실패하는 것을 확인했다. -
2026-06-08: Phase 7 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 40s를 확인했다. -
2026-06-08: Phase 7 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 39s를 확인했다. -
2026-06-08: Phase 7 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 21s를 확인했다. -
2026-06-08: Phase 7 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 9s를 확인했다. -
2026-06-08: Phase 7 reviewer gate 1차 검토: 스냅샷 생성 성공 로그가 transaction commit 이전에 기록되는 점과 PRD Metrics의 최종 점수 1점 미만 제외 수 관측 누락으로
FAIL판정을 확인했다. -
2026-06-08: Phase 7 reviewer 수정 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest실행 결과 신규lowScoreExcludedCount테스트가 fake 미구현으로compileTestKotlin실패하는 것을 확인했다. -
2026-06-08: Phase 7 reviewer 수정 GREEN 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingAggregationRepositoryTest실행 결과BUILD SUCCESSFUL in 50s를 확인했다. -
2026-06-08: Phase 7 reviewer 수정 후 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 37s를 확인했다. -
2026-06-08: Phase 7 reviewer 수정 후 포맷 검증:
./gradlew ktlintCheck는 최초 import 순서 위반으로 실패했고, import 정렬 후 재실행해BUILD SUCCESSFUL in 18s를 확인했다. -
2026-06-08: Phase 7 reviewer 수정 후 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 28s를 확인했다. -
후속 구현 중 각 task 완료 시 실행 명령, 목적, 결과를 이 섹션에 누적한다.
-
2026-06-09: 사용자 추가 요구에 따라 PRD와 plan-task에 스냅샷 job 이력, 스케줄 job 기록, 관리자 날짜 범위 수동 생성, 실패 job 관리자 전용 재시도 API, 스냅샷 테이블 완전 공백 시 제한적 fallback 계획을 문서화했다.
-
2026-06-09: Phase 8 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과 신규 job port/entity/service 미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 8 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 15s를 확인했다. -
2026-06-09: Phase 8 스케줄러 연결 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 4s를 확인했다. -
2026-06-09: Phase 8 DDL 대체 검증:
rg -n "creator_ranking_snapshot_job|aggregation_start_at_utc|aggregation_end_at_utc|trigger_type|status|processing_started_at|processed_at|last_error" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 job 테이블명, 기간/트리거/상태/처리 시각/실패 사유 컬럼 및 index 문구를 확인했다. -
2026-06-09: Phase 8 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 19s를 확인했다. -
2026-06-09: Phase 8 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 14s를 확인했다. -
2026-06-09: Phase 8 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 51s를 확인했다. -
2026-06-09: Phase 8 reviewer gate 1차 검토: repository 테스트가
PENDING저장 상태와PROCESSING전이를 직접 검증하지 않아FAIL판정을 확인했다. -
2026-06-09: Phase 8 reviewer 수정 후 focused 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 16s를 확인했다. -
2026-06-09: Phase 8 reviewer 수정 후 포맷 검증:
./gradlew ktlintCheck는 최초 unused import로 실패했고, import 제거 후 재실행해BUILD SUCCESSFUL in 6s를 확인했다. -
2026-06-09: Phase 8 reviewer 수정 후 ranking 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*'실행 결과BUILD SUCCESSFUL in 15s를 확인했다. -
2026-06-09: Phase 8 reviewer 수정 후 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 51s를 확인했다. -
2026-06-09: Phase 9 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest --tests kr.co.vividnext.sodalive.v2.admin.ranking.creator.AdminCreatorRankingSnapshotJobControllerTest실행 결과 신규 관리자 API 클래스,createManualJob/findJobs/retryFailedJob,markPending미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 9 focused GREEN 및 관리자 API 표면 검증: retry 전이 guard 보강 후 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 1m 21s를 확인했다.AdminCreatorRankingSnapshotJobControllerTest의MockMvc요청으로POST /admin/rankings/creators/snapshot-jobs,GET /admin/rankings/creators/snapshot-jobs,POST /admin/rankings/creators/snapshot-jobs/{jobId}/retry의 성공 응답과 비관리자 403/익명 401을 검증했다. -
2026-06-09: Phase 9 ranking/admin 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.admin.ranking.creator.*'실행 결과 최초 병렬 Gradle 실행 중 Kotlin/kapt cache 경합으로 실패했고, 단독 재실행해BUILD SUCCESSFUL in 23s를 확인했다. -
2026-06-09: Phase 9 포맷 검증:
./gradlew ktlintCheck는 최초 테스트 파일 닫는 brace 앞 공백과 main import 순서 위반으로 실패했고, 정리 후 재실행해BUILD SUCCESSFUL in 11s를 확인했다. -
2026-06-09: Phase 9 전체 회귀 검증: retry 전이 guard 보강 후
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 20s를 확인했다. -
2026-06-09: Phase 10 Task 10.1 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest실행 결과isSnapshotTableEmpty미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 10 Task 10.1 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 27s를 확인했다. -
2026-06-09: Phase 10 Task 10.2 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 결과aggregationPort,nowProvider생성자 파라미터 미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 10 Task 10.2 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 42s를 확인했다. -
2026-06-09: Phase 10 Task 10.3 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과 fallback/job 로그 이벤트 부재로 신규 로그 테스트 4건 실패를 확인했다. -
2026-06-09: Phase 10 Task 10.3 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 10s를 확인했다. -
2026-06-09: Phase 10 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 40s를 확인했다. -
2026-06-09: Phase 10 포맷 검증:
./gradlew ktlintCheck는 최초 테스트 import 순서 위반으로 실패했고, import 정렬 후 재실행해BUILD SUCCESSFUL in 6s를 확인했다. -
2026-06-09: Phase 10 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 59s를 확인했다. -
2026-06-09: Phase 10 reviewer gate 1차 검토: cold-start fallback 경로에서 인증 회원의 차단 크리에이터 마스킹이 누락되어
FAIL판정을 확인했다. -
2026-06-09: Phase 10 reviewer 수정 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 결과 fallback 차단 마스킹 신규 테스트 1건 실패를 확인했다. -
2026-06-09: Phase 10 reviewer 수정 GREEN 확인: fallback 결과에도 기존 차단 마스킹을 적용한 뒤 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 6s를 확인했다. -
2026-06-09: Phase 10 reviewer 수정 후 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 30s를 확인했다. -
2026-06-09: Phase 10 reviewer 수정 후 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 15s를 확인했다. -
2026-06-09: Phase 10 reviewer 수정 후 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 56s를 확인했다. -
2026-06-09: Phase 10 reviewer 수정 후 follow-up gate: 목표/보안 검증, 코드 품질 검토, QA focused 검증이 모두
PASS판정을 반환했고 blocking issue가 없음을 확인했다. -
2026-06-09: 사용자 후속 요청에 따라 cold-start fallback 성공 시 조회 API가 직접 스냅샷을 저장하지 않고
CreatorRankingSnapshotJobService/CreatorRankingSnapshotRefreshService책임으로 위임하도록 PRD와 plan-task를 갱신했다. 동일 집계 기간 중복 생성을 막기 위해 기간 기반 Redisson lock key(lock:creator-ranking-snapshot-refresh:{start}:{end})와 신규 Phase 11 Task 11.1~11.3을 추가했다. 문서 변경 검증으로rg -n "cold-start|ensureLastCompletedWeekSnapshotForColdStart|lock:creator-ranking-snapshot-refresh|Task 11|fallback 성공" docs/20260608_크리에이터_랭킹/prd.md docs/20260608_크리에이터_랭킹/plan-task.md및git diff -- docs/20260608_크리에이터_랭킹/prd.md docs/20260608_크리에이터_랭킹/plan-task.md를 실행해 반영 범위를 확인했다. -
2026-06-09: creator_ranking_snapshot 최신/직전 조회 기준 확인:
rg -n "max\(latest\.aggregation_end_at_utc\)|max\(previous\.aggregation_end_at_utc\)|order by .*id|findLatestSnapshots|findPreviousCompletedSnapshots" src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence및 repository 코드 확인 결과 최신/직전 조회는id가 아니라aggregation_end_at_utc의 max/previous max 기준이며, 기간 내 정렬은final_score desc임을 확인했다. -
2026-06-09: Phase 11 Task 11.1 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과RedissonClient생성자 인자와ensureLastCompletedWeekSnapshotForColdStart미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 11 Task 11.1 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 12s를 확인했다. -
2026-06-09: Phase 11 Task 11.2 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과snapshotJobService생성자 파라미터 미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 11 Task 11.2 GREEN 확인: 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 16s를 확인했다. -
2026-06-09: Phase 11 focused 재검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 2s를 확인했다. -
2026-06-09: Phase 11 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 1m 9s를 확인했다. -
2026-06-09: Phase 11 포맷 검증:
./gradlew ktlintCheck는 최초 main import 순서 위반으로 실패했고, import 정렬 후 재실행해BUILD SUCCESSFUL in 23s를 확인했다. -
2026-06-09: Phase 11 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 2m 52s를 확인했다. -
2026-06-09: Phase 11 reviewer gate 1차 Code Quality 검토: 스케줄러 고정 lock과 cold-start 기간 lock이 달라 동일 기간 refresh가 동시에 실행될 수 있어
FAIL판정을 확인했다. -
2026-06-09: Phase 11 reviewer 수정 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과 스케줄 job이 cold-start와 같은 기간 lock을 사용하지 않아 신규 테스트 2건 실패를 확인했다. -
2026-06-09: Phase 11 reviewer 수정 GREEN 확인: 스케줄 job refresh와 cold-start refresh가 공통 기간 기반 lock 경계를 사용하도록 수정한 뒤 동일 focused 테스트 재실행 결과
BUILD SUCCESSFUL in 56s를 확인했다. -
2026-06-09: Phase 11 reviewer 수정 후 focused 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 9s를 확인했다. -
2026-06-09: Phase 11 reviewer 수정 후 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 43s를 확인했다. -
2026-06-09: Phase 11 reviewer 수정 후 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 27s를 확인했다. -
2026-06-09: Phase 11 reviewer 수정 후 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 10s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 Code Quality 검토: 공통 period lock은 적용됐지만 transaction commit 전에 lock이 해제될 수 있어
FAIL판정을 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과TransactionTemplate/transaction manager 생성자 인자 미구현으로compileTestKotlin실패를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 GREEN 확인:
PlatformTransactionManager로PROPAGATION_REQUIRES_NEWTransactionTemplate을 내부 생성하고, period lock 안의 transaction commit 이후 unlock되도록 수정한 뒤 job service focused 테스트 재실행 결과BUILD SUCCESSFUL in 12s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 후 focused 검증:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 3s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 후 ranking/API 범위 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*'실행 결과BUILD SUCCESSFUL in 45s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 후 포맷 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 17s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 후 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 1m 9s를 확인했다. -
2026-06-09: Phase 11 reviewer 2차 수정 후 Code Quality 재검토 결과 이전 blocking issue가 해소되어
PASS판정을 확인했다.