Files

13 KiB

PRD: 메인 홈 추천 AI 캐릭터 스냅샷 산식 변경

1. Overview

메인 홈 추천 탭의 AI 캐릭터 스냅샷 생성과 조회가 전날 데이터 기반 추천 점수를 사용하도록 수정한다.


2. Problem

  • 기존 메인 홈 추천 AI 캐릭터 스냅샷은 최근 7일 채팅 데이터와 기존 신규 부스트를 기준으로 계산되어, 전날 성과를 빠르게 반영하기 어렵다.
  • 기존 PRD에서는 AI 캐릭터 팔로우 증가량을 산식에서 제외했으나, 이번 요구사항에서는 전날 신규 팔로우 수를 점수에 포함해야 한다.
  • AI 캐릭터에는 별도 데뷔 날짜가 없으므로 신규 부스트 기준일을 캐릭터 생성 날짜/시간으로 명확히 고정해야 한다.
  • 산식 변경이 추천 노출 순서에 직접 영향을 주므로 계산 기간, 집계 대상, 부스트 구간, 조회 정렬 기준을 문서로 고정해야 한다.

3. Goals

  • AI 캐릭터 추천 점수를 전날 데이터 기반으로 산출한다.
  • AI 캐릭터 점수 산식을 (AI 채팅 수 * 45% + 최근 활성 사용자 수 * 35% + 팔로우 증가 수 * 20%) * 신규 부스트로 변경한다.
  • AI 채팅 수는 전날 AI 캐릭터가 답변한 메시지 수로 계산한다.
  • 최근 활성 사용자 수는 전날 해당 캐릭터와 1회 이상 채팅한 중복 없는 사용자 수로 계산한다.
  • 팔로우 증가 수는 전날 발생한 AI 캐릭터 신규 팔로우 수로 계산한다.
  • 신규 부스트는 ChatCharacter.createdAt 기준으로 10일 이내 1.15, 20일 이내 1.10, 30일 이내 1.05, 그 외 1.0을 적용한다.
  • 홈 통합 조회와 AI 캐릭터 전체보기 조회는 최신 AI 캐릭터 스냅샷의 점수순 결과를 사용한다.
  • 홈 통합 조회는 최신 스냅샷 상세 조회 결과가 20개 미만이면 스냅샷 캐릭터를 제외한 활성 AI 캐릭터 랜덤 조회로 부족분을 채운다.

4. Non-Goals

  • 메인 홈 추천 API URL, 응답 필드, 응답 JSON 스키마는 변경하지 않는다.
  • AI 캐릭터 상세, AI 채팅방, 크리에이터 채널 API의 공개 스키마는 변경하지 않는다.
  • 추천 결과 수동 편집, 관리자 화면, A/B 테스트, 머신러닝 개인화는 이번 범위에 포함하지 않는다.
  • AI 캐릭터 팔로우 생성/취소 동작 자체를 변경하지 않는다.
  • 기존 AI 캐릭터 characterIdcreatorId의 의미를 변경하지 않는다.

5. Target Users

  • 회원: 메인 홈 추천 탭에서 전날 반응이 좋은 AI 캐릭터를 발견하고 채팅 또는 크리에이터 채널로 이동하는 사용자
  • 앱 클라이언트: 기존 메인 홈 추천 응답 계약을 유지한 채 AI 캐릭터 추천 순서만 최신 스냅샷 기준으로 사용하는 클라이언트
  • 운영자: 전날 채팅/사용자/팔로우 반응이 추천 순서에 반영되는지 확인해야 하는 운영 담당자

6. User Stories

  • 사용자는 메인 홈 추천 탭에서 전날 채팅 반응이 많았던 AI 캐릭터를 우선 보고 싶다.
  • 사용자는 여러 사용자가 실제로 대화한 AI 캐릭터를 추천받고 싶다.
  • 사용자는 최근 팔로우가 늘어난 AI 캐릭터를 추천 목록에서 발견하고 싶다.
  • 사용자는 신규 AI 캐릭터가 일정 기간 동안 적절한 노출 기회를 받기를 기대한다.
  • 앱 클라이언트는 기존 API 응답 구조를 바꾸지 않고 추천 순서만 변경된 결과를 받고 싶다.

