닉네임 변경/소셜 첫 로그인 동시성 레이스 컨디션 수정 - #253
Conversation
서로 다른 두 유저가 동시에 같은 새 닉네임으로 변경을 요청하면 중복 조회에서 둘 다 통과해 변경을 시도할 수 있다. 더티 체킹이라 트랜잭션 커밋 시점에야 DataIntegrityViolationException이 터져 그대로 500으로 샜음을 재현 테스트로 확인한 뒤, saveAndFlush로 메서드 안에서 직접 잡아 DuplicateNicknameException (409)으로 변환했다.
같은 소셜 계정으로 첫 로그인 요청이 동시에 들어오면(더블탭, 타임아웃 재시도 등) 가입 여부 확인에서 둘 다 미가입으로 보고 각자 저장을 시도할 수 있다. (email, provider) 유니크 제약으로 DB가 하나는 거부하는데, 이 예외가 그대로 새서 실제로는 가입에 성공한 요청까지 500으로 실패했음을 재현 테스트로 확인했다. 이 레이스는 스크랩 레이스와 달리 두 요청 모두 "같은 계정으로 로그인 성공"을 원하므로 409 대신 실패한 저장 시도만 무시하고 둘 다 로그인에 성공시켜야 한다. Postgres는 트랜잭션 안에서 한 번 실패한 문장이 있으면 이후 모든 명령을 거부하므로, 저장을 REQUIRES_NEW로 분리된 트랜잭션(SocialSignUpRegistrar)에서 실행해 실패해도 로그인 트랜잭션의 후속 조회가 정상 동작하게 했다.
|
Warning Review limit reached
Next review available in: 59 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
작업 배경
UserService.updateUserNickname()과AuthService.signIn()에서 각각 발견해 실제로 재현 후 수정했다.변경 사항
UserService.updateUserNicknameexistsByNickname체크 후 더티 체킹으로만 변경하던 것을,saveAndFlush로 메서드 안에서 직접 flush해DataIntegrityViolationException을DuplicateNicknameException(409)으로 변환AuthService.signIn+SocialSignUpRegistrar(신규)REQUIRES_NEW로 분리된 트랜잭션에서 실행. 실패(유니크 제약 위반)해도 그 트랜잭션만 롤백되고,signIn()의 트랜잭션에서 예외를 잡아 무시 → 두 요청 모두 로그인에 성공두 레이스는 겉보기엔 같은 패턴이지만 해결 방식이 다르다: 닉네임 변경은 두 요청이 "서로 다른 유저의 서로 다른 의도"라 한쪽에 409를 주는 게 맞고, 소셜 로그인은 두 요청이 "같은 계정으로 로그인 성공"이라는 동일한 결과를 원하므로 실패를 조용히 흡수하고 둘 다 성공시키는 게 맞다. 후자는 Postgres가 트랜잭션 내 실패한 문장 이후 모든 명령을 거부하는 특성 때문에 단순 catch로는 안 되고, 저장을 별도 트랜잭션(REQUIRES_NEW)으로 격리해야만 가능했다 — 실제로 캐치 후 그대로 커밋을 시도했다가
UnexpectedRollbackException으로 재차 실패하는 것을 먼저 재현한 뒤 지금 구조로 수정했다.영향 범위
AuthService에 트랜잭션 전파 방식이 다른 신규 저장 경로가 추가됨. 그 외 신규 컴포넌트/필드 없음.검증 매트릭스
동시에_서로_다른_유저가_같은_닉네임으로_변경하면_한쪽만_성공한다동시에_같은_소셜계정으로_첫_로그인하면_둘_다_로그인에_성공한다두 테스트 모두 수정 전 코드에 대해 먼저 실행해 실제 예외(
DataIntegrityViolationException, 이후UnexpectedRollbackException)가 그대로 새는 것을 확인한 뒤, 수정 후 통과함을 재검증했다.Test Plan
🤖 Generated with Claude Code