453. MVCC (다중 버전 동시성 제어)

⚠️ 이 문서는 "누가 데이터를 고치고 있을 때 다른 사람은 그 데이터를 아예 보지도 못하고 기다려야 하는" 끔찍한 잠금(Lock)의 한계를 깨고, **데이터의 과거 버전(스냅샷)을 여러 개 만들어두어 읽는 사람과 쓰는 사람이 서로를 방해하지 않게 만드는 현대 RDBMS의 핵심 엔진인 'MVCC'**를 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: Multi-Version Concurrency Control. 하나의 데이터에 대해 여러 개의 버전(과거의 모습들)을 유지하여, 트랜잭션마다 자기가 봐야 할 '적절한 과거의 모습'을 보여주는 기술이다.
  2. 가치: 가장 큰 장점은 **"읽기(Select)는 쓰기(Update)를 막지 않고, 쓰기는 읽기를 막지 않는다"**는 것이다. 덕분에 자물쇠(Lock) 대기 시간이 사라져 동시 처리 속도가 압도적으로 빨라진다.
  3. 기술 체계: MySQL(InnoDB)과 Oracle은 이를 구현하기 위해 **Undo Log(441번 문서)**를 활용하여 과거의 데이터를 조립(스냅샷 생성)해 낸다.

Ⅰ. 개요: 은행원의 멈춘 시간 (Context & Necessity)

"A 고객의 잔액이 100만 원이네. 이자율 계산해야지..." (은행원 T1이 데이터를 읽고 있다.) 이때 A 고객이 스마트폰으로 접속해서 50만 원을 출금하려고 한다(T2).

  • 과거의 DB (2PL, Lock 사용): 은행원(T1)이 읽는 동안 S-Lock(공유 락)이 걸려 있어서, 고객(T2)은 출금을 못 하고 "잠시 후 다시 시도해 주세요"라며 무한 대기해야 한다. (고객 극대노)
  • 현대의 DB (MVCC 사용): 고객(T2)은 바로 출금하고 통장 잔액을 50만 원으로 고친다(Lock 없음). 하지만 은행원(T1)의 모니터에는 고객이 방금 고친 50만 원이 아니라, **자기가 처음 조회했을 때의 '100만 원짜리 스냅샷(과거 버전)'**이 계속해서 보인다!

MVCC는 이렇게 각자의 시간(버전)을 분리해 줌으로써 모두가 행복해지는 마법을 부렸다.

📢 섹션 요약 비유: MVCC는 **'웹페이지의 구글 캐시(저장된 페이지)'**와 같습니다. 원본 웹사이트 주인이 지금 막 사이트 디자인을 뜯어고치고 있어도, 나는 구글이 어제 찍어둔 '저장된 페이지(과거 버전)'를 보면서 기사를 읽을 수 있는 것과 똑같은 원리입니다.


Ⅱ. MVCC의 핵심 작동 원리 (Undo Log) ★

DB는 데이터의 과거 버전을 도대체 어디에 숨겨두는 걸까? 정답은 롤백(Rollback)할 때 쓰려고 만들어둔 **Undo Log(언두 로그)**를 재활용하는 것이다.

  1. 초기 상태: X = 100
  2. T2의 수정: T2가 X를 50으로 바꾼다.
    • 진짜 디스크의 데이터는 50으로 바뀐다.
    • 하지만 바뀌기 전의 원본 X=100은 Undo Log라는 휴지통에 슬쩍 넣어둔다.
  3. T1의 조회: T1이 X를 달라고 한다.
    • DB 엔진은 T1의 트랜잭션 시작 시간을 본다. "아, 너는 T2가 고치기 전에 들어온 옛날 사람이네?"
    • 디스크에 있는 진짜 데이터 50을 주지 않는다. 대신 Undo Log를 뒤져서 X=100이라는 옛날 퍼즐을 다시 조립해서 T1에게 던져준다. (이것을 Consistent Read, 일관된 읽기라고 부른다.)

