[Spring Boot3] 코드 한 줄로 동시성 제어하기

hhh·약 17시간 전
0

스프링부트🌱

목록 보기
13/13
post-thumbnail

💦 출석체크 API에서 동시성 이슈가 발생하다

최근 진행한 동아리 웹사이트 프로젝트에서, 유저가 하루에 한 번 출석하면 씨앗 보상을 지급하는 기능을 구현하던 중 예상치 못한 문제가 발생했다.


보안팀과 함께 진행한 내부 취약점 분석 세미나에서 직접 개발한 서비스를 대상으로 공격 시나리오를 자체적으로 시뮬레이션하던 중, 출석 버튼을 반복 클릭하거나 출석 API를 병렬 요청할 경우 여러 개의 요청이 동시에 서버로 들어오는 현상이 확인됐다.


서버 단에서는 중복 요청과 중복 적립을 방지하기 위한 로직을 이미 마련해두었음에도, 요청이 처리 속도보다 빠르게 몰릴 경우 Race Condition(경쟁 상태)이 발생하며 실제 서비스까지 영향을 줄 수 있음을 깨닫고 고민에 빠졌다.

🧩 Race Condition이란?

위와 같은 상황에서, 동일 사용자가 출석 버튼을 여러 번 클릭하거나 curl -P20과 같이 출석 API에 병렬 요청을 보낼 경우 여러 요청이 동시에 서버로 들어와 중복 출석 처리가 발생할 수 있다.

이처럼 여러 요청이 동시에 같은 자원에 접근하고 수정하려는 상황에서
처리 순서에 따라 예기치 않은 결과가 발생하는 것
Race Condition이라고 한다.

예시 상황
1. A, B 요청이 동시에 들어옴
2. 두 요청 모두 "오늘 출석 기록 없음"을 확인함
3. 두 요청이 거의 동시에 insert 시도
4. 중복 데이터 발생

🔒 해결방안 : 비관적 락으로 동시성 제어하기

동시성 문제를 해결하는 방법에는 낙관적 락(Optimistic Lock), 분산 락(Redis RedLock), 큐 기반 비동기 처리 등 다양한 전략이 존재한다.

하지만 이번처럼 출석 처리처럼 단순하고 충돌 가능성이 높은 단일 자원 갱신 로직에는 가장 직접적이고 간단한 방식인 JPA의 비관적 락(Pessimistic Lock)을 적용했다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<DailySeed> findByUser(User user);
  • findByUser 메서드는 사용자의 출석 데이터를 관리하는 DailySeedRepository에서 정의한 메서드로, 해당 유저의 오늘 출석 기록이 있는지 조회하는 데 사용된다.

  • 이 메서드에 @Lock(LockModeType.PESSIMISTIC_WRITE)를 적용함으로써, 여러 요청이 동시에 들어오더라도 중복으로 씨앗이 적립되지 않도록 조회 단계에서부터 row-level 락을 건다.

  • 해당 쿼리에 비관적 락을 걸면, 한 요청이 트랜잭션을 마칠 때까지 다른 요청이 같은 유저의 출석 데이터에 접근할 수 없도록 차단된다.

✅ 락 도입 후 다시 병렬 요청 테스트

락 적용 이전에는 병렬 요청 시 출석 성공(200)중복 출석(400) 응답이 무작위로 섞여 응답되었다.

락 적용 이후에는 여러 번 테스트를 반복해도 총 20건의 병렬 요청 중 정확히 1건출석 성공(200) 으로 처리되고, 나머지 요청은 모두 중복 출석(400) 으로 처리되며 동시성 문제가 해결된 것을 확인할 수 있다.

🧠 번외) 비관적 락 vs 낙관적 락

비관적 락 (Pessimistic Lock)

  • 먼저 락을 걸고 진입을 제한
  • 성능: 낮음 (경쟁이 많을수록 안정적)
  • 사용 예시: 출석, 결제, 포인트 적립 등

낙관적 락 (Optimistic Lock)

  • 우선 처리 후 충돌 발생 시 롤백
  • 성능: 높음 (충돌 가능성 낮을 때 유리)
  • 사용 예시: 게시글 수정, 마이페이지 변경 등

📌 마무리

"테스트에서는 멀쩡한데, 실제 사용자가 누르면 에러가 난다?"
→ 그렇다면 Race Condition을 의심해보자.

출석처럼 한 번만 처리되어야 하는 민감한 로직에서는 동시성 제어는 필수!
-> Spring + JPA 환경에서는 @Lock 한 줄로도 손쉽게 구현할 수 있다.

단 한 줄의 락이 전체 서비스의 신뢰성을 지켜줄 수 있다는 점에서,
이번 경험은 단순한 기능일수록 더욱 견고한 설계가 필요함을 다시금 느낄 수 있는 기회였다. 💪

profile
백엔드개발자의 개발 기록 끄적끄적✏️

0개의 댓글