Files

11 KiB

PRD: 메인 콘텐츠 추천 오디오 스냅샷 폴백

1. Overview

메인 콘텐츠 추천 탭의 오디오 스냅샷 기반 3개 섹션이 각각 독립적으로 스냅샷 없음 fallback refresh를 수행하도록 보강한다.


2. Problem

  • AudioRecommendationQueryService.getRecommendationsNEW_AND_HOT_AUDIO_*, MOST_COMMENTED_AUDIO_*, RECOMMENDED_AUDIO_* 3개 스냅샷을 조회한다.
  • 현재 조회 흐름은 NEW_AND_HOT_AUDIO_*가 비어 있을 때만 lazy refresh를 시도한다.
  • MOST_COMMENTED_AUDIO_* 또는 RECOMMENDED_AUDIO_*만 비어 있는 경우에는 refresh가 실행되지 않아 해당 섹션이 빈 배열로 내려간다.
  • NEW_AND_HOT_AUDIO_* fallback이 refreshDailySnapshots()를 호출하면 6개 오디오 스냅샷 variant를 모두 갱신하지만, 같은 요청에서 이미 읽어둔 MOST_COMMENTED_AUDIO_*, RECOMMENDED_AUDIO_*는 재조회하지 않는다.
  • 홈 추천의 AI_CHARACTER, CHEER_CREATOR, POPULAR_COMMUNITY는 각 섹션별 fallback, lock, double-check, timeout, empty marker 정책을 갖고 있어 오디오 추천과 동작 일관성이 다르다.

3. Goals

  • 오디오 추천의 스냅샷 기반 3개 섹션 모두 독립 fallback을 갖는다.
  • fallback 대상은 visibility variant를 포함한 실제 조회 섹션 타입 기준으로 분리한다.
    • NEW_AND_HOT_AUDIO_SAFE
    • NEW_AND_HOT_AUDIO_ALL
    • MOST_COMMENTED_AUDIO_SAFE
    • MOST_COMMENTED_AUDIO_ALL
    • RECOMMENDED_AUDIO_SAFE
    • RECOMMENDED_AUDIO_ALL
  • 각 섹션 스냅샷이 없으면 스케줄러와 동일한 오디오 스냅샷 refresh 로직으로 저장한 뒤, 해당 섹션을 다시 조회한다.
  • fallback은 중복 refresh를 막기 위해 섹션 단위 lock, double-check, JVM 내 single-flight를 사용한다.
  • fallback refresh 실패, timeout, lock miss는 전체 API 실패로 전파하지 않고 해당 섹션 빈 배열로 처리한다.
  • refresh 결과 0건인 섹션은 정상 refresh 완료 상태를 저장해 매 요청마다 fallback을 반복하지 않게 한다.
  • 기존 공개 API URL, 응답 JSON 필드, 오디오 추천 산식, 스냅샷 스케줄 시각은 변경하지 않는다.

4. Non-Goals

  • GET /api/v2/audio/recommendations 응답 스키마를 변경하지 않는다.
  • NEW_AND_HOT, MOST_COMMENTED, RECOMMENDED_AUDIO 점수 산식과 집계 window를 변경하지 않는다.
  • 오디오 배너, 오리지널 시리즈, 최신 오디오, 무료 오디오, 포인트 오디오 조회 정책은 변경하지 않는다.
  • 신규 추천 스냅샷 테이블 또는 DDL을 만들지 않는다.
  • 홈 추천 RecommendationSnapshotFallbackService를 무리하게 공통화하지 않는다. 오디오 추천에 필요한 최소 재사용/확장만 검토한다.
  • 전체보기 findNewAndHotAudios의 공개 API 스키마와 paging 계약은 변경하지 않는다.

5. Target Users

  • 회원/비회원: 메인 콘텐츠 추천 탭에서 스냅샷 누락으로 특정 오디오 추천 섹션이 비는 상황을 덜 겪어야 하는 사용자
  • 앱 클라이언트: 기존 응답 계약을 유지한 채 가능한 추천 섹션을 안정적으로 받는 클라이언트
  • 운영자: 스케줄러 실패 또는 일부 스냅샷 누락 후 첫 조회에서 자동 복구 흐름을 기대하는 운영 담당자

