Files

58 KiB

메인 홈 추천 응원 크리에이터 스냅샷 수정 Plan/Task

후속 변경 상태

항목 내용
상태 3차 Phase별 리뷰 완료
확정일 2026-07-31
요구사항 기준 docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md 전체
현재 Phase Phase 1~7 3차 리뷰 완료
현재 활성 Goal 없음
다음 Goal 없음

시나리오 계약

  • Happy path: 인기 커뮤니티와 동일한 최근 7일 UTC half-open 범위로 CHEER_CREATOR 점수를 계산하고, 점수순 상위 16개 스냅샷을 저장한다. Real surface: DefaultHomeRecommendationQueryRepositoryTest, RecommendationSnapshotRefreshServiceTest.
  • Score: 응원 점수는 ((donationAmount * 0.45) + (fanTalkCount * 0.30) + (donationCount * 0.10)) * newBoost다. 후원 금액은 CHANNEL_DONATIONDONATIONuse_can_calculate.can을 그대로 사용하고, 후원 수는 UseCanCalculate.useCan 기준으로 중복 제거한다. Real surface: RecommendationScorePolicyTest, DefaultHomeRecommendationQueryRepositoryTest.
  • Boost: 신규 부스트는 크리에이터 데뷔일 기준 010일 1.15, 1120일 1.10, 21~30일 1.05, 31일 이상 1.0이다. Real surface: RecommendationScorePolicyTest, DefaultHomeRecommendationQueryRepositoryTest.
  • Fallback: 최신 CHEER_CREATOR 스냅샷이 없으면 lock, double-check, 동일 refresh 로직 재사용, refresh 후 재조회 순서로 fallback을 실행한다. lock 대기는 최대 300ms, 홈 API refresh 완료 대기는 최대 1,500ms다. Real surface: fallback service test, HomeRecommendationQueryServiceTest.
  • Empty marker: CHEER_CREATOR refresh 결과가 0건이면 targetId = 0 marker를 저장해 정상 refresh 완료 상태를 남기고, 조회 응답에서는 marker를 제외한다. Real surface: RecommendationSnapshotPersistenceAdapterTest, HomeRecommendationQueryServiceTest.
  • Personalized filter: 인증 회원의 cheerCreators에서 조회자 본인과 CreatorFollowing.isActive == true인 팔로우 크리에이터를 제외한다. 비활성 팔로우 이력과 비회원 조회는 기존 동작을 유지한다. Real surface: DefaultHomeRecommendationQueryRepositoryTest.
  • Adjacent regression: 메인 홈 추천 API URL과 CHEER_CREATOR 응답 필드는 변경하지 않는다. AI 캐릭터, 인기 커뮤니티, 최근 데뷔 등 다른 섹션 산식과 공개 스키마는 이번 변경으로 바꾸지 않는다. Real surface: 기존 focused tests, HomeRecommendationControllerTest.

범위와 전제

  • 이번 문서는 docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md의 구현 계획이다.
  • 신규 공개 API, 신규 응답 필드, 운영 DDL 추가는 범위에 포함하지 않는다.
  • 기존 recommendation_snapshot 테이블과 RecommendedSectionType.CHEER_CREATOR를 재사용한다.
  • CHEER_CREATOR 집계는 현재 구조와 성능 특성을 유지해 DB-side exact scoring을 기본으로 한다. Kotlin 단에는 산식/부스트 근거 테스트용 정책 함수를 둔다.
  • 집계 기간은 인기 커뮤니티와 동일하게 KST 전날을 포함한 최근 7일이며, DB 조회에는 UTC half-open window를 사용한다.
  • fallback orchestration은 AI 캐릭터 전용 구현을 그대로 복사하지 않고, 섹션별 lock key와 refresh action을 받을 수 있는 최소 공통 runner를 우선 적용한다.
  • 다른 스냅샷 섹션으로 empty marker를 확장하는 작업은 이번 구현 범위에서 제외한다. 단, CHEER_CREATOR에 적용할 때 이후 공통화가 가능하도록 조건문/상수명을 명확히 둔다.
  • 본인·팔로우 제외는 스냅샷 생성이 아닌 findCheerCreatorRecommendationDetails(...) 상세 조회 시점에서 기존 memberId로 적용한다.
  • 기존 16명 스냅샷 후보 안에서만 필터링하며, 필터 결과가 8명 미만이어도 하위 후보 조회나 스냅샷 저장 수 확대를 하지 않는다.

기존 CHEER_CREATOR 로직 유지/변경 경계

  • 유지: RecommendedSectionType.CHEER_CREATOR enum 값과 code는 변경하지 않는다.
  • 유지: 홈 응원 크리에이터 응답 필드인 creatorId, creatorNickname, creatorProfileImage는 변경하지 않는다.
  • 유지: 홈 첫 화면 응답은 최대 8명, 스냅샷 후보 조회는 최대 16개를 사용한다.
  • 유지: 상세 조회 시점의 활성 크리에이터 필터와 차단 필터는 유지한다.
  • 유지: 비회원 조회와 비활성 팔로우 이력이 있는 크리에이터 조회는 기존 동작을 유지한다.
  • 유지: 후원 금액은 use_can_calculate.can 값을 그대로 합산한다.
  • 변경: 점수 가중치는 후원 금액 45%, 팬Talk 수 30%, 후원 수 10%로 바꾼다.
  • 변경: 후원 수는 UseCanCalculate.useCan 기준 distinct count로 계산한다.
  • 변경: 팬Talk 수는 CreatorCheers.isActive == true row 수로 계산한다.
  • 변경: 집계 기간은 인기 커뮤니티와 동일한 최근 7일로 한다.
  • 변경: 신규 부스트는 기존 크리에이터 공통 부스트 1.5/1.3/1.2가 아니라 CHEER_CREATOR 전용 1.15/1.10/1.05를 사용한다.
  • 추가: CHEER_CREATOR 최신 스냅샷이 없을 때 fallback refresh를 실행한다.
  • 추가: CHEER_CREATOR refresh 결과 0건이면 empty snapshot marker를 저장한다.
  • 추가: 인증 회원 본인과 활성 팔로우 중인 크리에이터를 cheerCreators 상세 조회에서 제외한다.

