(KO) CUBRID Lock Manager — 다중 단위 잠금, 모드 변환, 그리고 데드락 탐지
목차
- 학술적 배경
- DBMS 공통 설계 패턴 (Common DBMS Design)
- CUBRID의 구현
- 소스 코드 가이드
- 소스 검증 (2026-04-29 기준)
- CUBRID 너머 — 비교 설계와 연구 동향 (Beyond CUBRID — Comparative Designs & Research Frontiers)
- 출처
학술적 배경
섹션 제목: “학술적 배경”Lock manager란 데이터베이스 객체에 대한 잠금을 부여하거나 해제해 트랜잭션 사이의 논리적 직렬성을 강제하는 컴포넌트를 의미한다. Database Internals(Petrov, 5장)는 lock과 latch를 신중하게 구분한다. lock은 트랜잭션 데이터의 무결성을 보호한다. 트랜잭션 수명만큼 지속되며, 데이터 구조 바깥에 따로 저장된다. 반면 latch는 물리 구조(예: B-tree 분할 중의 페이지)를 보호하고 짧게만 유지된다. CUBRID에서는 lock manager가 lock 쪽이고, page buffer의 PGBUF latch가 latch 쪽이다.
대표적인 잠금 기반 동시성 제어 프로토콜은 2단계 잠금(2PL, Two-Phase Locking) 이다. 모든 트랜잭션은 잠금을 획득만 하는 성장 단계(growing) 와 해제만 하는 축소 단계(shrinking) 두 단계를 가진다. 잠금이 한 번 해제된 뒤에는 새 잠금을 더 이상 획득할 수 없다는 점이 핵심이다. 2PL은 직렬화 가능한 스케줄을 보장한다. 교과서는 2PL의 구조적 비용도 함께 짚는다. 그 비용이란 경쟁과 데드락이다. 표준 대응책은 세 가지다. 첫째는 timeout이고, 둘째는 보수적 2PL(시작 전에 모든 잠금 획득)이며, 셋째는 트랜잭션 매니저가 유지하면서 주기적으로 사이클을 검사하는 waits-for graph(WFG) 다. 사이클이 발견되면 그 안의 한 트랜잭션을 victim으로 선택해 abort한다([WEIKUM01]). 트랜잭션 타임스탬프에 기반한 회피 기법 (wait-die, wound-wait; [RAMAKRISHNAN03])은 탐지의 대안이다. CUBRID는 WFG 탐지 를 주로 쓰고 timeout을 폴백으로 함께 둔다.
구현을 좌우하는 두 가지 추가 요소가 있다.
- 잠금 모드와 호환성 행렬. 공유(S)·배타(X) 외에, 실제 시스템은
다중 단위 잠금(multi-granularity locking) 을 위한
의도(intention) 모드(IS, IX, SIX)를 추가로 둔다. 굵은 수준(예:
테이블)에서 잠금을 잡는다는 것은 그 아래 더 작은 수준(예: 행)에서
잠금을 잡을 예정이라는 신호다. 의도 모드 덕분에 두 트랜잭션이 같은
테이블의 다른 행을 잠그더라도 테이블 수준에서 거짓 충돌이 일어나지
않는다. CUBRID는 여기에
BU(bulk update),SCH-S/SCH-M(스키마 안정성/변경), 그리고 특수 마커NON_2PL을 더한다. - 잠금 변환. 모드 A의 잠금을 이미 보유한 트랜잭션이 모드 B를
요청하면, 둘의 최소 상한 이 새로 부여된다. 예를 들어
S + IX조합이라면 결과는SIX다. 모드별로 규칙을 검색하는 대신, 정사각형 변환 테이블 한 번의 룩업으로 끝낸다.
이하는 위 두 조각과 WFG 데드락 탐지기가 src/transaction/lock_manager.{h,c}
에서 어떻게 실현되는지를 따라간다.
DBMS 공통 설계 패턴 (Common DBMS Design)
섹션 제목: “DBMS 공통 설계 패턴 (Common DBMS Design)”텍스트북이 2PL, 다중 단위 잠금(MGL, multi-granularity locking),
대기 그래프(waits-for graph)를 준다면, 이 섹션은 거의 모든 DBMS lock
manager가 어떤 형태로든 채택하는 공학적 관행(engineering
conventions) 을 나열한다. Oracle, MySQL InnoDB, SQL Server,
PostgreSQL, CUBRID가 모두 이 패턴을 공유한다. 다음 섹션 ## CUBRID의 구현 은 발명이 아니라, 이 공유 설계 공간에서 선택된 한 조합이라고
읽는 것이 옳다.
잠금(lock)과 래치(latch)의 분리
섹션 제목: “잠금(lock)과 래치(latch)의 분리”잠금은 논리적이다. 트랜잭션에 묶이고, lock table에 따로 보관되며,
직렬화(serialization) 순서에 영구적 효과를 남긴다. 래치는 물리적
이다. 짧게만 유지되고, 페이지나 인메모리 자료구조에 내장되며,
동시성에 의한 손상을 막는다. 거의 모든 현대 DBMS는 이 둘을 모두
갖는다. 둘을 섞으면 잘못된 설계가 나온다는 점이다. 예를 들어 저수준
페이지 작업 중에 논리적 잠금을 들고 있거나, 트랜잭션 상태를 래치로
보호하는 식이다. CUBRID에서는 LK_ENTRY가 잠금 쪽이고, 페이지 버퍼의
PGBUF_LATCH가 래치 쪽이다.
자원 해싱 (Resource hashing)
섹션 제목: “자원 해싱 (Resource hashing)”잠금 가능한 자원은 어떤 객체 식별자(object identifier)를 키로 하는
해시 테이블에 들어 있다. 식별자의 모양은 엔진마다 다르다. CUBRID는
OID (volid, pageid, slotid)를 쓴다. Oracle은 page+slot+xid 범위
(range)를 쓰고, PostgreSQL의 술어 잠금(predicate locking)은
(relation, key range) 서술자(descriptor)를 쓴다. 자원당 상태는
holder 리스트와 waiter 리스트의 쌍으로 표현된다.
집계 모드 캐시 (Aggregate-mode cache)
섹션 제목: “집계 모드 캐시 (Aggregate-mode cache)”단순 호환성 검사는 O(holders) 비용이 든다. 리스트를 순회하면서 모드
교집합을 계산해야 하기 때문이다. 표준 최적화는 자원 레코드 자체에
집계(aggregate) 값을 캐시해 두는 방법이다. 부여된 모든 모드의 합
total_holders_mode와 대기 중인 모든 모드의 합 total_waiters_mode를
저장해 두면, 호환성 검사는 매트릭스 룩업 한 번으로 끝난다. CUBRID가
이 패턴을 쓰며, Oracle은 같은 개념을 consolidated mode 라 부른다.
같은 패턴이 엔진을 가리지 않고 반복된다.
이중 관점 스레딩 (Dual-view threading)
섹션 제목: “이중 관점 스레딩 (Dual-view threading)”같은 잠금 사실은 두 각도에서 질의된다. 하나는 자원별 관점이다(“R을
누가 잡고 있는가?). 다른 하나는 트랜잭션별 관점이다(T가 무엇을
잡고 있는가?”). 가장 깔끔한 구현은 두 리스트에 모두 꿰어 둔 단일
레코드 를 두는 방법이다. lock table은 자원 관점을 색인하고,
트랜잭션별 테이블은 트랜잭션 관점을 색인한다. 단일 구조체 하나가
양쪽 포인터를 모두 보유한다. CUBRID의 LK_ENTRY가 그 정석적 예다.
다중 단위 잠금 + 의도 모드 (MGL + intention modes)
섹션 제목: “다중 단위 잠금 + 의도 모드 (MGL + intention modes)”Gray의 위계: IS, IX, S, SIX, X. 굵은 단위에서 잠금을
잡는다는 것은 곧 그 아래 더 작은 단위에서 잠금을 잡을 의도가 있다는
신호이며, 의도 모드 덕분에 두 트랜잭션이 같은 테이블의 다른 행을
잠궈도 테이블 수준에서 거짓 충돌이 일어나지 않는다. 호환성 행렬은
2×2(S, X)에서 5×5 또는 7×7로 자란다. CUBRID는 어휘를 12개 모드로
확장한다. BU(bulk update), SCH-S / SCH-M(스키마 안정성/변경),
U(update), 그리고 일찍 해제된 잠금을 표시하는 NON2PL 마커.
정사각 변환 테이블 (Conversion via square table)
섹션 제목: “정사각 변환 테이블 (Conversion via square table)”이미 잡고 있는 잠금을 다시 요청할 때는 승격(upgrade) 시켜야 한다.
그렇지 않으면 자기 자신과 데드락에 빠지기 때문이다. 새 모드는 보유
모드와 요청 모드의 최소 상한(least upper bound) 이다. 예를 들어
S + IX = SIX, IS + S = S 같은 식이다. 엔진은 이를 즉석에서
계산하거나, (held, requested)로 색인된 정사각 테이블에서 룩업하는
방식 중 하나를 택한다. CUBRID는 테이블 방식을 택했다.
FIFO 대기 큐 + 기아 방지 (Starvation guard)
섹션 제목: “FIFO 대기 큐 + 기아 방지 (Starvation guard)”holders만 보고 부여하면, 들어오는 S 요청 스트림이 대기 중인 X를
영원히 추월하는 일이 벌어진다. 표준 해법은 부여 검사를 holders ∧
waiters 호환 으로 두는 것이다. 즉 새 요청은 현재 부여된 집계와 현재
대기 중인 집계 양쪽 모두와 호환되어야 한다. CUBRID는 이
규칙을 강제하며, PostgreSQL도 같은 패턴을 쓴다.
데드락 처리: 세 가지 갈래 (Three schools)
섹션 제목: “데드락 처리: 세 가지 갈래 (Three schools)”- Timeout (저렴, false positive 잦음). 거의 모든 엔진(CUBRID 포함)에 폴백(fallback)으로 존재.
- 대기 그래프 사이클 스캔(WFG cycle scan) (CUBRID, MySQL InnoDB). 백그라운드 탐지기가 WFG를 순회해 사이클을 발견하고 victim을 abort 시켜 끊는다. 순서 선택지: 가장 어린, 가장 최근에 차단된, 가장 적게 잠금을 보유한 등.
- 타임스탬프 기반 회피(timestamp-based avoidance) — wait-die, wound-wait. 텍스트북 고전이지만 장기 분석(analytics) 워크로드와 궁합이 나쁘기 때문에 프로덕션에서는 흔치 않다.
잠금 지속 시간 정책 = 격리 수준 (Lock duration = isolation)
섹션 제목: “잠금 지속 시간 정책 = 격리 수준 (Lock duration = isolation)”잠금 획득(acquisition) 메커니즘은 격리 수준에 관계없이 균일하다.
격리는 해제(release) 경로에 인코딩되어 있다. READ COMMITTED 에서 인스턴스 읽기 잠금은 단기 지속(short-duration)이며
statement 종료 시점에 해제된다. 같은 트랜잭션 안에서 같은 레코드를
다시 접근할 때 충돌을 탐지할 수 있도록, 해제된 잠금은 사이드
리스트에 추적된다. CUBRID에서는 그 사이드 리스트가 non2pl 이다.
REPEATABLE READ 와 SERIALIZABLE 에서는 잠금이 장기 지속이며,
커밋까지 유지된다. 클래스 잠금과 스키마 잠금은 격리 수준과 무관하게
항상 장기 지속이다.
이론 ↔ CUBRID 명칭 매핑 (Theory ↔ CUBRID mapping)
섹션 제목: “이론 ↔ CUBRID 명칭 매핑 (Theory ↔ CUBRID mapping)”§학술적 배경의 텍스트북 개념과 CUBRID의 명명된 엔티티가 다음과
같이 대응된다. ## CUBRID의 구현은 각 행에 대한 천천히 줌인.
| 이론 (Theory) | CUBRID 명칭 |
|---|---|
| 잠금 가능한 자원 (lockable resource) | LK_RES, 키는 LK_RES_KEY{type, OID, class_OID} |
| 보유·대기 잠금 인스턴스 | LK_ENTRY (granted_mode + blocked_mode) |
| 잠금 모드 (S / X + 의도 모드) | lock_table.h의 12가지 모드 (NA…SCH-M) |
| 호환성 행렬 (compatibility matrix) | 12×12 (NA, NON2PL, U는 표에서 생략) 룩업, total_holders_mode / _waiters_mode 기준 |
| 잠금 변환 (held + requested의 최소 상한) | lock_table.c의 정사각 변환 테이블, 재요청 시 조회 |
| 트랜잭션별 상태 | LK_TRAN_LOCK (root / class / inst 보유 리스트 + local pool) |
| 전역 레지스트리 (global registry) | lk_global_data (hash → LK_RES, 트랜잭션별 테이블, free list) |
| 대기 그래프 (waits-for graph) | LK_WFG_NODE / LK_WFG_EDGE, 데드락 데몬이 주기적 스캔 |
| 데드락 희생자 (deadlock victim) | 희생자 wait state에 LOCK_RESUMED_ABORTED 설정 |
| 일찍 해제된 잠금 추적 (RC) | non2pl 리스트, NON2PL 모드 마커 |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”CUBRID는 위의 관행을 세 가지 부품으로 구체화한다. 단일 전역
lk_global_data, 트랜잭션별 LK_TRAN_LOCK 테이블, 그리고 자원과
트랜잭션 두 관점에 모두 꿰인 공유 LK_ENTRY 레코드가 그 셋이다.
차별화된 선택은 넷이다. (1) 디스크 레코드 정체성과 정확히 일치하는
OID 형태의 자원 키. (2) Gray의 MGL 위에 BU, SCH-S /
SCH-M, U, NON2PL 을 더한 12 모드 어휘. (3) lock_scan
(클래스 단위의 의도 잠금 — 본문에서는 코드를 별도로 인용하지 않으며,
심볼은 Source Walkthrough에서 다룬다)과 lock_object(인스턴스 단위의
실제 잠금)로 갈라진 두 API 분리. (4) 가장 최근에 차단된 victim
정책을 따르는 주기적 WFG 사이클 스캔, 그리고 폴백으로서의
요청별 timeout.
잠금 요청의 흐름 (How a lock request flows)
섹션 제목: “잠금 요청의 흐름 (How a lock request flows)”쿼리는 lock manager를 두 개의 API 표면 으로 만난다. 단위별로 API가 하나씩 있다. 이 패턴은 의도적으로 갈라 둔 것이다. 먼저 클래스에 의도 잠금(intention lock) 을 걸고, 그 다음 행에 실제 잠금 을 건다.
flowchart LR
Q["query / DML"] --> S["lock_scan(class_oid, mode)\n• SELECT는 IS\n• INSERT/UPDATE/DELETE는 IX\n• SERIALIZABLE SELECT는 S"]
S --> O["lock_object(record_oid, class_oid, mode)\n• SELECT는 S\n• 쓰기는 X"]
O --> R{"holders ∧ waiters\n모두와 호환?"}
R -- "예" --> W["statement 실행"]
R -- "아니오" --> B["suspend → WFG 엣지 추가\n→ 데드락 데몬이 사이클 스캔"]
B --> W
W --> U{"격리 수준?"}
U -- "READ COMMITTED" --> RC["lock_unlock_object(\n move_to_non2pl=true)\n매 statement마다 (인스턴스 잠금)"]
U -- "RR / SERIALIZABLE" --> EOT["커밋/롤백 시 해제\n(strict 2PL)"]
각 박스는 아래 서브섹션에서 차례로 풀어 본다. OID 형태의 자원 계층,
이중 리스트 관점을 지원하는 LK_ENTRY 필드 레이아웃, 12개 모드
어휘와 12×12 호환성 행렬, 획득 상태 머신, 에스컬레이션, WFG 데드락
탐지기 순으로 다룬다. 박스 자체는 움직이지 않으며, 다만 상세도만
깊어질 뿐이다.
자원 계층과 OID 형태의 키
섹션 제목: “자원 계층과 OID 형태의 키”CUBRID에서 잠금이 가능한 모든 객체는 OID = (volid, pageid, slotid)
로 명명된다. 데이터베이스, 클래스, 인스턴스가 모두 여기에 해당한다.
의도 잠금이 사용하는 계층은 이 OID 계층 그 자체다.
flowchart TB DB["데이터베이스 / Root Class\n(OID_Root_class_oid)"] C1["클래스 C_a\n(class OID)"] C2["클래스 C_b"] I1a["인스턴스 I_a1\n(instance OID, class_oid = C_a)"] I1b["인스턴스 I_a2"] I1n["..."] I2a["인스턴스 I_b1"] I2n["..."] DB --> C1 DB --> C2 C1 --> I1a C1 --> I1b C1 --> I1n C2 --> I2a C2 --> I2n
잠금 자원 키, LK_RES, 그리고 획득되었거나 대기 중인 잠금 하나에
해당하는 LK_ENTRY. 이 셋이 핵심 타입이다.
// struct lk_res_key — src/transaction/lock_manager.hstruct lk_res_key{ LOCK_RESOURCE_TYPE type; /* class, instance, root_class */ OID oid; OID class_oid;};
// struct lk_res — src/transaction/lock_manager.hstruct lk_res{ LK_RES_KEY key; /* hash key */ LOCK total_holders_mode; /* aggregate lock mode of all holders */ LOCK total_waiters_mode; /* aggregate of all waiters */ LK_ENTRY *holder; /* per-resource holder list */ LK_ENTRY *waiter; /* per-resource waiter list */ LK_ENTRY *non2pl; /* released-early-but-still-tracked list */ pthread_mutex_t res_mutex; LK_RES *hash_next; /* hash chain */ LK_RES *stack; /* freelist */ UINT64 del_id; /* lock-free reclamation */};
// struct lk_entry — src/transaction/lock_manager.hstruct lk_entry{ struct lk_res *res_head; /* back to the resource */ THREAD_ENTRY *thrd_entry; int tran_index; LOCK granted_mode; LOCK blocked_mode; int count; /* re-entrant request counter */ UINT64 del_id; LK_ENTRY *stack; LK_ENTRY *next; /* per-resource list (holder or waiter) */ LK_ENTRY *tran_next; /* per-transaction list */ LK_ENTRY *tran_prev; LK_ENTRY *class_entry; /* parent class's LK_ENTRY (for instances) */ int ngranules; /* count of finer-grain children below */ int instant_lock_count; int bind_index_in_tran; XASL_ID xasl_id;};
그림 1 — 위쪽은 CUBRID 의 잠금 단위 계층이다. 데이터베이스(루트
클래스) DB 가 클래스 Cₐ, Cᵦ 를 담고, 각 클래스가 인스턴스
Iₐ₁..Iₐₙ, Iᵦ₁..Iᵦₙ 을 담는다. 아래쪽은 그 계층의 어떤 노드
든 동일하게 OID = (volid, pageid, slotid) 레코드로 표현된다는
점을 보여준다. 이 OID 는 db_identifier C 구조체로 뒷받침된다
(int pageid, short slotid, short volid). 그림의 화살표는
인스턴스 Iᵦₙ 이 자신의 OID 로 매핑되는 한 예시지만, 같은 3-튜플
이 데이터베이스, 클래스, 인스턴스 어느 것이든 식별한다는 점이
핵심이다. 잠금 매니저는 이 셋을 한 가지 방식으로 취급한다는 점이
여기서 흘러나온다. (출처: 원본 lock manager 발표 deck, 슬라이드
4-5.)
같은 잠금 상태에 대한 두 가지 시점
섹션 제목: “같은 잠금 상태에 대한 두 가지 시점”같은 LK_ENTRY 하나가 두 개의 다른 리스트에 동시에 묶여 있어, 어느
시점(자원 시점 vs 트랜잭션 시점)에서도 스캔 없이 잠금 상태를 조회할
수 있다.
flowchart LR
subgraph TT["트랜잭션 시점 (LK_TRAN_LOCK)"]
direction TB
R["root_class_hold"]
C["class_hold_list"]
I["inst_hold_list"]
R -.-> C
C -.-> I
end
subgraph RT["자원 시점 (LK_RES, LK_RES_KEY로 해시)"]
direction TB
H["holder list (granted)"]
W["waiter list (blocked)"]
end
E1["LK_ENTRY #1\n(tran 7, X-mode)\nres = R_91"]
E2["LK_ENTRY #2\n(tran 7, IX-mode)\nres = C_a"]
E3["LK_ENTRY #3\n(tran 9, blocked X)\nres = R_91"]
I -- "tran_next/prev" --> E1
C -- "tran_next/prev" --> E2
H -- "next" --> E1
W -- "next" --> E3
tran_next/tran_prev 양방향 연결 리스트는 트랜잭션 단위이며,
root·class·instance 잠금을 세 하위 리스트로 분리해 둔다. 단일 next
포인터는 자원 단위로 holder 또는 waiter를 엮는다. 어느 시점에든 한
LK_ENTRY는 정확히 하나의 자원 리스트(holder 또는 waiter)에, 그리고
정확히 하나의 트랜잭션 리스트에 동시에 들어 있다.

