[Spring Boot] 동시성 제어

곽태민·3일 전
0

TIL

목록 보기
68/68

spring Boot에서 동시성 제어하기


Study 과제 중 TDD를 기반으로 테스트 코드를 작성하는데, 포인트 충전/사용 시스템을 Kotlin 으로
개발하면서 요구 사항에 "동시성 문제 해결" 이 있었다. 실제 데이터베이스를 사용하는게 아닌
인메모리를 사용하게 되면서 DB의 Transaction 없이 동시성을 처리를 해야했다.

그래서 Kotlin, Java에 내장되어있는 lock 매커니즘을 이용해서 처리를 해서 정리를 해보려고 한다.

문제 상황


class PointService(
    private val userPointTable: UserPointTable,
    private val pointHistoryTable: PointHistoryTable
) {
    fun chargeUserPoint(userId: Long, amount: Long): UserPoint {
        val current = userPointTable.selectById(userId)  // 현재: 1000
        val newPoint = current.point + amount             // 1000 + 100 = 1100
        return userPointTable.insertOrUpdate(userId, newPoint)
    }
}

만약 두 명의 사용자가 동시에 100 포인트를 충전한다면?

  • 예상: 1000 -> 1200
  • 실제: 1000 -> 1100

Race Condition 발생

Thread A: selectById(1)1000 읽음
Thread B: selectById(1)1000 읽음
Thread A: 1000 + 100 = 1100
Thread B: 1000 + 100 = 1100
Thread A: insertOrUpdate(1, 1100)
Thread B: insertOrUpdate(1, 1100) 💥 덮어쓰기!

Spring Boot는 멀티 스레드 환경이기 때문에 각 HTTP 요청이 별도 스레드에서 처리되며, 같은 데이터에 여러 스레드가 동시 접근할 수 있다.

해결 방법


내장되어 있는 ConcurrentHashMap을 사용해서 해결을 했다.

// ❌ HashMap은 thread-safe하지 않음
private val userLocks = HashMap<Long, Any>()

// ✅ ConcurrentHashMap은 thread-safe
private val userLocks = ConcurrentHashMap<Long, Lock>()

if 조건문 으로 처리를 했던 코드는 require() 함수로 대체를 한다.

// Kotlin답게 - 간결하고 명확
require(amount > 0) { "충전 금액은 양수여야 합니다." }
require(amount % 100 == 0L) { "포인트 사용은 100 단위로만 가능합니다." }

// 조건이 true여야 할 것을 작성!

장점은 다음과 같다.

  • ✅ kotlin의 표현식 기반 문법과 잘 어울림
  • ✅ 간결하고 직관적
  • ✅ JVM 최적화 지원
  • ✅ 별도의 라이브러리 불필요

마무리


Kotlin으로 Spring Boot를 개발한다면, 언어의 특성을 살려 더 간결하고 안전한 동시성 제어를 구현할 수 있다. Redis나 외부 라이브러리를 이용하지 않고 동시성 제어를 할 수 있다.

profile
Node.js 백엔드 개발자입니다!

0개의 댓글