실행 명령

  • 문서 명령 확인: ./gradlew tasks --all
  • 산식/부스트 단위 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest
  • 스냅샷 저장 marker 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
  • 응원 크리에이터 query 통합 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
  • refresh/fallback 조회 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
  • fallback service 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest
  • 홈 API 회귀 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest
  • 포맷 검증: ./gradlew ktlintCheck
  • 전체 회귀: ./gradlew test

Phase 1: 문서와 기준 고정

  • Task 1.1: PRD 기반 구현 계획 문서 작성
    • 파일 경로:
      • Verify: docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md
      • Create: docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md
    • RED: 문서 작업은 TDD 예외. TDD 예외 사유: 코드 동작 변경 전 요구사항과 구현 순서를 고정하는 작업이다.
    • GREEN: PRD의 산식, 최근 7일 UTC half-open 범위, 후원 수 distinct 기준, empty marker, fallback timeout/lock 정책을 task로 분해한다.
    • REFACTOR: 기존 홈 추천 구현 파일과 테스트 파일 기준으로 task별 수정/검증 경로를 맞춘다.
    • 기대 결과: 구현 시작 전에 PRD와 plan-task가 같은 디렉터리에 준비된다.

Task R1.1 PRD 관련 문서 경로 정합성 복구

Goal 실행 P1-R1: REV-P1-001에서 확인한 존재하지 않는 샘플 PRD 경로를 실제 가이드 경로로 정정한다.

  • 시작 조건: reviews/phase-1-review.mdREV-P1-001 확정.

  • 완료 증거: 문서 경로 정정, 대상 파일 존재 확인, git diff --check 통과, 전체 검증 기록 누적.

  • 범위 밖: PRD 요구사항·결정 내용 변경, 코드·테스트 변경, 다른 문서의 링크 일괄 정리.

  • TDD 예외 사유: 문서 링크 정정만 수행하며 런타임 동작을 변경하지 않는다.

  • prd.mddocs/prd/sample-prd.md를 실제 파일인 docs/sample/sample-prd.md로 정정한다.

  • test -f docs/sample/sample-prd.mdgit diff --check -- docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md로 링크 대상과 문서 diff를 확인한다.

  • 무엇을/왜/어떻게 검증했는지 전체 검증 기록에 누적한다.

Task R1.2 후속 변경 상태 정합성 복구

Goal 실행 P1-R2: REV-P1-002에서 확인한 상단 상태표의 요구사항 범위·Phase·다음 Goal을 현재 계획과 검증 상태에 맞게 정리한다.

  • 시작 조건: reviews/phase-1-review.mdREV-P1-002 확정.

  • 완료 증거: 상태표가 PRD 전체, Phase 1~7, 미완료 후속 Goal을 정확히 가리킴, git diff --check와 문서 명령 유효성 확인, 전체 검증 기록 누적.

  • 범위 밖: PRD 요구사항 변경, 코드·테스트 변경, 이전 검증 기록 삭제.

  • TDD 예외 사유: 현재 작업 상태 문구만 복구하며 런타임 동작을 변경하지 않는다.

  • 상태표의 요구사항 기준을 PRD 전체로, Phase 상태를 Phase 1~7 현재 판정으로, 다음 Goal을 실제 미완료 Goal로 맞춘다.

  • git diff --check -- docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md./gradlew tasks --all로 문서 diff와 명령 유효성을 확인한다.

  • 완료 후 다음 Goal을 P3-R2로 갱신하고 전체 검증 기록에 무엇을/왜/어떻게 검증했는지 누적한다.


Phase 2: 산식과 부스트 정책

  • Task 2.1: 응원 점수 계산식 계약 테스트 보강

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt
    • RED: CHEER_DONATION_AMOUNT_WEIGHT = 0.45, CHEER_FAN_TALK_WEIGHT = 0.30, CHEER_DONATION_COUNT_WEIGHT = 0.10을 기대하는 실패 테스트를 작성한다. calculateCheerScore(...)가 세 입력값과 부스트를 PRD 산식대로 계산하는지도 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest
    • GREEN: 응원 점수 상수를 PRD 값으로 변경하고 기존 calculateCheerScore(...)가 같은 상수를 사용하게 한다.
    • REFACTOR: 기존 AI/최근 데뷔/인기 커뮤니티 상수와 함수 값은 변경하지 않았는지 같은 테스트 안에서 회귀 assertion을 유지한다.
    • 계산식 테스트 케이스:
      • donationAmount=0, fanTalkCount=0, donationCount=0, newBoost=1.0이면 0.0
      • donationAmount=100, fanTalkCount=0, donationCount=0, newBoost=1.0이면 45.0
      • donationAmount=0, fanTalkCount=10, donationCount=0, newBoost=1.0이면 3.0
      • donationAmount=0, fanTalkCount=0, donationCount=10, newBoost=1.0이면 1.0
      • donationAmount=100, fanTalkCount=10, donationCount=10, newBoost=1.0이면 49.0
      • donationAmount=100, fanTalkCount=10, donationCount=10, newBoost=1.15이면 56.35
    • 기대 결과: 응원 점수 산식 근거가 Kotlin 단위 테스트로 고정된다.
  • Task 2.2: 응원 크리에이터 신규 부스트 경계값 테스트 추가

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt
    • RED: 응원 크리에이터 전용 신규 부스트가 데뷔일 기준 0일/10일 1.15, 11일/20일 1.10, 21일/30일 1.05, 31일 1.0으로 계산되는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest
    • GREEN: calculateCheerCreatorNewBoost(debutAt, now) 또는 동등한 전용 함수를 추가하고 CHEER_NEW_BOOST_* 상수를 사용한다.
    • REFACTOR: 기존 calculateCreatorNewBoost(...)는 최근 데뷔/인기 커뮤니티용 기존 값 1.5/1.3/1.2를 유지한다.
    • 기대 결과: CHEER_CREATOR만 낮아진 신규 부스트 값을 사용하고 다른 크리에이터 기반 섹션은 기존 부스트를 유지한다.