Figure 2 — 이중 관점 스레딩의 실제 모습. 위쪽 줄: 트랜잭션
별 테이블 세 개(LK_TRAN_LOCK 0/1/2)가 각자 자기
inst_hold_list/class_hold_list/root_class_hold로 이어진다.
아래쪽 상자: LK_RES 레코드의 전역 해시 테이블이 레코드마다
holder/waiter 리스트를 따로 들고 있다. 위에서 아래로, 그리고
아래쪽 그룹 안쪽에서 그어진 화살표는 하나의 LK_ENTRY 가 두
관점 모두에서 닿을 수 있음 을 보여준다 — 트랜잭션 쪽에서는
tran_next/tran_prev, 자원 쪽에서는 next 로 도달한다. 커밋 시
해제는 한쪽 리스트만 훑고, 호환성 검사는 다른 쪽만 훑으면 되며,
둘 다 amortized O(1) 에 끝나는 것이 그 이유다. (출처: deck slide
35.)
잠금 테이블 — lk_global_data
섹션 제목: “잠금 테이블 — lk_global_data”전역 단일 구조체다. 다음 항목들을 보유한다.
m_obj_hash_table—LK_RES_KEY(OID 기반)에서LK_RES로의 해시. 해시 함수는pageid와slotid를 결합한 뒤htsize = MAX_NTRANS * 300으로 나머지 연산을 수행한다. 충돌은LK_RES.hash_next로 체이닝한다.tran_lock_table[tran_index]— 트랜잭션 단위LK_TRAN_LOCK.obj_free_entry_list— 재활용용LK_ENTRY전역 free list. 트랜잭션 로컬 풀(lk_tran_lock.lk_entry_pool, 기본 10개)이 비었을 때 사용된다.
flowchart TB HT["m_obj_hash_table\n(LK_RES_KEY → LK_RES)"] TLT["tran_lock_table[]\n(tran_index 별)"] FREE["obj_free_entry_list\n(전역 LK_ENTRY freelist)"] HT --> R0["LK_RES (slot 0)\nholder, waiter, hash_next"] R0 --> R0H["holder LK_ENTRY 체인"] R0 --> R0W["waiter LK_ENTRY 체인"] R0 -. "해시 충돌" .-> R1["LK_RES (slot 0, 체인)"] TLT --> TL0["LK_TRAN_LOCK[0]\n• inst_hold_list\n• class_hold_list\n• root_class_hold\n• lk_entry_pool[10]"] TLT --> TLN["LK_TRAN_LOCK[N]"] R0H -. "동일 LK_ENTRY,\n다른 연결" .-> TL0
잠금 모드(12종)와 호환성 행렬
섹션 제목: “잠금 모드(12종)와 호환성 행렬”CUBRID의 잠금 어휘, 강도 순:
| 값 | 모드 | 의미 |
|---|---|---|
| 0 | NA | 잠금 없음 |
| 1 | NON2PL | 비호환 2PL 마커 (조기 해제됨) |
| 2 | NULL | 자리 표시자 |
| 3 | SCH-S | 스키마 안정성 (DDL이 대기) |
| 4 | IS | intention shared |
| 5 | S | shared |
| 6 | IX | intention exclusive |
| 7 | BU | bulk update |
| 8 | SIX | shared + intention exclusive |
| 9 | U | update (X로 승격할 의도가 있는 S) |
| 10 | X | exclusive |
| 11 | SCH-M | 스키마 변경 |
호환성 행렬(12×12; 아래 9×9 표에는 NA, NON2PL, U가 생략돼 있다)은
정적 룩업 테이블이다. Lock_Manager.pdf의 20번 슬라이드에서:
| NULL | SCH-S | IS | S | IX | BU | SIX | X | SCH-M | |
|---|---|---|---|---|---|---|---|---|---|
| NULL | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SCH-S | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| IS | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ |
| S | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| IX | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ |
| BU | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
| SIX | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| X | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| SCH-M | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
새 요청은 자원의 total_holders_mode 그리고 total_waiters_mode
둘 다와 호환되어야 즉시 부여된다.
- holder 검사는 현재 부여된 잠금과의 충돌을 막는다.
- waiter 검사는 starvation 방지 의 역할을 한다. 현재 holder들과 호환된다는 이유만으로 S 요청들이 대기 중인 X 요청을 새치기하지 못하도록 막아 주기 때문이다.
집계 모드 비트가 holder 리스트의 개별 스캔을 대체해 주므로, 호환성 검사 한 번은 O(1)에 끝난다.
차단된 스레드의 깨어남 사유
섹션 제목: “차단된 스레드의 깨어남 사유”차단된 스레드는 요청별 timeout으로 대기하다가 다음 사유 중 하나로 재개된다.
// enum LOCK_WAIT_STATE — src/transaction/lock_manager.ctypedef enum{ LOCK_SUSPENDED, LOCK_RESUMED, /* 잠금 부여됨 */ LOCK_RESUMED_TIMEOUT, LOCK_RESUMED_DEADLOCK_TIMEOUT, /* 데드락 victim으로 선택됨 */ LOCK_RESUMED_ABORTED, /* 데드락에 의해 abort 필요 */ LOCK_RESUMED_ABORTED_FIRST, LOCK_RESUMED_ABORTED_OTHER, LOCK_RESUMED_INTERRUPT} LOCK_WAIT_STATE;granted vs blocked 엔트리 초기화
섹션 제목: “granted vs blocked 엔트리 초기화”두 헬퍼는 어느 모드 필드를 채우는가에 따라 갈린다.
// lock_initialize_entry_as_granted — src/transaction/lock_manager.cstatic voidlock_initialize_entry_as_granted (LK_ENTRY * entry_ptr, int tran_index, LK_RES * res, LOCK lock){ entry_ptr->tran_index = tran_index; entry_ptr->res_head = res; entry_ptr->granted_mode = lock; entry_ptr->blocked_mode = NULL_LOCK; entry_ptr->count = 1; /* ... 리스트 포인터들 NULL ... */}
// lock_initialize_entry_as_blocked — src/transaction/lock_manager.cstatic voidlock_initialize_entry_as_blocked (LK_ENTRY * entry_ptr, THREAD_ENTRY * thread_p, int tran_index, LK_RES * res, LOCK lock){ entry_ptr->tran_index = tran_index; entry_ptr->thrd_entry = thread_p; entry_ptr->res_head = res; entry_ptr->granted_mode = NULL_LOCK; entry_ptr->blocked_mode = lock; entry_ptr->count = 1; /* ... 리스트 포인터들 NULL ... */}holder 리스트에 들어 있는 엔트리는 granted_mode 가 유의미하다.
waiter는 blocked_mode 가 유의미하다. 변환(conversion) 진행 중인
holder는 두 필드를 모두 사용한다. 이때 granted_mode 는 현재 보유
모드를, blocked_mode 는 대기 중인 승격 모드를 나타낸다.
잠금 획득 — lock_object → lock_internal_perform_lock_object
섹션 제목: “잠금 획득 — lock_object → lock_internal_perform_lock_object”// lock_internal_perform_lock_object — src/transaction/lock_manager.c (annotated excerpt)static intlock_internal_perform_lock_object (THREAD_ENTRY *thread_p, int tran_index, const OID *oid, const OID *class_oid, LOCK lock, int wait_msecs, LK_ENTRY **entry_addr_ptr, LK_ENTRY *class_entry){ /* ... */
start: if (class_oid != NULL && !OID_IS_ROOTOID (class_oid)) { /* 인스턴스 잠금 요청 */ ret_val = lock_escalate_if_needed (thread_p, class_entry, tran_index); /* ... 클래스 잠금이 요청을 흡수하면 granted 반환 ... */ } else { /* 클래스 잠금 요청 — 기존 엔트리부터 찾기 */ entry_ptr = lock_find_class_entry (tran_index, oid); if (entry_ptr != NULL) { res_ptr = entry_ptr->res_head; goto lock_tran_lk_entry; } }
/* 잠금 테이블에서 자원을 찾거나 추가 */ search_key = lock_create_search_key ((OID *) oid, (OID *) class_oid); (void) lk_Gl.m_obj_hash_table.find_or_insert (thread_p, search_key, res_ptr); is_res_mutex_locked = true;
if (res_ptr->holder == NULL && res_ptr->waiter == NULL && res_ptr->non2pl == NULL) { /* 새 자원: 무조건 부여 */ /* ... lock_initialize_resource_as_allocated, granted 엔트리 할당 ... */ } else { /* total_holders_mode | total_waiters_mode 와 호환성 검사 */ /* ... 호환되면 holder 리스트에 splice, 집계 모드 갱신 ... */ /* ... 아니면 waiter 리스트에 splice, 집계 모드 갱신, 스레드 정지 ... */ }}전체 흐름:
flowchart TD
A["lock_object(thread, oid, class_oid, mode)"]
A --> B{"클래스 잠금인가\n인스턴스 잠금인가?"}
B -- "인스턴스" --> C["lock_escalate_if_needed\n(부모 클래스)"]
C --> D{"클래스 잠금이\n요청을 흡수?"}
D -- "예" --> Z1["LK_GRANTED 반환"]
D -- "아니오" --> H
B -- "클래스" --> E{"이 트랜잭션의\n기존 엔트리?"}
E -- "예" --> J["count 증가 / 변환 요청"]
E -- "아니오" --> H["m_obj_hash_table.find_or_insert(LK_RES_KEY)"]
H --> F{"자원이 비어 있는가\n(holder/waiter 모두 NULL)?"}
F -- "예" --> G["새로 부여:\ninitialize_entry_as_granted,\nholder 리스트에 splice"]
G --> Z2["LK_GRANTED 반환"]
F -- "아니오" --> K{"holders|waiters와\n호환?"}
K -- "예" --> L["holder 리스트에 splice,\ntotal_holders_mode 갱신"]
L --> Z3["LK_GRANTED 반환"]
K -- "아니오" --> M["waiter 리스트에 splice,\ntotal_waiters_mode 갱신,\n스레드 정지"]
M --> N{"재개 사유"}
N -- "LOCK_RESUMED" --> Z4["부여됨"]
N -- "LOCK_RESUMED_TIMEOUT" --> Z5["LK_NOTGRANTED_DUE_TIMEOUT"]
N -- "LOCK_RESUMED_ABORTED\n(데드락 victim)" --> Z6["트랜잭션 abort"]