7. Core Features

Feature A. AI 캐릭터 스냅샷 점수 산식 변경

Requirements

  • AI 캐릭터 점수는 아래 산식으로 계산한다.
    • score = ((aiChatCount * 0.45) + (activeUserCount * 0.35) + (followIncreaseCount * 0.20)) * newBoost
  • aiChatCount는 집계 기간 안에 AI 캐릭터가 답변한 활성 메시지 수다.
  • AI가 답변한 메시지는 기존 AI 캐릭터 추천 집계와 동일하게 chat_participant.participant_type = 'CHARACTER'인 활성 participant가 작성한 활성 chat_message로 판정한다.
  • activeUserCount는 집계 기간 안에 해당 캐릭터와 1회 이상 채팅한 중복 없는 활성 사용자 수다.
  • followIncreaseCount는 집계 기간 안에 해당 AI 캐릭터에 대응하는 creator member를 신규 팔로우한 수다.
  • AI 캐릭터에 대응하는 creator member는 기존 홈 추천 응답의 creatorId와 동일한 ChatCharacter.creatorMember.id다.
  • 팔로우 증가는 creator_following.creator_id = ChatCharacter.creatorMember.id인 활성 팔로우 이력 중 집계 기간에 생성된 row 기준으로 계산한다.
  • 재팔로우가 기존 비활성 row를 다시 활성화하는 방식이면 신규 팔로우 수에 포함하지 않는다.
  • 점수 산식 상수는 코드와 테스트에서 재사용 가능한 형태로 관리한다.

Edge Cases

  • aiChatCount, activeUserCount, followIncreaseCount가 모두 0인 캐릭터는 스냅샷 후보에서 제외한다.
  • AI 답변은 있지만 사용자 메시지가 없는 경우 activeUserCount = 0으로 계산한다.
  • 사용자 메시지는 있지만 AI 답변이 없는 경우에도 activeUserCountfollowIncreaseCount가 있으면 산식에 따라 점수를 계산할 수 있다.
  • 비활성 메시지, 비활성 participant, 비활성 사용자, 비활성 캐릭터는 집계에서 제외한다.
  • creatorMember가 없거나 연결된 member가 비활성/비 CREATOR/비 AI_CHARACTER이면 스냅샷 후보와 조회 결과에서 제외한다.

Feature B. 전날 데이터 기반 집계 기간

Requirements

  • 스냅샷 점수 입력값은 전날 데이터만 사용한다.
  • 전날 기준은 KST 기준 전일 00:00:00 이상, 다음날 00:00:00 미만의 half-open 범위로 정의한다.
  • 운영 DB 시간은 UTC이므로, 실제 조회와 계산에서는 KST 기준 전일 00:00:00 이상, 다음날 00:00:00 미만 범위를 UTC 기준 windowStartUtc <= createdAt < windowEndExclusiveUtc로 변환해 사용한다.
  • 스케줄러 실행 시각이 KST 06:00이면, 실행일 전날 KST 일자를 집계 대상으로 삼는다.
  • 스냅샷 생성 시 snapshotAt은 기존 RecommendationSnapshot 저장/조회 계약을 유지하되, 점수 입력 집계 기간은 전날 하루로 제한한다.

Edge Cases

  • 스케줄러 재실행으로 같은 sectionType, snapshotAt 스냅샷을 다시 만들 때 기존 최신 스냅샷 대체 정책을 유지한다.
  • 집계 기간에 해당하는 데이터가 없으면 AI 캐릭터 스냅샷은 빈 결과가 될 수 있으며, 홈 조회 API는 기존처럼 빈 배열을 반환한다.

Feature C. 신규 부스트 변경