Phase 3: 최근 7일 집계 window와 DB 스냅샷 query

  • Task 3.1: CHEER_CREATOR refresh window를 인기 커뮤니티와 동일한 최근 7일로 변경

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotRefreshService.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotRefreshServiceTest.kt
    • RED: refreshCheerCreatorSnapshots(nowUtc)RecommendationSnapshotWindowPolicy.previousKstSevenDayUtcWindow(nowUtc)로 얻은 windowStartUtc, windowEndExclusiveUtc, snapshotAt을 사용하도록 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest
    • GREEN: CHEER_CREATOR 단일 refresh 경로를 분리하고, 기존 일괄 refresh에서 인기 커뮤니티와 같은 최근 7일 UTC half-open window를 넘긴다.
    • REFACTOR: POPULAR_COMMUNITY의 기존 7일 window와 동일한 window를 사용한다. HomeRecommendationQueryPort.findCheerCreatorSnapshots(...) 시그니처는 windowEndExclusiveUtc 의미가 드러나도록 정리한다.
    • 기대 결과: 스케줄러와 fallback이 같은 CHEER_CREATOR 최근 7일 refresh 경로를 호출할 수 있다.
  • Task 3.2: 채널 후원과 일반 후원 금액/후원 수 집계 기준 변경

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: 같은 UseCanCalculate.useCan을 참조하는 여러 row가 있을 때 donationAmountuse_can_calculate.can 값을 그대로 합산하고, donationCount는 distinct use_can 기준 1건으로 계산되는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: donation stats query에서 금액은 기존 sum(ucc.can)을 유지하고, 후원 수는 count(distinct ucc.use_can_id) 또는 엔티티 매핑에 맞는 동일 의미 컬럼으로 변경한다.
    • REFACTOR: CanUsage.CHANNEL_DONATIONCanUsage.DONATION을 포함하고, status = RECEIVED, is_refund = false 등 기존 제외 조건은 유지한다.
    • 기대 결과: 후원 이벤트 단위 중복 제거가 점수의 후원 수 항목에만 적용된다.
  • Task 3.3: 팬Talk 수와 half-open 시간 조건 적용

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: CreatorCheers.isActive == true row만 집계하고, created_at >= windowStartUtc and created_at < windowEndExclusiveUtc 조건으로 집계 경계를 검증하는 실패 테스트를 작성한다. windowEndExclusiveUtc와 같은 시각의 row는 제외되어야 한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: creator_cheers 집계 조건을 active row와 half-open time range로 맞춘다.
    • REFACTOR: 후원 집계 조건도 같은 half-open range를 사용해 <= :snapshotAt 방식이 남지 않게 정리한다.
    • 기대 결과: 최근 7일 KST 경계가 후원과 팬Talk 집계에 동일하게 적용된다.
  • Task 3.4: 응원 크리에이터 DB-side 점수와 부스트 적용

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: DB 스냅샷 query 결과 점수가 RecommendationScorePolicy.calculateCheerScore(...)와 동일하고, 데뷔일 기준 신규 부스트 1.15/1.10/1.05/1.0 경계값을 반영하는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: native SQL score expression과 boost case expression을 RecommendationScoreSpec의 응원 전용 상수로 변경한다.
    • REFACTOR: creator_debut 계산은 콘텐츠 첫 공개일과 라이브 첫 진행일 중 빠른 날짜라는 기존 정의를 유지한다.
    • 기대 결과: DB-side exact scoring과 Kotlin 정책 테스트의 산식 값이 일치한다.
  • Task 3.5: 후보 제외, 정렬, limit 회귀 테스트 보강

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: 후원/팬Talk가 모두 0인 크리에이터 제외, 데뷔일이 없는 크리에이터 제외, 비활성 크리에이터 제외, 점수 내림차순/randomTieBreaker 오름차순/limit 16 동작을 검증하는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: 기존 후보 제외 조건과 정렬/limit을 PRD 기준에 맞게 유지 또는 보정한다.
    • REFACTOR: AI 캐릭터와 인기 커뮤니티 스냅샷 query가 영향받지 않았는지 focused test로 확인한다.
    • 기대 결과: CHEER_CREATOR 스냅샷 저장 후보만 정확히 변경된다.

Task R3.1 CHEER_CREATOR 후보 경계·정렬 완료 근거 보강

Goal 실행 P3-R1: REV-P3-001에서 누락이 확인된 Task 3.5의 후보 제외·상위 16개·저장 정렬 회귀 증거를 테스트로 고정한다.

  • 시작 조건: reviews/phase-3-review.mdREV-P3-001 확정.

  • 완료 증거: 실패 재현 테스트 작성·확인, 필요한 경우 최소 구현, repository/persistence focused test 통과, 전체 검증 기록 누적.

  • 범위 밖: 점수 가중치·집계 기간·스냅샷 저장 수 변경, 랜덤 정책 변경, 다른 추천 섹션 쿼리 수정.

  • RED: 실제 데뷔 이력이 있지만 후원·팬Talk가 모두 0인 후보, 미래 데뷔 이력만 있는 후보, 비활성 크리에이터가 제외되는 테스트를 추가한다.

  • RED: 17개 이상의 점수 후보에서 점수 내림차순 상위 16개만 반환되는 repository 테스트와, 저장된 동점 스냅샷이 randomTieBreaker 오름차순으로 조회되는 CHEER_CREATOR persistence 테스트를 추가한다.

  • GREEN: 새 테스트가 구현 결함을 드러낼 때만 해당 조건·정렬 경로를 최소 수정하고, 테스트 누락뿐이면 프로덕션 코드는 변경하지 않는다.

  • REFACTOR/GATE: DefaultHomeRecommendationQueryRepositoryTestRecommendationSnapshotPersistenceAdapterTest focused test 및 git diff --check를 실행해 결과를 누적한다.

Task R3.2 종료된 라이브 데뷔 이력 복구

Goal 실행 P3-R2: REV-P3-002에서 확인한 CHEER_CREATOR 데뷔일 계산이 채널명이 있는 종료 라이브를 제외하는 문제를 수정하고 회귀를 방지한다.

  • 시작 조건: reviews/phase-3-review.mdREV-P3-002 확정과 P1-R2 완료.

  • 완료 증거: 종료 라이브 재현 테스트의 의도한 실패, CHEER_CREATOR 데뷔 CTE 최소 수정, repository focused test·ktlintCheck·git diff --check 통과, 전체 검증 기록 누적.

  • 범위 밖: 점수 가중치·집계 window·저장 수·정렬 변경, 다른 추천 섹션의 데뷔 정책 변경, 공개 API 변경.

  • RED: 활성 콘텐츠는 없고 channel_name이 있는 종료 라이브와 최근 7일 응원 활동만 있는 활성 크리에이터가 CHEER_CREATOR 후보에 포함되는 실패 테스트를 추가한다. 빈 channel_name의 종료 라이브는 데뷔 이력으로 인정하지 않는 경계를 함께 유지한다.

  • RED 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest를 실행해 현재 lr.is_active = true 조건 때문에 종료 라이브 후보가 누락되는 assertion 실패를 확인한다.

  • GREEN: findCheerCreatorSnapshots(...)creator_debut 라이브 branch에서 lr.is_active = true만 제거하고, channel_name is not null, channel_name <> '', begin_date_time <= :snapshotAt 조건은 유지한다.

  • REFACTOR/GATE: repository focused test, ./gradlew ktlintCheck, git diff --check를 실행하고 점수·window·후보 상한·다른 섹션 쿼리가 변경되지 않았음을 기록한다.