6. User Stories

  • 사용자는 추천 탭 진입 시 New & Hot, 최근 댓글 많은 오디오, 추천 오디오가 각각 가능한 데이터로 채워지기를 기대한다.
  • 사용자는 한 섹션의 스냅샷이 없더라도 추천 탭 전체가 실패하지 않기를 기대한다.
  • 앱 클라이언트는 특정 스냅샷 섹션이 없는 날에도 기존 응답 구조 그대로 빈 배열 또는 복구된 결과를 받기를 원한다.
  • 운영자는 오디오 추천 스케줄러가 실패한 뒤 첫 사용자 조회가 스케줄러와 같은 refresh 로직으로 스냅샷을 복구하기를 원한다.

7. Core Features

Feature A. 오디오 스냅샷 섹션별 독립 fallback

Requirements

  • AudioRecommendationQueryService.getRecommendationsNEW_AND_HOT, MOST_COMMENTED, RECOMMENDED_AUDIO 각각에 대해 fallback 조회 경로를 사용한다.
  • fallback 판단은 현재 사용자 visibility에 맞는 RecommendedSectionType 기준으로 수행한다.
  • 한 섹션의 스냅샷이 비어 있더라도 다른 섹션의 기존 스냅샷 조회 결과를 버리거나 재정렬하지 않는다.
  • fallback refresh 후에는 refresh를 요청한 섹션을 다시 조회한다.
  • NEW_AND_HOT이 비어 fallback을 실행한 경우에도 MOST_COMMENTED, RECOMMENDED_AUDIO가 비어 있으면 각 섹션의 fallback 판단이 독립적으로 수행되어야 한다.
  • 각 섹션 fallback은 refresh 결과를 직접 응답으로 조립하지 않고 recommendation_snapshot에 저장된 row를 재조회해 사용한다.

Edge Cases

  • 특정 섹션 refresh가 실패해도 다른 섹션 응답은 가능한 범위에서 유지한다.
  • fallback 후 상세 조회 필터에서 모두 제외되면 해당 섹션은 빈 배열로 반환한다.
  • SAFEALL variant 중 현재 요청에서 사용하지 않는 variant의 누락 여부는 해당 요청의 fallback 조건이 아니다.

Feature B. 섹션 단위 lock, double-check, single-flight

Requirements

  • fallback refresh는 섹션 타입 단위로 lock key를 분리한다.
  • 권장 lock key 형식은 lock:audio-recommendation-snapshot-refresh:{SECTION_TYPE}이다.
  • lock 대기 시간은 홈 추천 fallback과 동일하게 최대 300ms를 우선 적용한다.
  • API 요청이 fallback refresh 완료를 기다리는 시간은 홈 추천 fallback과 동일하게 최대 1,500ms를 우선 적용한다.
  • timeout은 요청 대기 timeout이며, 이미 시작된 background refresh를 반드시 중단한다는 의미가 아니다.
  • 동일 JVM에서는 같은 SECTION_TYPE에 대해 single-flight를 적용해 동시 요청이 중복 refresh를 시작하지 않게 한다.
  • lock 획득 후에는 대상 스냅샷 존재 여부를 다시 확인하고, 이미 존재하면 refresh를 실행하지 않는다.

Edge Cases

  • lock 획득 직전에 다른 요청 또는 스케줄러가 스냅샷을 저장할 수 있으므로 double-check가 필요하다.
  • lock 획득 실패 시 refresh를 시작하지 않고 짧게 재조회한 뒤 없으면 빈 배열을 반환한다.
  • timeout 이후 background refresh가 완료되면 다음 요청은 저장된 스냅샷을 사용한다.

Feature C. 오디오 스냅샷 empty marker

Requirements

  • 오디오 스냅샷 섹션도 refresh 결과 0건이면 정상 refresh 완료 상태를 저장해야 한다.
  • 기존 recommendation_snapshot 구조를 재사용하고, 홈 추천과 같은 targetId = 0 empty snapshot marker 방식을 우선 적용한다.
  • marker 적용 대상은 오디오 스냅샷 기반 6개 section type이다.
  • 조회 쿼리는 marker가 사용자 응답에 노출되지 않도록 target_id <> 0 정책을 유지한다.
  • 존재 여부 확인은 marker를 포함해 판단하여 집계 결과가 없는 섹션이 매 요청마다 fallback refresh를 반복하지 않게 한다.