Requirements

  • AI 캐릭터 신규 부스트는 데뷔 날짜가 아니라 ChatCharacter.createdAt 기준으로 계산한다.
  • 신규 부스트 구간은 다음과 같다.
    • 캐릭터 생성 후 10일 이내: 1.15
    • 캐릭터 생성 후 20일 이내: 1.10
    • 캐릭터 생성 후 30일 이내: 1.05
    • 그 외: 1.0
  • 경계일 계산은 기존 추천 점수 정책의 ChronoUnit.DAYS.between(baseAt.toLocalDate(), 기준일.toLocalDate()) 방식과 일관되게 한다.
  • 부스트 기준일은 스냅샷 생성 기준 시각인 snapshotAt으로 한다.

Edge Cases

  • ChatCharacter.createdAt이 null인 캐릭터는 스냅샷 산식에 포함하지 않는다.
  • 생성일이 기준 시각보다 미래인 데이터는 데이터 오류로 보고 스냅샷 후보에서 제외한다.

Feature D. AI 캐릭터 스냅샷 조회

Requirements

  • 홈 통합 조회 GET /api/v2/home/recommendations의 AI 캐릭터 섹션은 최신 AI_CHARACTER 스냅샷 순서를 사용한다.
  • AI 캐릭터 전체보기 조회도 최신 AI_CHARACTER 스냅샷 순서를 사용한다.
  • AI 캐릭터 스냅샷은 최종 점수 기준 20위까지 저장한다.
  • 기존 노출 정보는 유지한다.
    • characterId
    • creatorId
    • 캐릭터 이름
    • 캐릭터 소개
    • 프로필 이미지
    • 작품명
    • 사용자들이 친 전체 채팅 수
  • 스냅샷 정렬은 점수 내림차순, 동일 점수면 더 늦게 생성된 캐릭터를 우선한다.
  • 조회 시점에도 비활성 또는 노출 제한 캐릭터는 제외한다.
  • 홈 통합 조회에서 스냅샷 상세 조회 결과가 20개 미만이면 이미 조회한 스냅샷 캐릭터 id를 제외하고 활성 AI 캐릭터를 랜덤으로 조회해 부족분을 뒤에 붙인다.

Edge Cases

  • 스냅샷에는 존재하지만 조회 시점에 캐릭터 또는 creatorMember가 비활성화된 경우 응답에서 제외한다.
  • fallback refresh 후에도 최신 스냅샷이 없으면 기존 홈 추천 API 동작과 동일하게 빈 배열을 반환한다.
  • 상세 조회 결과가 스냅샷 저장 개수보다 적어도 홈 조회 전체는 성공 처리한다.
  • 랜덤 보충 후보가 부족하면 중복 없이 조회 가능한 AI 캐릭터만 반환한다.
  • 빈 스냅샷 refresh 완료 marker가 있거나 전체보기 paging 조회이면 랜덤 보충을 적용하지 않는다.

Feature E. 스냅샷 없음 fallback refresh

