본문 바로가기

Study

레디스 락 vs DB 락

동시성을 제어하기 위한 쿠폰 발급 프로젝트에서

  • Lettuce 를 사용한 레디스 락
  • Redisson 을 사용한 레디스 락
  • DB 락 (비관적 락) 

방식을 사용하여 락을 구현했다.

세 락 방법의 장단점과 이론적인 성능 퍼포먼스를 조사해보고, 실제 부하 테스트 결과와 비교해본 뒤 결과분석과 우리 프로젝트에 가장 적합한 락 방식이 무엇인지 알아보자.

 

 Lettuce

Java 진영의 redis 클라이언트 라이브러리 중 하나로, 블로킹/논블로킹 방식을 모두 지원하는 강력한 라이브러리이다. SpringBoot 2.0 이후부터 redis 클라이언트로 Lettuce 를 기본으로 지원한다. 
Lettuce 는 setnx 방식으로 락을 구현할 수 있는데, setnx 는 redis 에 key-value 값을 저장하는 명령어이다. 즉 특정 key-value 저장을 시도하여 성공하면 Lock 을 획득하고, 저장에 실패하면 Lock 획득에 실패한 것이다. Lock 을 획득한 스레드는 작업 종료 후 해당 key-value 를 삭제함으로써 unLock 을 하게 되고 이후 접근하는 다른 스레드가 Lock 을 획득할 수 있는 구조이다.
이 방식에서 여러 스레드가 반복문을 돌면서 계속 Lock 획득을 시도하도록 구현하는 것이 스핀락이다. 반복을 사용하지 않으면 스레드당 한번만 Lock 획득을 시도하도록 구현할 수도 있다.(수강신청할 때 광클)

단점

  • 스핀락 방식은 여러 스레드가 반복적으로 redis 에게 Lock 을 확인하는 요청을 보내기 때문에, 요청이 몰릴 경우 redis 서버에 많은 부하가 가해질 수 있다.
  • 자체적인 TimeOut 이 존재하지 않기 때문에, 락을 반환하지 않는 문제에 대해서 어플리케이션 레벨에서 타임아웃을 따로 구현해야 한다.

 

Redisson

Lettuce 와 마찬가지로 Java 의 redis 클라이언트이지만, Lettuce 와 다르게 분산 환경에서 강점을 가지고 있다. Redisson 의 락은 pub-sub 구조를 사용한다. 재시도 로직을 직접 구현해야 하는 Lettuce 와는 다르게, Redisson 은 pub-sub 기반의 분산 락 구조를 이미 구현하여 제공해주고 있다. 이 방식은 대기중인 스레드가 지속적으로 Lock 획득을 시도하지 않고, 구독 채널로 메세지가 발행되었을 때 Lock 획득을 시도하는 방식이기 때문에 스핀락에 비해 redis 에 가해지는 부하가 적다는 장점이 있다. 그리고 Lettuce 와 달리 기본 제공되는 Lock 인터페이스에서 타임아웃을 제공하므로 어플리케이션 레벨에서 타임아웃을 직접 구현하지 않아도 된다. 즉 Lettuce 의 단점이 Redisson 에서는 장점이다.

 

DB Lock

  • DB 락은 비관적/낙관적 락과 Named 락(MySQL에서 제공하는 분산 락) 이 있다.
  • 프로젝트에는 비관적 락을 사용하여 진행하였다.(쿠폰 발급과 같이 요청이 몰리는 서비스이기 때문에 낙관적 락은 적절하지 않다고 판단하였고, Named 락은 고려하지 않았기 때문에 따로 더 공부가 필요한 부분이다)

비관적 락 방식은 충돌이 일어날 것을 예상하고, 데이터 조회와 동시에 락(공유 락 또는 베타 락)을 걸어버리는 방식이다. 해당 레코드는 수정사항이 커밋된 이후에 락을 해제하므로 데이터 정합성을 보장하지만, 레코드 자체에 락을 거는 방식이기 때문에 성능이 저해되며 다른 요청의 데이터 접근까지 막아버리기 때문에 주의하여 사용해야 한다.

