Skip to content

Latest commit

 

History

History
199 lines (129 loc) · 8.12 KB

File metadata and controls

199 lines (129 loc) · 8.12 KB

낙관적 락 (Optimistic Lock) VS 비관적 락 (Pessimistic Lock)

학습 목표

  • Lock의 개념과 필요성을 이해한다.
  • 낙관적 락과 비관적 락의 차이를 이해하고, 상황에 따라 선택할 수 있다.

락(Lock)이란?

여러 세션이 동시에 같은 데이터를 건드릴 때, 꼬이지 않도록 잠시 잠가두는 안전 장치

트랜잭션 챕터에서 보았듯, 세션 A가 커밋을 수행하지 않은 상태에서 세션 B가 동시에 같은 데이터를 수정하면 원자성이 위반될 수 있다.

이를 방지하려면, 한 세션이 데이터를 수정하는 동안(커밋 또는 롤백 전까지) 다른 세션이 해당 데이터에 접근하지 못하도록 막아야 한다.

이것이 락(Lock) 이다.


락이 없으면 어떤 일이 생길까?

데이터베이스는 다중 사용자가 접근하는 시스템이기 때문에, 한 사용자가 데이터를 읽거나 수정하는 동안 다른 사용자의 작업을 잠시 막는 방법이 필요하다.

다음 상황을 가정해보자.

서여와 봉구스는 같은 공동 계좌 (잔액 100,000원)에서 동시에 50,000원씩 인출하려 한다.
시점 서여 (세션 A) 봉구스 (세션 B) 실제 잔액
T1 잔액 조회 → 100,000원 100,000원
T2 잔액 조회 → 100,000원 100,000원
T3 50,000원 차감 후 저장 → 50,000원 50,000원
T4 50,000원 차감 후 저장 → 50,000원 50,000원 ← 잘못됨

봉구스는 T2에 읽은 100,000원을 기준으로 계산하기 때문에, T3에서 서여가 이미 차감한 사실을 모른다.
결과적으로 서여의 인출이 덮어씌워진다. 이를 갱신 손실(Lost Update) 이라 한다.

락 없음 → 둘 다 잔액 100,000원 조회 → 각자 50,000원 빼서 저장 → 최종 잔액 50,000원 (서여의 인출이 덮어씌워짐, Lost Update)

락 있음 → 서여 먼저 처리 후 봉구스 처리 → 최종 잔액 0원


락의 동작 방식

락은 트랜잭션 단위로 동작한다.

  1. 트랜잭션 시작
  2. 데이터에 접근할 때 락 획득
  3. 데이터 읽기 / 수정
  4. 커밋 또는 롤백
  5. 락 해제 → 다른 세션이 해당 데이터에 접근 가능

락은 트랜잭션이 시작되면서 걸리고, 커밋 또는 롤백 될 때 풀린다.

그 사이 시간 동안, 다른 세션은 그 데이터에 접근하지 못하고 줄 서서 기다려야 한다.

락을 보유하고 있는 시간이 길수록, 다른 세션의 대기 시간도 늘어난다.

따라서 트랜잭션은 짧게 유지하는 것이 중요하다.


락의 종류

락은 언제, 얼마나 적극적으로 잠글 것인가 에 따라 두 가지 전략으로 나뉜다.

비관적 락 (Pessimistic Lock)

혹시 모르니, 내가 끝날 때까지 아무도 건들지 마!

  • 충돌이 자주 발생할 것이라고 가정하고, 데이터를 읽는 순간부터 미리 락을 건다. (선점 락)
  • 무조건 잠그고 작업한다.

동작 방식

  1. 데이터를 읽을 때 즉시 락 획득 (SELECT FOR UPDATE)
  2. 다른 세션이 해당 데이터를 수정하거나 FOR UPDATE로 읽으려 하면 대기 상태로 전환 (일반 SELECT는 가능)
  3. 락을 보유한 세션이 커밋 또는 롤백하면 락 해제
  4. 대기 중이던 세션이 락을 획득하고 작업 진행
-- 비관적 락 예시 (PostgreSQL / MySQL 공통)
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 이 시점부터 다른 세션은 id = 1 행을 수정하거나 FOR UPDATE로 읽을 수 없음
UPDATE accounts SET balance = balance - 50000 WHERE id = 1;
COMMIT;

비유