Phase 4: refresh 저장과 empty marker

  • Task 4.1: CHEER_CREATOR 빈 결과 marker 저장 정책 추가

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
    • RED: replaceSnapshots(RecommendedSectionType.CHEER_CREATOR, snapshotAt, emptyList()) 호출 시 targetId = 0 marker가 저장되고, findLatestSnapshots(CHEER_CREATOR)는 빈 배열이며, existsLatestSnapshot(CHEER_CREATOR)는 true인 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
    • GREEN: empty marker 적용 대상을 AI_CHARACTERCHEER_CREATOR로 제한하는 명시적 상수/함수를 추가한다.
    • REFACTOR: POPULAR_COMMUNITY 등 다른 섹션으로 marker 정책을 확장하지 않는다.
    • 기대 결과: 데이터가 없는 날에도 CHEER_CREATOR refresh 완료 상태가 저장된다.
  • Task 4.2: 실제 row 재실행 시 marker 대체 보장

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
    • RED: 같은 sectionType, snapshotAt에 marker가 있는 상태에서 실제 스냅샷 row로 replaceSnapshots(...)를 호출하면 marker가 제거되고 실제 row만 남는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
    • GREEN: 기존 deleteBySectionTypeAndSnapshotAt 후 저장 흐름이 marker 대체를 보장하는지 확인하고, 부족하면 해당 경로만 보정한다.
    • REFACTOR: 조회 쿼리의 target_id <> 0 조건은 유지한다.
    • 기대 결과: 빈 결과 refresh 후 재실행으로 실제 추천 row가 생겨도 marker가 응답/존재 상태를 오염시키지 않는다.
  • Task 4.3: refresh service가 CHEER_CREATOR 저장 수와 marker 상태를 로그로 남김

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotRefreshService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotRefreshServiceTest.kt
    • RED: refreshCheerCreatorSnapshots(nowUtc) 성공 시 event=cheer_creator_recommendation_snapshot_refresh_success, savedCount, windowStartUtc, windowEndExclusiveUtc, snapshotAt이 로그에 남는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest
    • GREEN: 기존 구조화 로그 관례에 맞춰 section refresh 성공/실패 로그를 추가한다.
    • REFACTOR: 전체 일괄 refresh 성공 로그는 유지하되, 섹션별 로그와 중복되어도 검색 가능한 event key를 사용한다.
    • 기대 결과: 운영에서 CHEER_CREATOR refresh 결과 0건과 실패를 구분할 수 있다.

Task R4.1 CHEER_CREATOR refresh 실패 로그 보강

Goal 실행 P4-R1: REV-P4-001에서 확인한 섹션별 refresh 실패 로그 누락을 보완해 성공·빈 결과·실패를 운영 로그로 구분한다.

  • 시작 조건: reviews/phase-4-review.mdREV-P4-001 확정.

  • 완료 증거: 실패 로그 RED 테스트, 최소 로그 구현, refresh focused test 통과, 전체 검증 기록 누적.

  • 범위 밖: fallback 실패 로그 형식 변경, 로그 수집 인프라·메트릭 시스템 추가, refresh 트랜잭션 정책 변경.

  • RED: refreshCheerCreatorSnapshots(nowUtc)의 query 또는 저장 실패 시 event=cheer_creator_recommendation_snapshot_refresh_failure, window·snapshotAt, 오류 정보가 기록되고 예외는 기존처럼 전파되는 테스트를 추가한다.

  • GREEN: 성공 경로를 변경하지 않는 최소 runCatching 또는 try/catch 로그를 추가한 뒤 원래 예외를 다시 던진다.

  • REFACTOR/GATE: RecommendationSnapshotRefreshServiceTestgit diff --check를 실행하고 중복 로그가 의도된 event key로 구분되는지 확인한다.

  • 무엇을/왜/어떻게 검증했는지 전체 검증 기록에 누적한다.


