🚨 문제 발생 배경 및 원인
문제 발생
운영 중인 해녀체험 장려 서비스 - 숨비소리에서 동일한 사용자가 중복으로 등록되는 동시성 문제가 발생했다.
두 개의 트랜잭션이 거의 동시에 실행되었고, 각각 중복된 회원가입이 되었다.
이 문제는 회원가입 시 기존 회원 여부를 체크한 후, 회원이 아니면 회원으로 등록하는 로직에서 발생했다.
전에 모니터링 도구를 도입한 덕분에 쉽게 해당 시점 로그를 볼 수 있었다.
로그에서 확인된 바로는, 로그인 성공 로그가 두 개의 쓰레드에서 0.001초 차이로 발생했다. (사용자가 중복으로 클릭했거나, 프론트에서 중복으로 호출됐나..?)
문제 원인
- 두 개의 트랜잭션이 동시에 실행되면서 동시성 문제
unique 제약조건 미설정
- 해당 테스트 부재
문제 코드

✅ 해결 과정
해당 문제를 해결하기 위해 여러가지 방법이 존재한다.
1. Unique 제약조건 추가 (선택)
- 간단하게 providerType과 providerId에 Unique 제약조건을 추가하여 DuplicateKeyException를 통한 예외 처리로 해결
- Unique 제약조건으로 회원 여부를 확인할 때 인덱스를 사용하여 성능을 최적화
- 다른 사람과의 동시성 문제가 아닌 본인의 회원가입에서 발생하는 중복 문제이므로, 복잡하지 않게 처리할 수 있음
- 서비스 최초 로그인 시에만 중복 호출이 발생할 가능성이 있으므로 발생 가능성이 낮음
2. SERIALIZABLE 격리 수준
- MySQL 기본 설정인 REPEATABLE READ에서 SERIALIZABLE로 격리 수준을 올려 해결가능
- 모든 트랜잭션이 순차적으로 실행되므로 해결은 가능하지만, 동시성 처리 능력이 매우 낮기 때문에 선택X
3. Synchronized
- Java에서 동기화(synchronization)를 위한 키워드로, 해당 블록이나 메서드를 한 번에 하나의 쓰레드만 실행 할 수 있어 동시성 문제 해결가능
- 마찬가지로 여러 쓰레드가 해당 메서드를 동시에 호출 할 수 없어 성능 저하 우려, 데드락 가능성
- 해당 서비스도 아직까지는 단일 서버이지만, 분산 서버 환경에서는 사용하지 못하기 때문에 선택 X
4. Named Lock, 분산락
- Named Lock이나 Redis 분산락을 통해 providerType + providerId 조합으로 락을 생성해 해당 메서드 실행 시 해당 키에 해당하는 락을 점유해 중복 요청 방지
- Mysql Named Lock → 데이터베이스 부하 발생 가능, Redis 분산락 → Redis 인프라 필요
- 동시성 발생 가능성이 낮은 기능이라 너무 과도함
해결 코드
1. Unique 제약 조건 설정


2. 코드 수정

- 원래는
findByProviderTypeAndProviderId 쿼리를 사용했는데, Unique 인덱스를 효율적으로 사용하기 위해 findByProviderIdAndProviderType 로 변경
- 복합 인덱스 (providerId, providerType)의 성능을 최적화하고, 인덱스 활용을 효율적으로 하기 위해서이다.
- providerId가 providerType보다 고유값이 많기 때문에 먼저 providerId로 빠르게 조회한뒤 providerType으로 필터링하여 디스크 I/O 최소화

테스트 코드


성과
- 중복 회원 등록 문제 해결
- Unique 제약조건을 적용해 providerId + providerType 조합의 중복 등록을 방지함
- 회원 조회 쿼리 성능 개선
- 기존 Full Table Scan → Index Seek 방식으로 변경
→ 응답 속도 40ms → 3.5ms, 약 11.4배 성능 향상
- (※ 실제 테스트 환경 기준: 약 10만 건의 사용자 데이터로 검증 진행)
- 서비스 안정성 향상
- 동시성 문제로 인한 데이터 무결성 이슈 해결 → 신뢰성 높은 회원 가입 시스템 구축
❗️ 결론
이번 문제를 해결하면서 동시성 문제를 다루는 방법과 적절한 해결책을 선택하는 기준을 배울 수 있었다.
개발 편의성이나 테스트 편의성 때문에 제약조건을 일부러 안거는 경우도 있어, 나 또한 안 걸었던 것 같다.
그러나 제약조건을 미리 설정하는 것이 애플리케이션에서 발생할 수 있는 예상치 못한 오류를 사전에 방지할 수 있다는 중요한 교훈을 얻었다.
물론 안 걸수도 있지만, 더더욱 애플리케이션에서 처리를 잘 해야한다...!