도서관에서 내가 책을 빌리면, 사서가 해당 책에 "대출 중 - 반납 전까지 접근 불가" 딱지를 붙인다.
다른 사람은 내가 반납할 때까지 그 책을 빌릴 수 없다.

적합한 상황

충돌이 나면 절대 안 되는 핵심 데이터

  • 은행 계좌 잔액 변경
  • 항공권 / 콘서트 티켓 좌석 예매
  • 창고 재고 처리

장점 / 단점

내용
장점 충돌이 원천 차단된다. 데이터 정합성이 보장된다.
단점 락이 오래 유지되면 다른 세션이 대기해야 한다. 트래픽이 몰리면 성능 저하로 이어질 수 있다.

낙관적 락 (Optimistic Lock)

어차피 충돌 잘 안 나겠지. 근데 마지막에 확인은 해볼게.

  • 충돌이 드물 것이라고 가정하고, 데이터를 읽을 때 락을 걸지 않는다. (비선점 락)
  • 대신 커밋 시점에 '내가 읽었을 때와 지금 데이터가 같은가?'를 확인한다.
- 읽을 때 → 잠그지 않음
- 수정 후 커밋할 때 → '내가 읽었을 때랑 데이터가 바뀌었나?' 확인
- 바뀌었으면 → 잠그는 게 아니라 그냥 에러를 던지고 처음부터 다시 시도

동작 방식

테이블에 version 컬럼을 추가해 변경 여부를 추적한다.

버전 번호를 체크할때는 날짜, 타임 스탬프, 체크섬, 해시등의 방법을 사용한다.

  1. 데이터를 읽을 때 version 값도 함께 읽어둔다.
  2. 수정 후 커밋할 때, DB에 저장된 version이 읽었을 때와 같은지 확인한다.
  3. 같다면 → 커밋 성공, version 1 증가
  4. 다르다면 → 누군가 먼저 수정했다는 뜻 → 충돌(에러) 발생 → 재시도 필요
    • 레코드의 버전이 다르면, 트랜잭션을 중단하고 다시 시작할 수 있다.
-- 낙관적 락 예시
-- 1. 데이터 읽기 (락 없음)
SELECT likes, version FROM posts WHERE id = 42;
-- 결과: likes = 100, version = 5
 
-- 2. 커밋 시 version 확인
UPDATE posts
SET likes = 101, version = 6
-- 내가 읽었을 때의 version과 지금 DB version이 같아야만 업데이트 됨
WHERE id = 42 AND version = 5;
-- 업데이트된 행이 0개라면 → 다른 세션이 먼저 수정한 것 → 충돌 처리

비유

책을 빌릴 때 자물쇠 없이 그냥 빌려간다.
반납할 때 '이 책, 내가 빌려간 이후로 다른 사람이 수정했나요?' 확인한다.
수정됐다면 다시 최신 내용을 읽고 처음부터 다시 시도해야 한다.

적합한 상황

동시 접근은 많지만, 같은 데이터를 동시에 수정하는 경우가 드물 때

  • 게시글 좋아요 수, 상품 조회수
  • 포인트 적립
  • 댓글 작성

장점 / 단점

내용
장점 락을 거의 걸지 않아서 성능이 좋다. 대기 없이 병렬 처리가 가능하다. 데드락(Deadlock) 발생 가능성이 낮다.
단점 충돌이 발생했을 때 데이터를 다시 읽고, 업데이트해야 하는 추가 작업이 필요하다. (충돌을 해결하기 위해선 충돌 발생 시점부터 트랜잭션을 다시 시작해야 하기 때문) 충돌이 잦은 환경에서는 오히려 성능이 나빠진다.

어떤 락을 선택해야 할까?

비관적 락 낙관적 락
언제 잠금? 읽는 순간 커밋 시점
충돌 처리 대기 (자동 처리) 에러 → 재시도 (직접 구현)
적합한 상황 충돌 빈도 높음, 정합성 최우선 충돌 빈도 낮음, 성능 중요
성능 트래픽 많을수록 저하 가능 충돌 없을 때 빠름

비관적 락은 낙관적 락보다 데이터 정합성이 우수하다.

하지만 락을 선점한 채 서로 다른 데이터를 기다리는 상황이 생기면, 두 세션이 영원히 진행되지 못하는 교착 상태(Deadlock) 가 발생할 수 있다.


참고 자료

https://hwangrolee.github.io/blog/DB-%EB%9D%BD(Database-Lock)-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EA%B0%9C%EB%85%90/