92 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 기준 지난 주 크리에이터 랭킹 중 visibleFromAt <= now 조건을 만족하는 최신 공개 스냅샷의 상위 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는 최신 생성 스냅샷이 아니라 visibleFromAt <= now 조건을 만족하는 최신 공개 스냅샷을 읽어 응답을 조립한다. 단, 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 제한적 원천 데이터 fallback 집계를 시도할 수 있고, 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 - 집계 기준 시각: 매주 월요일 00:00:00 KST. 이 시각을 주간 집계 종료 경계로 사용한다.
- 집계 기간: 생성 시점 기준 KST 지난 주 월요일 00:00:00 이상, 이번 주 월요일 00:00:00 미만
- DB 조회 기간: KST 집계 기간을 UTC 기준
LocalDateTime또는 프로젝트 표준 시간 타입으로 변환한 기간 - 스냅샷 생성 스케줄 후보: 매주 월요일 KST 01:00,
@Scheduled(cron = "0 0 1 * * MON", zone = "Asia/Seoul") - 스냅샷 노출 전환 시각: 매주 월요일 KST 09:00. 스냅샷과 job 이력에
visibleFromAt으로 저장한다. - 현재 기본 크리에이터 랭킹 타입:
WEEKLY. 스냅샷과 job 이력에rankingType으로 저장한다. - 다중 서버 인스턴스에서 스냅샷 스케줄러가 중복 실행되지 않도록 기존 Redisson 기반 분산 lock을 사용한다.
- 랭킹 스냅샷 lock key는
lock:creator-ranking-snapshot-refresh로 고정하고, lock 획득 실패 인스턴스는 정상 skip한다. - 조회 API는 스냅샷 기반 응답을 기본으로 하며, 최신 생성 스냅샷이 아니라
visibleFromAt <= now조건을 만족하는 최신 공개 스냅샷을 응답한다. - 조회 시 09:00 KST 전에는 01:00 KST에 생성된 새 주차 스냅샷이 있어도 직전 공개 스냅샷을 유지한다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 제한적 원천 데이터 fallback 집계를 시도할 수 있다.
- fallback 응답도 fallback 대상 기간의
visibleFromAt <= now조건을 만족할 때만 공개한다. - 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 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
1.1 DDL 영향도: visible_from_at, ranking_type
creator_ranking_snapshot에는ranking_type varchar(30) not null,visible_from_at timestamp not null을 추가한다.creator_ranking_snapshot_job에는ranking_type varchar(30) not null,visible_from_at timestamp not null을 추가한다.- 현재 기본 타입 값은
WEEKLY로 문서화하고, 코드 구현 시CreatorRankingType또는 동등한 enum/상수로 고정한다. visible_from_at은 집계 종료일 월요일 09:00:00 KST를 UTC로 변환한 값이다. 예: 2026-06-08 09:00:00 KST는 2026-06-08 00:00:00 UTC다.docs/20260608_크리에이터_랭킹/create-ranking-tables.sql의 기존 CREATE DDL은 이미 적용된 기준으로 유지하고, 하단에 운영 반영용 ALTER DDL을 추가한다.- 운영 DB 변경은
ADD nullable column -> backfill -> MODIFY NOT NULL -> index 보강/교체순서로 적용한다. - backfill은
ranking_type='WEEKLY',visible_from_at=aggregation_end_at_utc + interval 9 hour기준으로 수행한다. - 같은 타입/기간 재생성 삭제 기준은
ranking_type + aggregation_start_at_utc + aggregation_end_at_utc다. - 중복 방지 기준은
ranking_type + aggregation_start_at_utc + aggregation_end_at_utc + creator_idunique index다. - 최신 공개 스냅샷 조회는
ranking_type = WEEKLY and visible_from_at <= nowUtc조건에서 가장 큰visible_from_at을 찾은 뒤 해당 스냅샷 row를final_score desc기준으로 읽는다. - 직전 공개 스냅샷 조회는 최신 공개 스냅샷보다 작은
visible_from_at중 가장 큰 값을 기준으로 읽는다. - job 목록/재시도 조회는
ranking_type + aggregation period + status,ranking_type + visible_from_at + status,ranking_type + aggregation period + trigger_type + created_at인덱스를 사용한다. - 공개 API 응답 DTO에는
rankingType,visibleFromAt, 집계 기간, fallback 여부를 노출하지 않는다.
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:
Phase 12: 크리에이터 랭킹 시간 정책 변경
Phase 1~11은 완료 당시의 구현 이력이다. 시간 정책 변경은 완료된 task를 다시 수행하는 방식이 아니라, Phase 12에서 기존 07:30 생성 스케줄, 최신 완료 주차 조회, 기존 DDL/엔티티/port 구조를
01:00 생성 후보,09:00 노출 전환,visibleFromAt <= now최신 공개 스냅샷 조회 기준으로 변경한다.
-
Task 12.1: 집계/생성/노출 시각 분리 정책 추가
- Files:
- Modify:
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
- Modify:
- RED: 월요일 00:00:00 KST를 집계 종료 경계로 유지하고, 집계 종료일 월요일 09:00:00 KST가
visibleFromAtUtc로 변환되는 테스트를 작성한다. 2026-06-08 09:00:00 KST가 2026-06-08 00:00:00 UTC로 변환되는지 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicyTest - GREEN:
resolveVisibleFromAtUtc(aggregationEndAtKst)또는 동등한 메서드를 추가하고, 기존 집계 기간 산출은 변경하지 않는다. - REFACTOR: 생성 후보 시각(01:00 KST)은 scheduler 책임으로 두고, period policy는 집계 기간과 공개 노출 시각 계산에 집중한다.
- 기대 결과: 집계 기준 시각과 공개 노출 전환 시각이 코드와 테스트에서 분리된다.
- Files:
-
Task 12.2:
rankingType,visibleFromAt스냅샷/job 저장 구조 반영- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshot.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotJob.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/port/out/CreatorRankingSnapshotJobPort.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/DefaultCreatorRankingSnapshotJobRepository.kt - Modify:
docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotJobRepositoryTest.kt
- Modify:
- RED: 스냅샷과 job record가
rankingType=WEEKLY,visibleFromAtUtc를 저장하고, 같은 타입/기간/크리에이터 중복 저장이 불가능하며, 같은 타입/기간 replace가 기존 row를 제거하는 repository 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest - GREEN: entity/record/port에
rankingType,visibleFromAtUtc를 추가하고, 운영 DB 변경용 ALTER DDL을 문서화한다. 기본 타입WEEKLY를 생성/조회 경로에 전달한다. - REFACTOR: DDL 컬럼명은
ranking_type,visible_from_at으로 유지하고, Kotlin 필드명은 기존 시간 필드 관례에 맞춰visibleFromAtUtc로 둔다. - 기대 결과: 스냅샷과 job 이력이 공개 노출 기준으로 조회될 수 있는 데이터를 가진다.
- Files:
-
Task 12.3: 스냅샷 생성 스케줄을 월요일 01:00 KST로 변경
- Files:
- 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/CreatorRankingSnapshotJobService.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/CreatorRankingSnapshotRefreshServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotJobServiceTest.kt
- Modify:
- RED: scheduler method에
@Scheduled(cron = "0 0 1 * * MON", zone = "Asia/Seoul")가 선언되어 있는지 검증하고, 스케줄 job이visibleFromAtUtc를 월요일 09:00 KST 기준으로 저장하는 테스트를 작성한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN: 기존 07:30 cron을 01:00 cron으로 변경하고, refresh/job 생성 경로에
visibleFromAtUtc를 전달한다. - REFACTOR: lock key는 기존 중복 실행 방지 정책을 유지하되, 기간 기반 lock 내부에서
rankingType이 필요한 경우 lock key에 포함할지 테스트로 고정한다. - 기대 결과: 생성 후보 시각이 집계 종료 1시간 뒤로 당겨져도 공개 노출은 09:00까지 지연된다.
- Files:
-
Task 12.4: 조회 API를 최신 생성 스냅샷이 아닌 최신 공개 스냅샷 기준으로 변경
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/CreatorRankingSnapshotRepository.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/application/CreatorRankingQueryService.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/persistence/DefaultCreatorRankingSnapshotRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingQueryServiceTest.kt
- Modify:
- RED: 01:00 KST에 새 스냅샷이 생성되어도 08:59:59 KST 조회는 직전 공개 스냅샷을 반환하고, 09:00:00 KST 조회는 새 스냅샷을 반환하는 테스트를 작성한다. 직전 공개 스냅샷 기준
rankChange,isNew,showRankChange계산도 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest - GREEN: snapshot port에
findLatestVisibleSnapshots(rankingType, nowUtc)와findPreviousVisibleSnapshots(rankingType, nowUtc)또는 동등한 메서드를 추가하고, query service가 이 메서드만 사용하도록 변경한다. - REFACTOR: 기존
findLatestSnapshots()/findPreviousCompletedSnapshots()가 더 이상 공개 조회에 쓰이지 않으면 제거하거나 관리자/테스트 전용으로 명확히 제한한다. - 기대 결과: 공개 API가 latest generated가 아니라 latest visible 스냅샷만 응답한다.
- Files:
-
Task 12.5: 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/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 대상 기간의
visibleFromAtUtc > nowUtc이면 새 주차 결과를 응답하지 않는 테스트를 작성한다.visibleFromAtUtc <= nowUtc이면 기존 fallback 응답과 스냅샷 생성 위임이 유지되는지도 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest - GREEN: fallback 집계 전에 공개 가능 여부를 검사하고, 공개 불가 시 빈 응답 또는 직전 공개 스냅샷 응답을 유지한다.
- REFACTOR: 공개 API 응답 DTO에는
visibleFromAtUtc,rankingType, fallback 여부를 추가하지 않는다. - 기대 결과: 초기 상태 보강책도 09:00 공개 전환 정책을 우회하지 않는다.
- Files:
-
Task 12.6: 시간 정책 변경 문서/DDL 정합성 검증
- Files:
- Verify:
docs/20260608_크리에이터_랭킹/prd.md - Verify:
docs/20260608_크리에이터_랭킹/plan-task.md - Verify:
docs/20260608_크리에이터_랭킹/create-ranking-tables.sql - Modify:
docs/20260608_크리에이터_랭킹/plan-task.md
- Verify:
- RED: 테스트 작성 예외.
TDD 예외 사유: 문서와 DDL 변경 범위 검증 task다. - 대체 검증 방법:
rg -n "07:30|01:00|09:00|visibleFromAt|visible_from_at|rankingType|ranking_type|latest visible|최신 공개|최신 생성" docs/20260608_크리에이터_랭킹rg -n "visible_from_at|ranking_type|creator_ranking_snapshot|creator_ranking_snapshot_job" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql./gradlew tasks --all
- GREEN: 문서에 남은 07:30 표현은 과거 검증 기록 또는 기존 정책 언급인지 확인하고, 현재 목표/신규 task에는 01:00 생성과 09:00 노출 전환만 남긴다.
- REFACTOR: 검증 기록에 실행 명령, 목적, 결과를 누적한다.
- 기대 결과: PRD, 구현 계획, DDL이 같은 시간 정책과 공개 조회 기준을 설명한다.
- 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 성공 후 응답을 깨지 않고 스냅샷 생성 책임을 위임하는 흐름을 검증한다. Task 12.4, Task 12.5에서 조회 API가 최신 생성 스냅샷이 아니라
visibleFromAt <= now조건을 만족하는 최신 공개 스냅샷만 응답하도록 검증한다. - 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으로 동일 기간 스냅샷 생성 중복을 방지하는 보강책을 검증한다. Task 12.1~12.6에서 집계 기준 00:00 KST, 생성 후보 01:00 KST, 노출 전환 09:00 KST,
rankingType/visibleFromAtDDL 영향과 최신 공개 스냅샷 조회 정책을 검증한다. - 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판정을 확인했다. -
2026-06-24: 크리에이터 랭킹 시간 정책 변경 문서 작업을 시작해 PRD에 집계 기준 00:00:00 KST, 생성 후보 01:00:00 KST, 노출 전환 09:00:00 KST, 최신 공개 스냅샷(
visibleFromAt <= now) 조회 정책을 반영했다.plan-task.md에는visible_from_at/ranking_typeDDL 영향도와 신규 Phase 12 Task 12.1~12.6을 추가했다. -
2026-06-24: 문서 정합성 확인:
rg -n "07:30|0 30 7|최신 완료|완료 주차" docs/20260608_크리에이터_랭킹/prd.md docs/20260608_크리에이터_랭킹/plan-task.md실행 결과 현재 PRD에는 변경 전 정책 표현이 남아 있지 않고,plan-task.md의 남은 07:30/최신 완료 표현은 Phase 1~11 완료 당시 이력 또는 과거 검증 기록이며 Phase 12 note에서 신규 변경 범위를 구분했음을 확인했다. -
2026-06-24: 시간 정책/DDL 키워드 확인:
rg -n "00:00:00 KST|01:00|09:00|visibleFromAt|visible_from_at|rankingType|ranking_type|최신 공개|최신 생성" docs/20260608_크리에이터_랭킹/prd.md docs/20260608_크리에이터_랭킹/plan-task.md docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 PRD, plan-task, DDL에 신규 시간 정책과 컬럼명이 반영됐음을 확인했다. -
2026-06-24: DDL 핵심 컬럼 확인:
rg -n "visible_from_at|ranking_type|creator_ranking_snapshot|creator_ranking_snapshot_job" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 두 테이블의ranking_type,visible_from_at컬럼과 조회/관리 인덱스를 확인했다. -
2026-06-24: 문서 변경 후 Gradle 명령 유효성 확인:
./gradlew tasks --all은 sandbox 기본 권한에서~/.gradlewrapper lock 파일 접근 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 2s를 확인했다. -
2026-06-24: 사용자 피드백에 따라 이미 적용된
create-ranking-tables.sql의 CREATE DDL 변경을 되돌리고, 파일 하단에 기존 적용 DB 변경용 ALTER DDL을 추가했다. 컬럼 추가는 기존 row를 고려해 nullable로 추가한 뒤WEEKLY와aggregation_end_at_utc + 9시간기준으로 backfill하고, 이후MODIFY NOT NULL및 인덱스 보강/교체를 수행하는 순서로 정리했다. -
2026-06-24: 피드백 반영 후 문서/DDL 재검증:
git diff -- docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 CREATE DDL 본문은 변경하지 않고 하단 ALTER 섹션만 추가됐음을 확인했다.rg -n "이미 위 CREATE DDL|alter table creator_ranking_snapshot|add column ranking_type|update creator_ranking_snapshot|modify column ranking_type|drop index|create index idx_creator_ranking_snapshot_visible_score|alter table creator_ranking_snapshot_job|idx_creator_ranking_snapshot_job_visible_status" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 ALTER/backfill/modify/index 변경 순서를 확인했다. -
2026-06-24: 피드백 반영 후 Gradle 명령 유효성 확인:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL in 808ms를 확인했다. -
2026-06-24: 완료된 Phase 본문 수정에 대한 혼동을 줄이기 위해 Phase 2.1, Phase 4.2, Phase 5.1의 완료 task 문구는 기존 이력대로 되돌리고, Phase 12 시작부에 “Phase 1~11은 완료 당시 구현 이력이며 시간 정책 변경은 Phase 12에서 수행한다”는 note를 추가했다.
-
2026-06-24: 완료 Phase 문구 원복 후 Gradle 명령 유효성 확인:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL in 823ms를 확인했다. -
2026-06-24: PRD/plan-task 크로스 체크 결과, PRD Feature A의 변경 전 기간 기준 표현이 09:00 공개 노출 전환 전 응답 정책과 충돌할 수 있어 “스냅샷 생성 또는 fallback 집계 기준 시점” 기준으로 수정했다.
rg -n "조회 시점 기준|2026-06-08 월요일 KST에 조회하면" docs/20260608_크리에이터_랭킹/prd.md로 PRD 본문에 변경 전 표현이 남지 않았고,rg -n "집계 기준 시각|생성 후보 시각|노출 전환 시각|visibleFromAt <= now|rankingType|visible_from_at|ranking_type|운영 반영용 ALTER" docs/20260608_크리에이터_랭킹/prd.md docs/20260608_크리에이터_랭킹/plan-task.md docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 PRD, plan-task, DDL의 요구사항 반영 지점을 확인했다. -
2026-06-24: PRD/plan-task 크로스 체크 수정 후 Gradle 명령 유효성 확인:
./gradlew tasks --all실행 결과BUILD SUCCESSFUL in 762ms를 확인했다. -
2026-06-24: Phase 12 Task 12.1 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicyTest실행 결과resolveVisibleFromAtUtc미구현으로compileTestKotlin실패를 확인했다. GREEN 확인: 동일 focused 테스트 재실행 결과BUILD SUCCESSFUL in 17s를 확인했다. -
2026-06-24: Phase 12 Task 12.2 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotJobRepositoryTest실행 결과CreatorRankingType,rankingType,visibleFromAtUtc미구현으로compileTestKotlin실패를 확인했다. GREEN 확인: 스냅샷/job 저장 구조 반영 후 동일 focused 테스트 재실행 결과BUILD SUCCESSFUL in 51s를 확인했다. -
2026-06-24: Phase 12 Task 12.3~12.5 RED 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler.CreatorRankingSnapshotSchedulerTest --tests kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.DefaultCreatorRankingSnapshotRepositoryTest --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryServiceTest실행 결과findLatestVisibleSnapshots/findPreviousVisibleSnapshots미구현으로compileTestKotlin실패를 확인했다. GREEN 확인: 01:00 KST cron, 최신 공개 스냅샷 조회, cold-start fallback 공개 전 차단 반영 후 Phase 12 focused 테스트 재실행 결과BUILD SUCCESSFUL in 18s를 확인했다. -
2026-06-24: Phase 12 문서/DDL 정합성 검증:
rg -n "07:30|01:00|09:00|visibleFromAt|visible_from_at|rankingType|ranking_type|latest visible|최신 공개|최신 생성" docs/20260608_크리에이터_랭킹실행 결과 남은 07:30 표현은 완료된 Phase 4/과거 검증 기록과 Phase 12 변경 note임을 확인했고, 현재 목표/신규 task에는 01:00 생성과 09:00 노출 전환이 반영됐음을 확인했다.rg -n "visible_from_at|ranking_type|creator_ranking_snapshot|creator_ranking_snapshot_job" docs/20260608_크리에이터_랭킹/create-ranking-tables.sql로 ALTER/backfill/not-null/index 변경 순서를 확인했다. -
2026-06-24: Phase 12 ranking/API/admin 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*' --tests 'kr.co.vividnext.sodalive.v2.admin.ranking.creator.*'실행 결과BUILD SUCCESSFUL in 42s를 확인했다. -
2026-06-24: Phase 12 포맷 검증:
./gradlew ktlintCheck는 최초 신규 테스트 긴 줄과 import 순서 위반으로 실패했고, 포맷 정리 후 재실행해BUILD SUCCESSFUL in 10s를 확인했다. -
2026-06-24: Phase 12 전체 회귀 검증:
./gradlew test실행 결과BUILD SUCCESSFUL in 2m 18s를 확인했다. -
2026-06-24: Phase 12 reviewer gate 1차 Code Quality 검토: 스케줄 job 생성/PROCESSING/refresh/DONE/FAILED 기록이 하나의
REQUIRES_NEW트랜잭션에 묶여 refresh 실패 시FAILED기록도 롤백될 수 있어FAIL판정을 확인했다. -
2026-06-24: Phase 12 reviewer 수정 focused 검증:
CreatorRankingSnapshotJobService의 scheduled job 상태 전이를 content ranking 패턴처럼savePendingJob,markProcessing,refresh,markDone,markFailed별도 transaction으로 분리하고, refresh rollback 이후 FAILED 상태 commit 순서 테스트를 추가했다../gradlew test --tests kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobServiceTest실행 결과BUILD SUCCESSFUL in 29s를 확인했다. -
2026-06-24: Phase 12 reviewer 수정 후 ranking/API/admin 회귀 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*' --tests 'kr.co.vividnext.sodalive.v2.admin.ranking.creator.*'실행 결과BUILD SUCCESSFUL in 49s를 확인했다. -
2026-06-24: Phase 12 reviewer 수정 후 포맷/전체 회귀 검증:
./gradlew ktlintCheck실행 결과BUILD SUCCESSFUL in 32s,./gradlew test실행 결과BUILD SUCCESSFUL in 1m 43s를 확인했다. -
2026-06-24: Phase 12 reviewer 수정 후 Code Quality 재검토 결과 이전 blocking issue가 해소되어
PASS판정을 확인했다. -
2026-06-24: Phase 12 코드 리뷰 및 재검증: 공개 조회 경로가
findLatestVisibleSnapshots(WEEKLY, nowUtc)/findPreviousVisibleSnapshots(WEEKLY, currentAggregationStartAtUtc, nowUtc)를 사용하고, 01:00 KST scheduler, 09:00 KSTvisibleFromAtUtc, cold-start fallback 공개 전 차단, scheduled job 실패 시FAILED상태 별도 transaction commit 흐름을 재확인했다. blocking issue는 발견하지 않았다. -
2026-06-24: Phase 12 코드 리뷰 후 fresh 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.ranking.*' --tests 'kr.co.vividnext.sodalive.v2.api.home.*' --tests 'kr.co.vividnext.sodalive.v2.admin.ranking.creator.*'실행 결과BUILD SUCCESSFUL in 38s를 확인했다../gradlew ktlintCheck는 sandbox 기본 권한에서~/.gradlewrapper lock 파일 접근 권한 문제로 실패했고, 권한 승인 후 재실행해BUILD SUCCESSFUL in 855ms를 확인했다../gradlew test실행 결과BUILD SUCCESSFUL in 1m 41s를 확인했다.