단점

  • 비관적 락은 DataBase 주체로 락을 거는 방식이기 때문에 기본적으로 Inmemory 저장소인 redis 를 이용한 락보다 성능이 떨어진다.(그러나 실제 테스트코드 실행 시간, Jmiter 로 확인해본 지표 상으로 비관적 락이 압도적으로 좋은 성능을 나타냈다. 물론 테스트코드 실행 시간은 객관적인 지표가 될 수는 없지만, 다른 레퍼런스나 부하테스트 지표에서도 비관적 락의 성능이 좋게 나온 것은 의아하다.)
  • 비관적 락 방식은 레코드단위로 Lock 을 걸어버리기 때문에, 여러 테이블의 값을 읽어야 할 경우 다른 스레드와 데드락에 걸릴 위험이 존재한다. (하나의 레코드만 참조하는 경우에는 데드락 문제는 피해갈 수 있다.) 여러 테이블의 레코드를 참조하는 요청의 경우 비관적 락 사용 시 데드락이 발생할 확률이 높다.
  • 해당 테이블의 데이터를 조회하는 다른 요청의 접근도 막아버리기 때문에 테이블을 분리하거나 하는 방법을 고려하는 것이 좋다.
  •  비관적/낙관적 락 방식은 스케일 아웃된 DB 환경에서는 사용할 수 없으며, 이럴 경우 redis 를 사용한 분산 락을 고려해야 한다.

 

레디스 락은 무적인가?

위 글에서 정리한 내용으로 보면 레디스 락은 DB 락에 비해 장점이 많고 여러 상황에서 정답처럼 사용할 수 있는 것으로 보인다.

그러나 다음과 같은 단점이 있는데,

1. 싱글스레드 기반이므로 병목현상 발생

2. 단일 실패지점 문제
-> 클러스터링, 센티널 등 서버를 분산하는 방법으로 극복할 수 있다
(클러스터링, 페일오버, 레플리케이션 등 아직 이해가 모호한 키워드들에 대해 명확히 정리)

-> 우리는 레디스를 싱글 인스턴스로 개발 시작했으므로, 모니터링 등을 통해 cpu 부하 지점을 정하고 요청이 많아지는 시점에 클러스터링을 고려해야 함!

따라서 위 흐름으로 레디스를 사용하는 것에 대한 결점과 대응 방안을 적절하게 가져가서 안전한 아키텍쳐링을 구현하는 것이 바람직하겠다!

 

 

 

부하테스트 결과


 

Lettuce 응답시간 그래프

 

Redisson 응답시간 그래프

 

Pessimistic Lock 응답시간 그래프

 

 

 

 

결론

그렇다면 결론적으로 DB 락을 사용하는 것 보다 redis 락을 사용하는 것이 성능상으로, 그리고 분산환경에서도 유리하기 때문에 redis 락을 사용하는 것이 좋고, Lettuce 의 단점을 Redisson 이 대부분 보완했기 때문에 Redisson 으로 분산 락을 구현하는 것이 정답이다! 라고 생각할 수 있겠다. 그러나 상황에 따라서는 Redisson 보다 Lettuce 를 사용하는 것이 나을 수 있고, 레디스 락보다 DB 락을 사용하는 것이 나을 수 있다.
Redisson 은 라이브러리가 상대적으로 무겁고 별도의 의존성을 추가해 주어야 하며, 제공된 인터페이스를 사용하기 때문에 동작 방식을 알기 위해서 따로 학습이 필요하다. 실무에서도 Lock 획득을 재시도할 필요가 없는 요구사항일 경우 Lettuce 를 사용하기도 한다. 
또한 분산 환경이 아닐 경우, 충돌이 자주 발생하지 않는 환경을 가정할 경우 레디스 락을 무조건 채택하기보다 낙관적 락을 사용하는 방식이 성능상 이득을 보는 경우가 많다. 또 테스트 결과에서 알 수 있듯, 요청의 수가 많지 않은 경우 비관적 락을 사용하는것이 가장 높은 성능을 보였다. 따라서 요구사항과 서비스의 규모 등을 고려하여 락 방식을 선택하는 것이 적절하다.

 

 

※ Facade 패턴?

프로젝트에서 겪었던 Transaction 커밋 시점과 unLock 시점 불일치로 인해 동시성을 보장하지 못했던 이슈를 Facade 패턴으로 많이들 해결하는 모양이다. 어떤 패턴이고 어떻게 문제를 해결할 수 있는지 공부해보자.

'Study' 카테고리의 다른 글

TCP/UDP  (0) 2024.07.24
OSI 7 계층  (0) 2024.07.24
[JPA(2)] - 연관관계 매핑  (0) 2024.06.19
[Spring/JPA] N+1 문제와 해결 방법(fetch Join)  (0) 2024.06.03
[Java]가비지 컬렉션(GC)  (0) 2024.05.22