Requirements

  • 최신 AI_CHARACTER 스냅샷이 없으면 fallback으로 스케줄러와 동일한 AI 캐릭터 스냅샷 refresh 로직을 재사용한다.
  • fallback refresh는 홈 조회 API가 직접 점수를 계산해 응답하지 않고, 스케줄러와 동일한 저장 로직으로 스냅샷을 만든 뒤 저장된 값을 다시 조회해 반환한다.
  • fallback refresh는 AI 캐릭터 섹션 단위 lock 또는 single-flight 처리를 적용해 동시에 여러 요청이 같은 refresh를 중복 실행하지 않도록 한다.
  • lock 획득 후에는 다른 요청이 이미 스냅샷을 생성했을 수 있으므로 최신 스냅샷을 다시 조회하고, 여전히 없을 때만 refresh를 실행한다.
  • fallback refresh가 성공하면 최신 스냅샷을 다시 조회해 Feature D의 조회 로직과 동일한 방식으로 응답한다.
  • fallback refresh timeout은 refresh 작업의 강제 중단 시간이 아니라 홈 API 요청이 refresh 완료를 기다리는 최대 대기 시간이다.
  • fallback refresh의 lock 대기 시간은 최대 300ms로 제한한다.
  • fallback refresh를 시작한 홈 API 요청은 refresh 완료를 최대 1,500ms까지 기다린다.
  • fallback refresh가 홈 API 대기 시간을 초과하면 현재 홈 요청은 AI 캐릭터 섹션을 빈 배열로 반환한다.
  • 홈 API 대기 시간이 초과되어도 이미 시작된 refresh 작업은 가능한 경우 취소하지 않고 백그라운드에서 완료되도록 한다.
  • 백그라운드 refresh가 완료되면 이후 홈 조회는 저장된 최신 스냅샷을 조회해 반환한다.
  • fallback refresh가 진행 중인 동안 lock 또는 single-flight 상태를 유지해 후속 요청이 중복 refresh를 시작하지 않도록 한다.

Edge Cases

  • lock을 획득하지 못했지만 다른 요청이 refresh 중이면 짧게 대기한 뒤 최신 스냅샷을 다시 조회한다.
  • 대기 후에도 refresh가 완료되지 않아 최신 스냅샷이 없으면 해당 요청도 AI 캐릭터 섹션을 빈 배열로 반환하고 중복 refresh는 시작하지 않는다.
  • fallback refresh가 실패하면 실패 로그를 남기고 홈 조회 전체를 실패시키지 않으며 AI 캐릭터 섹션은 빈 배열로 반환한다.
  • fallback refresh가 성공했지만 집계 대상 데이터가 없어 스냅샷이 생성되지 않으면 AI 캐릭터 섹션은 빈 배열로 반환한다.
  • fallback refresh 중 예외가 발생해도 다른 추천 섹션 조회 결과는 가능한 범위에서 유지한다.

8. Technical Constraints

  • Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다.
  • 기존 kr.co.vividnext.sodalive.v2.recommendation 패키지 경계와 v2.api.home에서 v2.recommendation을 호출하는 의존 방향을 유지한다.
  • 기존 RecommendationSnapshot 기반 저장/조회 구조를 재사용한다.
  • 기존 공개 API 응답 DTO는 필드 추가 없이 유지한다.
  • AI 캐릭터 스냅샷 생성 쿼리는 candidate pre-limit 없이 산식 계산과 정렬을 수행한 뒤 최종 20개 저장 limit을 적용한다.
  • 스케줄러 refresh와 fallback refresh는 산식, 기간, 저장 limit, 정렬 기준이 갈라지지 않도록 같은 application service 경로를 사용한다.
  • 홈 API 대기 timeout과 refresh 작업/DB query timeout은 분리한다. 홈 API 대기 timeout이 발생해도 refresh 작업이 반드시 같은 timeout으로 중단되면 안 된다.
  • fallback refresh의 lock은 단일 서버 환경에서는 JVM local lock을 사용할 수 있지만, 다중 인스턴스 운영 환경에서는 DB lock 또는 분산 lock 등 인스턴스 간 중복 실행을 막을 수 있는 방식을 구현 계획에서 확정한다.
  • 신규 DDL이 필요한 경우 운영 DB 반영용 SQL 문서는 MySQL 기준으로 별도 작성한다.
  • 점수 산식과 신규 부스트 값은 테스트에서 경계값을 검증할 수 있도록 상수화한다.

9. Metrics

  • AI 캐릭터 스냅샷 생성 성공/실패 로그
  • 스냅샷 최종 저장 수
  • fallback refresh 실행/성공/실패/timeout 로그
  • fallback refresh lock 획득 성공/실패 로그
  • aiChatCount, activeUserCount, followIncreaseCount 입력값 분포
  • AI 캐릭터 홈 섹션 응답 성공/실패 로그
  • AI 캐릭터 홈 섹션 노출 수