[7-4] [락의 개념 — MVCC vs Lock의 원리와 구현 비교]

minpractice_jhj·2025년 10월 13일

Side Projects

목록 보기
11/16
post-thumbnail

요약

MVCC는 “무엇이 보이느냐(가시성)”를, 락은 “누가 먼저 쓰느냐(경합)”을 다룬다.

행 락과 대기 전략(NOWAIT / SKIP LOCKED)을 통해 “기다릴지 / 포기할지 / 건너뛸지”를 설계한다.

타임아웃·데드락 표준으로 “영원 대기”를 방지하며, 인덱스·FK가 락의 범위를 결정한다.


1. 일반적인 트랜잭션 락 (RDB 공통 원리)

1) 핵심 개념

트랜잭션 락은 데이터의 일관성(Consistency)격리성(Isolation)을 보장하기 위한 경합 제어 도구다.

DB는 동시에 여러 사용자가 같은 데이터를 수정하려 할 때

“누가 먼저 접근할 것인가”를 결정하기 위해 락을 건다.

즉, 한 트랜잭션이 데이터를 수정 중이면

다른 트랜잭션은 그 데이터에 접근할 수 없도록 “잠금(Lock)”을 설정한다.

모든 RDBMS는 이 락을 통해 읽기·쓰기 충돌이 발생했을 때의 처리 방식을 정한다.

이때 시스템은 보통 아래 세 가지 중 하나로 대응한다.

  • 대기(WAIT): 락이 해제될 때까지 기다림
  • 포기(NOWAIT): 락이 걸려 있으면 즉시 실패
  • 재시도(RETRY): 실패 후 일정 조건에서 다시 시도

핵심 문장:

락은 여러 트랜잭션이 동시에 실행되더라도

데이터를 순서 있게, 일관되게 유지하기 위한 장치다.


2) 동작 구조 요약

요소설명예시
가시성 (MVCC 이전 세대 의미)COMMIT된 데이터만 보임트랜잭션 격리 수준에 따라 Dirty Read 방지
경합 (Concurrency Control)쓰기 충돌 시 순서를 보장X락 선점 시 다른 트랜잭션 대기
대기 전략충돌 시 시스템이 취할 태도WAIT, NOWAIT, SKIP
타임아웃 / 데드락무한 대기 방지 메커니즘Lock Timeout, Deadlock Detection
락 범위 결정SQL 조건에 따라 락 범위 결정WHERE 조건 인덱스 유무가 결정적
FK 연쇄 잠금참조 무결성 검증 중 부모/자식 동시 락UPDATE parent → child FK check

인덱스가 없는 WHERE 조건으로 UPDATE를 실행하면

DB는 어떤 행이 바뀔지 알 수 없으므로 테이블 전체를 스캔하며 락을 걸게 된다.


3) 일반 RDBMS의 처리 흐름 (MySQL / Oracle / SQL Server 공통)

  1. Transaction AUPDATE row1 → X락 획득
  2. Transaction BUPDATE row1 → 대기 상태(WAIT)
  3. Timeout 설정 시 일정 시간 후 실패
  4. Deadlock 발생 시 DB 엔진이 한쪽 트랜잭션을 강제 ROLLBACK

핵심 문장:

일반적인 트랜잭션 락은 “대기”를 허용하는 구조다.

즉, 동시에 같은 데이터를 수정하려 하면

시스템이 락 큐(Lock Queue)를 만들어 “순서대로 처리”한다.


2. PostgreSQL 트랜잭션 락 (엔진 특성)

PostgreSQL은 같은 문제를 MVCC(Multi-Version Concurrency Control) 구조로 재해석했다.

즉, “락 중심 제어” 대신 “버전 기반 가시성”으로 읽기와 쓰기를 분리한다.


1) MVCC — “무엇이 보이느냐(가시성)”

PostgreSQL은 각 행(row)에 xmin, xmax를 기록한다.

트랜잭션은 Snapshot을 통해 “보이는 버전만 읽기” 때문에

SELECT는 락을 걸지 않는다.

즉, 읽기-쓰기 간 충돌이 사라지고 “쓰기 간” 락만 남는다.

결과

  • 읽기는 “버전 분리”
  • 쓰기는 “락 경쟁”

2) Lock — “누가 먼저 쓰느냐(경합)”

PostgreSQL은 쓰기 충돌만 Row-Level Lock으로 제어한다.

전략의미명령어
WAIT기본 모드 — 락 해제까지 대기FOR UPDATE
NOWAIT대기하지 않고 즉시 예외 발생FOR UPDATE NOWAIT
SKIP LOCKED잠긴 행을 건너뛰고 다음 행 조회FOR UPDATE SKIP LOCKED

3) WAIT — 순차 일관성(Consistency) 우선

설명: 기본 모드. 잠긴 행이 풀릴 때까지 대기한다.

적용 시점: 결제, 재고, 송금 등 정합성이 중요한 업무

UPDATE accounts
SET balance = balance - 1000
WHERE id = 1
FOR UPDATE;

이미 다른 트랜잭션이 같은 계좌를 수정 중이라면 순서대로 대기 후 실행된다.

  • 장점: 데이터 일관성 보장
  • 단점: 대기 시간 증가 가능

비즈니스 예시:

A 사용자가 결제 중일 때 B가 결제를 시도하면

B는 A의 트랜잭션이 끝날 때까지 대기한다.

→ 이중 결제나 중복 승인 방지에 유용하다.


4) NOWAIT — 빠른 실패(Fast-Fail) 전략

설명: 잠긴 행이 있으면 즉시 실패 (ERROR: could not obtain lock)

적용 시점: 빠른 응답이 필요한 API, 포인트 적립, 예약 등

