본문으로 건너뛰기

13.4 실전 고수 팁 — 동시성 제어를 위한 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 연장)을 고려하세요.