처음에는 Redis getFairLock
을 사용하여 처리 순서를 보장하려 했으나, 테스트 로그를 통해 순서가 보장되지 않는다고 잘못 판단했다. 이로 인해 FairLock
은 성능만 저하시킨다고 생각해 사용을 배제했다.
그러나 서비스 로그를 통해 Redis Fair Lock이 요청 순서를 정확히 보장하고 있었음을 뒤늦게 확인했다.
초기 테스트 코드는 스레드 시작 및 완료 로그를 출력하여 처리 순서를 확인했다.
@Test
@DisplayName("Redis 락 동시성 제어 테스트")
void redisLockConcurrencyTest() throws InterruptedException {
for (int i = 0; i < threadCount; i++) {
int threadId = i;
executorService.submit(() -> {
try {
System.out.println("Thread 시작: " + Thread.currentThread().getId());
Member threadMember = new Member("Test User" + threadId, "test" + threadId + "@example.com",
"password", MemberRole.USER);
memberRepository.save(threadMember);
couponService.issueCoupon(coupon.getId(), new RequestDto(threadMember.getId()), threadMember);
} catch (GlobalException e) {
System.out.println("Thread 예외: " + Thread.currentThread().getId() + ", " + e.getMessage());
} finally {
latch.countDown();
System.out.println("Thread 완료: " + Thread.currentThread().getId());
}
});
}
}
테스트 로그 결과:
Thread 시작: 70
Thread 시작: 63
Thread 시작: 61
...
Thread 완료: 61
Thread 완료: 70
Thread 완료: 63
...
문제점: 이 로그를 기반으로 FairLock
이 제대로 동작하지 않는다고 판단했다.
그러나 테스트 코드의 Thread 시작
로그는 실제 락 처리 순서를 반영하지 않았다.
서비스 코드에서 실제로 락을 대기하고 획득하는 부분에 로그를 추가해 검증했다.
@Transactional
public void issueCoupon(Long couponId, RequestDto request, Member member) {
String lockKey = "coupon:" + couponId;
RLock lock = redissonClient.getFairLock(lockKey);
try {
System.out.println("대기중: " + Thread.currentThread().getId());
if (!lock.tryLock(1, 10, TimeUnit.SECONDS)) {
System.out.println("Thread " + Thread.currentThread().getId() + "락 획득 실패.");
throw new IllegalStateException("동시에 너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.");
}
System.out.println("Thread " + Thread.currentThread().getId() + "락 획득.");
// 비즈니스 로직 처리
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new GlobalException(COUPON_NOT_FOUND));
coupon.validateQuantity();
coupon.decreaseQuantity();
Issuance issuance = Issuance.builder()
.member(member)
.coupon(coupon)
.build();
issuanceRepository.save(issuance);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Thread " + Thread.currentThread().getId() + " released lock.");
}
}
}
대기 중 로그와 락 획득 및 해제 로그를 비교해 FairLock
이 요청 순서를 정확히 보장하고 있음을 확인했다.
대기 중 순서:
63, 71, 65, 70, 62, 61, 66, 64, 68, 67, 103, 98, 139...
락 획득 순서:
70, 63, 61, 65, 64, 71, 62, 66, 68, 67, 103, 98, 139...
분석:
Redis Fair Lock은 요청 순서를 보장:
FairLock
은 Redis 대기열을 기반으로 락 획득 순서를 보장한다는 점이 확인됐다.초기 순서 불일치는 자연스러운 현상:
FairLock
사용 유지 결정:
FairLock
을 사용하면 성능이 약간 저하될 수 있지만, 요청 순서가 중요한 경우에는 반드시 사용해야 한다는 결론을 내렸다.