Phase 5: fallback refresh

  • Task 5.1: 섹션 스냅샷 fallback 공통 runner 도입

    • 파일 경로:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackService.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/AiCharacterSnapshotFallbackPort.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackServiceTest.kt
    • RED: 공통 runner가 section type, lock key, refresh action, offset/limit을 받아 double-check 조회 후 refresh를 실행하는 실패 테스트를 작성한다. 기존 AI 캐릭터 fallback 테스트도 통과해야 한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest
    • GREEN: AI 캐릭터 전용 lock/timeout/single-flight 흐름을 공통 runner로 이동하거나, AI 서비스가 공통 runner를 위임 호출하도록 최소 변경한다.
    • REFACTOR: Redisson RLock 획득/해제는 refresh worker thread 안에서 수행한다. 테스트에서는 deterministic executor를 사용해 sleep 기반 테스트를 피한다.
    • 기대 결과: CHEER_CREATOR fallback을 추가할 때 lock/double-check/timeout 구현을 중복 작성하지 않는다.
  • Task 5.2: CHEER_CREATOR fallback target 추가

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackService.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotRefreshService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackServiceTest.kt
    • RED: 최신 CHEER_CREATOR 스냅샷이 없을 때 lock key lock:recommendation-snapshot-refresh:CHEER_CREATOR로 lock을 얻고, refreshCheerCreatorSnapshots(nowUtc)를 호출한 뒤 저장된 스냅샷을 재조회하는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest
    • GREEN: CHEER_CREATOR fallback target을 등록하고 lock 대기 300ms, 홈 대기 1,500ms 상수를 PRD 값으로 유지한다.
    • REFACTOR: AI_CHARACTER fallback lock key와 상호 간섭하지 않도록 section별 key를 분리한다.
    • 기대 결과: 스냅샷 없음 상황에서 홈 API가 스케줄러와 동일한 CHEER_CREATOR refresh 로직을 재사용한다.
  • Task 5.3: lock miss, timeout, 실패, empty marker fallback 케이스 검증

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/RecommendationSnapshotFallbackServiceTest.kt
    • RED: lock 획득 실패 시 중복 refresh를 시작하지 않는 테스트, 홈 대기 1,500ms timeout 시 빈 배열을 반환하되 background refresh를 cancel하지 않는 테스트, refresh 실패 시 빈 배열과 warn log를 남기는 테스트, marker가 있으면 fallback을 반복하지 않는 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest
    • GREEN: 공통 runner의 future/single-flight 상태, 예외 처리, timeout 처리, existsLatestSnapshot double-check 흐름을 구현한다.
    • REFACTOR: fallback service는 점수 계산이나 상세 DTO 조립을 직접 하지 않고 snapshot 조회와 refresh orchestration만 담당한다.
    • 기대 결과: 홈 조회 경로에서 refresh 중복, 장시간 대기, 전체 API 실패가 발생하지 않는다.
  • Task 5.4: 홈 응원 크리에이터 조회에 fallback 연결

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
    • RED: findCheerCreatorRecommendations(...)가 최신 스냅샷 없음이면 CHEER_CREATOR fallback을 호출하고, fallback 결과 스냅샷 순서대로 상세를 조립하는 실패 테스트를 작성한다. marker가 있어 existsLatestSnapshot(CHEER_CREATOR) == true이면 fallback을 호출하지 않는 테스트도 추가한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
    • GREEN: 기존 findAiCharacterSnapshotsWithFallback(...)와 같은 패턴으로 CHEER_CREATOR 스냅샷 조회에 fallback을 연결한다.
    • REFACTOR: 스냅샷 후보 16개 조회, 상세 조회 후 최대 8개 반환, 차단 필터 전달은 기존 동작을 유지한다.
    • 기대 결과: 홈 통합 조회의 최근 응원이 많은 크리에이터 섹션이 스냅샷 없음 상황을 자체 복구할 수 있다.

Task R5.1 fallback single-flight·double-check 회귀 증거 보강

Goal 실행 P5-R1: REV-P5-001에서 누락이 확인된 동일 섹션 동시 요청 single-flight와 lock 내부 double-check를 결정적 테스트로 고정한다.

  • 시작 조건: reviews/phase-5-review.mdREV-P5-001 확정.

  • 완료 증거: 동시성·double-check RED 테스트 작성·확인, 필요한 경우 최소 구현, fallback focused test 통과, 전체 검증 기록 누적.

  • 범위 밖: worker 수 조정, timeout·lock 대기 값 변경, 다른 섹션 fallback 정책 확대.

  • RED: 동일 JVM에서 동시에 들어온 둘 이상의 CHEER_CREATOR fallback 요청이 하나의 refresh future만 공유하고 refreshCheerCreatorSnapshots(...)를 1회만 호출하는 테스트를 latch 기반으로 추가한다.

  • RED: 최초 조회 뒤 lock 획득 전 다른 요청/스케줄러가 대상일 marker 또는 실제 row를 저장하면 lock 안의 double-check가 refresh를 생략하고 최신 상태를 다시 조회하는 테스트를 추가한다.

  • GREEN: 새 테스트가 구현 결함을 드러낼 때만 refreshFutures 또는 lock 내부 존재 확인 경로를 최소 수정한다.

  • REFACTOR/GATE: sleep 없이 RecommendationSnapshotFallbackServiceTest focused test와 git diff --check를 실행해 결과를 누적한다.


Phase 6: API 회귀와 최종 검증

  • Task 6.1: 홈 API 응답 스키마 회귀 검증

    • 파일 경로:
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/recommendation/HomeRecommendationResponseTest.kt
    • RED: 홈 통합 조회의 CHEER_CREATOR 응답이 기존 creatorId, creatorNickname, creatorProfileImage 필드만 유지하고 신규 필드를 추가하지 않는 회귀 테스트를 확인/보강한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest
    • GREEN: DTO/Controller 변경 없이 application service 결과가 기존 response로 매핑되게 한다.
    • REFACTOR: 공개 API URL과 JSON field name 변경이 없음을 assertion으로 유지한다.
    • 기대 결과: 클라이언트 공개 스키마는 변경되지 않는다.
  • Task 6.2: focused regression 실행

    • 파일 경로:
      • Modify: docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md
    • RED: 구현 task 완료 후 계획 문서에 기록할 focused command 목록을 확정한다.
    • 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
    • GREEN: 아래 명령을 실행하고 결과를 이 문서 하단 검증 기록에 누적한다.
    • REFACTOR: 실패한 명령이 있으면 원인과 재실행 결과를 같은 task 아래에 기록한다.
    • 실행 명령:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest
    • 기대 결과: 산식/집계/marker/fallback/API 회귀가 최소 명령으로 검증된다.
  • Task 6.3: 전체 회귀와 문서 검증

    • 파일 경로:
      • Modify: docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md
    • RED: 구현 완료 후 전체 회귀 명령 실행 전에는 검증 기록이 구현 전 상태여야 한다.
    • 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
    • GREEN: ./gradlew ktlintCheck, ./gradlew test, ./gradlew tasks --all, git diff --check를 실행하고 결과를 검증 기록에 남긴다.
    • REFACTOR: 문서와 코드의 산식/timeout/window 값이 다르면 구현 또는 문서를 수정한 뒤 재검증한다.
    • 기대 결과: 전체 테스트, 포맷, 문서 명령 유효성, diff 공백 검사가 모두 통과한다.

Task R6.1 cheerCreators item 공개 스키마 회귀 테스트 보강

Goal 실행 P6-R1: REV-P6-001에서 확인한 빈 배열 중심 스키마 검증을 실제 item의 정확한 필드 계약 검증으로 보강한다.

  • 시작 조건: reviews/phase-6-review.mdREV-P6-001 확정.

  • 완료 증거: 비어 있지 않은 item 직렬화/컨트롤러 테스트, 정확한 필드 집합 검증, API focused test 통과, 전체 검증 기록 누적.

  • 범위 밖: DTO 필드 추가·이름 변경, API URL 변경, 다른 홈 섹션 스키마 정리.

  • RED: cheerCreators에 item을 넣고 creatorId, creatorNickname, creatorProfileImage 값과 필드 수 3을 검증하는 직렬화 테스트를 추가한다.

  • RED: 홈 API 테스트에서 비어 있지 않은 cheerCreators item의 동일 필드 계약을 검증하고 예상 밖 필드가 없음을 확인한다.

  • GREEN: 기존 DTO가 테스트를 만족하면 프로덕션 코드는 변경하지 않고, 계약 불일치가 드러날 때만 기존 공개 스키마로 최소 복구한다.

  • REFACTOR/GATE: HomeRecommendationControllerTest, HomeRecommendationResponseTest, git diff --check를 실행해 결과를 누적한다.


