본문 바로가기
카테고리 없음

동시성 제어를 위한 락 전략.. 무엇을 골라 잡아야할까...

by eoruadl 2026. 3. 6.

들어가며...

동시성 제어에 대한 공부를 하면서 비관적 락, 낙관적 락에 대해 알게 되었다.

이커머스 서비스를 개발하면서 여러 도메인에 락 전략을 적용하며 들었던 생각을 정리해보려고 한다.

그리고 문득 들었던 생각이 비관적 락과 낙관적 락의 성능 차이가 있을까??

어느 정도의 트래픽이 생겨야 성능 차이가 생길까?? 란 의문이 들었다.

그래서 그 부분에 대한 테스트도 진행하였고 그 결과를 공유해보려고 한다.

 

락 전략 선택에 대한 고민

현재까지 개발된 상황에서 동시성 제어를 해야 할 부분을 생각해 보았다.

그중 재고 차감과 좋아요 처리에 대해 락 전략을 어떤 걸 사용해야 할지 고민했었다.

 

먼저 재고 차감의 경우 정합성이 중요하다 생각했다. 실제 재고보다 많은 주문이 들어왔지만 주문이 정상처리되고 이후에 사용자에게 상품이 품절돼서 주문이 취소되었다는 슬픈 상황이 발생하거나 반대로 재고가 남아있음에도 품절 처리가 된다면 매출 손실이 생길 수도 있다.

 

그래서 재고 차감의 경우 비관적 락을 채택하였다. 하나의 스레드가 재고 조회를 하여 수정(차감)할 때까지 다른 스레드가 해당 Row에 접근하지 못하도록 하여 정합성을 완벽하게 보장해야 한다고 생각했다.

동시에 여러 사용자가 동일한 상품을 주문하게 되면 대기 시간이 길어져 성능이 떨어질 수 있지만 한정판 상품 및 선착순으로 주문을 해야 하는 상품이 아닌 이상 그럴 일은 거의 없을 거라 판단했다.

 

그리고 낙관적 락을 선택하게 되면 하나의 사용자만 주문 성공하고 나머지 사용자의 경우 여러 번 재시도를  해야 할 경우 성능상 더 안 좋을 수도 있겠다고 생각했다.

 

좋아요의 경우 처음에 낙관적 락을 선택하려고 했다. 상품을 좋아요 하는 경우에는 경합이 일어날 일이 거의 없을 거라 생각했다.

하지만 Version 컬럼을 따로 두는 게 싫기도 했고 비관적락과 성능 차이도 얼마 나지 않을 것 같았다.

 

그러다 Atomic Update를 알게 되었다.

비관적 락과 비슷한 개념이라 생각이 들었지만 별도의 락 선언 없이 DB 엔진의 원자적 연산 기능을 사용하는 거라 쿼리를 실행할 때 아주 짧은 락을 걸고 순차적으로 쿼리를 처리해서 Lost Update를 방지할 수 있다.

그리고 Like의 경우 테이블을 분리해 두었고 Product 테이블에 likeCount 필드로 비정규화 시켜놓은 상태라서 likeCount + 1을 해주는 간단한 처리만 해주면 되었다.

 

그래서 결과적으로 Atomic Update 전략을 사용하였다.

 

이런저런 고민을 하다가 락 전략의 성능 차이가 과연 심할까??라는 의문이 들었고 두 로직에 대해 각각의 락 전략을 이용해 테스트를 해보기로 하였다.


동시성 테스트

재고 차감과 좋아요에 대한 동시성 테스트를 진행하였다.

성공했는지 실패했는지에 대한 결과만 확인하면 돼서 CountDownLatch를 이용하여 동시성 테스트를 진행하였고, 스레드 수를 늘려가며 테스트하였다.

 

비관적 락, 낙관적 락, Atomic Update를 각각 실행하였으며 소요 시간, 성공, 실패, 경합 이렇게 네 가지를 로그를 찍어서 확인했다.

 

재고 차감 테스트

 

스레드 수를 10 -> 50 -> 100 -> 200 -> 500으로 늘려가며 테스트를 진행하였다.

낙관적 락의 경우 재시도 없이 테스트했을 때 경합이 발생하여 총 소요 시간을 측정할 수 없다고 판단해서 재시도까지 포함한 소요 시간을 측정하였다.

재고 차감 동시성 테스트 결과 (비관적 락 - 낙관적 락 - Atomic Update - 낙관적 락 재시도 포함 순)

 

결과를 비교해 보았을 때 스레드가 10개일 때는 소요 시간이 비슷했지만 스레드 수가 많아질수록 낙관적 락이 시간이 가장 오래 걸렸다. 비관적 락이 시간이 가장 오래 걸릴 거라고 예상했지만 오히려 낙관적 락이 시간이 제일 많이 소요되었고 Atomic Update의 경우가 소요 시간이 제일 짧았다.

 

좋아요 테스트

좋아요 동시성 테스트 결과 (비관적 락 - 낙관적 락 - Atomic Update - 낙관적 락 재시도 포함 순)

 

좋아요의 경우도 스레드 수가 많아질수록 Atomic Update의 소요 시간이 가장 짧았고, 낙관적 락의 경우 소요 시간이 가장 오래 걸렸다.

 

결론

비관적 락이 대기를 하며 순차적으로 스레드 처리를 하니 시간이 더 오래 걸릴 거라 예상하였지만 재시도를 해서 걸리는 시간까지 고려하면 낙관적 락을 사용하였을 때 시간이 가장 오래 걸렸다.

그리고 Atomic Update의 경우 가장 처리 속도가 빨랐는데 별도의 락이 걸리지 않고 DB 엔진단에서 쿼리 하나로 처리가 돼서 성능 부담이 가장 적었던 것이다.

 

테스트 결과를 보니 낙관적 락을 사용해야 할 Case가 있을까?? 라는 의문이 들었다. 물론 비관적 락을 사용하였을 때 데드락이 발생할 수도 있고 여러가지 이유로 낙관적 락을 사용해야할 경우가 발생할 수도 있지만 아직 내 경험으로는 생각하기 쉽지 않은 것 같다.

 

하나의 Row에 100개, 200개의 스레드가 동시에 접근하려는 경우가 많진 않겠지만 그런 상황이 생길 것으로 예상된다면

Atomic Update -> 비관적 락 -> 낙관적 락 순서로 락 전략 선택을 고민해 볼 것 같다.

 

실제 운영 환경에서는 다양한 변수들이 존재하여 지금의 테스트 결과가 정확하지 않을 것이라 생각한다.

그리고 대규모 서비스의 경우 비관적 락이나 낙관적 락이 아닌 다른 전략을 사용하여 문제를 해결할 것이라 생각되는데 관련해서 공부를 더 해봐야겠다.

댓글