여러 사용자가 동시에 같은 데이터를 수정하는 상황은 생각보다 흔합니다.
예를 들어 재고가 1개 남은 상품을 두 사용자가 거의 동시에 주문한다고 해보겠습니다.
두 요청이 모두 “재고 1개 있음”을 읽고 각각 주문을 완료해버리면, 실제 재고보다 더 많이 팔리는 문제가 생길 수 있습니다.
이런 문제를 막기 위해 데이터베이스나 애플리케이션에서는 락을 사용합니다.
락은 쉽게 말해 “지금 이 데이터는 누가 건드리고 있으니 조심해”라고 표시해두는 장치입니다.
비관적 락은 이름 그대로 충돌이 발생할 가능성을 비관적으로 보는 방식입니다.
즉, “다른 트랜잭션이 동시에 같은 데이터를 수정할 수 있으니, 내가 작업하는 동안 미리 잠가두자”라는 접근입니다.
보통 데이터베이스의 SELECT ... FOR UPDATE 같은 방식으로 row에 락을 걸어 처리합니다.
SELECT *
FROM product
WHERE id = 1
FOR UPDATE;
이렇게 락을 잡은 트랜잭션이 끝나기 전까지, 다른 트랜잭션은 해당 데이터를 수정하지 못하고 기다릴 수 있습니다.
비관적 락의 가장 큰 장점은 충돌을 확실하게 막기 쉽다는 점입니다.
한 트랜잭션이 데이터를 잡고 있는 동안 다른 트랜잭션이 끼어들기 어렵기 때문에, 재고 차감이나 계좌 이체처럼 정합성이 중요한 작업에서 안정적으로 사용할 수 있습니다.
면접에서 예시를 든다면 “동시에 수정되면 안 되는 데이터에 대해 DB 락을 걸어 순차적으로 처리하는 방식”이라고 말하면 됩니다.
문제는 대기입니다.
한 트랜잭션이 락을 오래 잡고 있으면 다른 트랜잭션들이 줄줄이 기다려야 합니다.
트래픽이 많거나 트랜잭션 시간이 길면 성능 저하가 커질 수 있고, 잘못 사용하면 데드락이 발생할 수도 있습니다.
그래서 비관적 락을 쓸 때는 트랜잭션 범위를 짧게 유지하는 것이 중요합니다.
락을 잡은 상태로 외부 API 호출이나 오래 걸리는 작업을 끼워 넣으면 문제가 커질 수 있습니다.
낙관적 락은 충돌이 자주 발생하지 않을 것이라고 보는 방식입니다.
먼저 락을 걸어 막아두지 않고, 데이터를 수정할 때 버전 값 등을 비교해서 “내가 읽은 이후 누가 먼저 수정했는지”를 확인합니다.
보통 테이블에 version 컬럼을 두고 처리합니다.
UPDATE product
SET stock = stock - 1,
version = version + 1
WHERE id = 1
AND version = 3;
내가 처음 읽었을 때 버전이 3이었는데, 저장하려는 순간에도 버전이 3이면 수정에 성공합니다.
그런데 중간에 다른 트랜잭션이 먼저 수정해서 버전이 4가 되었다면, 위 쿼리는 영향을 준 row가 0개가 됩니다. 이때 충돌이 발생했다고 판단할 수 있습니다.
낙관적 락은 락을 잡고 기다리게 만드는 시간이 적습니다.
충돌이 적은 서비스에서는 성능상 유리할 수 있습니다.
예를 들어 사용자가 각자 자신의 프로필을 수정하는 경우처럼 같은 데이터를 동시에 수정할 가능성이 낮다면, 미리 락을 잡는 것보다 낙관적 락이 더 자연스러울 수 있습니다.
또한 실패했을 때 사용자에게 “다른 사용자가 먼저 수정했습니다. 다시 확인해주세요.”처럼 안내하고 재시도하게 만들 수도 있습니다.
낙관적 락은 충돌이 발생했을 때 재시도 로직이 필요합니다.
동시에 같은 데이터를 많이 수정하는 상황이라면 계속 충돌이 나고, 재시도가 반복되면서 오히려 비용이 커질 수 있습니다.
또 충돌을 사용자에게 어떻게 보여줄지, 자동 재시도를 할지, 최신 데이터를 다시 불러오게 할지 같은 정책도 정해야 합니다.
즉, 낙관적 락은 락을 아예 안 쓰는 것이 아니라 “처음부터 막기보다 마지막에 충돌을 확인하는 방식”이라고 이해하는 편이 좋습니다.
| 구분 | 비관적 락 | 낙관적 락 |
|---|---|---|
| 기본 관점 | 충돌이 자주 날 것이라고 봄 | 충돌이 드물 것이라고 봄 |
| 처리 방식 | 먼저 락을 잡고 작업 | 작업 후 버전 등으로 충돌 확인 |
| 장점 | 정합성을 강하게 지키기 쉬움 | 대기 시간이 적고 성능상 유리할 수 있음 |
| 단점 | 대기, 데드락, 성능 저하 가능 | 충돌 시 재시도/예외 처리 필요 |
| 적합한 상황 | 재고, 결제, 계좌 등 충돌 민감 영역 | 충돌이 드문 수정 작업 |
이 표만 외우기보다, “충돌을 미리 막느냐, 나중에 감지하느냐”로 이해하면 훨씬 말하기 쉽습니다.
정답은 상황마다 다릅니다.
충돌 가능성이 높고 데이터 정합성이 매우 중요하다면 비관적 락이 더 어울릴 수 있습니다.
대표적으로 재고 차감, 좌석 예약, 포인트 사용처럼 동시에 같은 자원을 수정하는 경우입니다.
반대로 충돌 가능성이 낮고, 실패 시 재시도나 사용자 안내가 가능한 경우에는 낙관적 락이 더 깔끔할 수 있습니다.
예를 들어 게시글 수정, 프로필 수정, 관리자 설정 변경 같은 작업이 여기에 가까울 수 있습니다.
낙관적 락과 비관적 락은 동시 수정 상황에서 데이터 충돌을 다루는 방법입니다.
비관적 락은 충돌이 발생할 가능성이 높다고 보고, 데이터를 읽거나 수정할 때 먼저 락을 걸어 다른 트랜잭션이 접근하지 못하게 합니다. 정합성은 강하게 지킬 수 있지만, 대기 시간이 늘거나 데드락이 생길 수 있습니다.
반면 낙관적 락은 충돌이 자주 나지 않는다고 보고, 먼저 락을 걸지 않습니다. 대신 저장 시점에 version 같은 값을 비교해서 중간에 다른 트랜잭션이 수정했는지 확인합니다. 충돌이 발생하면 예외 처리나 재시도가 필요하지만, 충돌이 적은 환경에서는 성능상 유리할 수 있습니다.
그래서 실무에서는 충돌 가능성이 높고 정합성이 중요한 재고나 결제 쪽은 비관적 락을 고려하고, 충돌이 드문 수정 작업은 낙관적 락을 고려합니다.