Files

23 KiB

메인 콘텐츠 추천 오디오 스냅샷 폴백 Plan/Task

시나리오 계약

  • Happy path: GET /api/v2/audio/recommendations가 사용자 visibility에 맞는 NEW_AND_HOT, MOST_COMMENTED, RECOMMENDED_AUDIO 스냅샷을 각각 조회하고, 비어 있는 섹션만 fallback refresh 후 재조회한다. Real surface: AudioRecommendationQueryServiceTest, AudioRecommendationEndToEndTest.
  • Independent fallback: NEW_AND_HOT 스냅샷이 존재해도 MOST_COMMENTED 또는 RECOMMENDED_AUDIO가 비어 있으면 해당 섹션 fallback을 독립적으로 실행한다. Real surface: AudioRecommendationQueryServiceTest, 신규 fallback service test.
  • Visibility: 비회원/19금 노출 불가 회원은 *_SAFE, 19금 노출 가능 회원은 *_ALL section type으로 fallback을 수행한다. Real surface: AudioRecommendationQueryServiceTest.
  • Lock and wait: fallback은 lock:audio-recommendation-snapshot-refresh:{SECTION_TYPE} lock, 300ms lock wait, 1,500ms API wait, JVM single-flight, lock 안 double-check를 사용한다. Real surface: 신규 fallback service test.
  • Empty marker: 오디오 snapshot-backed 6개 section type은 refresh 결과 0건이면 targetId = 0 marker를 저장하고, snapshot 조회 응답에서는 marker를 제외한다. Real surface: RecommendationSnapshotPersistenceAdapterTest.
  • Refresh reuse: fallback refresh는 AudioRecommendationSnapshotRefreshService의 스케줄러와 동일한 snapshot 생성 로직을 사용한다. Real surface: AudioRecommendationSnapshotRefreshServiceTest, 신규 fallback service test.
  • Adjacent regression: GET /api/v2/audio/recommendations URL, response field, 오디오 점수 산식, 배너/오리지널/최신/무료/포인트 섹션 조회 정책은 변경하지 않는다. Real surface: AudioRecommendationFacadeTest, AudioRecommendationEndToEndTest.

범위와 전제

  • 이번 문서는 docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/prd.md의 구현 계획이다.
  • 신규 공개 API, 신규 응답 필드, 운영 DDL 추가는 범위에 포함하지 않는다.
  • 기존 recommendation_snapshot 테이블과 오디오 관련 RecommendedSectionType 6개를 재사용한다.
  • AudioRecommendationQueryService.getRecommendations의 snapshot-backed 3개 섹션만 이번 fallback 보강의 대상이다.
  • findNewAndHotAudios 전체보기 fallback은 기존 lazy refresh 동작을 유지하되, 공통 helper 변경 시 회귀 테스트로 보호한다.
  • 오디오 스냅샷은 기존 findLatestSnapshots(...) 정책을 유지한다. 홈 추천처럼 대상일 snapshotAt exact 조회로 바꾸는 것은 이번 범위에 포함하지 않는다.
  • 기존 AudioRecommendationSnapshotRefreshService는 일괄 refresh만 제공하므로, 단일 섹션 refresh 추가와 일괄 refresh 재사용 중 더 작은 변경을 구현 단계에서 선택한다.
  • 우선 권장안은 오디오 전용 fallback service를 두고, 홈 추천 fallback service의 lock/timeout/double-check/single-flight 패턴을 복제보다 작은 형태로 재사용 가능한 helper로 분리할지 검토하는 것이다.

기존 오디오 추천 로직 유지/변경 경계

  • 유지: GET /api/v2/audio/recommendations endpoint와 응답 JSON 필드.
  • 유지: NEW_AND_HOT, MOST_COMMENTED, RECOMMENDED_AUDIO 점수 산식과 limit.
  • 유지: SAFE/ALL visibility variant 선택 기준.
  • 유지: AudioRecommendationSnapshotScheduler의 매일 00:00 KST refresh.
  • 유지: banners, originalSeries, latestAudios, freeAudios, pointAudios 조회 경로.
  • 변경: MOST_COMMENTEDRECOMMENDED_AUDIO만 비어 있어도 fallback refresh를 시도한다.
  • 변경: NEW_AND_HOT fallback도 다른 두 섹션과 같은 공통 fallback 경로를 사용하도록 정리한다.
  • 추가: 오디오 snapshot-backed 6개 section type에 empty marker를 적용한다.
  • 추가: 오디오 fallback refresh lock/timeout/single-flight 로그를 남긴다.

