병행 제어 (Concurrency Control)
핵심 인사이트 (3줄 요약)
- 본질: 병행 제어 (Concurrency Control)는 여러 트랜잭션이 동시에 데이터베이스에 접근할 때 데이터의 일관성과 무결성을 보장하는 메커니즘이다.
- 가치: 적절한 병행 제어를 통해 시스템 처리량(throughput)을 높이고 응답 시간을 단축시키면서도 데이터의 정확성을 유지할 수 있다.
- 융합: 오프 클럭 (Optimistic) vs 페시밀이스트 (Pessimistic) 제어, 그리고 분산 환경에서의 시계indow-based 및 벡터 시계 기법이 결합되어 진화하고 있다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
개념 정의
병행 제어 (Concurrency Control)는 여러 사용자가 동시에 데이터베이스에 접근하여操作할 때, 트랜잭션의 고립성을 보장하고 데이터 일관성을 유지하는 기법이다.
병행 접근이 필요한 이유
병행 제어가 없으면 시스템은 심각한 데이터 불일치 문제를 겪게 된다.
┌─────────────────────────────────────────────────────────────────────┐
│ 병행 제어 없이는 발생하는 문제점 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [예시: 두 명이 동시에 예매하는 항공권 시스템] │
│ │
│ 트랜잭션 A: 고객 甲의 예매 요청 처리 │
│ 트랜잭션 B: 고객 乙의 예매 요청 처리 │
│ │
│ 상황: 남은 좌석 = 1석 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 시간 │ 트랜잭션 A │ 트랜잭션 B │ 좌석 수 │ │
│ │ ───┼────────────────────┼────────────────────┼─────── │ │
│ │ T1 │ SELECT 좌석 → 1 │ │ 1 │ │
│ │ T2 │ │ SELECT 좌석 → 1 │ 1 │ │
│ │ T3 │ UPDATE 좌석 = 0 │ │ 0 │ │
│ │ T4 │ (COMMIT) │ │ 0 │ │
│ │ T5 │ │ UPDATE 좌석 = 0 │ 0!! │ │
│ │ T6 │ │ (COMMIT) │ 0 │ │
│ │ │ │ │ │
│ │ │ 두 트랜잭션 모두 좌석 1 → 0으로 읽음 → 최종 좌석 = 0 │ │
│ │ │ 그러나 실제 좌석은 1석인데 두 명 다 예약 → 중복 예매! │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 결과: 좌석 1개에 두 명의 승객이 예약됨 → 시스템 신뢰도 현저히 저하 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] 위 상황은 lost update 또는 double booking 문제로 불린다. 두 트랜잭션이 동시에 좌석 수를 읽고 각각 UPDATE하면, 첫 번째 UPDATE 결과가 덮어씌워져 한 번의 UPDATE만 반영된 것처럼 보인다. 이 문제를 해결하려면, 한 트랜잭션이 좌석 데이터를 읽고 있는 동안 다른 트랜잭션이 해당 행을 修改하지 못하도록 잠금 (Lock)을 설정해야 한다.
비유
병행 제어 없이는 교차로 신호등 없는 상태와 같다. 두 방향의 차가 동시에 통과하면 충돌이 발생하지만, 신호등이 있으면 한쪽이 양보하면서 순차적으로 안전하게 교차로를 지날 수 있다.
- 📢 섹션 요약 비유: 레스토랑에서 여러 웨이트리스가 같은 테이블의 주문을 동시에 받으면 주문이 중복되거나 누락될 수 있듯이, 병행 제어 없이는 데이터베이스에서 중복 작업과 누락이 발생합니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
병행 제어 문제의 유형
동시 실행 시 발생할 수 있는 네 가지 대표적인 문제가 있다.
| 문제 | 영문명 | 설명 | 예시 |
|---|---|---|---|
| 갱신 손실 | Lost Update | 두 트랜잭션이 동일 데이터를 읽고 각각 갱신, 한 결과가 손실 | 좌석 중복 예매 |
| ** Dirty Read** | Dirty Read | 커밋되지 않은 변경을 다른 트랜잭션이 읽음 | 계좌 잔액 롤백 후 기준 미달 읽기 |
| ** Non-Repeatable Read** | Non-Repeatable Read | 동일 트랜잭션에서 동일 데이터를 다시 읽을 때 값이 다름 | 상품 가격 조회 후 구매 사이에 가격 변동 |
| ** 유령 읽기** | Phantom Read | 동일 조건으로 조회할 때 이전에 없던 행이 나타남 | 订单 목록 조회 후 새 주문 추가 |
잠금 기반 병행 제어 (2PL)
2단계 잠금 프로토콜 (Two-Phase Locking, 2PL)은 가장 대표적인 페시밀이스트 (悲観적) 병행 제어 기법이다.
┌─────────────────────────────────────────────────────────────────────┐
│ 2PL (Two-Phase Locking) 동작 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [잠금 확장 단계 (Growing Phase)] │
│ → 필요한 잠금을 점점 추가해 나가는 단계 │
│ │
│ [잠금 수축 단계 (Shrinking Phase)] │
│ → 잠금을 해제하기 시작하면 더 이상 새로운 잠금을 획득 불가 │
│ │
│ 예시: 계좌 이체 트랜잭션 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Growing: │ │
│ │ ① 계좌 A에 대해 X-lock 요청 → 획득 │ │
│ │ ② 계좌 B에 대해 X-lock 요청 → 획득 │ │
│ │ ③ 이체 작업 수행 │ │
│ │ │ │
│ │ Shrinking: │ │
│ │ ④ 계좌 A_unlock → X-lock 해제 │ │
│ │ ⑤ 계좌 B_unlock → X-lock 해제 │ │
│ │ │ │
│ │ ⚠ 규칙: Shrinking 시작 후에는 새로운 잠금 획득 불가! │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] 2PL의 핵심은 잠금 확장 단계에서 잠금 수축 단계로 전환되면, 이후에는 새로운 잠금을 획득할 수 없다는 것이다. 이렇게 함으로써 트랜잭션들이 서로 교착 상태 (Deadlock)에 빠지는 것을 방지하고, 스케줄 (실행 순서)의 직렬 가능성 (Serializability)을 보장한다. X-lock (배타적 잠금)은 데이터를 변경하려는 트랜잭션이 획득하며, 다른 트랜잭션의 읽기/쓰기를 모두 차단한다.
공유/배타적 잠금 (S/X Lock)
읽기와 쓰기에 대해 각각 다른 잠금 모드를 적용하여 동시성을 높이는 기법이 공유/배타적 잠금이다.
┌─────────────────────────────────────────────────────────────────────┐
│ 공유/배타적 잠금 호환성 매트릭스 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ │ S-lock (공유) │ X-lock (배타적) │
│ ───────────┼─────────────────┼──────────────────── │
│ S-lock │ 허용 │ 차단 │
│ (공유) │ (동시 읽기 OK) │ (동시 쓰기 불가) │
│ ───────────┼─────────────────┼──────────────────── │
│ X-lock │ 차단 │ 차단 │
│ (배타적) │ (읽기+쓰기 불가) │ (동시 쓰기 불가) │
│ ───────────┼─────────────────┼──────────────────── │
│ │
│ [행동 예시] │
│ │
│ T1: SELECT 잔액 FROM 계좌 (S-lock 획득) → 다른 T도 읽기 가능 │
│ T2: SELECT 잔액 FROM 계좌 (S-lock 획득) → T1과 동시 읽기 OK │
│ T3: UPDATE 잔액 = 500 (X-lock 대기 → T1, T2解锁 후 획득) │
│ │
│ [잠금Granularity에 따른 분류] │
│ │
│ • 数据库 레벨 잠금: 데이터베이스 전체에 잠금 → 동시성最低 │
│ • 테이블 레벨 잠금: 테이블 전체에 잠금 │
│ • 페이지/익스텐트 레벨 잠금: 디스크 페이지 단위 잠금 │
│ • 행 레벨 잠금: 행 하나에 잠금 → 동시성最高 (오버헤드도 높음) │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] S-lock (공유 잠금)은 여러 트랜잭션이 동시에 획득할 수 있어서 동시 읽기가 가능하다. 반면 X-lock (배타적 잠금)은 하나의 트랜잭션만 획득할 수 있어 읽기/쓰기 모두를 차단한다. T1이 S-lock을持有的 동안 T3가 X-lock을 요청하면, T3는 차단 (BLOCK)된다. T1과 T2가 모두 S-lock을持有的 동안 T3가 X-lock을 요청하면, T3는 T1과 T2가 모두 unlock할 때까지 기다려야 한다. 잠금의粒度가 작을수록 동시성은 높아지지만, 잠금 관리 오버헤드(메모리, CPU)도 증가한다.
MVCC (Multi-Version Concurrency Control)
MVCC는 잠금을 사용하지 않고 고른 읽기를 가능하게 하는 오프 클럭 (낙관적) 병행 제어 기법이다.
┌─────────────────────────────────────────────────────────────────────┐
│ MVCC 동작 원리 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ MVCC 핵심 개념: │
│ → 데이터를 수정할 때 기존 버전은 유지하고, 새 버전을 생성 │
│ → 읽기는 특정 버전(트랜잭션 시작 시점)를 기준으로 수행 │
│ │
│ [예시: 계좌 잔액 읽기] │
│ │
│ 시간 │ 操作 │ 버전들 │
│ ───┼────────────────┼──────────────────────────────── │
│ T1 │ 초기 상태 │ [v1: 잔액=1000] │
│ T2 │ T1이 읽음(1000)│ [v1: 잔액=1000] ← T1이 보는 버전 │
│ T3 │ UPDATE +100 │ [v1: 잔액=1000] [v2: 잔액=1100] │
│ T4 │ T1이 다시 읽음 │ [v1: 잔액=1000] [v2: 잔액=1100] │
│ │ │ ↑ T1은 여전히 v1을 읽음 │
│ T5 │ T1이 COMMIT │ │
│ │
│ [READ COMMITTED vs REPEATABLE READ in MVCC] │
│ │
│ READ COMMITTED: 매 읽기마다 최신 커밋된 버전 선택 │
│ REPEATABLE READ: 트랜잭션 시작 시점의 버전 선택 │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] MVCC에서 UPDATE는 기존 행을 덮어쓰지 않고 새 버전을 만든다. 이렇게 하면 읽기 트랜잭션이 예전 버전을 보고 있는 동안, 쓰기 트랜잭션이 새 버전을 쓸 수 있다. 잠금을 획득하려고 대기하는 대신, 그냥 예전 버전을 읽으면 되므로 읽기 쓰기 충돌이 없다. 각 행에는 트랜잭션 ID (트랜잭션이 시작/종료한 시점)를記録하는 필드가 있어, 버전 선택에 사용된다. PostgreSQL과 MySQL (InnoDB)이 MVCC를 지원한다.
- 📢 섹션 요약 비유: MVCC는图书馆에서 같은 책의 이전 版과 새 版을 모두 보유하면서, 독자에게는自己喜欢的版을 빌려주는 것과 같습니다. 다른 사람이 새 版을 읽고 있어도, 이전 版을 기다릴 필요 없이 바로 이전 版을 받을 수 있습니다.
Ⅲ. 융합 비교 및 다각도 분석
비교: 2PL vs MVCC
| 구분 | 2PL (잠금 기반) | MVCC (다중 버전) |
|---|---|---|
| 접근 방식 | 페시밀이스트 (悲観적) | 오프 클럭 (낙관적) |
| 읽기 동작 | 공유 잠금 필요 | 버전 선택으로 잠금 불필요 |
| 쓰기 동작 | 배타적 잠금 필요 | 새 버전 생성 |
| 동시성 | 낮음 (읽기-쓰기 차단) | 높음 (읽기-쓰기 동시 가능) |
| 메모리 오버헤드 | 낮음 | 높음 (버전 관리) |
| 대표 DB | Oracle (이전 버전), DB2 | PostgreSQL, MySQL InnoDB |
| Dirty Read | 방지 | 방지 |
| Non-Repeatable Read | 방지 | 방지 |
비교: 페시밀이스트 vs 오프 클럭 제어
| 구분 | 페시밀이스트 (Pessimistic) | 오프 클럭 (Optimistic) |
|---|---|---|
| 전제 | 충돌이 자주 발생한다고 가정 | 충돌은 드물다고 가정 |
| 동작 | 작업 전에 잠금 획득 | 일단 실행, 완료 시 검증 |
| 실패 시 | 잠금 대기 (블로킹) | ROLLBACK 및 재시도 |
| 적합 상황 | 쓰기 충돌 빈번 | 읽기 중심, 충돌 드묾 |
| 성능 특성 | 충돌 시 대기 시간 증가 | 충돌 시 재시도 비용 |
- 📢 섹션 요약 비유: 페시밀이스트는 "문제가 생길 것 같으니 미리 준비하세요"이고, 오프 클럭은 "문제는 거의 없으니 일단 해보고 문제가 생기면 그때 해결하세요"입니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 항공 좌석 예매: 페시밀이스트 (2PL/X-lock)이 적합하다. 좌석이라는 자원이有限이고 충돌이 빈번하므로, 쓰기 전에 좌석을 배타적으로 잠그고 처리를 완료하는 것이 안전하다.
-
시나리오 — 대시보드 조회: MVCC가 적합하다. 주로 읽기操作이고, 약간의延迟가 허용되므로 잠금 없이 빠르게 읽는 것이 성능상 유리하다.
┌─────────────────────────────────────────────────────────────────────┐
│ 병행 제어 전략 선택 가이드 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [선택 기준: 충돌 빈도 vs 응답 시간] │
│ │
│ 충돌 빈도 高 + 응답 시간 중요 ───▶ 2PL (페시밀이스트) │
│ 예: 재고 관리, 좌석 예매, 결제 처리 │
│ │
│ 충돌 빈도 低 + 응답 시간 중요 ───▶ MVCC (오프 클럭) │
│ 예: 분석 쿼리, 대시보드,Reporting │
│ │
│ [잠금Granularity 선택 기준] │
│ │
│ • 단일 행 수정이 대부분 → 행 레벨 잠금 (동시성 MAX) │
│ • 배치 일괄 수정 → 테이블 레벨 잠금 (오버헤드 MIN) │
│ • 섞인 경우 →.Dynamic granularity (적응형) │
│ │
└─────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] 병행 제어 전략은 workload 특성에 따라 달라져야 한다. 좌석 예매처럼 충돌이 잦은 환경에서는 사전에 잠금을 획득하는 2PL이 적합하고, 분석 쿼리처럼 충돌이 드문 환경에서는 대기 시간 없는 MVCC가 유리하다. 잠금 granularity도 중요하며, Oracle의 Automatic Segment Space Management처럼 동적으로 조정하는 기능도 있다.
도입 체크리스트
- 기술적: 동시 접속 예상 수와 평균 작업 시간을 산정했는가? 잠금 초과(timeout) 설정과 데드락 감지 메커니즘을 갖추었는가?
- 운영·보안적: 장시간 활성 트랜잭션을 모니터링하여 잠금 경합을 분석하고 있는가?
안티패턴
-
데드락 (Deadlock): 두 트랜잭션이 서로가 가진 잠금을 기다리며 무한 대기하는 상태. 방지 방법: 잠금 순서规范化, 타임아웃 설정.
-
기아 (Starvation): 특정 트랜잭션이 계속 잠금 대기만 하고 실행되지 않는 상태.
-
📢 섹션 요약 비유: 서로 양보하지 않는 두 사람이 좁은 문 앞에서 서로를 기다리며 막히는 것과 같아서, 데드락을防止하려면 잠금 순서를 약속으로 정해두는 것이 필요합니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 병행 제어 없음 | 적절한 병행 제어 | 개선 효과 |
|---|---|---|---|
| 정량 | 좌석 중복 예매율 10~30% | 0% | 데이터 정확성 100% |
| 정량 | 동시 사용자 10명 | 동시 사용자 1000명+ | 확장성 100x+ |
| 정성 | 데이터 불일치 常見 | 데이터 일관성 보장 | 신뢰성大幅 향상 |
미래 전망
- Adaptive Locking: AI가 workload 패턴을 학습하여 잠금 granularity와 방식을동적으로 조정
- Distributed MVCC: 분산 환경에서 글로벌 트랜잭션 순번 (Google Spanner의 TrueTime)을利用した全順序保证
┌─────────────────────────────────────────────────────────────────────┐
│ 병행 제어 기술의 진화 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1970s: 2PL (단순하지만 데드락 문제) │
│ │ │
│ ▼ │
│ 1980s: MVCC 등장 (Oracle, PostgreSQL의 기반) │
│ │ │
│ ▼ │
│ 2000s: Generalized MVCC +自适应 granular locking │
│ │ │
│ ▼ │
│ 2010s~: 분산 환경에서의 MVCC (Google Spanner의 TrueTime) │
│ │
└─────────────────────────────────────────────────────────────────────┘
- 📢 섹션 요약 비유: 처음에는 한 번에 하나만乘坐할 수 있는 놀이기구가 있었고 (2PL), 나중에는 같은 기구를 타면서도 각각 다른 차에서乘坐할 수 있는 새로운 기구가 발명되었습니다 (MVCC).
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 잠금 (Lock) | 병행 제어를実現하는 기본 메커니즘으로, S-lock과 X-lock이 있다. |
| 2PL | Two-Phase Locking으로, Growing/Shrinking 두 단계로 잠금을 관리하는 프로토콜이다. |
| MVCC | Multi-Version Concurrency Control로, 잠금 없이 고른 읽기를 가능하게 한다. |
| 직렬 가능성 (Serializability) | 병행 실행 결과가 어떤 직렬 실행 결과와 동일한 성질이다. |
| 데드락 (Deadlock) | 두 개 이상의 트랜잭션이 서로가 가진 잠금을 기다리며 무한 대기하는 상태이다. |
| 격리 수준 (Isolation Level) | 고립성의 수준을 설정하는 것으로, READ UNCOMMITTED에서 SERIALIZABLE까지 있다. |
👶 어린이를 위한 3줄 비유 설명
- 병행 제어,就像是여러 명이 동시에 같은 칠판에 글을 쓸 때, 누가 어느 부분을 언제 쓸지 규칙이 있는 것과 같습니다.
- 만약 규칙이 없으면 한 명이 글자를 쓰는 동안 다른 명이 지우면 아무것도 남지 않아요.
- 병행 제어는 "이 사람은 1번부터 10번까지, 저 사람은 11번부터 20번까지"라고 영역을 나눠주어, 서로 부딪히지 않게 해줘요!