Phase 7: 본인·팔로우 크리에이터 노출 제외

Phase 결과: 인증 회원의 cheerCreators에서 조회자 본인과 활성 팔로우 크리에이터가 제외되고, 비활성 팔로우 이력과 비회원 조회는 기존 동작을 유지한다.

선행조건: Phase 1~6 완료와 PRD Feature D의 2026-07-31 후속 요구사항 확정.

Phase 완료 조건: P7-T1P7-GATE 완료, focused test·영향 범위 회귀·문서 검증 결과 누적.

Task 7.1 cheerCreators 상세 조회 개인화 필터 보강

Goal 실행 P7-T1: 기존 memberId 기반 상세 조회에서 본인과 활성 팔로우 크리에이터만 제외하는 최소 조회 조건을 추가한다.

  • 시작 조건: PRD Feature D의 본인·활성 팔로우 제외, 비활성 팔로우·비회원 유지, 16명 후보 안에서만 필터링한다는 결정 확정.
  • 완료 증거: 아래 체크박스 전체 완료, repository focused test 통과, 직접 영향 회귀 통과, 실행 결과를 이 문서의 전체 검증 기록에 누적.
  • 범위 밖: 스냅샷 산식·정렬·저장 수, 16명 밖 하위 후보 보충, 공개 DTO/API, 다른 추천 섹션의 팔로우 필터.

Files:

  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
  • Verify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
  • Verify: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/HomeRecommendationControllerTest.kt

Interfaces:

  • Consumes: HomeRecommendationQueryPort.findCheerCreatorRecommendationDetails(creatorIds: List<Long>, memberId: Long?): List<HomeCheerCreatorRecommendationRecord>

  • Produces: 같은 시그니처와 반환 타입을 유지하면서, memberId != null일 때만 본인과 활성 팔로우를 제외하는 상세 조회 계약.

  • RED: DefaultHomeRecommendationQueryRepositoryTest에 조회자 크리에이터, 활성 팔로우 크리에이터, 비활성 팔로우 이력만 있는 크리에이터, 관계가 없는 크리에이터를 준비한다. memberId = viewer.id로 조회했을 때 비활성 팔로우 이력 크리에이터와 관계가 없는 크리에이터만 반환하는 shouldExcludeSelfAndActiveFollowedCreatorsFromCheerCreatorDetails 테스트를 작성한다. memberId = null은 모든 활성 후보를 유지하는 shouldKeepAnonymousCheerCreatorDetailsWithoutMemberFilters 테스트도 작성한다.

  • RED 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest를 실행해 현재 조회가 조회자 본인과 활성 팔로우 크리에이터를 함께 반환하여 첫 번째 테스트가 의도한 assertion 실패를 내는지 확인한다.

  • GREEN: findCheerCreatorRecommendationDetails(...)memberId가 있을 때 member.id != memberId를 적용하고, 동일 회원과 후보 크리에이터 사이에 CreatorFollowing.isActive == true인 row가 존재하지 않는 조건을 추가한다. memberId == null이면 두 조건은 적용하지 않는다.

  • GREEN 확인: 같은 repository focused test를 재실행해 본인·활성 팔로우 제외, 비활성 팔로우·비회원 유지, 기존 양방향 차단 제외 테스트가 모두 통과하는지 확인한다.

  • REFACTOR: 이번 조건에 필요한 QueryDSL alias/helper만 남기고, HomeRecommendationQueryServiceTest로 기존 16명 후보 조회·필터 후 최대 8명 조립이 유지되는지 확인한다. HomeRecommendationControllerTestktlintCheck로 공개 스키마·포맷 회귀를 확인하고 실제 명령·결과를 전체 검증 기록에 남긴다.

Phase 7 완료 조건

  • P7-T1의 RED·GREEN·REFACTOR 체크박스와 완료 증거가 모두 충족됐다.
  • 인증 회원 본인·활성 팔로우만 제외되고 비활성 팔로우·비회원·양방향 차단·활성 크리에이터 정책이 조합되는 것이 검증됐다.
  • 스냅샷 산식·저장 수·정렬, 응답 DTO, 다른 추천 섹션에 변경이 없다.

Phase 7 Gate

Goal 실행 P7-GATE: cheerCreators 개인화 필터와 직접 영향 회귀를 최종 판정한다.

  • 시작 조건: P7-T1 완료.
  • 완료 증거: 아래 명령 전부 통과, git diff --check 출력 없음, 전체 검증 기록 누적.
  • 범위 밖: 게이트 통과를 위한 테스트 삭제·완화와 관련 없는 리팩터링.
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest
./gradlew ktlintCheck
git diff --check

Expected: 모든 명령이 exit code 0이고, 인증 회원 본인·활성 팔로우는 제외되며 비활성 팔로우·비회원 결과와 기존 API 스키마는 유지된다.


Coverage Check

  • Feature A: Task 2.1, Task 3.2, Task 3.4에서 후원 금액 45%, 팬Talk 30%, 후원 수 10%, 후원 수 distinct 기준을 검증한다.
  • Feature B: Task 3.1, Task 3.3에서 최근 7일 KST 범위를 UTC half-open 조회 범위로 변환하고 windowEndExclusiveUtc 경계를 검증한다.
  • Feature C: Task 2.2, Task 3.4에서 데뷔일 기준 응원 전용 신규 부스트와 경계값을 검증한다.
  • Feature D: Task 3.5, Task 5.4, Task 6.1, Task 7.1에서 최신 CHEER_CREATOR 스냅샷 순서, 후보 16개/응답 8개, 본인·활성 팔로우 제외, 비활성 팔로우·비회원 유지, 기존 응답 스키마 유지를 검증한다.
  • Feature E: Task 5.1, Task 5.2, Task 5.3, Task 5.4에서 fallback refresh 재사용, double-check, 300ms lock 대기, 1,500ms 홈 API 대기, timeout 후 background 완료, 중복 refresh 방지를 검증한다.
  • Feature F: Task 4.1, Task 4.2, Task 5.3에서 CHEER_CREATOR empty marker 저장, 조회 제외, 존재 여부 true, marker 기반 fallback 반복 방지를 검증한다.
  • Non-Goals: Task 4.1, Task 6.1, Task 6.3, Task 7.1에서 다른 스냅샷 섹션 marker 확장 없음, 공개 API URL/응답 필드 변경 없음, 16명 밖 후보 보충 없음, 신규 DDL 없음, 관리자/ML/A-B 제외를 확인한다.

