23 KiB
23 KiB
PRD: 크리에이터 랭킹
1. Overview
지난 주 월요일 00:00:00 KST부터 일요일 23:59:59.999999999 KST까지의 활동 데이터를 기준으로 크리에이터 랭킹 점수를 계산하고, 최종 점수 상위 20명을 조회할 수 있는 기능을 제공한다.
2. Problem
- 크리에이터의 매출, 콘텐츠 반응, 응원, 팬 충성도를 한 번에 비교할 수 있는 주간 랭킹 기준이 필요하다.
- 서버 시스템 timezone이 UTC로 동작하더라도 랭킹 산정 기간은 KST 기준 지난 주 월요일부터 일요일까지로 고정되어야 한다.
- DB와 서버 timezone은 UTC이므로, KST 기준으로 산출한 랭킹 기간을 UTC 조회 조건으로 변환해 원천 데이터를 조회해야 한다.
- 계산 산식이 여러 도메인 데이터에 걸쳐 있어 조회 API 내부에 직접 구현하면 테스트와 스냅샷 기반 성능 개선이 어려워진다.
- 동일한 랭킹 산식을 주간 스냅샷 생성, 운영 조회, 캐시 갱신에서 재사용할 수 있도록 계산 책임과 조회 책임을 분리해야 한다.
3. Goals
- KST 기준 지난 주 월요일부터 일요일까지의 주간 크리에이터 랭킹을 계산한다.
- 최종 점수 기준 상위 20명의 크리에이터를 조회할 수 있다.
- 랭킹 계산 산식은 독립된 application/domain 컴포넌트로 분리한다.
- 계산 기간 산출은 서버 기본 timezone에 의존하지 않고 명시적으로
Asia/Seoul기준을 사용한다. - KST 기준 집계 시작/종료 시각을 UTC 기준 조회 시작/종료 시각으로 변환한 뒤 DB 데이터를 조회한다.
- 각 점수 카테고리의 원천 지표와 가중치를 테스트 가능한 형태로 관리한다.
- 조회 시 매번 무거운 원천 집계를 수행하지 않도록 주간 랭킹 계산 결과를 스냅샷으로 저장한다.
- 추후 성능 개선을 위해 캐시 저장소를 추가할 수 있는 포트 경계를 둔다.
4. Non-Goals
- 이번 PRD에서는 별도 관리자 화면 신규 개발을 포함하지 않는다. 단, 기존 관리자 영역에서 호출할 수 있는 스냅샷 수동 생성/재시도용 관리자 전용 API는 포함한다.
- 크리에이터 랭킹 산식의 머신러닝 모델화, 개인화, A/B 테스트는 포함하지 않는다.
- 실시간 랭킹 또는 현재 주 진행 중 랭킹은 포함하지 않는다. 단, 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 제한적으로 원천 데이터 fallback 집계를 시도할 수 있다.
- 기존 공개 API 스키마를 임의 변경하지 않는다.
- 랭킹 결과 수동 보정 기능은 포함하지 않는다.
- 점수 산식의 가중치를 관리자에서 동적으로 수정하는 기능은 포함하지 않는다.
5. Target Users
- 회원: 주간 인기 크리에이터를 탐색하는 사용자
- 앱 클라이언트: 랭킹 화면에 상위 크리에이터 목록과 순위/순위 변화 정보를 노출하는 클라이언트
- 운영자: 주간 크리에이터 성과를 확인하고 랭킹 산식의 결과를 검증하는 내부 사용자
6. User Stories
- 사용자는 지난 주 기준으로 가장 높은 최종 점수를 받은 크리에이터 20명을 보고 싶다.
- 사용자는 랭킹 순위, 지난 주 대비 순위 변화, 크리에이터 프로필 이미지, 닉네임을 확인하고 싶다.
- 앱 클라이언트는 홈 내부 랭킹 탭에서 동일한 API 응답으로 랭킹 화면을 구성하고 크리에이터 상세로 이동하고 싶다.
- 운영자는 특정 크리에이터의 최종 점수가 어떤 카테고리 점수로 구성되었는지 추적할 수 있어야 한다.
- 개발자는 시스템 timezone이 UTC여도 KST 기준 집계 기간이 흔들리지 않는지 테스트로 확인하고 싶다.
7. Core Features
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 미만이다.
- 서버 기본 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 직후 조회해도 방금 시작한 이번 주 데이터가 포함되지 않아야 한다.
- 연도/월 경계를 넘어가는 주차도 동일한 규칙으로 계산한다.
- DST가 없는 KST 기준을 사용하되, 구현은
ZoneId.of("Asia/Seoul")처럼 명시적인 timezone을 사용한다.
Feature B. 콘텐츠 + 라이브 점수
Requirements
- 콘텐츠 + 라이브 점수는 라이브 계열 매출 합산 지표 70%, 콘텐츠 구매 합산 지표 30%로 계산한다.
- 라이브 계열 매출 합산 지표는
CanUsage.DONATION,CanUsage.LIVE,CanUsage.SPIN_ROULETTE의 사용 캔 합계로 계산한다. - 콘텐츠 구매 합산 지표는
CanUsage.ORDER_CONTENT1종의 사용 캔 합계로 계산한다. - 환불된 사용 내역은 점수 계산에서 제외한다.
- 크리에이터별 기간 내 합계를 원천 지표로 보관하거나 응답 내부 추적이 가능해야 한다.
Edge Cases
- 라이브 또는 콘텐츠 구매 데이터가 없으면 해당 지표는 0점으로 계산한다.
- 음수 캔 또는 환불 데이터가 섞여 있으면 기존
UseCan환불 정책과 동일한 방식으로 제외한다.
Feature C. 참여 반응 점수
Requirements
- 참여 반응 점수는 콘텐츠 좋아요 수 50%, 콘텐츠 댓글 수 50%로 계산한다.
- 콘텐츠 좋아요 수는 기간 내 활성 콘텐츠 좋아요 수를 크리에이터별로 합산한다.
- 콘텐츠 댓글 수는 기간 내 활성 콘텐츠 댓글과 대댓글 수를 크리에이터별로 합산한다.
- 해당 콘텐츠의 크리에이터가 직접 작성한 댓글과 대댓글은 콘텐츠 댓글 수에서 제외한다.
- 비활성 콘텐츠, 삭제 또는 비활성 처리된 좋아요/댓글은 기존 도메인 정책에 맞춰 제외한다.
Edge Cases
- 좋아요 또는 댓글이 없으면 해당 지표는 0점으로 계산한다.
- 콘텐츠 댓글 수가 없거나 크리에이터 본인 댓글/대댓글만 있으면 댓글 지표는 0점으로 계산한다.
Feature D. 응원 점수
Requirements
- 응원 점수는 채널 후원 캔 합계 60%, 채널 후원 수 20%, 팬 Talk 수 20%로 계산한다.
- 채널 후원 캔 합계는
CanUsage.CHANNEL_DONATION의 사용 캔 합계로 계산한다. - 채널 후원 수는
CanUsage.CHANNEL_DONATION사용 건수로 계산한다. - 팬 Talk 수는 기존
CreatorCheers의 최상위 등록 수로 계산하고 답글은 포함하지 않는다. - 환불된 채널 후원 내역은 점수 계산에서 제외한다.
Edge Cases
- 채널 후원 또는 팬 Talk 데이터가 없으면 해당 지표는 0점으로 계산한다.
- 팬 Talk 답글이 별도 row로 저장되어 있어도 팬 Talk 수에 포함하지 않는다.
Feature E. 팬 충성도 점수
Requirements
- 팬 충성도 점수는 최종 팔로우 수 70%, 팔로우 증가 수 30%로 계산한다.
- 최종 팔로우 수는 랭킹 대상 기간 종료 시점 기준 활성 팔로우 수를 의미한다.
- 팔로우 증가 수는 랭킹 대상 기간 동안 활성 팔로우 수가 몇 명 증가했는지를 의미한다.
- 기본 정의는
기간 내 신규 활성 팔로우 수 - 기간 내 비활성화된 팔로우 수로 한다. - 신규 활성 팔로우 수는
CreatorFollowing.createdAt이 랭킹 대상 기간 안에 있는 팔로우만 집계한다. - 비활성화된 팔로우 수는
CreatorFollowing.isActive == false이고CreatorFollowing.updatedAt이 랭킹 대상 기간 안에 있는 팔로우만 집계한다. - 과거 언팔로우 후 기간 내 재팔로우한 경우는
createdAt이 과거 시점이므로 신규 증가로 반영하지 않는다. - 이번 산식은 현재
creator_followingrow의createdAt,updatedAt,isActive기준으로 계산하며, 한 기간 안에서 여러 번 발생한 팔로우/언팔로우 이벤트 히스토리까지 별도로 복원하지 않는다. - 팔로우 증가 수가 음수이면 음수 원천 지표와 음수 카테고리 점수를 허용하고, 최종 점수에 그대로 반영한다.
Edge Cases
- 기간 내 재팔로우로 다시 활성화된 팔로우는 최종 팔로우 수에는 포함될 수 있지만 팔로우 증가 수의 신규 생성분에는 포함하지 않는다.
- 기간 내 언팔로우 후 재팔로우해 최종 상태가 활성인 row는
isActive == false조건에 걸리지 않으므로 비활성화된 팔로우 수에도 포함하지 않는다.
Feature F. 최종 점수 계산 및 정렬
Requirements
- 최종 점수는
(콘텐츠/라이브 카테고리 점수 * 0.35) + (참여 반응 점수 * 0.30) + (응원 점수 * 0.25) + (팬 충성도 점수 * 0.10)으로 계산한다. - 최종 점수 1점 이상인 크리에이터만 랭킹에 포함한다.
- 최종 점수 내림차순으로 최대 20명을 조회한다.
- 동점자는 랜덤으로 추출한다.
- 스냅샷에는 최종 점수 1점 이상인 모든 후보를 저장하지 않고, Top 20 산정에 필요한 후보만 저장한다.
- Top 20 산정에 필요한 후보는 20위 점수보다 높은 후보와 20위 점수에 동점인 후보 전체를 의미한다.
- 조회 시 스냅샷에 저장된 후보 중 최종 점수 동점자를 랜덤 정렬해 상위 20명을 추출한다.
- 동점 랜덤 추출을 위한 별도
randomTieBreaker값은 스냅샷에 저장하지 않는다. - 각 하위 지표는 0~100 정규화하지 않고 원천 값(raw value)을 그대로 사용한다.
- 캔 단위 지표는 좋아요, 댓글, 팔로우 같은 개수 지표보다 최종 점수에 더 큰 영향을 줄 수 있으며, 이는 의도된 정책이다.
Edge Cases
- 특정 지표 값이 없으면 해당 원천 값은 0으로 계산한다.
- 최종 점수가 1점 미만이면 20명이 되지 않아도 응답에서 제외한다.
Feature G. 랭킹 조회 API
Requirements
- 홈 내부 랭킹 탭에서 주간 크리에이터 랭킹 상위 20명을 조회하는 API를 제공한다.
- API endpoint는
GET /api/v2/home/rankings/creators를 사용한다. - API는 최신 완료 주차의 스냅샷을 기준으로 조회하며 별도 query parameter 없이 기본 랭킹을 반환한다.
- 응답에는 순위 변화 표시 여부, 순위, 지난 주 대비 순위 변화, 신규 진입 여부, 크리에이터 id, 닉네임, 프로필 이미지를 포함한다.
showRankChange는items와 같은 레벨에 내려주며, 클라이언트가 순위 변화 UI를 표시할지 판단하는 값이다.- 각 크리에이터의 순위 변화 값은
items[].rankChange에 숫자로 내려준다. - 순위가 올라갔으면 양수, 순위가 내려갔으면 음수로 내려준다.
- 예를 들어 직전 완료 주차 10위, 최신 완료 주차 5위이면
rankChange는5다. - 예를 들어 직전 완료 주차 1위, 최신 완료 주차 10위이면
rankChange는-9다. - 직전 완료 주차에는 순위에 없고 최신 완료 주차에 진입한 크리에이터는
items[].isNew == true로 내려주며, 클라이언트는 이를New로 표시한다. - 신규 진입 크리에이터의
rankChange는 비교 가능한 이전 순위가 없으므로null로 내려준다. - 응답의 크리에이터 id는 크리에이터 상세 이동에 사용한다.
- 응답 스키마 예시는 다음과 같다.
{
"showRankChange": true,
"items": [
{
"rank": 1,
"rankChange": 5,
"isNew": false,
"creatorId": 123,
"nickname": "creator",
"profileImageUrl": "https://cdn.example.com/profile.png"
},
{
"rank": 2,
"rankChange": null,
"isNew": true,
"creatorId": 456,
"nickname": "new creator",
"profileImageUrl": "https://cdn.example.com/profile-new.png"
}
]
}
- 운영 검증 또는 디버깅이 필요하면 카테고리별 점수와 원천 지표를 내부용 응답 또는 로그로 확인할 수 있어야 한다.
- 비활성 및 탈퇴 크리에이터는 랭킹에 노출하지 않는다.
- 조회자와 크리에이터 사이에 차단 관계가 있으면 랭킹 row는 유지하되 응답의 크리에이터 id는
0, 닉네임은 빈 문자열로 내려준다. - 차단 관계가 있는 크리에이터의 프로필 이미지는 기본 이미지 URL로 내려주고, 이동 대상 id는
0으로 내려준다. - 인증 사용자 조건이 필요하지 않은 공개 조회를 기본으로 하되, 차단 마스킹 정책은 인증 사용자에게 적용한다.
- 조회 API는 스냅샷 기반 응답을 기본으로 하며, 공개 API 응답 스키마는 fallback 여부와 관계없이 변경하지 않는다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서만 조회 API가 제한적으로 원천 데이터 fallback 집계를 시도할 수 있다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 최신 완료 주차 스냅샷 기준으로 응답한다.
- 스냅샷 테이블이 완전히 비어 있는 초기 상태에서 fallback 집계가 성공하면, 조회 API는 응답을 반환하면서 스냅샷 생성 책임을
CreatorRankingSnapshotRefreshService/CreatorRankingSnapshotJobService쪽으로 위임해 같은 기간의creator_ranking_snapshot생성을 트리거한다. - cold-start fallback에서 스냅샷 생성 트리거는 운영 배포 직후 내부 테스트 등 초기 검증 상황을 위한 보강책이며, 장기 실시간 집계 경로로 사용하지 않는다.
- cold-start fallback 스냅샷 생성은 동일 집계 기간에 대해 한 번만 실행되도록 기간 기반 Redisson lock을 사용하고, lock 획득 실패 시 다른 요청 또는 작업이 처리 중인 정상 skip으로 간주한다.
Edge Cases
- 최종 점수 1점 이상인 랭킹 후보가 20명 미만이면 가능한 만큼만 내려준다.
- 랭킹 계산 결과가 없으면 빈 배열로 성공 응답한다.
- 최신 완료 주차 스냅샷이 없고 스냅샷 테이블도 완전히 비어 있으면 제한적 원천 데이터 fallback 집계를 시도한 뒤 결과를 응답한다.
- fallback 성공 뒤 스냅샷 생성 트리거가 실패하더라도 공개 API 응답 스키마는 변경하지 않고, 실패는 로그/job 이력으로 추적한다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 기존 최신 완료 주차 스냅샷 기준 응답을 유지한다.
- 직전 완료 주차 스냅샷이 없으면
showRankChange는false로 내려주고, 각 item의rankChange는null,isNew는false로 내려준다.
Feature H. 주간 랭킹 스냅샷
Requirements
- 주간 랭킹은 조회 시 매번 원천 데이터를 집계하지 않고, 계산 결과를 스냅샷으로 저장한 뒤 조회 API는 스냅샷을 읽는다.
- 스냅샷 생성 기준 기간은 KST 기준 지난 주 월요일 00:00:00 이상, 이번 주 월요일 00:00:00 미만이다.
- 스냅샷 생성 시 원천 데이터 조회 조건은 KST 집계 기간을 UTC로 변환한 기간을 사용한다.
- 스냅샷 저장 대상은 20위 점수보다 높은 후보와 20위 점수에 동점인 후보 전체로 제한한다.
- 최종 점수 1점 이상인 후보가 20명 미만이면 해당 후보만 저장한다.
- 스냅샷은 크리에이터 id, 최종 점수, 카테고리별 점수, 원천 지표, 집계 시작/종료 시각을 저장한다.
- 최종 순위는 스냅샷 저장 시 고정하지 않고 조회 시 최종 점수 내림차순과 동점 랜덤 정렬 결과에 따라 부여한다.
- 순위 변화는 최신 완료 주차 응답에서 부여된 순위와 직전 완료 주차 스냅샷 기준 순위를 비교해 계산한다.
- 동점 랜덤 정렬 정책 때문에 동점 구간에 포함된 크리에이터의 순위와 순위 변화는 조회 결과마다 달라질 수 있으며, 이는 허용한다.
- 스냅샷 생성은 이번 주 데이터가 포함되지 않도록 주간 집계 대상 기간이 종료된 뒤 실행한다.
- 기본 스케줄 후보는 매주 월요일 KST 07:30이며, 스케줄러는
Asia/Seoulzone을 명시한다. - 다중 서버 인스턴스에서 같은 스케줄이 동시에 실행되더라도 클러스터 전체에서 한 인스턴스만 스냅샷 생성을 수행해야 한다.
- 클러스터 단일 실행은 신규 DB 테이블을 추가하지 않고, 기존 프로젝트에 설정된 Redisson 기반 분산 lock을 우선 사용한다.
- 주간 랭킹 스냅샷 lock key는
lock:creator-ranking-snapshot-refresh를 사용하며, lock 획득 실패 인스턴스는 스냅샷 생성을 skip한다. - 같은 집계 기간에 대해 스냅샷을 재생성할 수 있어야 하며, 재생성 시 기존 같은 기간 스냅샷을 중복 노출하지 않는다.
- 조회 API는 최신 완료 주차의 스냅샷을 기준으로 응답한다.
- 스냅샷 생성 직전 집계 시작/종료 시각을 포함한 job 이력을 생성하고, 작업 완료 후 성공/실패 상태와 처리 결과를 기록한다.
- 스케줄러로 실행되는 주간 스냅샷 생성도 job 이력으로 기록한다.
- 운영자는 관리자 전용 API를 통해 날짜 범위를 직접 선택해 스냅샷 생성 job을 생성할 수 있어야 한다.
- 실패한 스냅샷 생성 job은 관리자 전용 재시도 API로 재시도할 수 있어야 하며, 기존 관리자 job 패턴과 같이 실패 상태 job을 대기 상태로 되돌려 worker가 다시 처리하도록 한다.
- 관리자 전용 job 목록 API는 날짜 범위, 실행 트리거, 상태, 실패 사유, 재시도 가능 여부를 확인할 수 있어야 한다.
- cold-start fallback 성공 후 스냅샷 저장은 조회 서비스가 직접 DB에 쓰지 않고, 스냅샷 refresh 책임을 가진 job/service 경계로 위임한다.
- cold-start fallback 스냅샷 저장 트리거는 집계 기간을 포함한 Redisson lock key를 사용해 동일 기간 중복 생성을 방지한다. 예:
lock:creator-ranking-snapshot-refresh:{aggregationStartAtUtc}:{aggregationEndAtUtc}. - lock을 획득한 요청만 refresh job/service를 실행하고, lock을 획득하지 못한 요청은 이미 다른 실행자가 처리 중인 것으로 보고 fallback 응답만 반환한다.
Edge Cases
- 최신 완료 주차 스냅샷이 없고 스냅샷 테이블이 완전히 비어 있으면 제한적 원천 데이터 fallback 집계를 시도하고, fallback 성공/실패를 장애 추적용 로그로 남긴다.
- fallback 성공 후 스냅샷 저장 트리거는 실패하더라도 조회 응답을 실패시키지 않되, job 상태 또는 구조화 로그로 실패를 추적할 수 있어야 한다.
- 스냅샷 테이블에 과거 스냅샷이 하나라도 있으면 원천 데이터 fallback을 시도하지 않고 최신 완료 주차 스냅샷 기준 응답을 유지한다.
- 스냅샷 생성 중 일부 원천 집계가 실패하면 해당 주차 스냅샷 저장을 실패 처리하고 부분 결과를 공개하지 않는다.
- Redisson lock 획득 실패는 다른 인스턴스가 같은 작업을 수행 중인 정상 skip으로 처리하고, 스냅샷 생성 실패로 집계하지 않는다.
- 실패 job 재시도 API는 실패 상태 job만 대상으로 하며, 이미 대기/처리 중/성공 상태인 job은 재시도 대상으로 변경하지 않는다.
Feature I. 랭킹 계산 컴포넌트 분리
Requirements
- 랭킹 계산과 조회는 Controller나 Facade 내부에 직접 구현하지 않고 별도 application/domain 컴포넌트로 분리한다.
- 크리에이터 랭킹 기능 본체는 추천 기능과 독립된 성격이므로
v2.recommendation가 아니라 별도kr.co.vividnext.sodalive.v2.ranking하위 패키지에 작성한다. - 예시 컴포넌트는 다음 책임을 갖는다.
- 기간 계산 정책: KST 기준 지난 주 기간을 산출한다.
- 점수 정책: 원천 지표의 raw value에 가중치를 적용해 카테고리/최종 점수를 계산한다.
- 집계 포트:
UseCan, 콘텐츠 반응,CreatorCheers,CreatorFollowing원천 데이터를 조회한다. - 스냅샷 생성 서비스: 원천 지표를 집계하고 랭킹 스냅샷을 저장한다.
- 조회 서비스: 저장된 스냅샷을 상위 20명 ranking 조회 결과로 조립한다.
- 홈 API 조합 Facade: ranking 조회 결과를 클라이언트 공개 응답 DTO로 변환한다.
- 추후 캐싱을 추가할 수 있도록 조회 서비스는 스냅샷 조회 포트와 캐시 포트를 분리할 수 있는 경계를 둔다.
Edge Cases
- 캐시가 추가되더라도 산식 테스트는 캐시와 분리된 순수 정책 테스트로 유지한다.
- 조회 API는 스냅샷 기반 응답을 기본으로 하며, 스냅샷 테이블이 완전히 비어 있는 초기 상태를 제외하고 원천 데이터 실시간 계산 fallback을 두지 않는다.
8. Technical Constraints
- Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다.
- 랭킹 계산, 스냅샷 생성, 스냅샷 조회, 차단 마스킹 등 기능 본체는
kr.co.vividnext.sodalive.v2.ranking하위에 작성한다. - 클라이언트 endpoint는 홈 내부 랭킹 탭에서 호출하므로
/api/v2/home/rankings/creators를 사용한다. - 클라이언트 공개 API 표면인 Controller와 API 조합 Facade는 기존 홈 API 관례를 따라
kr.co.vividnext.sodalive.v2.api.home하위에 작성하고, 크리에이터 랭킹 응답 DTO는kr.co.vividnext.sodalive.v2.api.home.dto.ranking하위에 작성한다. - 기존 엔티티 후보는
UseCan,CanUsage,AudioContent,AudioContentLike,AudioContentComment,CreatorCheers,CreatorFollowing,Member등이다. - 기존 공개 API 스키마는 변경하지 않는다.
- 계산 기간은 서버 기본 timezone이 아니라 명시적인 KST 기준으로 산출하고, DB 조회 시에는 UTC 기간으로 변환한다.
- QueryDSL 또는 native SQL 중 기존 성능/패턴에 맞는 방식을 선택하되, 산식 자체는 테스트 가능한 domain/application 정책으로 분리한다.
- 주간 랭킹 조회는 스냅샷 기반으로 제공한다.
- 캐싱은 이번 PRD의 필수 구현은 아니지만, 랭킹 조회 서비스가 캐시 포트를 도입할 수 있는 구조여야 한다.
- 스냅샷 스케줄러는 기존 Redisson 설정을 재사용해 클러스터 단일 실행을 보장하고, 별도 scheduler lock용 DB 테이블은 추가하지 않는다.
9. Metrics
- 랭킹 조회 API latency
- 랭킹 계산 소요 시간
- 주간 스냅샷 생성 성공/실패 수
- 주간 스냅샷 생성 지연 시간
- 스냅샷 job 상태별 수와 실패 job 재시도 수
- 관리자 수동 생성 job 요청 수와 성공/실패 수
- 스냅샷 테이블 완전 공백 fallback 시도/성공/실패 수
- 랭킹 후보 크리에이터 수
- 최종 점수 1점 미만으로 제외된 크리에이터 수
- 랭킹 조회 성공/실패 로그 수
- 캐시 도입 후 cache hit ratio
10. Open Questions
현재 PRD 기준 미결정 항목은 없다.