docs(content-ranking): 랭킹 시간 정책 문서를 갱신한다

This commit is contained in:
2026-06-24 22:33:13 +09:00
parent d5f4dc529a
commit ce2b628cc2
2 changed files with 165 additions and 27 deletions

View File

@@ -1,7 +1,7 @@
# PRD: 크리에이터 랭킹
## 1. Overview
지난 주 월요일 00:00:00 KST부터 일요일 23:59:59.999999999 KST까지의 활동 데이터를 기준으로 크리에이터 랭킹 점수를 계산하고, 최종 점수 상위 20명을 조회할 수 있는 기능을 제공한다.
지난 주 월요일 00:00:00 KST부터 이번 주 월요일 00:00:00 KST 미만까지의 활동 데이터를 기준으로 크리에이터 랭킹 점수를 계산하고, 공개 노출 전환 시각이 지난 스냅샷의 최종 점수 상위 20명을 조회할 수 있는 기능을 제공한다.
---
@@ -22,6 +22,8 @@
- KST 기준 집계 시작/종료 시각을 UTC 기준 조회 시작/종료 시각으로 변환한 뒤 DB 데이터를 조회한다.
- 각 점수 카테고리의 원천 지표와 가중치를 테스트 가능한 형태로 관리한다.
- 조회 시 매번 무거운 원천 집계를 수행하지 않도록 주간 랭킹 계산 결과를 스냅샷으로 저장한다.
- 주간 랭킹 시간 정책을 집계 기준 시각, 스냅샷 생성 후보 시각, 공개 노출 전환 시각으로 분리한다.
- 조회 API는 최신 생성 스냅샷이 아니라 `visibleFromAt <= now` 조건을 만족하는 최신 공개 스냅샷을 기준으로 응답한다.
- 추후 성능 개선을 위해 캐시 저장소를 추가할 수 있는 포트 경계를 둔다.
---
@@ -57,14 +59,16 @@
### Feature A. 주간 랭킹 기간 산출
#### Requirements
- 랭킹 대상 기간은 조회 시점 기준 "지난 주 월요일 00:00:00 KST 이상, 이번 주 월요일 00:00:00 KST 미만"으로 계산한다.
- 예를 들어 2026-06-08 월요일 KST에 조회하면 대상 기간은 2026-06-01 00:00:00 KST 이상, 2026-06-08 00:00:00 KST 미만이다.
- 랭킹 대상 기간은 스냅샷 생성 또는 fallback 집계 기준 시점의 "지난 주 월요일 00:00:00 KST 이상, 이번 주 월요일 00:00:00 KST 미만"으로 계산한다.
- 예를 들어 2026-06-08 월요일 KST에 스냅샷을 생성하거나 fallback 집계를 수행하면 대상 기간은 2026-06-01 00:00:00 KST 이상, 2026-06-08 00:00:00 KST 미만이다.
- 집계 기준 시각은 매주 월요일 00:00:00 KST이며, 이 시각을 집계 종료 경계로 사용한다.
- 서버 기본 timezone이 UTC여도 기간 산출은 `Asia/Seoul` 기준으로 수행한다.
- DB와 서버 timezone은 UTC이므로, KST 기준 기간을 UTC 기준 `Instant` 또는 프로젝트 표준 시간 타입으로 변환해 DB 조회 조건에 사용한다.
- 예를 들어 2026-06-01 00:00:00 KST 이상, 2026-06-08 00:00:00 KST 미만은 2026-05-31 15:00:00 UTC 이상, 2026-06-07 15:00:00 UTC 미만으로 변환해 조회한다.
#### Edge Cases
- 월요일 00:00:00 KST 직후 조회해도 방금 시작한 이번 주 데이터가 포함되지 않아야 한다.
- 월요일 00:00:00 KST 이후 09:00:00 KST 전까지 조회해도 새로 종료된 주차가 공개 노출 전환 전이면 이전 공개 스냅샷을 응답해야 한다.
- 연도/월 경계를 넘어가는 주차도 동일한 규칙으로 계산한다.
- DST가 없는 KST 기준을 사용하되, 구현은 `ZoneId.of("Asia/Seoul")`처럼 명시적인 timezone을 사용한다.
@@ -147,14 +151,18 @@
#### Requirements
- 홈 내부 랭킹 탭에서 주간 크리에이터 랭킹 상위 20명을 조회하는 API를 제공한다.
- API endpoint는 `GET /api/v2/home/rankings/creators`를 사용한다.
- API는 최신 완료 주차의 스냅샷을 기준으로 조회하며 별도 query parameter 없이 기본 랭킹을 반환한다.
- API는 별도 query parameter 없이 기본 랭킹을 반환한다.
- API는 최신 생성 스냅샷이 아니라 `visibleFromAt <= now` 조건을 만족하는 최신 공개 스냅샷을 기준으로 조회한다.
- 새 주차 스냅샷이 월요일 01:00:00 KST에 생성되었더라도 `visibleFromAt`인 월요일 09:00:00 KST 전에는 공개 조회에 사용하지 않는다.
- 예를 들어 2026-06-08 08:59:59 KST 조회는 2026-06-08 09:00:00 KST 공개 예정 스냅샷이 생성되어 있어도 직전 공개 스냅샷을 응답한다.
- 예를 들어 2026-06-08 09:00:00 KST 이후 조회는 해당 시각까지 공개된 최신 스냅샷을 응답한다.
- 응답에는 순위 변화 표시 여부, 순위, 지난 주 대비 순위 변화, 신규 진입 여부, 크리에이터 id, 닉네임, 프로필 이미지를 포함한다.
- `showRankChange``items`와 같은 레벨에 내려주며, 클라이언트가 순위 변화 UI를 표시할지 판단하는 값이다.
- 각 크리에이터의 순위 변화 값은 `items[].rankChange`에 숫자로 내려준다.
- 순위가 올라갔으면 양수, 순위가 내려갔으면 음수로 내려준다.
- 예를 들어 직전 완료 주차 10위, 최신 완료 주차 5위이면 `rankChange``5`다.
- 예를 들어 직전 완료 주차 1위, 최신 완료 주차 10위이면 `rankChange``-9`다.
- 직전 완료 주차에는 순위에 없고 최신 완료 주차에 진입한 크리에이터는 `items[].isNew == true`로 내려주며, 클라이언트는 이를 `New`로 표시한다.
- 예를 들어 직전 공개 스냅샷 10위, 최신 공개 스냅샷 5위이면 `rankChange``5`다.
- 예를 들어 직전 공개 스냅샷 1위, 최신 공개 스냅샷 10위이면 `rankChange``-9`다.
- 직전 공개 스냅샷에는 순위에 없고 최신 공개 스냅샷에 진입한 크리에이터는 `items[].isNew == true`로 내려주며, 클라이언트는 이를 `New`로 표시한다.
- 신규 진입 크리에이터의 `rankChange`는 비교 가능한 이전 순위가 없으므로 `null`로 내려준다.
- 응답의 크리에이터 id는 크리에이터 상세 이동에 사용한다.
- 응답 스키마 예시는 다음과 같다.
@@ -190,40 +198,50 @@
- 인증 사용자 조건이 필요하지 않은 공개 조회를 기본으로 하되, 차단 마스킹 정책은 인증 사용자에게 적용한다.
- 조회 API는 스냅샷 기반 응답을 기본으로 하며, 공개 API 응답 스키마는 fallback 여부와 관계없이 변경하지 않는다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 조회 API가 제한적으로 원천 데이터 fallback 집계를 시도할 수 있다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 최신 완료 주차 스냅샷 기준으로 응답한다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서 fallback 집계가 성공하면, 조회 API는 응답을 반환하면서 스냅샷 생성 책임을 `CreatorRankingSnapshotRefreshService`/`CreatorRankingSnapshotJobService` 쪽으로 위임해 같은 기간의 `creator_ranking_snapshot` 생성을 트리거한다.
- fallback 응답도 공개 노출 전환 정책을 따라야 하며, fallback 대상 기간의 `visibleFromAt <= now` 조건을 만족하지 않으면 새 주차 결과를 응답하지 않는다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 `visibleFromAt <= now` 조건을 만족하는 최신 공개 스냅샷 기준으로 응답한다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서 fallback 집계가 성공하면, 조회 API는 공개 가능한 기간의 응답을 반환하면서 스냅샷 생성 책임을 `CreatorRankingSnapshotRefreshService`/`CreatorRankingSnapshotJobService` 쪽으로 위임해 같은 기간의 `creator_ranking_snapshot` 생성을 트리거한다.
- cold-start fallback에서 스냅샷 생성 트리거는 운영 배포 직후 내부 테스트 등 초기 검증 상황을 위한 보강책이며, 장기 실시간 집계 경로로 사용하지 않는다.
- cold-start fallback 스냅샷 생성은 동일 집계 기간에 대해 한 번만 실행되도록 기간 기반 Redisson lock을 사용하고, lock 획득 실패 시 다른 요청 또는 작업이 처리 중인 정상 skip으로 간주한다.
#### Edge Cases
- 최종 점수 1점 이상인 랭킹 후보가 20명 미만이면 가능한 만큼만 내려준다.
- 랭킹 계산 결과가 없으면 빈 배열로 성공 응답한다.
- 최신 완료 주차 스냅샷이 없고 스냅샷 테이블도 완전히 비어 있으면 제한적 원천 데이터 fallback 집계를 시도한 뒤 결과를 응답한다.
- 공개 가능한 최신 스냅샷이 없고 스냅샷 테이블도 완전히 비어 있으며 fallback 대상 기간의 `visibleFromAt <= now` 조건을 만족하면 제한적 원천 데이터 fallback 집계를 시도한 뒤 결과를 응답한다.
- 공개 가능한 최신 스냅샷이 없고 fallback 대상 기간의 `visibleFromAt > now`이면 새 주차 결과를 조기 노출하지 않고 빈 배열로 성공 응답한다.
- fallback 성공 뒤 스냅샷 생성 트리거가 실패하더라도 공개 API 응답 스키마는 변경하지 않고, 실패는 로그/job 이력으로 추적한다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 기존 최신 완료 주차 스냅샷 기준 응답을 유지한다.
- 직전 완료 주차 스냅샷이 없으면 `showRankChange``false`로 내려주고, 각 item의 `rankChange``null`, `isNew``false`로 내려준다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 기존 최신 공개 스냅샷 기준 응답을 유지한다.
- 직전 공개 스냅샷이 없으면 `showRankChange``false`로 내려주고, 각 item의 `rankChange``null`, `isNew``false`로 내려준다.
### Feature H. 주간 랭킹 스냅샷
#### Requirements
- 주간 랭킹은 조회 시 매번 원천 데이터를 집계하지 않고, 계산 결과를 스냅샷으로 저장한 뒤 조회 API는 스냅샷을 읽는다.
- 스냅샷 생성 기준 기간은 KST 기준 지난 주 월요일 00:00:00 이상, 이번 주 월요일 00:00:00 미만이다.
- 주간 랭킹 시간 정책은 다음 세 시각을 분리한다.
- 집계 기준 시각: 매주 월요일 00:00:00 KST. 이 시각을 집계 종료 경계로 사용한다.
- 생성 후보 시각: 매주 월요일 01:00:00 KST. 스케줄러가 새 주차 스냅샷 생성을 시도하는 후보 시각이다.
- 노출 전환 시각: 매주 월요일 09:00:00 KST. 생성된 새 주차 스냅샷의 `visibleFromAt`으로 저장하고, 이 시각 이후 공개 조회에 사용한다.
- 스냅샷 생성 시 원천 데이터 조회 조건은 KST 집계 기간을 UTC로 변환한 기간을 사용한다.
- 스냅샷에는 `rankingType``visibleFromAt`을 저장한다.
- 현재 기본 크리에이터 랭킹의 `rankingType` 값은 `WEEKLY`로 시작하고, 향후 다중 크리에이터 랭킹 타입 확장 시 같은 스냅샷/job 구조를 재사용한다.
- 같은 랭킹 타입과 같은 집계 기간의 스냅샷을 재생성할 때는 기존 같은 `rankingType + aggregationStartAt + aggregationEndAt` row를 중복 노출하지 않는다.
- 스냅샷 저장 대상은 20위 점수보다 높은 후보와 20위 점수에 동점인 후보 전체로 제한한다.
- 최종 점수 1점 이상인 후보가 20명 미만이면 해당 후보만 저장한다.
- 스냅샷은 크리에이터 id, 최종 점수, 카테고리별 점수, 원천 지표, 집계 시작/종료 시각을 저장한다.
- 최종 순위는 스냅샷 저장 시 고정하지 않고 조회 시 최종 점수 내림차순과 동점 랜덤 정렬 결과에 따라 부여한다.
- 순위 변화는 최신 완료 주차 응답에서 부여된 순위와 직전 완료 주차 스냅샷 기준 순위를 비교해 계산한다.
- 순위 변화는 최신 공개 스냅샷 응답에서 부여된 순위와 직전 공개 스냅샷 기준 순위를 비교해 계산한다.
- 동점 랜덤 정렬 정책 때문에 동점 구간에 포함된 크리에이터의 순위와 순위 변화는 조회 결과마다 달라질 수 있으며, 이는 허용한다.
- 스냅샷 생성은 이번 주 데이터가 포함되지 않도록 주간 집계 대상 기간이 종료된 뒤 실행한다.
- 기본 스케줄 후보는 매주 월요일 KST 07:30이며, 스케줄러는 `Asia/Seoul` zone을 명시한다.
- 기본 생성 스케줄 후보는 매주 월요일 KST 01:00이며, 스케줄러는 `Asia/Seoul` zone을 명시한다.
- 다중 서버 인스턴스에서 같은 스케줄이 동시에 실행되더라도 클러스터 전체에서 한 인스턴스만 스냅샷 생성을 수행해야 한다.
- 클러스터 단일 실행은 신규 DB 테이블을 추가하지 않고, 기존 프로젝트에 설정된 Redisson 기반 분산 lock을 우선 사용한다.
- 주간 랭킹 스냅샷 lock key는 `lock:creator-ranking-snapshot-refresh`를 사용하며, lock 획득 실패 인스턴스는 스냅샷 생성을 skip한다.
- 같은 집계 기간에 대해 스냅샷을 재생성할 수 있어야 하며, 재생성 시 기존 같은 기간 스냅샷을 중복 노출하지 않는다.
- 조회 API는 최신 완료 주차의 스냅샷을 기준으로 응답한다.
- 같은 랭킹 타입과 같은 집계 기간에 대해 스냅샷을 재생성할 수 있어야 하며, 재생성 시 기존 같은 기간 스냅샷을 중복 노출하지 않는다.
- 조회 API는 `visibleFromAt <= now` 조건을 만족하는 최신 공개 스냅샷을 기준으로 응답한다.
- 스냅샷 생성 직전 집계 시작/종료 시각을 포함한 job 이력을 생성하고, 작업 완료 후 성공/실패 상태와 처리 결과를 기록한다.
- 스케줄러로 실행되는 주간 스냅샷 생성도 job 이력으로 기록한다.
- 스냅샷 job 이력에도 `rankingType``visibleFromAt`을 저장해 관리자 목록, 재시도, DDL 인덱스 기준이 스냅샷 테이블과 일치해야 한다.
- 운영자는 관리자 전용 API를 통해 날짜 범위를 직접 선택해 스냅샷 생성 job을 생성할 수 있어야 한다.
- 실패한 스냅샷 생성 job은 관리자 전용 재시도 API로 재시도할 수 있어야 하며, 기존 관리자 job 패턴과 같이 실패 상태 job을 대기 상태로 되돌려 worker가 다시 처리하도록 한다.
- 관리자 전용 job 목록 API는 날짜 범위, 실행 트리거, 상태, 실패 사유, 재시도 가능 여부를 확인할 수 있어야 한다.
@@ -232,10 +250,11 @@
- lock을 획득한 요청만 refresh job/service를 실행하고, lock을 획득하지 못한 요청은 이미 다른 실행자가 처리 중인 것으로 보고 fallback 응답만 반환한다.
#### Edge Cases
- 최신 완료 주차 스냅샷이 없고 스냅샷 테이블이 완전히 비어 있으면 제한적 원천 데이터 fallback 집계를 시도하고, fallback 성공/실패를 장애 추적용 로그로 남긴다.
- 최신 공개 스냅샷이 없고 스냅샷 테이블이 완전히 비어 있으며 fallback 대상 기간의 `visibleFromAt <= now` 조건을 만족하면 제한적 원천 데이터 fallback 집계를 시도하고, fallback 성공/실패를 장애 추적용 로그로 남긴다.
- fallback 성공 후 스냅샷 저장 트리거는 실패하더라도 조회 응답을 실패시키지 않되, job 상태 또는 구조화 로그로 실패를 추적할 수 있어야 한다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 최신 완료 주차 스냅샷 기준 응답을 유지한다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 최신 공개 스냅샷 기준 응답을 유지한다.
- 스냅샷 생성 중 일부 원천 집계가 실패하면 해당 주차 스냅샷 저장을 실패 처리하고 부분 결과를 공개하지 않는다.
- 스냅샷 생성은 성공했지만 `visibleFromAt > now`이면 해당 스냅샷은 공개 조회 대상에서 제외한다.
- Redisson lock 획득 실패는 다른 인스턴스가 같은 작업을 수행 중인 정상 skip으로 처리하고, 스냅샷 생성 실패로 집계하지 않는다.
- 실패 job 재시도 API는 실패 상태 job만 대상으로 하며, 이미 대기/처리 중/성공 상태인 job은 재시도 대상으로 변경하지 않는다.