실행 명령

  • 문서 명령 확인: ./gradlew tasks --all
  • 오디오 조회 service 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
  • 오디오 refresh service 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest
  • snapshot marker 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
  • 오디오 API 회귀 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest
  • 포맷 검증: ./gradlew ktlintCheck
  • 전체 회귀: ./gradlew test

Phase 1: 문서와 현재 동작 고정

  • Task 1.1: PRD와 구현 계획 문서 작성

    • 파일 경로:
      • Create: docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/prd.md
      • Create: docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
    • RED: 문서 작업은 TDD 예외. TDD 예외 사유: 코드 동작 변경 전 요구사항과 구현 순서를 고정하는 작업이다.
    • GREEN: 오디오 snapshot-backed 3개 섹션의 독립 fallback, lock, timeout, empty marker, refresh 재사용 요구사항을 문서화한다.
    • REFACTOR: 기존 메인 콘텐츠 추천 탭 PRD와 홈 추천 snapshot fallback 문서의 정책 차이를 반영해 범위/비범위를 정리한다.
    • 기대 결과: 구현 시작 전에 PRD와 plan-task가 같은 디렉터리에 준비된다.
  • Task 1.2: 현재 오디오 snapshot fallback 부재를 회귀 테스트로 고정

    • 파일 경로:
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
    • RED: NEW_AND_HOT_AUDIO_SAFE는 기존 스냅샷이 있지만 MOST_COMMENTED_AUDIO_SAFE만 비어 있을 때, MOST_COMMENTED fallback port가 호출되어야 한다는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
    • GREEN: 아직 구현하지 않는다. 이 task는 구현 단계에서 실패를 확인한 뒤 Phase 3에서 통과시킨다.
    • REFACTOR: RECOMMENDED_AUDIO_SAFE만 비어 있는 케이스도 별도 테스트로 추가해 두 섹션이 NEW_AND_HOT 존재 여부에 묶이지 않음을 고정한다.
    • 기대 결과: 현재 결함이 테스트로 재현된다.

Phase 2: empty marker 저장 정책

  • Task 2.1: 오디오 스냅샷 section type empty marker 지원 추가

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
    • RED: replaceSnapshots(NEW_AND_HOT_AUDIO_SAFE, snapshotAt, emptyList()), replaceSnapshots(MOST_COMMENTED_AUDIO_SAFE, snapshotAt, emptyList()), replaceSnapshots(RECOMMENDED_AUDIO_SAFE, snapshotAt, emptyList()) 호출 시 marker가 저장되고 findSnapshots(...)는 빈 배열, existsSnapshot(...)는 true인 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
    • GREEN: supportsEmptySnapshotMarker(...)에 오디오 snapshot-backed 6개 section type을 추가한다.
    • REFACTOR: 홈 추천 AI_CHARACTER, CHEER_CREATOR, POPULAR_COMMUNITY marker 동작 회귀 assertion을 유지한다.
    • 기대 결과: 데이터가 없는 오디오 섹션도 정상 refresh 완료 상태를 저장한다.
  • Task 2.2: marker 대체와 latest 조회 제외 검증

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapter.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/RecommendationSnapshotPersistenceAdapterTest.kt
    • RED: 오디오 marker가 있는 같은 sectionType, snapshotAt에 실제 row를 저장하면 marker가 제거되는 실패 테스트를 작성한다. findLatestSnapshots(...)에서도 marker가 반환되지 않음을 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
    • GREEN: 기존 delete 후 save 흐름이 marker 대체를 보장하는지 확인하고, 조회 query의 target_id <> 0 조건이 latest 조회에도 적용되게 유지한다.
    • REFACTOR: marker 관련 상수와 지원 section 판정 함수 이름이 오디오/홈 모두에 어색하지 않은지 정리한다.
    • 기대 결과: marker가 사용자 응답이나 이후 실제 스냅샷을 오염시키지 않는다.

