본문으로 건너뛰기
Advertisement

실전 고수 팁 — 동시성 제어를 위한 Redis 분산 락(Distributed Lock) 활용

수평 확장(Scale-Out)된 서버 환경에서는 Java의 synchronized 키워드나 ReentrantLock 같은 단일 JVM 내부의 락(Lock)은 아무런 효력이 없습니다. 서로 다른 물리 서버가 동시에 같은 DB 레코드에 접근하는 문제는 **Redis 분산 락(Distributed Lock)**으로 해결해야 합니다.

1. Redisson(레디슨)을 이용한 분산 락

Redisson 라이브러리는 Redis 위에 구현된 자바 친화적인 분산 락을 제공합니다. tryLock() 호출 시 첫 번째 서버만 락을 획득하고, 다른 서버들은 락이 풀릴 때까지 대기합니다.

implementation 'org.redisson:redisson-spring-boot-starter:3.29.0'
@Service
@RequiredArgsConstructor
@Slf4j
public class CouponService {

private final RedissonClient redissonClient;
private final CouponRepository couponRepository;

public void issueCoupon(Long userId) {
String lockKey = "coupon:issue:lock:" + userId;
RLock lock = redissonClient.getLock(lockKey);

// 최대 5초 대기 후 락 획득 시도, 획득하면 3초간 유지 후 자동 해제
boolean acquired = false;
try {
acquired = lock.tryLock(5, 3, TimeUnit.SECONDS);

if (!acquired) {
throw new IllegalStateException("현재 다른 요청이 처리 중입니다. 잠시 후 다시 시도해주세요.");
}

// 락을 가진 스레드만 여기에 진입합니다.
Coupon coupon = couponRepository.findAvailable()
.orElseThrow(() -> new IllegalStateException("쿠폰 수량이 소진되었습니다."));
coupon.assignTo(userId);
couponRepository.save(coupon);

log.info("쿠폰 발급 성공: userId={}", userId);

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 반드시 finally에서 락 해제! 예외 발생 시에도 락 반납 보장
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}

2. Redis 락 vs DB 락(비관적 락) 비교

기준Redis 분산 락(Redisson)DB 비관적 락(@Lock(PESSIMISTIC_WRITE))
성능매우 빠름 (메모리 연산)느림 (DB 커넥션 점유, 행 수준 락)
외부 의존Redis 서버 필요별도 인프라 불필요
인프라 장애 시Redis 장애 시 전체 락 불가DB 장애 시 락도 같이 중단
사용 권장대량 트래픽 동시성 제어소규모 단순 비즈니스 정합성 보장

락의 TTL(만료 시간)을 너무 짧게 잡으면 비즈니스 로직이 완료되기 전 락이 풀려 동시성 문제가 재발합니다. 예상 처리 시간의 최소 3배 이상의 TTL을 설정하고, Redisson의 watchdog 기능(자동 TTL 연장)을 고려하세요.

Advertisement