From 1240f00ea2bc0eb90fa10ba4124755025677b6b4 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 20 Jun 2026 00:06:02 +0900 Subject: [PATCH] =?UTF-8?q?docs(osiv):=20lazy=20loading=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EA=B8=B0=EB=A1=9D=EC=9D=84=20=EB=82=A8=EA=B8=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md b/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md index c4c5b9b2..a796bb12 100644 --- a/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md +++ b/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md @@ -339,6 +339,33 @@ spring: - 어떻게: `src/main/resources/application.yml`, `src/test/resources/application.yml`의 `spring.jpa` 아래에 `open-in-view: false`를 추가했다. - 결과: 확인된 lazy loading 재현 실패가 없어 production code의 service/repository/controller 수정은 하지 않았다. 공개 API 스키마 변경도 없다. +- [x] **Task 0.5: 운영 LazyInitializationException 회귀 보완** + - Files: + - Add: `src/test/kotlin/kr/co/vividnext/sodalive/osiv/OsivLazyLoadingRegressionTest.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt` + - Modify: `docs/20260618_유저크리에이터채팅_WebSocket전환/prd.md` + - Modify: `docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md` + - RED: `ChatCharacterService.getCharacterDetail` 반환 후 `tagMappings.tag.tag`, `getOtherCharactersBySharedTags` 반환 후 `tagMappings.tag.tag`, `RankingRepository.getCreatorRankings` 반환 후 `Member.toExplorerSectionCreator`를 트랜잭션 밖에서 접근하는 테스트를 추가한다. + - 실패 확인: + - Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process -Dspring.jpa.open-in-view=false --tests kr.co.vividnext.sodalive.osiv.OsivLazyLoadingRegressionTest` + - Expected: 기존 코드에서는 `LazyInitializationException` 또는 동등한 실패가 발생한다. + - GREEN: 응답 조립에 필요한 `ChatCharacter.tagMappings.tag`, `Member.tags.tag`를 조회 쿼리에서 fetch join으로 선로딩한다. + - 통과 확인: + - Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process -Dspring.jpa.open-in-view=false --tests kr.co.vividnext.sodalive.osiv.OsivLazyLoadingRegressionTest` + - Expected: `BUILD SUCCESSFUL` + - REFACTOR: 공개 API 응답 스키마와 WebSocket 관련 구현은 변경하지 않는다. + - 검증 기록: + - 무엇: OSIV off 상태에서 운영 오류와 같은 lazy loading 경계를 재현하는 회귀 테스트를 추가하고, 필요한 연관을 fetch join으로 선로딩했다. + - 왜: `ChatCharacterController.getCharacterDetail`에서 `ChatCharacterTagMapping.tag`, `HomeService.fetchData`에서 `Member.tags`가 트랜잭션 밖에서 열려 `LazyInitializationException`이 발생했기 때문이다. + - 어떻게: `OsivLazyLoadingRegressionTest`를 추가해 `ChatCharacterService.getCharacterDetail`, `ChatCharacterService.getOtherCharactersBySharedTags`, `RankingRepository.getCreatorRankings` 반환 후 트랜잭션 밖 DTO 변환을 검증했다. + - RED: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process -Dspring.jpa.open-in-view=false --tests kr.co.vividnext.sodalive.osiv.OsivLazyLoadingRegressionTest` 실행 결과 3개 테스트 모두 `LazyInitializationException`으로 실패했다. + - GREEN: 같은 명령을 재실행해 `BUILD SUCCESSFUL in 1m 6s`로 통과했다. + - 인접 회귀: `./gradlew --no-daemon cleanTest test -Dkotlin.compiler.execution.strategy=in-process -Dspring.jpa.open-in-view=false --tests kr.co.vividnext.sodalive.osiv.OsivLazyLoadingRegressionTest --tests kr.co.vividnext.sodalive.api.home.HomeServiceTest --tests kr.co.vividnext.sodalive.chat.character.controller.ChatCharacterControllerTest`가 `BUILD SUCCESSFUL in 24s`로 통과했다. + - 전체 테스트 중단: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process -Dspring.jpa.open-in-view=false`는 `UserCreatorChatRedisIntegrationTest` 실행 중 `OutOfMemoryError`가 발생해 즉시 중단했다. 이후 검증 범위는 OSIV 회귀와 인접 테스트로 간결화했다. + - lint: `./gradlew --no-daemon ktlintCheck`가 `BUILD SUCCESSFUL in 14s`로 통과했다. + - 정적 점검: `rg -n "toExplorerSectionCreator\\(|tagMappings\\.map|tagMappings\\.joinToString|\\.tagMappings" src/main/kotlin/kr/co/vividnext/sodalive -S`로 동일 패턴 후보를 확인했다. `ExplorerService`는 클래스 단위 `@Transactional(readOnly = true)` 안에서 변환하고, `HomeService`/`RankingService`는 공통 `RankingRepository.getCreatorRankings` 선로딩으로 보완했다. `TranslationSourceExtractor`와 관리자/원작 DTO 변환의 `tagMappings` 접근은 운영 stacktrace 표면이 아니므로 별도 회귀 후보로 남겼다. + --- ### Phase 1: WebSocket 의존성과 인증 handshake 기반 추가 @@ -916,3 +943,10 @@ spring: - OSIV off 테스트: Phase 0 묶음 검증 명령에 포함 - 수정 방향: JPA lazy loading 직접 검증은 아니며, WebMvc 표면 회귀만 확인 - 처리 상태: XML 기준 `CreatorChannelHomeControllerTest` 2개, `CreatorChannelLiveControllerTest` 5개 모두 `failures=0`, `errors=0` +- API/기능: 캐릭터 상세/홈 크리에이터 랭킹 운영 회귀 + - 파일: `src/main/kotlin/kr/co/vividnext/sodalive/chat/character/controller/ChatCharacterController.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt` + - 위험 유형: service/repository 반환 후 controller/service DTO 변환 중 nested lazy proxy 접근 + - lazy 접근 대상: `ChatCharacter.tagMappings.tag`, `Member.tags.tag` + - OSIV off 테스트: `OsivLazyLoadingRegressionTest` + - 수정 방향: 상세/공유 태그 캐릭터 조회와 크리에이터 랭킹 조회에서 필요한 연관을 fetch join으로 선로딩 + - 처리 상태: `OsivLazyLoadingRegressionTest` 3개 모두 통과