Figure 3 — 잠금 획득의 한 구체적 시나리오. 트랜잭션 10 이
테이블 A 에 X_LOCK 을 요청한다. 해시 테이블에는 이미 테이블 A
에 대한 LK_RES 1 이 있고, holder 두 개가 매달려 있다. tran 7 은
IS_LOCK 을 보유 중이고, tran 11 은 blocked_mode = X_LOCK 으로
대기 중이다. 답은 집계 필드 가 들고 있다. 현재 부여된
IS+IX 의 최소 상한이 total_holders_mode = IX_LOCK 이고,
total_waiters_mode = X_LOCK 이다. 새 X_LOCK 요청은 holder 쪽
호환성도, waiter 쪽 호환성도 통과하지 못해 큐에 적재된다.
wait_msecs == LK_ZERO_WAIT 이면 정지하는 대신
LK_NOTGRANTED_DUE_TIMEOUT 을 반환하고 끝난다. (출처: deck slide
110.)
잠금 해제 — duration과 격리 수준
섹션 제목: “잠금 해제 — duration과 격리 수준”CUBRID는 잠금의 지속 시간(duration) 을 추적하며, 격리 수준에 따라 해제 지점을 다르게 둔다.
- READ COMMITTED 하의 인스턴스 잠금 — 해당 잠금을 획득한 SQL 구문이 끝나자마자 해제된다(short-duration). RC의 잠금 풋프린트를 작게 유지하는 핵심이다.
- REPEATABLE READ / SERIALIZABLE 하의 인스턴스 잠금 — 트랜잭션이 끝나는 시점에 해제된다. 표준 2PL을 따른다.
- 클래스 잠금 — 격리 수준에 관계없이 트랜잭션 종료 시점에 해제된다. 의도 잠금은 자식 잠금보다 오래 살아 남아야 하기 때문이다.
- 스키마 잠금(SCH-S/SCH-M) — 특수 케이스로 처리한다. SCH-M은
NULL외 모든 모드와 충돌한다.
해제 경로는 lock_unlock_object → lock_internal_perform_unlock_object
(둘 다 src/transaction/lock_manager.c).
lock_unlock_object_by_isolation이 격리 수준별 정책을 구현한다.