UPDATE points
SET amount = amount + 100
WHERE user_id = 42
FOR UPDATE NOWAIT;

락이 걸려 있으면 바로 예외 발생 → 재시도 큐에 등록

  • 장점: 빠른 응답, 타임아웃 불필요
  • 단점: 실패 시 재시도 로직 필요

비즈니스 예시:

포인트 적립 중 다른 요청이 이미 처리 중이라면

“처리 중입니다” 메시지를 띄우고 종료.

→ 서버 부하를 줄이고 재시도 큐에서 나중에 처리한다.


5) SKIP LOCKED — 병렬 효율(Parallelism) 극대화

설명: 잠긴 행을 건너뛰고 처리 가능한 행만 선택

적용 시점: 주문 큐, 배치, 멀티 워커 시스템

SELECT * FROM orders
WHERE status = 'READY'
FOR UPDATE SKIP LOCKED
LIMIT 10;

이미 다른 워커가 처리 중인 주문은 건너뛰고, 남은 주문만 선택한다.

  • 장점: 병렬 처리 효율 극대화, 락 대기 없음
  • 단점: 일부 레코드는 나중에만 처리됨 (비동기적)

비즈니스 예시:

여러 워커(worker)가 동시에 주문 큐를 가져갈 때,

이미 잠긴 주문은 건너뛰고 처리 가능한 주문부터 가져간다.

→ 병렬성을 높이고 락 경합을 최소화한다.


6) 대기 전략 비교 요약

전략대기 여부사용 시점장점단점대표 SQL
WAIT대기결제, 송금 등 정합성 업무순서 보장, 정확성대기 발생 가능FOR UPDATE
NOWAIT즉시 실패API, 예약, 포인트 등빠른 실패, 부하 감소재시도 필요FOR UPDATE NOWAIT
SKIP LOCKED건너뜀큐, 배치, 비동기 처리병렬 효율, 무한 대기 없음일부 지연 처리FOR UPDATE SKIP LOCKED

3. 타임아웃 · 데드락 제어

항목PostgreSQL 구현
Lock TimeoutSET lock_timeout = '3s';
Deadlock DetectionWait-for Graph 분석 (순환 대기 탐지)
표준 대응 방식실패 → 재시도 (Transaction Retry Loop)

PostgreSQL은 “영원 대기”를 허용하지 않는다.

충돌 시 항상 실패 후 재시도가 정석 패턴이다.


4. 인덱스와 FK의 영향

조건결과
인덱스 있음특정 행만 Row Lock (최소 범위)
인덱스 없음테이블 전체 Scan → 광범위 락 발생
FK 제약부모-자식 간 Transaction Lock 연쇄

정리:

인덱스가 MVCC의 효율을 결정한다.

잘못된 인덱스 설계는 불필요한 블로킹을 유발한다.


5. PostgreSQL 트랜잭션 락 정리

구분일반 트랜잭션 락PostgreSQL 트랜잭션 락
읽기-쓰기 충돌락 기반 대기MVCC 기반 비차단(Read only)
쓰기 충돌락 큐 대기Row Lock (Tuple-level)
대기 전략내부 엔진 설정SQL 레벨 제어 (NOWAIT, SKIP)
락 범위조건 및 인덱스 의존인덱스·FK 구조 직접 영향
데드락 처리강제 롤백Wait-for Graph 탐지 + Fail & Retry
철학“모두 기다려라”“읽기는 분리, 쓰기만 질서”

핵심 정리

관점일반 트랜잭션 락PostgreSQL 트랜잭션 락
핵심 질문“누가 먼저 접근하느냐”“무엇이 보이느냐”
중심 개념대기 기반 순서 제어버전 기반 가시성 분리
충돌 대응WAIT → DEADLOCK → ROLLBACKNOWAIT / SKIP LOCKED + Retry
락 범위SQL 범위 (테이블/페이지 단위)인덱스·FK 기반 행 단위
대표 전략Lock Queue 중심MVCC + Tuple Lock 하이브리드

정리 문장:

일반적인 트랜잭션 락은 ‘누가 먼저 쓰느냐’의 싸움이지만,

PostgreSQL의 트랜잭션 락은 ‘무엇이 보이느냐’를 분리한 뒤

‘누가 먼저 쓰느냐’를 최소 범위에서만 결정한다.


결론 — “대기 정책도 비즈니스 로직이다”

PostgreSQL은 단순히 트랜잭션 동작을 통제하는 수준을 넘어,

WAIT / NOWAIT / SKIP LOCKED 세 가지 대기 정책을

비즈니스 특성에 맞게 설계할 수 있도록 SQL 레벨에서 개방했다.

즉, “트랜잭션 제어”는 더 이상 엔진의 몫이 아니라

시스템 설계자의 선택 영역이다.


최종 요약

  • MVCC는 “보이는 세계(가시성)”를,
  • Lock은 “질서의 세계(순서)”를 다룬다.
  • PostgreSQL은 이 둘을 가장 세련된 방식으로 결합한 데이터베이스다.

“지금까지는 데이터베이스가 동시성을 어떻게 제어하는지,
즉 MVCC와 트랜잭션 락이 ‘보이는 세계(가시성)’와 ‘쓰는 세계(경합)’를 분리하는 방식을 살펴봤습니다.
다음 편에서는 비관적 락 vs 낙관적 락 두 가지 접근 방식을 비교하며,
내가 직접 구현 중인 “좋아요 기능”에 어떤 방식이 더 적합한지
실제 프로젝트 수준에서 분석하고 적용해보겠습니다.

profile
운동처럼 개발도 작은 실천이 성장의 힘이 된다고 믿는 개발자 minpractice_jhj 기록

0개의 댓글