본문 바로가기
성능 테스트

MySQL 비관락 vs Redis 분산락

by sangyunpark99 2025. 3. 3.
영태가 말합니다.
"형 동시성 제어할때 MySQL로 락 처리하면 되는데,
굳이 왜 Redis로 분산락 처리 하는거야? "

 

 

이번 글은 락의 동시성 제어 방식에 따른 성능 차이를 확인하고 정리했습니다.
MySQL의 비관적 락과 Redis 기반 분산락을 같은 조건에서 비교하여, 성능 지표를 수치화했습니다.

 

앞서 작성했던 docker+Nginx를 활용한 로드 밸런서 구축편과 docker 환경에서 DB migration하기(Flyway편)은 이번 성능 테스트를 위한 빌드업의 한 부분이였습니다.

 

https://sangyunpark99.tistory.com/entry/Docker%EC%99%80-Nginx%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EB%93%9C-%EB%B0%B8%EB%9F%B0%EC%8B%B1-%EA%B5%AC%EC%B6%95

 

Docker와 Nginx를 활용한 로드 밸런싱 구축

Docker를 사용해서 다중 서버를 두어 트래픽을 분산 시키기 위해서 어떻게 해야할까?  이번글은 Docker환경에서 Nginx로 로드 밸런싱을 적용시키는 방법에 대해 설명한 글입니다. 로드 밸런싱은 무

sangyunpark99.tistory.com

https://sangyunpark99.tistory.com/entry/docker-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-DB-migration%ED%95%98%EA%B8%B0-Flyway%ED%8E%B8

 

docker 환경에서 DB migration하기 (Flyway편)

docker 환경에서 DB migration을 자동으로 해줄 수는 없을까? 이번글에서는 docker에서 mysql DB를 Flyway를 사용해서 마이그레이션 하는 방법에 대해 작성했습니다.DB를 마이그레이션 한다는게 뭘까요? DB

sangyunpark99.tistory.com

 

전체 아키텍처는 다음과 같습니다.

 

어떻게 테스트 할까요?

 

제가 테스트하려는 상황은 멀티 노드(서버) 환경에서 재고를 소모하는 API에 동시에 많은 요청이 들어올 때,
MySQL 비관적 락과 Redis 분산락이 각각 어떤 성능을 보이는지 비교하는 것입니다.

 

API 요청 횟수는 10만번을 기준으로 잡습니다.

 

먼저 Flyway를 사용해서 MySQL DB에 재고와 관련된 데이터를 초기화 해줍니다.

재고는 10만개를 저장시킵니다.

CREATE DATABASE IF NOT EXISTS test;

CREATE TABLE IF NOT EXISTS stock (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    quantity BIGINT NOT NULL
) engine=InnoDB;

INSERT INTO stock (quantity) VALUES (100000);

 

 

다음으로, MySQL 비관락이 사용되는 코드입니다.

 

요청을 보낼 Controller의 코드는 아래와 같습니다.

@RestController
public class StockController {

    private final StockService stockService;

    public StockController(StockService stockService) {
        this.stockService = stockService;
    }

    @PostMapping("/stock/{id}")
    public ResponseEntity<?> decreaseStock(@PathVariable(name = "id") Long id) {
        stockService.decreaseOneStock(id);
        return ResponseEntity.ok("재고가 1개 감소 되었습니다.");
    }
}

 

요청을 처리할 Service 코드는 아래와 같습니다.

@Service
public class StockService {

    private final StockRepository stockRepository;

    public StockService(StockRepository stockRepository) {
        this.stockRepository = stockRepository;
    }

    @Transactional
    public void decreaseOneStock(final Long id) {
        Stock stock = stockRepository.findByIdWithPessimisticLock(id);
        stock.decrease(1L);
        stockRepository.saveAndFlush(stock);
    }
}

 

재고 감소 로직에 비관적 락이 적용된 코드는 아래와 같습니다.

public interface StockRepository extends JpaRepository<Stock, Long> {
    // select for update
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.id = :id")
    Stock findByIdWithPessimisticLock(Long id);
}

 

다음으로, Redis 분산락이 사용되는 코드입니다.

 

요청을 보낼 Controller의 코드는 아래와 같습니다

@PostMapping("/stock/redis/{id}")
public ResponseEntity<?> decreaseRedisStock(@PathVariable(name = "id") Long id) throws InterruptedException {
    redissonLockStockFacade.decrease(id);
    return ResponseEntity.ok("재고가 1개 감소 되었습니다.");
}

 

Redisson을 사용한 재고 감소 로직은 아래와 같습니다.

public void decrease(Long id) throws InterruptedException {
    RLock lock = redissonClient.getLock("stock : " + id.toString());
    Boolean locked = false;
    try {
        locked = lock.tryLock(3, 20, TimeUnit.SECONDS);

        if(!locked) {
            log.info("락 획득 실패");
            return;
        }
        stockService.decrease(id, 1L);

    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        if(locked) {
            lock.unlock();
        }
    }
}

 

부하 테스트는 어떻게 진행할까요?

 

ngrinder를 사용해서, 100명이 동시에 요청을 1000번 보내는 경우를 테스트했습니다.

총 100,000번 요청을 보내게 되므로 테스트 완료시 재고는 0개가 되어야 합니다.

 

먼저 mysql 비관락을 사용한 경우 테스트 결과입니다.

 

MySQL 비관락 테스트 결과

 

100,000번 테스트 요청 중 44,024번의 테스트가 실패했습니다.

 

DB에도 44,024개의 재고가 남아있습니다.

에러 그래프도 초반엔 에러가 없다가 점점 많은 에러가 생기게 됩니다.

 

Redis 분산락 테스트 결과

 

DB에 에러가 발생한 횟수보다 조금 더 많은 4039개의 재고가 남아있습니다.

 

그래프를 보면 에러가 3지점에서 발생하게 됩니다.

 

결과를 지표로 정리하면 다음과 같습니다.

 

  Mysql 비관락 Redis 분산락
TPS 517 456
Peak TPS 778 606
Successful Tests 55,976 96,432
Errors 44% (44,024 / 100,000) 약 3.5% (3,568 / 100,000)
Run time 1m 56s 3m 41s

 

(단, 분산락을 사용한 환경에서 처리되지 않은 재고수가 Errors 수보다 조금 더 높습니다.)

  • 빠른 응답속도만 보면 MySQL 비관적 락이 유리합니다.
  • 하지만 멀티 서버 환경에서의 안정성, 성공률 면에서는 Redis 분산락이 압도적으로 우수하다는 점이 확인됐습니다.

특히, 향후 서버 수가 늘어나거나 트래픽이 더 증가할 경우, Redis 분산락이 훨씬 유리할 가능성이 높습니다.
따라서, 장기적인 서비스 확장성과 안정성 확보를 고려할 때는 Redis 분산락이 더 적합한 선택으로 보입니다.