453. MVCC (다중 버전 동시성 제어)
⚠️ 이 문서는 "누가 데이터를 고치고 있을 때 다른 사람은 그 데이터를 아예 보지도 못하고 기다려야 하는" 끔찍한 잠금(Lock)의 한계를 깨고, **데이터의 과거 버전(스냅샷)을 여러 개 만들어두어 읽는 사람과 쓰는 사람이 서로를 방해하지 않게 만드는 현대 RDBMS의 핵심 엔진인 'MVCC'**를 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: Multi-Version Concurrency Control. 하나의 데이터에 대해 여러 개의 버전(과거의 모습들)을 유지하여, 트랜잭션마다 자기가 봐야 할 '적절한 과거의 모습'을 보여주는 기술이다.
- 가치: 가장 큰 장점은 **"읽기(Select)는 쓰기(Update)를 막지 않고, 쓰기는 읽기를 막지 않는다"**는 것이다. 덕분에 자물쇠(Lock) 대기 시간이 사라져 동시 처리 속도가 압도적으로 빨라진다.
- 기술 체계: 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(언두 로그)**를 재활용하는 것이다.
- 초기 상태: X = 100
- T2의 수정: T2가 X를 50으로 바꾼다.
- 진짜 디스크의 데이터는 50으로 바뀐다.
- 하지만 바뀌기 전의 원본
X=100은 Undo Log라는 휴지통에 슬쩍 넣어둔다.
- 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줄 비유 설명
- MVCC는 미술관에 있는 '마법의 그림'이에요.
- 화가 아저씨가 지금 막 그림에 빨간색 덧칠(Update)을 하고 있어요.
- 원래는 아저씨가 다 칠할 때까지 우리는 그림을 볼 수 없어야 하지만, 마법의 그림은 나한테만 아저씨가 덧칠하기 전의 '원래 그림(스냅샷)'을 뿅! 하고 보여준답니다!