Phase 3: 오디오 fallback orchestration

  • Task 3.1: 오디오 fallback port/service 추가

    • 파일 경로:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackServiceTest.kt
    • RED: 특정 오디오 sectionType의 latest snapshot이 없고 marker도 없으면 lock을 획득하고 refresh service를 호출한 뒤 같은 section latest snapshot을 재조회하는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest
    • GREEN: RecommendationSnapshotPort, AudioRecommendationSnapshotRefreshService, RedissonClient를 사용하는 오디오 전용 fallback service를 추가한다.
    • REFACTOR: 홈 추천 fallback의 lock wait 300ms, API wait 1,500ms, single-flight, double-check 패턴을 맞추되, 공통화가 과하면 오디오 전용 최소 구현으로 유지한다.
    • 기대 결과: 오디오 섹션 하나를 입력받아 fallback refresh와 재조회를 수행할 수 있다.
  • Task 3.2: lock miss, timeout, 실패, marker 케이스 검증

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotFallbackServiceTest.kt
    • RED: lock 획득 실패, 1,500ms timeout, refresh 예외, marker 존재, 동시 요청 single-flight 케이스를 실패 테스트로 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest
    • GREEN: 각 실패/대기 상황에서 빈 배열을 반환하고 warn/info log를 남기며 전체 API 예외로 전파하지 않게 구현한다.
    • REFACTOR: fallback service가 상세 DTO 조립이나 점수 계산을 직접 하지 않도록 유지한다.
    • 기대 결과: fallback이 요청 지연과 중복 refresh를 제한한다.
  • Task 3.3: refresh service에 섹션 단위 refresh 경로 추가 또는 일괄 refresh 재사용 확정

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotRefreshService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationSnapshotRefreshServiceTest.kt
    • RED: refreshSection(sectionType, now) 또는 동등한 경로가 입력 section type에 맞는 queryPort 함수와 limit을 사용해 replaceSnapshots(...)를 호출하는 실패 테스트를 작성한다. 일괄 refresh 재사용을 선택하면 fallback service test에서 refreshDailySnapshots() 호출을 명시적으로 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest
    • GREEN: 가장 작은 변경으로 스케줄러와 fallback이 같은 snapshot 생성 로직을 공유하게 한다.
    • REFACTOR: SAFE/ALL section type 매핑 중복이 커지면 기존 AudioRecommendationVisibility extension 또는 작은 helper로 정리한다.
    • 기대 결과: fallback refresh와 scheduler refresh 사이에 산식 drift가 생기지 않는다.

Phase 4: AudioRecommendationQueryService 연결

  • Task 4.1: getRecommendations의 3개 snapshot 조회를 fallback 경로로 변경

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
    • RED: MOST_COMMENTED_AUDIO_SAFE만 비어 있을 때 mostCommentedAudios가 fallback 재조회 결과로 조립되는 실패 테스트를 작성한다. RECOMMENDED_AUDIO_SAFE만 비어 있는 케이스도 추가한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
    • GREEN: NEW_AND_HOT, MOST_COMMENTED, RECOMMENDED_AUDIO 각각에 대해 findSnapshotsWithFallback(sectionType, offset, limit) 형태의 경로를 사용한다.
    • REFACTOR: 기존 refreshMissingNewAndHotSnapshots(...)와 Redis 날짜 marker는 새 fallback 경로로 대체하거나 전체보기 전용으로만 남긴다. 사용하지 않게 되면 관련 의존성/상수를 제거한다.
    • 기대 결과: 3개 오디오 스냅샷 섹션이 서로 독립적으로 fallback을 실행한다.
  • Task 4.2: visibility별 fallback section type 검증

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
    • RED: 비회원은 *_SAFE, 성인 콘텐츠 노출 가능 회원은 *_ALL section type으로 fallback service가 호출되는 실패 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
    • GREEN: 기존 newAndHotSectionType, mostCommentedSectionType, recommendedAudioSectionType 매핑을 fallback 호출에도 그대로 사용한다.
    • REFACTOR: 기존 성인 preference 조회 정책과 initializeDefaultPreference 미호출 회귀 테스트를 유지한다.
    • 기대 결과: fallback이 현재 사용자 visibility와 다른 variant를 잘못 갱신하거나 조회하지 않는다.
  • Task 4.3: findNewAndHotAudios 전체보기 회귀 정리

    • 파일 경로:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/application/AudioRecommendationQueryServiceTest.kt
    • RED: 전체보기 findNewAndHotAudios(member, offset, limit)가 기존처럼 offset/limit snapshot 순서를 유지하고, snapshot이 없을 때 새 fallback 경로 또는 기존 lazy refresh 정책 중 결정된 경로를 사용하는 테스트를 작성한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
    • GREEN: 전체보기 동작을 구현 결정에 맞춰 최소 수정한다.
    • REFACTOR: 홈 첫 화면 limit 12와 전체보기 paging limit이 섞이지 않도록 helper 인자를 명확히 유지한다.
    • 기대 결과: 첫 화면 fallback 보강이 전체보기 paging을 깨지 않는다.

