1. [문제 인식 및 정의]
2. [해결 방안]
2-1. [의사결정 과정]
2-2. [해결 과정]
3. [해결 완료]
3-1. [회고]
3-2. [전후 데이터 비교]
signup() 메서드의 기존 코드에서는 userRepository.existsByEmail() 호출 시 SELECT 쿼리가 실행되고 save() 호출 시 INSERT 쿼리가 실행되어 2개의 쿼리가 발생하고 있습니다.
User Entity의 email 속성이 @Column(unique = true)private String email;로 Unique 제약 조건이 적용되어 있으므로 SELECT를 생략하고 save()에서 예외 처리를 하는 것이 성능적으로 더 좋을 것 같다고 생각하였습니다.
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());
UserRole userRole = UserRole.of(signupRequest.getUserRole());
User newUser = new User(
signupRequest.getEmail(),
encodedPassword,
userRole
);
User savedUser = userRepository.save(newUser);
String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);
return new SignupResponse(bearerToken);
}
장점:
- 코드가 직관적
- 예외 발생 시 아래 코드 생략 가능
단점:
- SELECT 쿼리 추가 발생
장점:
- 불필요한 SELECT 쿼리 제거 가능
- 동시 요청이 많을 때 SELECT 생략하면 DB 부하 감소
단점:
- save()가 예외를 던지기 전까지 인코딩과 객체 생성 코드 실행됨
🔹2번째 방법을 사용하면 매번 비밀번호 인코딩과 객체 생성이 발생합니다. 그러나 데이터베이스 I/O 비용이 CPU 연산보다 크며 동시 요청이 많을수록 중복 검사를 위한 SELECT 쿼리가 병목될 가능성이 있으므로 성능 최적화를 위해 save() 에서 중복 검사를 수행하는 방법을 선택하였습니다.
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword());
UserRole userRole = UserRole.of(signupRequest.getUserRole());
User newUser = new User(
signupRequest.getEmail(),
encodedPassword,
userRole
);
User savedUser;
try {
savedUser = userRepository.save(newUser);
} catch (DataIntegrityViolationException e) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);
return new SignupResponse(bearerToken);
}
SELECT 후 INSERT 하는 경우 완벽한 동시성 제어가 불가능하다는 점과 데이터베이스 I/O(입출력) 연산은 비용이 크기 때문에 이를 줄이는 것이 성능 최적화의 핵심이라는 것을 알게되었습니다.
수정 전
SELECT + INSERT ⇒ 쿼리 2개 실행
수정 후
