P1-T2 리뷰 보고서
리뷰 정보
| 항목 |
내용 |
| 리뷰 대상 |
Phase 1 / P1-T2, P1-GATE |
| 기준 working tree |
P1-T2 구현 및 실제 Spring regression test 추가 상태 |
| 리뷰 일자 |
2026-09-10 |
| 리뷰어 |
automated review |
| 기준 문서 |
prd.md, plan-task.md |
| 리뷰 상태 |
추가 수정 및 회귀 검증 완료 |
범위
- 코드:
LanguageDetectEvent.kt, CreatorCommunityTranslationService.kt
- 테스트:
CreatorCommunityLanguageDetectTest.kt
- 제외: 다른 리소스 감지, 공개 API, Docker·HTTP·실제 Papago·MySQL 수동 검증
확정 발견 사항
| ID |
심각도 |
상태 |
제목 |
후속 goal |
REV-P1-T2-001 |
Blocker |
수정 완료 |
커밋 후 번역 예약이 독립 트랜잭션으로 커밋되지 않는다 |
P1-R1 |
REV-P1-T2-002 |
High |
수정 완료 |
잠금 조회가 stale managed 게시물 상태를 다시 읽지 않는다 |
P1-R1 |
REV-P1-T2-003 |
High |
수정 완료 |
이미 언어가 있는 이벤트가 요청 target을 버린다 |
P1-R1 |
REV-P1-T2-004 |
High |
수정 완료 |
이전 mock 검증은 실제 translation_job 저장을 증명하지 않는다 |
P1-R1 |
REV-P1-T2-005 |
High |
수정 완료 |
동시 언어 미확정 감지의 두 번째 target이 lock 뒤 유실된다 |
P1-R2 |
REV-P1-T2-006 |
Blocker |
수정 완료 |
동시 missing-memory 예약이 translation job unique key에 경합한다 |
P1-R3 |
근거와 판정
REV-P1-T2-001
- 코드:
CreatorCommunityTranslationService.requestTranslations는 기본 @Transactional이고, listener는 after-commit callback에서 이를 호출한다.
- 재현:
shouldExposeProxiedRequiresNewTranslationRequestEntryPoint는 Propagation.REQUIRES_NEW가 아닌 annotation으로 실패했다.
- 영향: 감지 언어 저장 뒤에도 job 예약이 실제로 커밋되지 않아 번역 흐름이 진행되지 않는다.
- 조치: 공유 서비스의 public 요청 진입점을
REQUIRES_NEW로 선언하고 실제 job 저장 test로 검증한다.
REV-P1-T2-002
- 코드: listener는
findByIdAndIsActiveTrueForUpdate 뒤 persistence context의 entity를 refresh하지 않는다.
- 재현:
shouldRejectStalePostAfterBlockedDetection는 감지 동안 본문을 변경한 뒤에도 이전 언어가 저장되어 실패했다.
- 영향: 오래된 감지 결과가 새 본문 언어로 저장될 수 있다.
- 조치: 잠금 획득 뒤 최신 entity 상태를 refresh하고 revision/content를 재검사한다.
REV-P1-T2-003
- 코드: listener는 이미 languageCode가 설정된 커뮤니티 이벤트를 감지 전 return한다.
- 재현:
shouldContinueBothRequestedTargetsWhenLanguageIsAlreadyKnown는 ja, en target job이 저장되지 않아 실패했다.
- 영향: 상세 요청별 번역 예약 범위가 사라진다.
- 조치: 현재 본문 검증 뒤 known-language 이벤트도 after-commit에서 원 target으로 shared service를 호출한다.
REV-P1-T2-004
- 코드/테스트: 기존 테스트는
CreatorCommunityTranslationService mock의 호출만 검증했다.
- 재현: 실제 Spring listener와 scheduler를 연결한
CreatorCommunityLanguageDetectTest에서 저장된 translation_job target을 확인하자 job이 없었다.
- 영향: mock interaction만 통과해도 사용자 흐름의 persistence 실패를 놓친다.
- 조치: 감지 provider만 제어하고 scheduler/repository는 실제 bean으로 사용한다.
REV-P1-T2-005
- 코드: lock과 refresh 뒤 languageCode가 이미 있으면 revision/content 재검증 전에 return한다.
- 재현: 같은 언어 미확정 본문으로 두 감지를 시작해 첫 감지를 먼저 커밋한 뒤 두 번째 감지를 진행하면 두 번째 target job이 없다.
- 영향: 동시 상세 요청이 서로 다른 언어를 요청할 때 한 요청의 번역 예약이 유실된다.
- 조치: 현재 revision/content가 유효하면 이미 저장된 언어를 보존하고, 두 번째 이벤트의 원 target을 after-commit에 등록한다.
REV-P1-T2-006
- 코드:
requestTranslations는 post lock 없이 materializer의 missing-memory 반환 뒤 scheduler의 job 존재 조회와 insert를 실행한다.
- 재현: 같은 언어 확정 post와 target으로 두 request transaction을 동시에 시작하면 두 transaction이 missing job을 보고 insert를 시도할 수 있다.
- 영향: job unique key 예외가 호출자 transaction까지 전파돼 상세 예약 흐름이 실패할 수 있다.
- 조치: request transaction 시작에서 active post write lock과 refresh를 수행해 source extraction, memory lookup, job 존재 조회와 insert를 직렬화한다.
실행 증거
| 명령 |
결과 |
핵심 증거 |
./gradlew test --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' |
실패 |
초기 test bean 보정 후 7개 중 5개 assertion 실패 |
결론
REV-P1-T2-001~006은 plan-task.md의 P1-R1~R3 회귀 수정 goal로 반영했고 모두 수정·검증을 완료했다.
수정 후 검증 기록
1차 수정 검증 — 2026-09-10
- 무엇을:
REQUIRES_NEW 번역 요청, stale entity refresh, known-language target continuation과 실제 translation_job 검증을 추가했다.
- 어떻게:
./gradlew test --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' — exit 0, BUILD SUCCESSFUL.
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.creator.channel.community.translation.*' --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.*' ktlintCheck bootJar — exit 0, BUILD SUCCESSFUL.
- 남은 항목: 사용자 정책상 Docker, HTTP, 실제 Papago, MySQL 수동 동시성 검증은 실행하지 않았다.
2차 수정 검증 — 2026-09-10
- 대상:
REV-P1-T2-005.
- RED:
./gradlew test --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' — exit 1, 8개 중 1개 실패.
두 감지가 언어 NULL에서 시작한 뒤 첫 감지의 ja job 저장 후 두 번째 en job이 누락됐다.
- GREEN: lock과 refresh 뒤 revision/content가 유효하면 이미 저장된 languageCode를 유지하고,
원 target을 after-commit에 등록하도록 수정했다.
- 검증:
./gradlew test --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' — exit 0, BUILD SUCCESSFUL.
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.creator.channel.community.translation.*' --tests 'kr.co.vividnext.sodalive.content.CreatorCommunityLanguageDetectTest' --tests 'kr.co.vividnext.sodalive.content.LanguageDetectionCacheServiceTest' --tests 'kr.co.vividnext.sodalive.i18n.translation.*' ktlintCheck — exit 0, BUILD SUCCESSFUL.
- 남은 항목: Docker, HTTP, 실제 Papago, MySQL 수동 동시성 검증은 사용자 정책상 실행하지 않았다.
3차 수정 검증 — 2026-09-10
- 대상:
REV-P1-T2-006.
- RED:
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.creator.channel.community.translation.CreatorCommunityTranslationServiceTest'
— exit 1. 실제 translation_job insert가 H2 SQLState 23505,
uk_translation_job_resource_field_target_hash unique index 충돌로 실패했다.
- GREEN:
requestTranslations transaction 시작에서 active post write lock과 PESSIMISTIC_WRITE refresh를 수행하도록 수정했다.
- 검증:
- 같은 focused test — exit 0,
BUILD SUCCESSFUL; 두 호출 정상 종료와 job 1개 저장을 확인했다.
- P1 직접 영향 회귀와 P1 Gate — exit 0,
BUILD SUCCESSFUL.
./gradlew ktlintCheck — exit 0, BUILD SUCCESSFUL.
- 남은 항목: Docker, HTTP, 실제 Papago, MySQL 수동 동시성 검증은 사용자 정책상 실행하지 않았다.