feat(home): 팔로잉 탭 최근 소식 정책을 보강한다
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
- 최근 소식은 랭킹, 커뮤니티 게시글 업로드, 콘텐츠 업로드가 섞인 피드라 매 요청마다 팔로잉한 모든 크리에이터의 모든 원천 데이터를 크게 조인하면 응답 지연과 DB 부하가 커질 수 있다.
|
||||
- 최근 소식은 전체 후보를 매번 조회하는 모델보다, 팔로우 중인 크리에이터의 이벤트가 발생할 때 각 follower의 우체통에 소식 row를 넣는 사용자별 Inbox Feed 모델이 요구사항에 더 맞다.
|
||||
- 따라서 공개 API 조립 계층과 도메인 조회 계층을 분리하고, 최근 소식은 사용자별 inbox row를 최신순으로 읽는 구조가 필요하다.
|
||||
- 현재 최근 소식 조회는 노출 가능한 과거 `CREATOR_RANKING` inbox도 함께 조회하므로, 전체 소식이 30개 미만이면 직전 공개 배치보다 오래된 크리에이터 순위가 표시될 수 있다.
|
||||
- 향후 `CONTENT_RANKING` inbox가 적재되면 같은 콘텐츠가 여러 랭킹 소식에 포함될 수 있으므로, 조회 시 동일 콘텐츠의 중복 노출을 방지하는 정책이 필요하다.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,6 +26,8 @@
|
||||
- DM/AI 채팅방 중 최신 대화순 10개를 응답한다.
|
||||
- 사용자가 팔로우한 크리에이터들의 이번 달 오늘 이후 스케줄을 오늘과 가까운 순으로 최대 3개 응답한다.
|
||||
- 사용자가 팔로우한 크리에이터들의 최근 소식을 최신 노출 가능 시각순 최대 30개 응답한다.
|
||||
- `CREATOR_RANKING` 최근 소식은 현재 시점에 공개된 최신 크리에이터 랭킹 배치만 응답한다.
|
||||
- `CONTENT_RANKING` 최근 소식은 같은 콘텐츠의 노출 가능한 inbox가 여러 개여도 가장 최신 항목 하나만 응답한다.
|
||||
- 최근 소식은 팔로우 중인 크리에이터의 이벤트 발생 시점에 사용자별 inbox row를 생성하고, 조회 시 열람 가능 시각/활성 여부/차단/성인 노출 조건을 적용한다.
|
||||
- 새로 팔로우한 사용자는 과거 소식을 받지 않는다.
|
||||
- 언팔로우하면 해당 크리에이터가 보낸 기존 inbox row를 비활성화한다.
|
||||
@@ -42,6 +46,7 @@
|
||||
- 최근 소식의 운영자 수동 고정/숨김 기능은 포함하지 않는다.
|
||||
- 최근 소식 발송용 외부 MQ, outbox table, 별도 worker, cursor/retry dashboard는 이번 범위에 포함하지 않는다.
|
||||
- 화보 업로드 기능 자체 구현은 포함하지 않는다. 단, 향후 콘텐츠 타입 확장을 고려한 응답 타입은 정의한다.
|
||||
- `CONTENT_RANKING` inbox 발행과 콘텐츠 랭킹 스냅샷 연동은 이번 보완 범위에 포함하지 않는다.
|
||||
- 전체보기/페이징 API는 이번 요구사항에 포함하지 않는다.
|
||||
|
||||
---
|
||||
@@ -59,7 +64,8 @@
|
||||
- 사용자는 팔로우한 크리에이터가 지금 진행 중인 라이브를 바로 확인하고 싶다.
|
||||
- 사용자는 최근 DM/AI 채팅방으로 빠르게 이동하고 싶다.
|
||||
- 사용자는 팔로우한 크리에이터의 이번 달 예정 라이브/콘텐츠 일정을 가까운 일정부터 보고 싶다.
|
||||
- 사용자는 팔로우한 크리에이터의 이번 주 랭킹 순위, 커뮤니티 게시글, 콘텐츠 업로드 소식을 최신순으로 보고 싶다.
|
||||
- 사용자는 팔로우한 크리에이터의 최신 공개 랭킹 순위, 커뮤니티 게시글, 콘텐츠 업로드 소식을 최신순으로 보고 싶다.
|
||||
- 사용자는 과거 크리에이터 랭킹 배치나 같은 콘텐츠의 중복 랭킹 소식 없이 최근 소식을 보고 싶다.
|
||||
- 앱 클라이언트는 소식 item의 타입별 터치 액션을 명확한 target id로 처리하고 싶다.
|
||||
|
||||
---
|
||||
@@ -174,6 +180,13 @@
|
||||
- inbox row에는 소식 타입, 발생 시각, 열람 가능 시각, 수신 회원 id, 크리에이터 id, target id, 표시용 제목/본문/이미지 path, 랭킹 순위 값 등 응답 생성에 필요한 최소 정보를 저장한다.
|
||||
- API 조회는 `memberId = 요청 회원 id`, `isActive = true`, `visibleFromAtUtc <= nowUtc`인 inbox row를 최신순으로 조회한다.
|
||||
- 조회 정렬은 `visibleFromAtUtc desc`, `newsId desc`를 기본으로 한다.
|
||||
- `CREATOR_RANKING`은 적용 가능한 `creator_ranking_snapshot_job` 중 `rankingType = WEEKLY`, `status = DONE`, `visibleFromAtUtc <= nowUtc`를 만족하는 최신 완료 job의 `visibleFromAtUtc`와 같은 inbox row만 조회한다.
|
||||
- 적용 가능한 완료 job이 전혀 없는 legacy/backfill 데이터에서만 `creator_ranking_snapshot`의 `WEEKLY`, `visibleFromAtUtc <= nowUtc` 최신 공개 시각을 fallback으로 사용한다.
|
||||
- 최신 완료 job의 결과가 0건이면 과거 snapshot으로 보충하지 않는다.
|
||||
- 다음 랭킹 배치가 공개되기 전에는 직전 공개 배치를 최신 배치로 유지하고, 새 배치가 공개된 뒤에는 이전 배치의 `CREATOR_RANKING` inbox를 조회하지 않는다.
|
||||
- 사용자가 팔로우한 크리에이터가 최신 공개 배치에 없으면 해당 크리에이터의 과거 `CREATOR_RANKING` inbox로 대체하지 않는다.
|
||||
- `CONTENT_RANKING`은 같은 회원과 같은 `targetId`를 콘텐츠 동일성 기준으로 사용하고, 노출 가능한 row 중 `visibleFromAtUtc desc`, `newsId desc` 기준 첫 항목 하나만 조회한다.
|
||||
- 랭킹 배치 필터와 콘텐츠 중복 제거는 전체 최대 30개 제한보다 먼저 적용한다.
|
||||
- 조회 시 원천 target의 비활성/삭제 여부, 차단 관계, 성인 노출 가능 여부를 최종 확인한다.
|
||||
- `FollowingNewsResponse` 최상위 응답 필드는 `newsId`, `type`, `visibleFromAtUtc`만 공통으로 포함한다.
|
||||
- 타입별 세부 값은 nullable nested DTO로 내려주며, `type`과 일치하는 nested DTO만 non-null이고 나머지는 `null`이다.
|
||||
@@ -190,12 +203,16 @@
|
||||
- 즉시 공개 콘텐츠는 `visibleFromAtUtc = releaseDate`로 저장할 수 있다.
|
||||
- 크리에이터 랭킹 소식은 크리에이터 랭킹 스냅샷 생성 시 inbox row를 생성할 수 있으나, `visibleFromAtUtc`는 랭킹 스냅샷의 `visibleFromAtUtc`를 그대로 사용한다.
|
||||
- 크리에이터 랭킹 스냅샷이 월요일 01:00 KST에 생성되고 월요일 09:00 KST에 화면 반영되는 경우, `CREATOR_RANKING` inbox row도 월요일 09:00 KST 전에는 API에 노출되지 않아야 한다.
|
||||
- 월요일 신규 랭킹 공개 전에는 직전 공개 배치의 `CREATOR_RANKING` 소식을 표시하고, 신규 배치 공개 시점부터는 신규 배치 소식만 표시한다.
|
||||
- 최근 소식에서 순위 변화와 신규 진입 여부는 사용하지 않는다. 랭킹 타입은 nested DTO의 `rank`만 내려준다.
|
||||
|
||||
#### Edge Cases
|
||||
- inbox row가 없거나 필터링 후 결과가 없으면 빈 배열을 내려준다.
|
||||
- inbox 적재 실패 시 API 조회에서 실시간 fallback 집계를 무조건 수행하지 않는다.
|
||||
- 랭킹 소식의 순위 값이 없거나 오래된 경우 해당 item은 생성하지 않는다.
|
||||
- 최신 공개 크리에이터 랭킹 배치에 해당하는 inbox가 없으면 과거 배치로 보충하지 않고 `CREATOR_RANKING` 소식을 가능한 개수만 응답한다.
|
||||
- 같은 콘텐츠의 `CONTENT_RANKING` row가 여러 개이고 `visibleFromAtUtc`가 같으면 `newsId`가 큰 row 하나만 응답한다.
|
||||
- 과거 크리에이터 랭킹과 중복 콘텐츠 랭킹을 제외한 뒤 최근 소식이 30개 미만이어도 과거·중복 랭킹으로 보충하지 않는다.
|
||||
- 같은 회원, 같은 소식 타입, 같은 `sourceKey`에 대해 중복 inbox row를 생성하지 않는다.
|
||||
- 언팔로우와 inbox 적재가 동시에 발생하면, 최종적으로 언팔로우 상태인 크리에이터의 새 소식은 노출하지 않는다.
|
||||
- 타입별 이미지가 없으면 해당 nested DTO의 이미지 URL 필드는 `null`로 내려준다.
|
||||
@@ -328,7 +345,8 @@ enum class FollowingNewsType {
|
||||
```
|
||||
|
||||
- `ChatRoomListItemResponse`는 기존 `v2.chat.dto` 응답 DTO를 직접 재사용한다.
|
||||
- `scheduleId`와 `newsId`는 서로 다른 원천 타입의 id 충돌을 피하기 위해 `{TYPE}:{targetId}` 형식의 문자열을 기본안으로 한다. 최근 소식의 이동 대상 id는 타입별 nested DTO 안의 id 필드를 사용한다.
|
||||
- `scheduleId`는 서로 다른 원천 타입의 id 충돌을 피하기 위해 `{TYPE}:{targetId}` 형식의 문자열을 사용한다.
|
||||
- `newsId`는 `home_following_news_inbox.id`의 10진 문자열이며, 최근 소식의 `visibleFromAtUtc desc`, `newsId desc` 정렬과 `CONTENT_RANKING` 동률 해소에 사용한다. 최근 소식의 이동 대상 id는 타입별 nested DTO 안의 id 필드를 사용한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -370,7 +388,9 @@ enum class FollowingNewsType {
|
||||
- publish service는 `publishContentUploaded(...)`, `publishFreeCommunityPostCreated(...)`, `publishCreatorRankingVisible(...)`처럼 이벤트별 명시적 메서드를 제공한다. 유료 커뮤니티 게시글은 publish service 호출 대상이 아니다.
|
||||
- 운영 규모가 커지면 publish service 내부에서 outbox row 저장 또는 비동기 worker 위임으로 전환할 수 있도록 호출부 계약을 작게 유지한다.
|
||||
- `CREATOR_RANKING` 타입은 크리에이터 랭킹 소식만 포함한다.
|
||||
- `CONTENT_RANKING` 타입은 향후 콘텐츠 랭킹 소식용으로 enum과 table 값만 예약하고, 이번 범위에서는 생성하지 않는다.
|
||||
- `CREATOR_RANKING` 조회의 최신 공개 배치는 적용 가능한 `creator_ranking_snapshot_job`의 `WEEKLY`, `DONE`, `visibleFromAtUtc <= nowUtc` 조건으로 우선 판정한다. 적용 가능한 완료 job이 전혀 없는 legacy/backfill 데이터에서만 `creator_ranking_snapshot`의 최신 공개 시각을 fallback으로 사용하며, 최신 완료 job의 결과가 0건이면 과거 snapshot으로 보충하지 않는다.
|
||||
- `CONTENT_RANKING` 타입은 향후 콘텐츠 랭킹 소식용으로 enum과 table 값을 유지하되, 이번 보완에서는 발행 기능을 추가하지 않고 기존 또는 향후 적재된 inbox의 조회 정책만 정의한다.
|
||||
- `CONTENT_RANKING`은 `targetId`별 최신 노출 가능 row 하나만 남기고, `visibleFromAtUtc`가 같으면 `newsId` 내림차순으로 하나를 선택한다.
|
||||
- 언팔로우 시 해당 회원과 크리에이터의 활성 inbox row를 비활성화한다.
|
||||
- 재팔로우 시 비활성화된 기존 inbox row는 복구하지 않는다.
|
||||
- 현재 `creator_following`에는 재팔로우 시점이 명확히 남지 않으므로, 조회 조건으로 재팔로우 시점을 추론하지 않는다.
|
||||
@@ -395,3 +415,10 @@ enum class FollowingNewsType {
|
||||
## 12. Open Questions
|
||||
- 현재 PRD 기준의 미결정 요구사항은 없다.
|
||||
- 구현 계획 단계에서는 기존 라이브 조회 코드의 진행 중 판단 조건과 스케줄 `isOnAir` 판단 조건을 같은 조건으로 추출할지 검토한다.
|
||||
|
||||
---
|
||||
|
||||
## 13. Decision Log
|
||||
|
||||
- 2026-07-30: `CREATOR_RANKING` 최근 소식은 KST 달력 주간이 아니라 현재 시점의 최신 공개 `WEEKLY`, `DONE` 크리에이터 랭킹 job을 기준으로 한다. 신규 배치 공개 전에는 직전 공개 배치를 유지하고, 공개 후에는 이전 배치를 노출하지 않으며, 최신 완료 job의 결과가 0건이면 과거 snapshot으로 보충하지 않는다.
|
||||
- 2026-07-30: `CONTENT_RANKING` 발행 기능은 이번 보완 범위에서 제외한다. 조회 시 동일 `contentId`의 노출 가능한 row 중 `visibleFromAtUtc desc`, `newsId desc` 기준 최신 항목 하나만 응답한다.
|
||||
|
||||
Reference in New Issue
Block a user