Figure 4 — lock_internal_perform_unlock_object 의 결정 트리.
release_flag = true 는 트랜잭션 종료 시점에 쓰이는 무조건 해제
경로다. release_flag = false 이면 함수는 재진입 요청(count > 0
이면서 blocked_mode 에 변환 대기가 걸려 있는 경우) 을 먼저
살핀다. 미해제 요청이 더 남아 있으면 잠금은 유지되고 count 만
하나 줄어든다. count == 0 이면서 blocked_mode == NULL_LOCK 일
때라야 엔트리가 holder 리스트에서 빠진다. (출처: deck slide
180.)
잠금 에스컬레이션
섹션 제목: “잠금 에스컬레이션”트랜잭션별 잠금 수가 한계에 다다르면 폭주가 발생할 수 있다. 한
클래스를 인스턴스 잠금이 lock_escalation_threshold 를 넘어서면
lock_escalate_if_needed (src/transaction/lock_manager.c)가 동작한다.
이 함수는 행 단위 잠금들을 단일 클래스 수준 잠금으로 치환하고,
인스턴스 엔트리를 해제한다. 메모리 압력에 대한 안전장치인 셈이다.
LK_ENTRY 가 ngranules(의도 잠금 아래 자식 수)와 class_entry
역참조를 보유하는 것도 이 때문이다.
데드락 탐지 — waits-for graph
섹션 제목: “데드락 탐지 — waits-for graph”CUBRID는 src/transaction/wait_for_graph.{h,c} 에 자리한
waits-for graph(WFG) 를 전용 검사기가 주기적으로 순회한다. 그래프의 두 타입은 lock_manager.c
에 정의되어 있다.
// TWFG (transaction wait-for graph) types — src/transaction/lock_manager.c/* TWFG (transaction wait-for graph) entry and edge */typedef struct lk_WFG_node LK_WFG_NODE;struct lk_WFG_node{ int first_edge; bool candidate; int current; int ancestor; INT64 thrd_wait_stime; int tran_edge_seq_num; bool checked_by_deadlock_detector; bool DL_victim;};
typedef struct lk_WFG_edge LK_WFG_EDGE;struct lk_WFG_edge{ int to_tran_index; int edge_seq_num; int holder_flag; int next; INT64 edge_wait_stime; LK_ENTRY *holder; LK_ENTRY *waiter;};T1이 T2가 보유한 잠금을 기다리며 차단되면, T1의 LK_WFG_NODE 에서
T2로 향하는 LK_WFG_EDGE 가 추가된다. 탐지기
(lock_detect_local_deadlock)는 그래프를 순회해 사이클을 찾는다.
사이클이 발견되면 LK_DEADLOCK_VICTIM(가장 최근에 차단된
트랜잭션)을 골라 LOCK_RESUMED_ABORTED 로 마킹한다. victim의 잠금
획득 루프가 이 abort 플래그를 보고 롤백으로 전파한다.
flowchart LR
subgraph Cycle["데드락 사이클 (T1 ↔ T3)"]
T1["T1\nR1 보유, R2 대기"]
T2["T2\nR2 보유, R3 대기"]
T3["T3\nR3 보유, R1 대기"]
T1 -- "R2 요청 (보유: T2)" --> T2
T2 -- "R3 요청 (보유: T3)" --> T3
T3 -- "R1 요청 (보유: T1)" --> T1
end
DET["lock_detect_local_deadlock\n(주기적)"]
DET -- "WFG에 대한 DFS" --> Cycle
DET --> VICTIM["LK_DEADLOCK_VICTIM\n= 가장 최근에 차단된 트랜잭션\n(예: T3)"]
VICTIM --> ABORT["victim의 lk_entry에\nLOCK_RESUMED_ABORTED 설정"]
local이라는 한정어는 의도적으로 붙여 둔 것이다. 분산 데드락 탐지는 노드 단위 매니저의 범위 밖이라는 점을 분명히 하기 위해서다. 그래프 유지가 늦어지는 경우의 폴백은 timeout 기반 데드락 해소다.