Edge Cases

  • marker만 있으면 snapshot 조회 결과는 빈 배열이어야 한다.
  • 같은 sectionType, snapshotAt에 실제 row가 생기는 재실행이 있으면 marker는 실제 row로 대체되어야 한다.
  • 오디오 marker 추가가 기존 홈 추천 marker 동작을 바꾸면 안 된다.

Feature D. 오디오 refresh 경로 재사용

Requirements

  • fallback refresh는 스케줄러와 같은 AudioRecommendationSnapshotRefreshService의 refresh 로직을 사용한다.
  • 현재 refreshDailySnapshots()가 6개 오디오 스냅샷을 일괄 갱신하는 구조는 유지할 수 있다.
  • 가능하면 단일 섹션 refresh 함수를 추가해 fallback 요청 섹션만 갱신하는 방식을 우선 검토한다.
  • 단일 섹션 refresh를 추가하더라도 기존 일괄 스케줄러는 6개 섹션을 계속 갱신해야 한다.
  • snapshotAt과 집계 window는 기존 오디오 추천 정책을 유지한다.
    • KST 기준 전날 23:59:59
    • NEW_AND_HOT: 최근 3일
    • MOST_COMMENTED: 최근 7일
    • RECOMMENDED_AUDIO: 최근 7일

Edge Cases

  • 단일 섹션 refresh가 과도한 중복을 만들면 기존 일괄 refresh를 호출하고 해당 섹션만 재조회하는 최소 구현을 허용한다.
  • 단, 일괄 refresh를 호출하는 경우에도 fallback trigger와 재조회는 섹션별로 독립이어야 한다.

8. Technical Constraints

  • Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다.
  • 기존 recommendation_snapshot 테이블과 RecommendedSectionType enum 값을 재사용한다.
  • 기존 AudioRecommendationQueryService, AudioRecommendationSnapshotRefreshService, AudioRecommendationSnapshotScheduler 경계를 우선 유지한다.
  • fallback orchestration은 홈 추천 RecommendationSnapshotFallbackService의 lock, timeout, double-check, single-flight 패턴을 기준으로 설계한다.
  • 공개 API 응답 DTO와 controller endpoint는 변경하지 않는다.
  • 성인 콘텐츠 visibility는 기존 MemberContentPreferenceService.canViewAdultContent(member) 결과에 따른 SAFE/ALL section type 선택을 유지한다.
  • findLatestSnapshots(...) 기반의 현재 오디오 snapshot 조회 정책은 유지한다. 대상일 snapshotAt exact 조회 방식으로 바꾸는 것은 이번 요구사항의 필수 범위가 아니다.
  • 신규 DDL은 만들지 않는다.

9. Metrics

  • 오디오 fallback refresh 실행/성공/실패/timeout 로그
  • 오디오 fallback lock 획득 성공/실패 로그
  • section type별 fallback refresh 대기 시간
  • section type별 empty marker 저장 횟수
  • newAndHotAudios, mostCommentedAudios, recommendedAudios 빈 응답 비율
  • audio_recommendation_snapshot_refresh_success 저장 수 또는 section별 저장 수

10. Open Questions

  • 없음.

11. Decisions

  • 이번 작업은 문서 작성만 수행한다.
  • 오디오 추천 snapshot-backed 3개 섹션 모두 홈 추천 snapshot-backed 3개 섹션과 같은 수준의 fallback 정책을 갖는 것을 목표로 한다.
  • fallback 실패는 전체 API 실패가 아니라 해당 섹션 빈 배열로 처리한다.
  • 기존 MOST_COMMENTED가 비면 빈 배열로 내려주던 초기 PRD 정책은 이번 요구사항으로 변경한다.
  • 스냅샷 산식, visibility 정책, 공개 응답 스키마는 변경하지 않는다.

  • docs/prd/sample-prd.md
  • docs/agent-guides/작업절차.md
  • docs/agent-guides/문서유지보수.md
  • docs/20260623_메인_콘텐츠_추천_탭_API/prd.md
  • docs/20260709_메인_홈_추천_AI캐릭터_스냅샷/prd.md
  • docs/20260710_메인_홈_추천_응원크리에이터_스냅샷/prd.md
  • docs/20260710_메인_홈_추천_인기커뮤니티_스냅샷/prd.md