전체 검증 기록

  • 2026-07-31: Phase 17 3차 리뷰로 PRD·plan-task·현재 코드·테스트를 정적으로 대조했다. 이전 리뷰에서 보완한 문서 정합성, 종료 라이브 데뷔 이력, CHEER_CREATOR refresh 실패 로그, fallback single-flight·double-check, 공개 응답 3개 필드, 본인·활성 팔로우 제외가 현재 구현과 회귀 테스트에 유지됨을 확인했다. 각 결과는 기존 reviews/phase-1-review.mdreviews/phase-7-review.md에 3차 리뷰로 별도 누적했다. 추가 확정 발견 사항이 없어 신규 회귀 Task/Goal은 추가하지 않았다. 사용자 지시에 따라 Gradle compile/test는 실행하지 않았다. git diff --check는 출력 없이 통과했다. 문서 명령 유효성 확인용 ./gradlew tasks --all은 일반 sandbox에서 ~/.gradle wrapper lock 권한으로 실패한 뒤 승인된 동일 명령에서 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-31: P3-R2CHEER_CREATOR 데뷔 CTE가 채널명이 있는 종료 라이브를 데뷔 이력으로 인정하도록 복구했다. RED 확인으로 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest를 실행해 shouldIncludeEndedLiveWithChannelNameAsCheerCreatorDebutAssertionFailedError로 실패하는 것을 확인했다. 이후 findCheerCreatorSnapshots(...) 라이브 branch에서 lr.is_active = true만 제거했고, 같은 repository focused test는 BUILD SUCCESSFUL로 통과했다. ./gradlew ktlintCheckBUILD SUCCESSFUL로 통과했으며, git diff --check는 출력 없이 통과했다.
  • 2026-07-31: P1-R2로 상단 후속 변경 상태를 현재 PRD 전체, Phase 1~7 판정, 실제 미완료 Goal P3-R2 기준으로 정리했다. 문서 정합성 복구만 수행해 TDD 예외로 처리했다. git diff --check -- docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md는 출력 없이 통과했고, ./gradlew tasks --allBUILD SUCCESSFUL로 통과했다.
  • 2026-07-31: Phase 17 2차 리뷰로 PRD·plan-task·현재 코드·테스트·관련 구현 이력을 정적 대조했다. REV-P1-002의 상단 상태표 불일치와 REV-P3-002의 종료 라이브 데뷔 이력 제외를 확정해 각각 P1-R2, P3-R2 신규 회귀 Task로 전환했고, Phase 2·4·5·6·7은 추가 확정 발견 사항이 없다. 결과는 기존 reviews/phase-1-review.mdreviews/phase-7-review.md에 2차 리뷰로 각각 누적했다. 사용자 지시에 따라 Gradle compile/test는 실행하지 않았다. git diff --check는 출력 없이 통과했고, ./gradlew tasks --all은 일반 sandbox에서 ~/.gradle wrapper lock 권한으로 실패한 후 승인된 동일 명령으로 재실행해 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-31: P1-R1, P3-R1, P4-R1, P5-R1, P6-R1 후속 보완의 최종 focused 회귀로 ./gradlew cleanTest test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest를 실행해 BUILD SUCCESSFUL로 통과했다. 이어서 ./gradlew ktlintCheckBUILD SUCCESSFUL, git diff --check는 출력 없이 통과했다. 전체 ./gradlew test는 테스트 assertion 실패가 아니라 build/test-results/test/TEST-*.xml 결과 파일 쓰기 실패로 중단되어 별도 환경/파일시스템 이슈 확인이 필요하다.
  • 2026-07-31: P1-R1prd.md의 샘플 PRD 링크를 실제 파일 docs/sample/sample-prd.md로 정정했다. test -f docs/sample/sample-prd.mdgit diff --check -- docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md를 실행해 출력 없이 통과했다.
  • 2026-07-31: P3-R1CHEER_CREATOR 후보 제외·상위 16개·동점 저장 정렬 회귀 테스트를 보강했다. 리뷰 판정처럼 구현 결함은 드러나지 않아 프로덕션 코드는 변경하지 않았다. ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTestBUILD SUCCESSFUL, 관련 git diff --check는 출력 없이 통과했다.
  • 2026-07-31: P4-R1refreshCheerCreatorSnapshots(nowUtc) 실패 시 event=cheer_creator_recommendation_snapshot_refresh_failure와 window·snapshotAt·오류 정보를 남기고 원 예외를 전파하도록 보강했다. RED 확인으로 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest.shouldLogCheerCreatorRefreshFailureWithWindowAssertionFailedError로 실패했고, GREEN 후 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTestBUILD SUCCESSFUL, 관련 git diff --check는 출력 없이 통과했다.
  • 2026-07-31: P5-R1로 동일 섹션 동시 요청 single-flight와 lock 내부 double-check를 sleep 없이 latch 기반 테스트로 보강했다. 리뷰 판정처럼 구현 결함은 드러나지 않아 프로덕션 코드는 변경하지 않았다. ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTestBUILD SUCCESSFUL, 관련 git diff --check는 출력 없이 통과했다.
  • 2026-07-31: P6-R1cheerCreators item 직렬화와 홈 API 응답의 creatorId, creatorNickname, creatorProfileImage 3개 필드 계약을 보강했다. 최초 controller focused test는 대상일 snapshot fixture 불일치와 테스트 환경 CDN host 기대값 불일치로 실패했고, fixture를 RecommendationSnapshotWindowPolicy.previousKstDayUtcWindow(LocalDateTime.now(UTC)).snapshotAt 및 실제 테스트 host 설정에 맞춘 뒤 ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTestBUILD SUCCESSFUL로 통과했다. 관련 git diff --check는 출력 없이 통과했다.
  • 2026-07-31: Phase별 리뷰 문서와 회귀 Task 추가 후 git diff --check를 실행해 출력 없이 통과했다. 문서 명령 유효성 확인용 ./gradlew tasks --all은 일반 sandbox에서 ~/.gradle wrapper lock 권한으로 실패했고, 승인 후 동일 명령을 재실행해 BUILD SUCCESSFUL로 통과했다. compile/test task는 실행하지 않았다.
  • 2026-07-31: Phase 1~7 구현 상태를 PRD·plan-task·현재 코드·테스트와 정적으로 대조했다. 사용자 지시에 따라 Gradle compile/test는 새로 실행하지 않았고 기존 검증 기록을 실행 증거로 참조했다. 확정 항목은 reviews/phase-1-review.md부터 reviews/phase-7-review.md까지 Phase별로 기록했으며, REV-P1-001, REV-P3-001, REV-P4-001, REV-P5-001, REV-P6-001을 각각 P1-R1, P3-R1, P4-R1, P5-R1, P6-R1 신규 회귀 Task로 전환했다. Phase 2와 Phase 7은 확정 발견 사항이 없다.
  • 2026-07-31: Phase 7 P7-T1 구현으로 findCheerCreatorRecommendationDetails(...)가 인증 회원 조회 시 조회자 본인과 CreatorFollowing.isActive == true인 팔로우 크리에이터를 제외하도록 보강했다. RED 확인으로 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest를 실행해 shouldExcludeSelfAndActiveFollowedCreatorsFromCheerCreatorDetails assertion 실패를 확인했고, GREEN 후 같은 명령은 BUILD SUCCESSFUL로 통과했다. 직접 영향 회귀 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest./gradlew ktlintCheckBUILD SUCCESSFUL로 통과했다.
  • 2026-07-31: 후속 요구사항으로 인증 회원 본인과 활성 팔로우 크리에이터를 cheerCreators 상세 조회에서 제외하는 정책을 PRD Feature D와 Phase 7 P7-T1/P7-GATE에 반영했다. 비활성 팔로우·비회원 유지와 16명 후보 밖 보충 없음을 경계로 고정했다. git diff --check는 출력 없이 통과했다. ./gradlew tasks --all은 일반 sandbox에서 ~/.gradle wrapper lock 파일 권한으로 실패했고, 승인 후 동일 명령을 재실행해 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-10: PRD 기반으로 plan-task.md를 생성했다. 구현 전 계획 문서 작성 작업이므로 코드 테스트는 아직 실행하지 않았고, 문서 형식/명령 유효성 검증을 진행한다.
  • 2026-07-10: 문서 검증으로 git diff --check -- docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/plan-task.md를 실행해 통과했다. ./gradlew tasks --all은 일반 sandbox에서 ~/.gradle wrapper lock 파일 접근 제한으로 실패했고, 권한 상승 재실행 결과 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-10: 구현 RED 확인으로 RecommendationScorePolicyTestCHEER_NEW_BOOST_*calculateCheerCreatorNewBoost(...) 미구현 컴파일 실패를 확인했고, DefaultHomeRecommendationQueryRepositoryTest는 half-open/distinct 집계 기대값 불일치 실패를 확인했다. RecommendationSnapshotPersistenceAdapterTestCHEER_CREATOR empty marker 미지원 실패를 확인했다.
  • 2026-07-10: focused regression으로 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.api.home.HomeRecommendationControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest를 실행해 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-10: 최종 검증으로 ./gradlew ktlintCheck, ./gradlew test, ./gradlew tasks --all, git diff --check를 실행했다. ./gradlew test는 최초 300초 timeout 후 600초 timeout으로 재실행해 BUILD SUCCESSFUL로 통과했고, 나머지 명령도 BUILD SUCCESSFUL 또는 출력 없음으로 통과했다.
  • 2026-07-10: 리뷰 보강으로 CHEER_CREATOR 스케줄러 refresh도 section lock을 사용하도록 수정하고, 공통 fallback runner의 기본 worker를 2개 thread로 변경해 AI/응원 섹션 간 fallback 대기 간섭을 줄였다. RecommendationSnapshotFallbackServiceTest에 lock miss, refresh 실패, timeout 후 worker 지속, AI block 중 CHEER fallback 독립 실행 테스트를 추가해 Task 5.3 커버리지와 완료 표시를 맞췄다. 보강 RED 확인 후 ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest 재실행 결과 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-10: 리뷰 보강 후 순차 검증으로 ./gradlew ktlintCheck, ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotRefreshServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.RecommendationSnapshotFallbackServiceTest, focused recommendation/API regression 묶음, git diff --check를 실행했다. 모두 BUILD SUCCESSFUL 또는 출력 없음으로 통과했다.
  • 2026-07-10: 추가 리뷰 보강으로 CHEER_CREATOR 홈/fallback 조회를 expected snapshotAt 기준으로 변경해 stale latest snapshot이 fallback을 막지 않게 했다. RecommendationSnapshotPort.findSnapshots(...), existsSnapshot(...)와 adapter/repository exact snapshot 조회 테스트를 추가했고, DefaultHomeRecommendationQueryRepositoryTestfindCheerCreatorSnapshots(...) 호출부는 두 번째 인자를 windowEndExclusive로 정리했다. 비활성 legacy AiCharacterSnapshotFallbackServiceTest는 삭제하고 AI 핵심 fallback coverage를 RecommendationSnapshotFallbackServiceTest로 이관했다. focused regression, ./gradlew test, ./gradlew ktlintCheck, git diff --check를 재실행해 BUILD SUCCESSFUL 또는 출력 없음으로 통과했다.
  • 2026-07-10: review-work에서 발견한 blocking 이슈를 수정했다. HomeRecommendationQueryService가 expected snapshotAt 계산에 사용한 같은 nowUtcrefreshCheerCreatorIfMissing(...)에 전달하도록 변경했고, RecommendationSnapshotFallbackServiceTest의 기본 현재 시각 의존 호출을 고정 nowUtc로 바꿨다. HomeRecommendationQueryServiceTest는 fallback에 전달된 nowUtc를 검증한다. 재검증으로 focused recommendation/API regression, ./gradlew ktlintCheck, ./gradlew test, git diff --check를 실행했고 모두 BUILD SUCCESSFUL 또는 출력 없음으로 통과했다.