Figure 5 — Wait-For Graph 사이클 탐지의 단계별 모습. 세 트랜잭션
(3, 6, 7) 과 네 개의 엣지가 WFG 를 이룬다. 알고리즘은 DFS 로
각 LK_WFG_NODE.current 와 .ancestor 를 표시해 두고, 이어지는
순회가 ancestor == -2(시작 표지) 를 단 노드로 다시 돌아오면
사이클로 판정한다. 그림 오른쪽의 필드 의미는
lock_detect_local_deadlock 이 실제로 훑는 first_edge,
current, ancestor, to_tran_index, edge_seq_num,
holder_flag, next 필드와 동일하다. (출처: deck slide 160.)
라이프사이클
섹션 제목: “라이프사이클”lock_initialize(src/transaction/lock_manager.c)는 해시 테이블, 트랜잭션 단위 테이블, free entry 리스트, 데드락 탐지 상태를 모두 할당한다.lock_finalize가 그것들을 해제한다.lock_finalize_tran_lock_table이 트랜잭션 측 절반을 처리한다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”심볼명을 anchor로 삼는다. 라인번호가 아니다. CUBRID 소스는 시간이 지나면 변한다. 그에 비해 함수명·struct 태그·enum 태그 같은 심볼은 잘 변하지 않는 안정적인 식별자다. 현재 위치는
git grep -n '<symbol>' src/transaction/으로 찾으면 된다. 이 섹션 끝의 위치 힌트 표는 문서가 마지막으로updated:된 시점에 관찰한 값이며, 시간이 지나면 어긋날 수 있다.
타입 정의 (src/transaction/)
섹션 제목: “타입 정의 (src/transaction/)”struct lk_entry(lock_manager.h) — 획득되었거나 대기 중인 잠금 하나. 자원 리스트와 트랜잭션 리스트에 동시에 묶임.struct lk_res_key(lock_manager.h) — OID 형태의 해시 키.struct lk_res(lock_manager.h) — 자원 단위 상태.total_holders_mode,total_waiters_mode, holder/waiter 리스트.enum LOCK_WAIT_STATE(lock_manager.c) — 깨어남 사유 (LOCK_RESUMED,LOCK_RESUMED_TIMEOUT,LOCK_RESUMED_DEADLOCK_TIMEOUT,LOCK_RESUMED_ABORTED, …).struct lk_WFG_node,struct lk_WFG_edge,struct lk_deadlock_victim(lock_manager.c) — WFG 노드/엣지와 victim 기록.
초기화 (src/transaction/lock_manager.c)
섹션 제목: “초기화 (src/transaction/lock_manager.c)”lock_initialize— 진입점. 아래 헬퍼들을 호출.lock_initialize_tran_lock_table— 트랜잭션 테이블.lock_initialize_object_hash_table—m_obj_hash_table(MAX_NTRANS * 300슬롯).lock_initialize_object_lock_entry_list— 전역LK_ENTRYfree list.lock_initialize_deadlock_detection— WFG 상태.
획득 / 변환 / 해제 (src/transaction/lock_manager.c)
섹션 제목: “획득 / 변환 / 해제 (src/transaction/lock_manager.c)”lock_object— 공개 진입점.lock_object_wait_msecs— timeout이 명시된 변형.lock_internal_perform_lock_object— 실제 작업 (위 발췌 참조).lock_internal_perform_unlock_object.lock_unlock_object.lock_unlock_objects_lock_set—LC_LOCKSET을 위한 일괄 해제.lock_initialize_entry_as_granted/lock_initialize_entry_as_blocked/lock_initialize_entry_as_non2pl— 엔트리 생성자.
에스컬레이션과 데드락 (src/transaction/lock_manager.c)
섹션 제목: “에스컬레이션과 데드락 (src/transaction/lock_manager.c)”lock_escalate_if_needed— 인스턴스 단위에서 클래스 단위로 승급.lock_detect_local_deadlock— WFG 사이클 주기적 스캔.- 파일 상단의
#include wait_for_graph.h— WFG 추상화는src/transaction/wait_for_graph.{h,c}에 있다.
잠금 모드 테이블
섹션 제목: “잠금 모드 테이블”src/transaction/lock_table.h— 호환성/변환 행렬 선언.src/transaction/lock_table.c— 12×12 실제 테이블.
이 개정 시점의 위치 힌트
섹션 제목: “이 개정 시점의 위치 힌트”이 라인번호는 문서가 마지막으로 updated: 된 시점의 관찰값이다. 다른
정의에 도착한다면, 위의 심볼명이 정본이다. 지나가는 길에 표를 갱신해
주면 된다.
| 심볼 | 파일 | 라인 |
|---|---|---|
struct lk_entry | lock_manager.h | 79 |
struct lk_res_key | lock_manager.h | 164 |
struct lk_res | lock_manager.h | 175 |
enum LOCK_WAIT_STATE | lock_manager.c | 180 |
struct lk_WFG_node/_edge/deadlock_victim | lock_manager.c | 256 |
lock_initialize_entry_as_granted | lock_manager.c | 920 |
lock_initialize_entry_as_blocked | lock_manager.c | 940 |
lock_initialize_tran_lock_table | lock_manager.c | 1061 |
lock_initialize_object_hash_table | lock_manager.c | 1121 |
lock_initialize_object_lock_entry_list | lock_manager.c | 1152 |
lock_initialize_deadlock_detection | lock_manager.c | 1180 |
lock_escalate_if_needed | lock_manager.c | 2974 |
lock_internal_perform_lock_object | lock_manager.c | 3223 |
lock_internal_perform_unlock_object | lock_manager.c | 3945 |
lock_initialize | lock_manager.c | 5627 |
lock_finalize_tran_lock_table | lock_manager.c | 5702 |
lock_finalize | lock_manager.c | 5859 |
lock_object | lock_manager.c | 5945 |
lock_object_wait_msecs | lock_manager.c | 6273 |
lock_unlock_object | lock_manager.c | 6630 |
lock_unlock_objects_lock_set | lock_manager.c | 6722 |
소스 검증 (2026-04-29 기준)
섹션 제목: “소스 검증 (2026-04-29 기준)”각 항목은 현재 소스에 대한 사실이며, 원본 분석 자료를 함께 보지 않아도 그 자체로 읽힌다. 끝의 부연은 어떻게 검증되었는지와, 관련이 있을 때 역사적 드리프트 또는 검증의 한계를 적는다. “미해결 질문”은 큐레이터가 해결을 미루고 기록해 둔 갭이다. 차후 독자는 이를 알려진 버그가 아니라 시작점으로 다룬다.
검증된 사실
섹션 제목: “검증된 사실”-
LK_ENTRY,LK_RES_KEY,LK_RES는src/transaction/lock_manager.h의 세 핵심 타입이다. 안정적인 식별자는 심볼명이며, 위 위치 힌트 표의 라인번호는 이 개정의updated:시점에만 유효하다. 포맷팅 변경이나 헤더 추가가 일어 나면 라인번호는 어긋난다. -
잠금 어휘는 12개 모드다 (NA, NON2PL, NULL, SCH-S, IS, S, IX, BU, SIX, U, X, SCH-M).
BU(bulk update)는IS,S,IX,SIX,X,SCH-M과 비호환 —lock_table.c에서 2026-04-29 검증. (현재 빌드에서 bulk-update 경로가 실제로 사용되는지 vs. 레거시인지는 미해결 질문.) -
객체 해시 크기는
MAX_NTRANS * 300, 하드코딩.lock_initialize_object_hash_table(lock_manager.c)에서 2026-04-29 검증. 300은 소스의 리터럴 곱셈자이지 파라미터가 아니다. 튜닝하려면 소스 변경, config 설정 아님. -
트랜잭션별 로컬 엔트리 풀 크기는
LOCK_TRAN_LOCAL_POOL_MAX_SIZE = 10. 2026-04-29 검증.lock_get_new_entry/lock_free_entry가 전역 free list 전에 로컬 풀을 먼저 검색한다. 핫 OLTP 트랜잭션이 공유 free-list 잠금을 건드리지 않고 잠금 수요 대부분을 충족할 수 있게 하는 로컬 우선 룩업 순서가 의미를 가진다. -
LK_ENTRY.class_entry는 다단계 체인이 아닌 한 칸 포인터다. 인스턴스의 경우 부모 클래스의LK_ENTRY를, 클래스의 경우 root class의LK_ENTRY를 가리킨다.lock_internal_perform_lock_object와lock_escalate_if_needed에서 2026-04-29 검증. 빠른 에스컬레이션을 위한 설계이지 계층 순회를 위한 것이 아니다. -
차단된 스레드의 깨어남 사유는
enum LOCK_WAIT_STATE(lock_manager.c)에 정의되어 있다. 확인된 멤버:LOCK_RESUMED,LOCK_RESUMED_TIMEOUT,LOCK_RESUMED_DEADLOCK_TIMEOUT,LOCK_RESUMED_ABORTED,LOCK_RESUMED_INTERRUPT. 2026-04-29 검증. -
데드락 victim 정책은 가장 최근에 차단된 트랜잭션이다.
lock_detect_local_deadlock을 읽어 2026-04-29 검증. 명명된 상수가 아니라 WFG에 노드를 삽입하는 순서에서 암묵적으로 결정 되며, 원본 분석 자료에도 직접 명시되어 있지 않다. (미해결 질문 §1 — 이것이 의도된 정책인지, 아니면 코드가 만들어 낸 부수 효과(side effect)인지는 아직 검증되지 않았다.)
미해결 질문
섹션 제목: “미해결 질문”-
가장 최근에 차단된 정책은 의도된 것인가, WFG 삽입 순서가 만든 부수 효과(side effect)인가? 소스 코드는 이 순서를 암묵적으로 산출하며, 정책 선택을 명시한 주석은 없다. 추적 경로:
src/transaction/wait_for_graph.{h,c}의 WFG 설계 의도를 읽고,lock_detect_local_deadlock주변의 CBRD 티켓 흐름을 확인. -
분산 데드락.
lock_detect_local_deadlock은 의도적으로 local이다. CUBRID 분산 구성(HA / shard load balancing)에서 노드 간 데드락을 timeout으로 처리할 가능성이 높다. heartbeat / CDC 분석을 가져올 때 함께 검증할 가치가 있다. -
NON2PL의 정확한 프로토콜.lock_initialize_entry_as_non2pl은READ COMMITTED하에 조기 해제된 잠금을 다른 트랜잭션이 직렬화 목적으로 여전히 보고 싶을 때의 마커로 쓴다. 정확한 handshake — 어느 트랜잭션이 언제 non2pl을 검사하며, non2pl 엔트리가 충돌에서 이기면 무엇이 일어나는지 — 는 unlock 경로 로 추적할 가치가 있다. -
MVCC와의 상호작용. 순수 SI는 read-read나 read-write 충돌 에 잠금이 필요 없지만, CUBRID는 쓰기 경로에서 인스턴스 잠금을 여전히 획득한다. MVCC 가시성과 잠금 기반 직렬화의 경계 (특히
SERIALIZABLE하에서)는 가장 가치 있는 후속 — 이 분석 을cubrid-mvcc.md와 잇는다. -
bulk-update 경로의 활성도.
BU는 어휘와 행렬에 있지만, bulk-update 코드 경로가 현재 빌드에서 실제로 사용되는지 아니면 레거시가 되었는지는 미상. 추적 경로:git log -S BU_LOCK -- src/transaction/과loaddb류 ingest 의 런타임 trace.
CUBRID 너머 — 비교 설계와 연구 동향 (Beyond CUBRID — Comparative Designs & Research Frontiers)
섹션 제목: “CUBRID 너머 — 비교 설계와 연구 동향 (Beyond CUBRID — Comparative Designs & Research Frontiers)”분석이 아닌 포인터(pointers). 각 항목은 후속 문서의 시작점이며, 깊이는 의도적으로 얕다.
- Bamboo: 잠금 조기 해제(releasing locks early). Guo et al.
(2021) — 후속 독자(reader)가 재검증(re-validation) 가능한 경우,
배타 잠금을 커밋 전에 해제해 경합(contended) OLTP의 tail latency
를 줄인다. CUBRID의
NON2PL기계는 이미 RC에서 일찍 해제된 잠금을 충돌 탐지용으로 추적하는데, Bamboo는 그 패턴을 일반화한 다. - Brook-2PL: 데드락 없는 2PL. 정적 의존성 순서(static dependency ordering)를 사전 분석하여 데드락을 원천적으로 불가능하게 만든다. 대가는 사전 분석 비용 — 안정적이고 반복되는 워크로드에서만 실용적.
- TXSQL: 경합 워크로드용 잠금 최적화. 적응형 잠금 모드 조정,
배칭(batching), 경합 인지 스케줄링. CUBRID의
lock_escalate_if_needed와 교차 수분(cross-pollination) 가능성. - 중앙 lock manager 없는 OCC. Hekaton, Silo, Cicada는 lock table을 제거 — 커밋 시점의 검증(validation)이 획득을 대체. 다른 설계 점이며, 인메모리(in-memory) 워크로드와 고 코어 수에서의 OCC vs 2PL 논쟁과 관련.
- VLL — Very Lightweight Locking. 파티셔닝(partitioning)으로 잠금 획득 비용을 낮춘다. lock table 자체가 경합 핫스팟이 될 때 유용한 패턴.
- HTM 보조 lock manager. 하드웨어 트랜잭셔널 메모리(hardware transactional memory)가 짧은 트랜잭션의 fast-path가 되어 잠금 획득 자체를 회피. 프로덕션에서는 아직 niche 이지만 OLTP에서 탐색 되어 왔다.
- 직렬화 가능성을 위한 술어 잠금 (PostgreSQL SSI). CUBRID의 SERIALIZABLE 하에서는 S / IX 테이블 잠금으로 폴백 접근에 대한 대안. 비용 비교가 어느 쪽이 어느 워크로드에서 저렴한지를 명확히 해 준다.
- 잠금 에스컬레이션의 형식 모델. KAIST DBLAB(Chang 2005)이
메모리 압박 완화와 에스컬레이션이 야기하는 의도치 않은 직렬화
사이의 트레이드오프를 형식화한다. CUBRID의
lock_escalation_threshold튜닝과 직접적 관련. - 관련 최근 연구 흐름 (Notion의 Storage – Concurrency 개요 에서 발췌): Cahill et al., Serializable Snapshot Isolation in PostgreSQL(2008/2012); Yu et al., Staring into the Abyss (VLDB 15); Wu et al., 실증 MVCC(VLDB 17); Bamboo(2021); Brook-2PL(2025); TXSQL(2025).
이 섹션의 의도는 다음 문서들의 씨앗을 뿌리는 것이지, 여기서 분석 하는 것이 아니다. 각 항목은 차례가 오면 자체 큐레이트 노트가 되어야 한다.
원본 분석 (raw/code-analysis/cubrid/storage/lock_manager/)
섹션 제목: “원본 분석 (raw/code-analysis/cubrid/storage/lock_manager/)”Lock_Manager.pdf— 주된 자료. 잠금 모드, 해시 테이블, 자원/엔트리 레이아웃, 호환성/변환 행렬.lock manager_발표.pdf와.pptx— 호환성/변환 단계별 예시 (예: 혼합 holder들 사이로 도착하는S요청).LOG_TDES_LOCK_Data.pdf— 트랜잭션 디스크립터 측에서 본lk_global_data,lk_tran_lock,lk_entry,lk_res와 그 관계를 서술.
교재 챕터 (knowledge/research/dbms-general/)
섹션 제목: “교재 챕터 (knowledge/research/dbms-general/)”- Database Internals (Petrov), 5장: §Locks vs Latches (≈ 4404 행), §Two-Phase Locking (≈ 4321행), §Deadlocks (≈ 4344행) — waits-for graph, wait-die / wound-wait 회피 기법 포함.
- 1장 §Lock manager (≈ 777행) — DBMS 아키텍처 안에서의 컴포넌트 역할.
Notion (CUBRID DEV WIKI)
섹션 제목: “Notion (CUBRID DEV WIKI)”- Storage – Concurrency 코드 분석 — Lock Manager · MVCC · Vacuum의 모듈 단위 구도.
- Lock Manager 코드 분석 — 자료구조·초기화·잠금 획득 흐름 —
LK_GLOBAL_DATA,LK_TRAN_LOCK그리고lock_internal_perform_lock_object상태 머신의 권위 있는 출처. §정신적 모델의 이중 관점(dual-view) 프레이밍이 여기서 증류되었다. - Lock Manager 코드 분석 — Isolation Level별 잠금 동작 —
lock_unlock_object의 격리 수준 분기. §잠금 요청의 흐름에서 격리 수준은 정책(policy)이지 메커니즘(mechanism)이 아니다라는 주장의 근거. - Lock Manager 코드 분석 — Deadlock Detection / Lock Escalation / Lock Suspend·Resume / Composite Lock / Instant Lock Mode / non2pl 리스트 상세 / 디버깅 가이드 / lock_scan() — 같은 워크스페이스의 하위 서브시스템 분석 챕터들 (이 문서에서 참조 깊이만 줄여 인용).
CUBRID 소스 (/data/hgryoo/references/cubrid/)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”src/transaction/lock_manager.hsrc/transaction/lock_manager.csrc/transaction/lock_table.hsrc/transaction/lock_table.csrc/transaction/wait_for_graph.h(데드락 탐지 추상화)src/transaction/wait_for_graph.c(데드락 탐지 구현)