Ⅲ. MVCC의 치명적인 단점 2가지

MVCC가 아무리 완벽해 보여도 만능은 아니다.

1. Undo Log (휴지통) 폭발 현상

  • 만약 은행원(T1)이 "어제부터 오늘까지의 모든 거래 내역 통계"를 내는 엄청나게 긴 트랜잭션(Long Transaction)을 돌리고 있다고 치자.
  • 이 트랜잭션이 끝날 때까지 DB는 Undo Log(과거 데이터)를 지우지 못하고 계속 쌓아둬야 한다. (T1이 언제 볼지 모르니까)
  • 결국 Undo Log 공간이 터져버려서(ORA-01555: snapshot too old 에러) DB가 멈춰버리는 대참사가 발생한다.

2. 쓰기(Write)끼리는 여전히 락(Lock)이 걸린다

  • "읽기는 쓰기를 막지 않는다"고 했다.
  • 하지만 "쓰기(Update)는 쓰기(Update)를 막는다."
  • 나와 엄마가 동시에 통장 잔고를 업데이트하려고 하면, 둘 중 하나는 결국 배타 락(X-Lock)에 걸려 무조건 대기해야 한다. 갱신 손실(Lost Update - 445번 문서)을 막아야 하기 때문이다.
┌──────────────────────────────────────────────────────────────┐
│           MVCC (다중 버전 동시성 제어) 작동 흐름 시각화               │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│ [ 💾 디스크의 실제 데이터 ] : 잔고 50만 원 (최신!)                    │
│ [ 🗑️ Undo Log (과거) ]   : 잔고 100만 원                         │
│                                                              │
│ 👨‍💻 사용자 A (늦게 온 사람) ───(조회)──▶ 50만 원 (디스크에서 바로 읽음) │
│                                                              │
│ 👨‍💼 사용자 B (일찍 온 사람) ───(조회)──▶ 100만 원 (Undo에서 복원해줌) │
│                                                              │
│ ★ 특징: A와 B가 똑같은 SELECT 쿼리를 날렸는데, DB가 사람(트랜잭션 나이)을  │
│       차별해서 서로 다른 두 가지 버전의 데이터를 보여줌!                 │
└──────────────────────────────────────────────────────────────┘

Ⅳ. 결론

"Lock 없는 동시성 제어의 예술." 과거의 개발자들은 동시성(속도)을 높이기 위해 격리 수준을 Read Uncommitted로 낮추며 엉망진창인 데이터(Dirty Read)를 감수해야만 했다. 하지만 MVCC가 도입되면서, 속도 저하 없이도 Repeatable Read(반복 읽기 가능 - 447번 문서)라는 엄청나게 높은 수준의 데이터 정합성을 누릴 수 있게 되었다. 현대 백엔드 엔지니어라면 자신이 사용하는 RDBMS(MySQL, PostgreSQL)가 MVCC를 기반으로 작동한다는 사실을 이해하고, 불필요하게 긴 롱 트랜잭션을 만들어 Undo 영역을 찢어버리는 실수를 하지 말아야 한다.


📌 관련 개념 맵

  • 관련 특성: 고립성 (Isolation - 443번 문서), Non-Repeatable Read 방어 (447번)
  • 대척점 기술: 2PL (2단계 잠금 규약 - 450번 문서)
  • 핵심 컴포넌트: Undo Log (언두 세그먼트 - 454번 문서)
  • 동작 원리: Consistent Read (읽기 일관성)

👶 어린이를 위한 3줄 비유 설명

  1. MVCC는 미술관에 있는 '마법의 그림'이에요.
  2. 화가 아저씨가 지금 막 그림에 빨간색 덧칠(Update)을 하고 있어요.
  3. 원래는 아저씨가 다 칠할 때까지 우리는 그림을 볼 수 없어야 하지만, 마법의 그림은 나한테만 아저씨가 덧칠하기 전의 '원래 그림(스냅샷)'을 뿅! 하고 보여준답니다!