Skip to content

leehanna602/hhplus-tdd

Repository files navigation

💡동시성 제어 방식

1. 동시성 제어의 필요성

동시에 포인트를 충전하거나 사용하는 상황에서 동시성 제어를 적절히 하지 않으면 다음과 같은 문제가 발생할 수 있습니다.

  • 데이터 불일치: 동시에 여러 작업이 수행될 때, 데이터베이스의 상태가 일관되지 않을 수 있습니다.
  • 정합성 문제: 포인트 충전 또는 사용 내역이 올바르게 기록되지 않거나, 포인트 잔액이 잘못 계산될 수 있습니다.
  • 포인트 잔액 초과 사용: 동시에 포인트를 사용할 경우, 포인트 잔액이 충분하지 않음에도 불구하고 포인트가 중복으로 사용될 수 있습니다.

이를 방지하기 위해 다음과 같은 방식으로 동시성 제어를 하였습니다.

2. 사용된 동시성 제어 방식

본 프로젝트에서는 ReentrantLock을 사용하여 동시성을 제어하였습니다.

2.1. ReentrantLock

  • 사용 이유: 자바의 synchronized 키워드보다 유연하게 동작할 수 있으며, 명시적으로 잠금과 해제를 제어할 수 있습니다.
  • 동작 원리: 특정 사용자가 포인트를 충전하거나 사용할 때, ReentrantLock을 통해 해당 작업을 단일 스레드에서만 처리할 수 있도록 하여 동시에 여러 스레드가 동일한 자원(포인트 잔고)에 접근하는 것을 방지합니다.
  • 사용 예시
    private final Lock lock = new ReentrantLock();
    
    public UserPoint transaction(long id, long amount, TransactionType type) {
      lock.lock();
      try {
        // 트랜잭션 로직
      } finally {
        lock.unlock();
      }
    }

2.2 ReentrantLock vs synchronized

특성 ReentrantLock synchronized
락 획득과 해제 명시적(lock()/unlock())
- 시작과 끝을 명시하기 때문에 여러 메서드에 나눠서 작성 가능
암시적(블록이 끝날 때 자동 해제)
- 메서드 안에 임계 영역의 시작과 끝이 존재
공정성 설정 Fair 모드를 사용하여 공정성 보장 가능
- 공정한 lock을 사용할 경우 경쟁이 발생했을 때 가장 오랫동안 기다린 쓰레드에게 lock을 제공
불가능
락의 상태 확인 isLocked(), isHeldByCurrentThread() 등을 통해 상태 확인 가능 불가능
조건 변수 여러 Condition 객체를 사용할 수 있음. 단일 조건 변수(암시적)
성능 높은 수준의 제어로 더 높은 성능 제공 가능 (명시적 제어) 간단한 동기화에 더 적합 (암시적 제어)
  • ReentrantLocksynchronized와 비교하여 다음과 같은 장점을 가집니다:
    • 공정성(Fairness) 설정: ReentrantLock은 공정성 설정이 가능하여, 대기 중인 스레드가 락을 공정하게 획득할 수 있습니다.
    • 더 많은 제어: ReentrantLocklock()unlock() 메서드를 사용해 락을 명시적으로 제어할 수 있어, 복잡한 락 해제 로직을 구현할 수 있습니다.
    • 조건 변수(Condition Variables): ReentrantLockCondition 객체를 통해 세밀한 조건 동기화를 지원합니다.
  • 단, 간단한 동기화의 경우 synchronized 키워드가 더 간결하고 이해하기 쉬운 장점을 가지고 있으므로, 상황에 따라 적절한 방식을 선택해야 합니다.

2.3. CompletableFuture를 이용한 비동기 테스트

  • 사용 이유: 비동기적으로 여러 스레드가 동시에 작업을 요청하는 시나리오를 테스트하기 위해 사용합니다.
  • 동작 원리: 여러 개의 CompletableFuture 작업을 생성하고, allOf() 메서드를 통해 모든 작업이 동시에 실행되도록 합니다. 이렇게 함으로써 다수의 사용자 요청이 동시에 들어오는 상황을 시뮬레이션할 수 있습니다.
  • 사용 예시
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        pointService.transaction(user1Id, 1000, TransactionType.CHARGE);
    });
    
    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        pointService.transaction(user2Id, 1000, TransactionType.USE);
    });
    
    CompletableFuture.allOf(future1, future2).join();

3. 테스트 및 검증

동시성 제어가 제대로 동작하는지 검증하기 위해 다음과 같은 테스트를 수행하였습니다:

  1. 동시 포인트 충전 테스트: 사용자가 동시에 포인트를 충전할 때, 총 포인트가 정확하게 증가하는지 확인하였습니다.
  2. 동시 포인트 사용 테스트: 사용자가 동시에 포인트를 사용할 때, 포인트 부족 시 예외가 발생하는지 확인하였습니다.
  3. 동시 포인트 충전/사용 혼합 테스트: 사용자가 동시에 포인트 충전과 사용을 혼합하여 요청할 때, 순서대로 처리되는지 확인하였습니다.
  4. 동시에 포인트 충전 요청 한도 초과로 실패: 포인트 충전시 포인트 한도가 초과될 경우, 한도 이내에서 충전이 완료 되는지 확인하였습니다.
  5. 동시에 포인트 사용 요청시 잔액 부족으로 실패: 포인트 사용시 잔액이 부족한 경우, 사용되지 않는지 확인하였습니다.

4. 결론

ReentrantLock을 통해 동시성 제어를 구현하였으며, 테스트를 통해 이를 검증하였습니다. synchronized 보다 공정성 있고, 명시적으로 세밀한 제어가 가능하다는 것을 알 수 있었습니다. 테스트 코드를 통하여 정합성 문제, 정책(잔고부족, 최대잔고) 문제 등에 대해 문제가 발생하지 않는지 확인할 수 있었습니다.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages