개발 공부/프로젝트

[Spring Boot] 실전 프로젝트 고도화 (3): DB에 줄 서지 마세요! (Redis 분산 락 & Redisson)

baby-t 2026. 1. 19. 15:23

지난 포스팅에서는 **JPA 비관적 락(Pessimistic Lock)**을 이용해 "돈 복사 버그(동시성 이슈)"를 해결했습니다.

DB 레벨에서 강력하게 락을 거는 방식이라 데이터 정합성은 완벽했지만, 한 가지 고민이 생겼습니다.

"만약 수만 명이 동시에 주문하면, DB 커넥션이 남아날까?"

비관적 락은 대기하는 동안 DB 커넥션을 점유합니다. 즉, 주문이 폭주하면 단순 조회나 로그인 같은 다른 기능까지 DB가 느려져서 먹통이 될 수 있다는 뜻입니다.

그래서 이번에는 DB 앞단에 빠르고 가벼운 경비원(Redis)을 세우는 "분산 락(Distributed Lock)" 방식을 도입해 보았습니다.


1. 분산 락(Distributed Lock)이란?

여러 대의 서버가 공통된 저장소(Redis)를 바라보며 락을 관리하는 기술입니다.

쉽게 말해 **Redis라는 "번호표 발급기"**를 두고, 번호표를 가진 요청만 DB에 접근하게 만드는 것입니다.

🛠️ 왜 Redis인가?

  • In-Memory: 디스크에 저장하는 DB보다 압도적으로 빠릅니다.
  • DB 부하 감소: 락 대기를 DB가 아닌 Redis에서 처리하므로, DB는 실제 비즈니스 로직(주문)만 처리하면 됩니다.

🛠️ 왜 Redisson인가?

Java의 Redis 클라이언트에는 Lettuce와 Redisson이 있습니다.

  • Lettuce: 락을 구현하려면 개발자가 직접 '스핀 락(Spin Lock, 계속 물어보는 방식)'을 짜야 해서 Redis에 부하를 줄 수 있습니다.
  • Redisson: 락 기능(RLock)을 라이브러리 차원에서 지원하며, Pub/Sub 방식을 써서 부하가 적고 구현이 매우 간편합니다. (실무 추천)

2. 구현 과정

1) 의존성 및 설정 (Docker & Gradle)

먼저 Docker를 이용해 로컬에 Redis를 띄우고, Redisson 라이브러리를 추가했습니다.

Groovy
// build.gradle
implementation 'org.redisson:redisson-spring-boot-starter:3.27.0'
Java
// RedissonConfig.java
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

2) Facade 패턴 적용 (핵심 설계) ✨

비즈니스 로직(Service)에 락 관련 코드가 섞이면 유지보수가 어렵습니다.

그래서 락을 걸고 해제하는 역할만 전담하는 Facade 클래스를 만들어 역할을 분리했습니다.

Java
@Component
@RequiredArgsConstructor
@Slf4j
public class RedissonLockStockFacade {

    private final RedissonClient redissonClient;
    private final OrderService orderService;

    public void createOrder(Long userId, OrderRequestDto requestDto) {
        // 사용자 ID별로 고유한 락 생성 (내 계좌만 잠금)
        RLock lock = redissonClient.getLock("lock:user:" + userId);

        try {
            // tryLock(대기시간, 점유시간, 단위)
            // 10초 동안 락을 기다리고, 락을 얻으면 1초 안에 작업 끝내야 함
            boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);

            if (!available) {
                log.info("락 획득 실패");
                return;
            }

            // 락 획득 성공 시 비즈니스 로직 수행
            orderService.createOrder(userId, requestDto);

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 작업 끝나면 락 해제 (필수!)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
  • OrderService: 이제 락 신경 안 쓰고 오직 "주문 생성"에만 집중하면 됩니다. (이전의 @Lock 어노테이션 제거)

3. 테스트 결과 🧪

100명의 스레드가 동시에 주문을 요청하는 테스트를 다시 돌려보았습니다.

  • 상황: 잔액 1,000원 / 주식 가격 1,000원
  • 공격: 100명이 동시에 매수 요청
  • 기대: Redis가 잘 막아준다면 딱 1명만 성공해야 함.
  •  

 

결과는 대성공입니다!

DB에 락을 걸지 않고도, Redis 메모리단에서 동시성 제어가 완벽하게 이루어졌습니다.


4. 결론: Pessimistic Lock vs Redis Distributed Lock

프로젝트를 진행하며 두 가지 방식을 모두 적용해 보았습니다.

특징 비관적 락 (DB Lock) Redis 분산 락 (Redisson)
장점 구현이 쉽다. (어노테이션 하나면 끝) DB 부하를 줄여준다. 성능이 좋다.
단점 트래픽이 몰리면 DB 성능 저하 우려. 별도의 Redis 인프라 구축 및 관리 필요.
결론 트래픽이 적다면 DB 락으로 충분. 대규모 트래픽 환경에서는 Redis가 필수.

이번 프로젝트는 추후 대용량 트래픽 처리를 목표로 하고 있으므로, Redis 분산 락을 최종 아키텍처로 채택했습니다.