Phase 5: API 회귀와 최종 검증

  • Task 5.1: 오디오 추천 API 응답 스키마 회귀 검증

    • 파일 경로:
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/adapter/in/web/AudioRecommendationEndToEndTest.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/application/AudioRecommendationFacadeTest.kt
    • RED: newAndHotAudios, mostCommentedAudios, recommendedAudios 필드명이 유지되고 신규 필드가 추가되지 않는 회귀 테스트를 확인/보강한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest
    • GREEN: controller/facade/DTO 변경 없이 application service 결과가 기존 response로 매핑되게 한다.
    • REFACTOR: 공개 API URL과 JSON field name 변경이 없음을 assertion으로 유지한다.
    • 기대 결과: 클라이언트 공개 스키마는 변경되지 않는다.
  • Task 5.2: focused regression 실행

    • 파일 경로:
      • Modify: docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
    • RED: 구현 task 완료 후 계획 문서에 기록할 focused command 목록을 확정한다.
    • 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
    • GREEN: 아래 명령을 실행하고 결과를 이 문서 하단 검증 기록에 누적한다.
    • REFACTOR: 실패한 명령이 있으면 원인과 재실행 결과를 같은 task 아래에 기록한다.
    • 실행 명령:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest
    • 기대 결과: 오디오 fallback, marker, refresh, API 회귀가 최소 명령으로 검증된다.
  • Task 5.3: 전체 회귀와 문서 검증

    • 파일 경로:
      • Modify: docs/20260712_메인_콘텐츠_추천_오디오_스냅샷_폴백/plan-task.md
    • RED: 구현 완료 후 전체 회귀 명령 실행 전에는 검증 기록이 구현 전 상태여야 한다.
    • 실패 확인: 해당 없음. TDD 예외 사유: 검증 기록 문서화 task다.
    • GREEN: ./gradlew ktlintCheck, ./gradlew test, ./gradlew tasks --all을 실행하고 결과를 문서 하단 검증 기록에 누적한다.
    • REFACTOR: PRD와 plan-task가 구현 결과와 어긋나면 먼저 문서를 갱신하고 필요한 focused test를 재실행한다.
    • 기대 결과: 포맷, 전체 테스트, 문서 명령 유효성을 모두 확인한다.

검증 기록

  • 2026-07-12: PRD와 구현 계획 문서만 작성했다. 코드 변경은 수행하지 않았다.
  • 2026-07-12: 문서 변경 검증을 수행했다.
    • git diff --check 성공.
    • ./gradlew tasks --all은 최초 샌드박스 실행에서 ~/.gradle lock 파일 권한 오류로 실패했고, 권한 승인 후 재실행해 BUILD SUCCESSFUL로 통과했다.
  • 2026-07-12: 오디오 snapshot-backed 3개 섹션의 독립 fallback 구현을 수행했다.
    • RED 확인:
      • RecommendationSnapshotPersistenceAdapterTest.shouldSaveAudioEmptySnapshotMarkerWhenReplacingWithEmptySnapshots는 오디오 section marker 미지원으로 실패했다.
      • AudioRecommendationSnapshotRefreshServiceTest.shouldRefreshRequestedAudioSnapshotSectionOnlyrefreshSection 미정의 컴파일 오류로 실패했다.
      • AudioRecommendationSnapshotFallbackServiceTestAudioRecommendationSnapshotFallbackService 미정의 컴파일 오류로 실패했다.
      • AudioRecommendationQueryServiceTest는 기존 생성자/조회 경로가 fallback service를 사용하지 않아 컴파일 오류로 실패했다.
    • GREEN 확인:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.RecommendationSnapshotPersistenceAdapterTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.in.web.AudioRecommendationEndToEndTest --tests kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacadeTest 성공.
    • 포맷/문서 명령:
      • ./gradlew ktlintCheck 성공.
      • ./gradlew tasks --all 성공.
      • git diff --check 성공.
    • 전체 회귀:
      • ./gradlew test는 최초 300초 제한에 걸렸고, 재실행은 사용자 요청으로 중단했다. 전체 테스트는 사용자가 별도로 재실행하기로 했다.
  • 2026-07-12: post-implementation review에서 blocking issue 2건을 확인하고 수정했다.
    • AudioRecommendationQueryService의 fallback 기준 시간을 Asia/Seoul 기준으로 분리해 JVM 기본 timezone 의존을 제거했다.
    • AudioRecommendationSnapshotScheduler가 fallback과 동일한 6개 section lock을 획득한 뒤 일괄 refresh를 실행하도록 수정했다.
    • 추가 RED 확인:
      • AudioRecommendationSnapshotSchedulerTest.shouldSkipWhenSectionLockNotAcquired는 section lock miss에도 일 배치를 실행해 실패했다.
      • AudioRecommendationSnapshotSchedulerTest.shouldRefreshOnlyWhenLockAcquired는 section lock unlock 검증 실패로 실패했다.
    • 수정 후 검증:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.adapter.out.scheduler.AudioRecommendationSnapshotSchedulerTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryServiceTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotFallbackServiceTest 성공.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationSnapshotRefreshServiceTest 성공.
      • ./gradlew ktlintCheck 성공.