(KO) CUBRID Locator — OID 워크스페이스, 벌크 fetch/flush, 그리고 서버 측 INSERT/UPDATE/DELETE 게이트웨이
목차
학술적 배경
섹션 제목: “학술적 배경”디스크에 닿는 관계형 엔진은 두 개의 전혀 다른 어휘 사이를 잇는
가교를 들고 있어야 한다. 아래쪽 계층 — heap manager, B-tree, page
buffer, log — 은 물리 주소 의 언어로 말한다. 어떤 레코드는
어떤 볼륨의 어떤 파일의 어떤 페이지의 몇 번 슬롯이라는, 매우 구체
적인 좌표다. CUBRID에서는 이 좌표를 OID = (volid, pageid, slotid)
로 부르고, PostgreSQL은 TID = (blocknumber, offsetnumber), Oracle은
ROWID라고 부른다는 점이다. 위쪽 계층 — 실행기, 스키마 연산, 카탈
로그, 클라이언트와 주고받는 네트워크 프로토콜 — 은 논리적 객체
의 언어로 말한다. 컬럼 값을 가진 행, 속성을 가진 클래스, 정체성을
가진 인스턴스가 그 어휘다. 이 두 언어 사이를 누군가는 통역해야 하며,
CUBRID에서 그 일을 맡은 컴포넌트의 이름이 locator 다.
이 통역기가 풀어야 하는 교과서 문제는 Database Internals(Petrov)의 3장 “File Formats 와 4장 Implementing B-Trees” 에서 객체 정체성 (object identity) 이라고 부르는 것이다. 즉, 페이지가 컴팩션 되어 도 살아남는 식별자, 같은 파일 안에서 페이지 사이를 옮겨 다녀도 유지되는 식별자(forwarding pointer), 그리고 인덱스 leaf에 박아 넣 어도 단 하나의 heap slot으로 정확히 안내해 주는 식별자가 필요하다는 점이다. OID는 그 결과로 만들어지는 산출물이고, locator는 OID를 만들고(새 레코드를 삽입할 때), 해석하고(fetch가 행 본문을 읽어 야 할 때), 변형하는(UPDATE/DELETE가 적용될 때) 일을 모두 책임지 는 계층이다. Stonebraker의 POSTGRES(1986)와 EXODUS storage manager (Carey & DeWitt, 1986)가 객체 지향 DB에서 이 계층의 표준 모양을 처음 제시했고, 그쪽 동네에서는 object manager 라고 불렀다. 오늘 날 CUBRID는 관계형 엔진이지만 이름에는 객체/관계 하이브리드 시스템 으로 출발했던 흔적이 남아 있다는 점이다.
이 그림을 완성하는 두 가지 교과서적 재료가 더 있다.
-
워크스페이스 패턴. Database Systems: The Complete Book (Garcia-Molina, Ullman, Widom)의 §10.6 “Object-Oriented Database Systems” 는 워크스페이스를 애플리케이션이 손댄 객체들을 모아 두는 인메모리 캐시 로 정의한다는 점이다. 읽으면 객체가 워크스페 이스로 들어오고, 쓰면 dirty 로 표시되며, commit 시점에 dirty 집합 전체가 한 번에 디스크로 flush된다. ObjectStore와 GemStone 같은 객체 저장소들이 이 패턴을 중심 설계 결정으로 채택했고, CUBRID 클라이언트가 오늘날까지도 MOP(Memory Object Pointer) ↔ OID 매핑을 들고 다니는 이유가 여기에 있다는 점이다. 모던 관계형 클라이언트는 더 이상 이런 워크스페이스를 들고 다니지 않는다는 점에서 이는 차별화된 선택이다.
-
벌크 fetch / 벌크 flush 대 행 단위 API. N개의 행을 만지면서 매번 서버로 왕복(round-trip) 하는 클라이언트는 N × RTT를 지불하 게 된다. 워크스페이스 기반 클라이언트는 트랜잭션 당 대략 한 번 의 RTT만 지불하는 방식이다. 즉, commit 시점에 dirty 집합 전체 를 하나의 버퍼에 담아 한꺼번에 보낸다. 이 버퍼(CUBRID의
LC_COPYAREA)는 N개의 객체에 대한 헤더와 N개의 행 본문을 이어 붙여 운반하며, 서버는 그것을 풀어 트랜잭션 범위의 단일 top-op 안에서 OID별로 디스패치한다는 점이다. 같은 모양은 분산 트랜잭션 모니터(X/Open XA), bulk-load API(PostgreSQLCOPY, MySQLLOAD DATA), 그리고 commit 직전에session.flush()를 부르는 ORM에서도 동일하게 등장한다.
이 문서는 CUBRID가 두 부분 — 객체 워크스페이스(locator_cl.c)와 서
버 측 fan-in(locator_sr.c) — 을 어떻게 구현했는지, 그리고 그 둘
을 묶는 와이어 모양(locator.c 의 LC_COPYAREA)이 어떻게 생겼는지
를 따라간다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”교과서가 모델을 준다면, 이 절은 거의 모든 행 지향 엔진이 어떤 형
태로든 채택하는 공학적 관행 들을 호명한다는 점이다. 다음 절
## CUBRID의 구현 의 구체적 선택은 이 공유 설계 공간 안에서 어느
다이얼을 어디에 맞췄는가로 읽는 편이 정확하다.
locator에서 만나는 세 계층
섹션 제목: “locator에서 만나는 세 계층”모든 DBMS는 행 이 무엇인가를 합의해야 하는 세 계층을 갖 는다.
- 객체/행 계층 (실행기, 파서, 타입 검사기, 카탈로그) 은 완전히
디코딩된 값으로 말한다.
DB_VALUE,PT_NODE, 컬럼 오프셋이 파싱된RECDES같은 것이다. - 저장 계층 (heap, btree, page buffer) 은 OID 와 그 OID 가 가리키는 원시 바이트 배열 로 말한다. 어디에 바이트가 사는 지는 알지만, 그 바이트가 무엇을 의미하는지는 모른다는 점이다.
- 횡단 서비스 (lock manager, MVCC, log, vacuum, HA 복제, 외래 키 체커) 는 OID 와 클래스 메타데이터로 말한다.
표준적인 INSERT 하나가 해야 할 일을 풀어 보면 이렇다. (1) heap에 페이지를 찾으라 한다. (2) OID 를 할당한다. (3) 그 OID 를 X-lock 으 로 잠근다. (4) 행 바이트를 쓴다. (5) 영향 받는 모든 B-tree 를 갱 신한다. (6) unique 제약을 검사한다. (7) 외래 키를 검사한다. (8) 손 댄 페이지마다 로그 레코드를 쓴다. (9) HA 가 켜져 있으면 복제 레 코드를 만든다. (10) 카탈로그 통계를 올린다. 이 일들 중 어느 한 계층도 다른 계층의 일을 다 알아야 할 이유는 없다. 누군가가 지휘 자(conductor) 를 맡아야 한다는 점이다. CUBRID 에서 그 지휘자가 locator 다.
Postgres — 별도 워크스페이스가 없다
섹션 제목: “Postgres — 별도 워크스페이스가 없다”PostgreSQL 에서는 같은 역할이 heap_insert, heap_update,
heap_delete 와 ExecInsertIndexTuples, ON-CONFLICT 기계로 흩어
져 있다. 실행기가 이들을 직접 호출하며, 모던(post-Berkeley)
PostgreSQL 에는 클라이언트 측 워크스페이스가 없다는 점이다. 클라
이언트가 보낸 튜플은 Bind/Execute 메시지의 ASCII/binary 매개변수로
서버에 전달되고, 서버가 그것을 buffer pool 안에서 HeapTuple 로
실체화한다. 즉, PostgreSQL 의 실행기 자체가 locator 역할을 하며,
디스패치는 암묵적으로 일어난다.
MySQL — handler 인터페이스
섹션 제목: “MySQL — handler 인터페이스”InnoDB 와 다른 storage engine 들은 ha_innobase(추상 handler
클래스의 구현체) 뒤에 숨는다. 행은 ha_write_row, ha_update_row,
ha_delete_row 로 들고 난다. 배치 UPDATE 를 위한
ha_bulk_update_row 가 있긴 하지만, 계약이 테이블 핸들 단위 이
지 트랜잭션 단위 가 아니다. 워크스페이스 비슷한 것이
있다면 row buffer 에서 디코딩된 Field* 배열 정도이고, 이는 statement
단위에 머문다.
Oracle — row source + dirty buffer write-behind
섹션 제목: “Oracle — row source + dirty buffer write-behind”Oracle 의 실행기는 row source 들을 만들어 DML operator 로 흘려
보내고, 실제 변경은 kdusru / kdusrf 가 수행한다. buffer cache
는 행 단위로 dirty 가 되며, background 의 DBWn 이 비동기로 디스크
에 쓴다. fetch 는 row source 를 통한 행 단위이고, 벌크 경로는 PL/SQL
의 FORALL ... BULK COLLECT 로 문법적 수단일 뿐 저장 계층 자체
가 벌크인 것은 아니다.
CUBRID — 명시적 클라이언트 워크스페이스 + 서버 측 fan-in
섹션 제목: “CUBRID — 명시적 클라이언트 워크스페이스 + 서버 측 fan-in”CUBRID 가 워크스페이스를 그대로 들고 다니는 이유는 객체-관계 시스 템(전신인 UniSQL 은 OODBMS 였다)으로 출발한 혈통에 있다. 시스템이 완전히 관계형으로 바뀐 뒤에도 클라이언트가 워크스페이스를 버리지 않은 이유는 다음과 같다.
- 같은 프로토콜이 C API(
src/compat/의db_*함수들)를 떠받 친다. 거기서 애플리케이션의 객체는 프로세스 메모리 안에서 살아 간다 — MOP 은 애플리케이션이 오랫동안 들고 있는 포인터다. - 카탈로그 자체가 locator 로 객체로 읽힌다. root class 와 클래스별 시스템 레코드가 모두 MOP 이라, 워크스페이스 기계는 자연스럽게 카탈로그 캐시 역할도 겸한다는 점이다.
- 단독 모드(SA_MODE) 에서는 클라이언트와 서버가 같은 프로세스로 컴파일되며, 워크스페이스가 그 사이 경계가 된다.
서버 쪽에서는 이에 대응하는 것이 locator_sr.c 의 force
패밀리 다. locator_attribute_info_force 가 정식 진입점이며,
실행기(qexec_execute_*), 트리거 기계, 스키마 조작기, 타입 검사
기, 파티션 프루너 가 결국 모두 이 함수로 모인다. 이 함수가
locator_insert_force / locator_update_force / locator_delete_force
로 디스패치하고, 그 함수들 이 heap, btree, FK, unique 검사, 복제,
log 를 올바른 순서로 몰고 간다는 점이다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론 (Theory) | CUBRID 명칭 |
|---|---|
| 레코드별 식별자 | OID = (volid, pageid, slotid) |
| 객체에 대한 메모리 포인터 | MOP (Memory Object Pointer) — 불투명 핸들 |
| MOP → OID 매핑 | 워크스페이스 해시 (ws_* API; ws_oid (mop)) |
| 영구화 전 임시 식별자 | temp OID — OID_ISTEMP (oid) |
| 워크스페이스 dirty 리스트 | ws_* dirty 리스트, locator_mflush 가 순회 |
| 벌크 fetch 버퍼 | LC_COPYAREA (locator.h) |
| 벌크 fetch 요청 | LC_LOCKSET / LC_LOCKHINT |
| 버퍼 안의 객체별 디스크립터 | LC_COPYAREA_ONEOBJ |
| 워크스페이스 mflush 스테이징 | LOCATOR_MFLUSH_CACHE (locator_cl.c) |
| 클라이언트 fetch 진입점 | locator_fetch_object / _class / _instance / _set |
| 클라이언트 flush 진입점 | locator_flush_class / _instance / _all_instances / locator_force |
| 서버 fetch 진입점 | xlocator_fetch / _lockset / _lockhint_classes |
| 서버 force 진입점 (전송) | xlocator_force |
| 서버 정식 DML 진입점 | locator_attribute_info_force |
| 서버 op 별 force | locator_insert_force / locator_update_force / locator_delete_force |
| 서버 제약 오케스트레이션 | locator_add_or_remove_index / locator_update_index / locator_check_foreign_key |
| 서버 스냅샷-aware 읽기 | locator_get_object / locator_lock_and_get_object |
| 카탈로그(클래스) 룩업 | xlocator_find_class_oid |
| OID 사전 발급 | xlocator_assign_oid / xlocator_assign_oid_batch |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”CUBRID 의 locator 는 세 얼굴을 가진다. 클라이언트 측(locator_cl.c)
에서는 워크스페이스 + 벌크 flush 코디네이터 다. MOP-OID 매핑을 유
지하고, 워크스페이스의 dirty 리스트를 살피고, dirty 객체들을
LC_COPYAREA 에 묶어 서버로 보낸다는 점이다. 서버 측(locator_sr.c)
에서는 DML fan-in 이다. 시스템의 모든 INSERT/UPDATE/DELETE 가, 누가
시작했든, locator_attribute_info_force → locator_{insert,update,delete}_force
를 통과하며, 이 경로가 heap + btree + FK + unique + log + 복제를 올바
른 순서로 굴린다는 점이다. 그 사이(locator.c + locator.h)에서는
프로토콜 계층 이다. LC_COPYAREA, LC_LOCKSET, LC_LOCKHINT 구조
체가 워크스페이스를 직렬화하는 와이어 모양이다.
차별화된 선택은 셋이다. (1) 워크스페이스가 명시적 이다 —
PostgreSQL 처럼 카탈로그 캐시에 녹아들어 있는 것이 아니라, 트랜잭션
경계를 넘어서도 살아 있는 별도 자료구조다. (2) 벌크 flush 는 한 트랜
잭션의 모든 dirty 객체를 하나의 버퍼에 담고, 서버 측에서 단일 top-op
안에서 디스패치하여 원자성을 확보한다는 점이다. (3) 서버의 정식 진입
점은 하나의 함수 (locator_attribute_info_force) 이며, 세 갈래의 독
립 코드 경로를 두는 대신 LC_COPYAREA_OPERATION 에 대한 switch 로
구현한다. 횡단 작업(잠금 획득, 스냅샷 읽기, 클래스 OID 해석, 파티션
프루닝)을 정확히 한 곳에 모아 둘 수 있다는 점이 이득이다.
DML 한 문장이 끝에서 끝까지 흐르는 방식
섹션 제목: “DML 한 문장이 끝에서 끝까지 흐르는 방식”flowchart LR
A["애플리케이션:\nINSERT/UPDATE/DELETE"] --> B["compat 계층\n(db_∗)"]
B --> C["워크스페이스:\nMOP 찾기/생성,\ndirty 표시"]
C --> D{"commit 인가?"}
D -- "아니오" --> E["MOP 을\n애플리케이션에 반환"]
D -- "예 (또는 명시 flush)" --> F["locator_mflush\n(워크스페이스 순회)"]
F --> G["LC_COPYAREA 패킹\n(LC_COPYAREA_MANYOBJS\n· N × LC_COPYAREA_ONEOBJ\n· 행 본문)"]
G --> H["wire: net_client_request_recv_copyarea"]
H --> I["서버: xlocator_force\n(전송 진입점)"]
I --> J["객체별 디스패치:\nlocator_attribute_info_force\n또는 인라인 switch"]
J --> K{"연산?"}
K -- "INSERT" --> KI["locator_insert_force"]
K -- "UPDATE" --> KU["locator_update_force"]
K -- "DELETE" --> KD["locator_delete_force"]
KI --> L["heap_insert_logical"]
KU --> L2["heap_update_logical"]
KD --> L3["heap_delete_logical"]
L --> M["btree_update\nlocator_add_or_remove_index"]
L2 --> M2["locator_update_index\n(old/new attr_info diff)"]
L3 --> M
M --> N["btree_check_unique\nFK 검사\nrepl_log_insert (HA)\nlog_append (WAL)"]
M2 --> N
L --> N
L2 --> N
L3 --> N
N --> Z["LC_COPYAREA 가\n최종 OID 와 함께\n반환"]
Z --> ZZ["클라이언트: ws_update_oid_and_class\n(temp OID → perm OID)"]
각 박스는 아래 서브섹션에서 풀어 본다. 한 가지 주의가 있다. 실행
기 경로 — 서버에서 INSERT INTO ... SELECT 나 UPDATE ... WHERE
를 수행하는 쿼리 실행 경로 — 는 xlocator_force 를 거치지 않는다는
점이다. 실행기는 attr_info 구조체를 서버 내부에서 직접 만들고
locator_attribute_info_force 를 곧장 호출한다. xlocator_force 진입
점은 클라이언트 주도 경로(워크스페이스 flush)에 한정된다. 두 경
로는 모두 locator_attribute_info_force → locator_*_force 에서 만
난다.
워크스페이스 모델 — locator_cl.c
섹션 제목: “워크스페이스 모델 — locator_cl.c”워크스페이스(ws)의 구현은 src/object/work_space.c 에 있고,
locator_cl.c 는 워크스페이스와 서버 사이를 오가는 다리 역할
을 한다. 워크스페이스의 핵심 자료구조는 MOP 의 해시다.
// MOP — src/object/work_space.h (sketch)struct db_object // typedef MOP{ OID oid; /* server OID; OID_ISTEMP until flushed */ MOP class_mop; /* MOP of the class object */ void *object; /* in-memory decoded object (MOBJ) */ unsigned dirty:1; /* needs flush */ unsigned deleted:1; /* logical delete */ unsigned no_objects:1; /* class with no instances cached */ /* ... */};MOP 은 애플리케이션이 문 사이를 가로질러 오래 들고 다닐 수 있는 핸들이다. 질의에서 반환되어 다시 다른 행을 따라 항해할 때 길잡이 가 된다는 점이다. locator 가 MOP 와 OID 사이 경계에서 통역을 해 준 다.
Fetch — 서버에서 워크스페이스로 객체 끌어오기
섹션 제목: “Fetch — 서버에서 워크스페이스로 객체 끌어오기”공개 진입점(locator_cl.h):
// locator_cl.h — fetch entriesextern MOBJ locator_fetch_object (MOP mop, DB_FETCH_MODE purpose, LC_FETCH_VERSION_TYPE fetch_version_type);extern MOBJ locator_fetch_class (MOP class_mop, DB_FETCH_MODE purpose);extern MOBJ locator_fetch_class_of_instance (MOP inst_mop, MOP *class_mop, DB_FETCH_MODE purpose);extern MOBJ locator_fetch_instance (MOP mop, DB_FETCH_MODE purpose, LC_FETCH_VERSION_TYPE fetch_version_type);extern MOBJ locator_fetch_set (int num_mops, MOP *mop_set, DB_FETCH_MODE inst_purpose, DB_FETCH_MODE class_purpose, int quit_on_errors);extern MOBJ locator_fetch_nested (MOP mop, DB_FETCH_MODE purpose, int prune_level, int quit_on_errors);다섯 진입점은 범위(scope) 가 다르다. _object 는 한 MOP, _class
는 클래스 MOP, _class_of_instance 는 인스턴스 MOP 으로부터 클래스
를 역산하는 경로, _set 은 MOP 의 벡터, _nested 는 속성 참조를
지정된 깊이까지 따라 들어간다. 다섯 모두 결국 locator_lock(단일
MOP) 또는 locator_lock_set(벡터) 으로 모이며, 거기서 LC_LOCKSET
을 빌드해 서버의 xlocator_fetch_lockset 으로 한 번 왕복한다.
벡터 형태가 따로 존재하는 이유는 prefetch 다. 한 MOP 이 여러
다른 MOP (예: 인덱스가 가리키는 여러 인스턴스를 가진 클래스)을
참조하는 상황에서 MOP 마다 왕복하면 N × RTT 가 되기 때문이다.
fetch_set 은 그 대신 워크스페이스가 서버에 “이 MOP 들을 다 쓸
거다 — 한 버퍼에 담아 보내라” 라고 요청하게 한다는 점이다. 서버는
하나의 LC_COPYAREA 에 다수의 LC_COPYAREA_ONEOBJ 디스크립터와
본문을 묶어 답장한다.
flowchart LR WS["워크스페이스"] -->|"N개 MOP\n에서 miss"| FS["locator_fetch_set(N, [mop_1..mop_N])"] FS --> LS["LC_LOCKSET 빌드\n(N개 reqobjs)"] LS --> NET["wire: net_client_request_2recv_copyarea"] NET --> SRV["서버: xlocator_fetch_lockset"] SRV --> HEAP["heap_get_visible_version × N"] HEAP --> CA["LC_COPYAREA 에\nN개 ONEOBJ 패킹"] CA --> NET2["wire: 응답"] NET2 --> CACHE["locator_cache:\nONEOBJ 마다\nMOP 에 기록,\ndirty 비트 클리어,\nchn 설정"] CACHE --> DONE["N개 MOP 모두\n워크스페이스에"]
Mflush — flush 를 위해 dirty 객체 묶기
섹션 제목: “Mflush — flush 를 위해 dirty 객체 묶기”flush 경로는 fetch 의 출력 측 쌍둥이다. 핵심 자료구조는 다음과 같다.
// LOCATOR_MFLUSH_CACHE — src/transaction/locator_cl.cstruct locator_mflush_cache{ LC_COPYAREA *copy_area; /* staging buffer */ LC_COPYAREA_MANYOBJS *mobjs; /* N-objects descriptor */ LC_COPYAREA_ONEOBJ *obj; /* current ONEOBJ slot */ LOCATOR_MFLUSH_TEMP_OID *mop_toids; /* MOPs whose OID is temp */ LOCATOR_MFLUSH_TEMP_OID *mop_uoids; /* MOPs being repartitioned */ MOP mop_tail_toid; MOP mop_tail_uoid; MOP class_mop; /* class of last mflushed obj */ MOBJ class_obj; /* its decoded class */ HFID *hfid; /* its heap */ RECDES recdes; /* current record body */ bool decache; /* drop after flush */ bool isone_mflush; /* single-object mflush */};flush 는 워크스페이스의 dirty 리스트에 대한 map 으로 구동된다.
stateDiagram-v2 [*] --> UNFETCHED : 워크스페이스 miss UNFETCHED --> FETCHED : locator_fetch_* FETCHED --> DIRTY : locator_update_instance / add_instance / remove_instance DIRTY --> FLUSHING : ws_map_dirty + locator_mflush FLUSHING --> FLUSHED : locator_mflush_force 성공 FLUSHED --> FETCHED : 다음 연산에서 FLUSHED --> FREED : decache 또는 프로세스 종료 DIRTY --> FREED : 명시적 decache (드물다) FETCHED --> FREED : eviction
locator_mflush 의 패킹 루프는 다음과 같이 돌아간다.
- dirty MOP 마다
LC_COPYAREA_ONEOBJ디스크립터를 채운다.operation은LC_FLUSH_INSERT/LC_FLUSH_UPDATE/LC_FLUSH_DELETE중 하나,flag는LC_FLAG_HAS_INDEX,LC_FLAG_HAS_UNIQUE_INDEX,LC_FLAG_TRIGGER_INVOLVED,LC_FLAG_UPDATED_BY_ME같은 비트의 조합이다.hfid는 클래스의 heap,class_oid는 클래스 OID,oid는 행 OID(임시일 수 있다),length/offset은 행 본문이 버퍼 안 어디에 있는지를 가리 킨다. - 행 본문은
locator_mem_to_disk(인스턴스) 또는locator_class_to_disk(클래스)가 인코딩한다. 둘 다 스키마 / primitive 계층을 호출해 인메모리 객체를 원시RECDES로 직렬화한다는 점이다. - 버퍼가 넘치면 그 자리에서
locator_mflush_force를 호출해 현재 내용을 서버로 비워낸 뒤, 버퍼를 리셋하고 넘쳤던 객체부터 루프를 이어 간다. - MOP 이 임시 OID 가 있으면
mop_toids에 기록해 둔다. flush 결과로 서버가 부여한 영구 OID 가 응답으로 돌아오면, 그 시점에ws_update_oid_and_class가 워크스페이스를 패치한다 는 점이다.
locator.h 에 정의된 와이어 모양:
// LC_COPYAREA — src/transaction/locator.hstruct lc_copy_area{ char *mem; /* the buffer */ int length; /* size */};
// LC_COPYAREA_MANYOBJS — at the END of the buffer, growing backwardstruct lc_copyarea_manyobjs{ LC_COPYAREA_ONEOBJ objs; /* first object descriptor */ int multi_update_flags; /* IS / START / END_MULTI_UPDATE */ int num_objs;};
// LC_COPYAREA_ONEOBJ — one per object, packed N-wise at endstruct lc_copyarea_oneobj{ LC_COPYAREA_OPERATION operation; /* LC_FLUSH_INSERT/UPDATE/DELETE/etc */ int flag; /* LC_FLAG_HAS_INDEX | ... */ HFID hfid; /* heap file id */ OID class_oid; /* class OID */ OID oid; /* row OID (may be temp) */ int length; int offset; /* offset of body in buffer */};이 버퍼는 양방향 으로 자란다. 행 본문은 mem 의 앞쪽에서 뒤
로, LC_COPYAREA_ONEOBJ 디스크립터는 끝쪽(LC_COPYAREA_MANYOBJS)
에서 거꾸로 자라는 식이다. locator.h 의 매크로 묶음이 디스크립터
를 순회할 때 쓰인다.
// locator.h — descriptor walk macros#define LC_MANYOBJS_PTR_IN_COPYAREA(copy_areaptr) \ ((LC_COPYAREA_MANYOBJS *) ((char *)(copy_areaptr)->mem \ + (copy_areaptr)->length \ - DB_SIZEOF(LC_COPYAREA_MANYOBJS)))#define LC_START_ONEOBJ_PTR_IN_COPYAREA(manyobjs_ptr) (&(manyobjs_ptr)->objs)#define LC_NEXT_ONEOBJ_PTR_IN_COPYAREA(oneobj_ptr) ((oneobj_ptr) - 1)디스크립터는 obj->offset 으로 행 본문을 가리키며, 본문 길이는
obj->length 다. 양 끝이 가운데(mflush->recdes.data)로 동시에 접
근하다 만나면 그것이 곧 한도(watermark) 이므로, 두 번 패스 없이도
패킹을 끝낼 수 있다는 점이다.
flowchart LR
subgraph CA["LC_COPYAREA 버퍼"]
direction LR
HEAD["행 본문 0\n행 본문 1\n행 본문 2"]
GAP["… 빈 영역 …"]
DESC2["ONEOBJ 2"]
DESC1["ONEOBJ 1"]
DESC0["ONEOBJ 0"]
META["LC_COPYAREA_MANYOBJS\n(num_objs, flags)"]
HEAD --> GAP --> DESC2 --> DESC1 --> DESC0 --> META
end
세 가지 flush 진입점
섹션 제목: “세 가지 flush 진입점”// locator_cl.h — flush entriesextern int locator_flush_class (MOP class_mop);extern int locator_flush_instance (MOP mop);extern int locator_flush_all_instances (MOP class_mop, bool decache);extern int locator_flush_for_multi_update (MOP class_mop);extern int locator_all_flush (void);locator_flush_instance 는 명시적 호출이다. 애플리케이션이나 상
위 계층이 commit 이전에 in-memory 변경을 바깥에 보이게 하고 싶을
때 부른다. locator_flush_class 와 _all_instances 는 더 넓은
sweep 이다. locator_all_flush 는 commit 경로가 부르는 것으로,
워크스페이스의 모든 파티션을 돌며 dirty 한 모든 것을 밀어 낸다는
점이다. locator_flush_for_multi_update 는 UPDATE 한 문에서 행
당 여러 번의 update 가 발생할 수 있는 경우(트리거, FK cascade)를
위한 특별 경로다. LC_COPYAREA_MANYOBJS.multi_update_flags 에
START_MULTI_UPDATE / END_MULTI_UPDATE 마커를 함께 실어 보낸다.
내부적으로는 모두 locator_mflush_initialize →
ws_map_dirty(locator_mflush, mflush) → locator_mflush_force →
locator_force (와이어 송신)로 모인다. 마지막 단계에서
net_client_request_recv_copyarea 가 서버의 xlocator_force 를
호출한다.
OID 의 생애 주기
섹션 제목: “OID 의 생애 주기”OID 의 생애에는 세 단계가 있다. 임시(temp) — 워크스페이스가 내부에서 발급한 sentinel 로, 서버는 아직 모른다는 점이다. 할당(assigned) — 서버가 heap slot 에 OID 를 묶었다는 점이다. 해석(resolved) — 서버가 행 본문까지 확인했다는 점이다.
클라이언트의 임시 OID 발급
섹션 제목: “클라이언트의 임시 OID 발급”db_create 가 새 인스턴스를 만들 때 워크스페이스는 임시 OID 를
하나 찍는다. OID_ISTEMP 가 true 인 sentinel 값으로, 실제
(volid, pageid, slotid) 튜플이 아니다. MOP 은 LC_FLUSH_INSERT
연산으로 dirty 리스트에 들어간다. 이 시점에는 서버와의 통신이
없다는 점이다.
서버의 영구 OID 발급
섹션 제목: “서버의 영구 OID 발급”flush 시점에 서버의 locator_insert_force 가 heap_insert_logical
을 호출한다. heap manager 가(자세한 동작은 cubrid-heap-manager.md
참고) 대상 페이지를 찾고 슬롯을 할당하며, 그 슬롯 id 가 영구
OID 가 된다. 새 OID 는 응답 버퍼의 LC_COPYAREA_ONEOBJ.oid 필드에
실려 돌아오고, 응답을 받은 locator_mflush_force 의 후처리가
mop_toids 를 돌며 ws_update_oid_and_class 로 MOP 을 다시 매핑
한다는 점이다.
행 본문이 쓰이기 전에 OID 가 먼저 알려져야 하는 드문 경우 — 가령
자기 자신을 참조해야 하는 카탈로그 엔트리 — 를 위해
xlocator_assign_oid 가 있다.
// xlocator_assign_oid — src/transaction/locator_sr.cintxlocator_assign_oid (THREAD_ENTRY *thread_p, const HFID *hfid, OID *perm_oid, int expected_length, OID *class_oid, const char *classname){ if (heap_assign_address (thread_p, hfid, class_oid, perm_oid, expected_length) != NO_ERROR) return ER_FAILED;
if (classname != NULL) locator_permoid_class_name (thread_p, classname, perm_oid); return NO_ERROR;}heap_assign_address 는 REC_ASSIGN_ADDRESS placeholder 만 들어 있는
슬롯을 할당한다(자세한 동작은 cubrid-heap-manager.md). OID 는
존재하고, 행 본문은 나중에 채워진다는 점이다.
배치 처리 — CREATE TABLE 한 번이 카탈로그 행 여럿을 한꺼번에 만드는
경우 — 에는 xlocator_assign_oid_batch 가 같은 일을 다중 OID 를
한 번의 왕복으로 처리한다. LC_OIDSET / LC_CLASS_OIDSET
(locator.h)이 그 요청 자료구조다.
OID 해석
섹션 제목: “OID 해석”// LC_FETCH_VERSION_TYPE — src/transaction/locator.htypedef enum { LC_FETCH_CURRENT_VERSION = 0x01, /* latest committed, no lock */ LC_FETCH_MVCC_VERSION = 0x02, /* visible to my snapshot */ LC_FETCH_DIRTY_VERSION = 0x03, /* updatable: S-lock + dirty */ LC_FETCH_CURRENT_VERSION_NO_CHECK = 0x04, /* skip server-side checks */} LC_FETCH_VERSION_TYPE;version-type 노브는 fetch 의 lock 정책 + visibility 정책이다. 헤더 에는 어느 값이 어느 상황에 맞는지가 길게 주석으로 적혀 있다.
SELECT읽기에는 MVCC version — lock 없음, 스냅샷 visibility, reader does not block writer 의 자리다.SELECT FOR UPDATE와 존재성 검사에는 dirty version — S-lock 을 잡고, 스냅샷이 못 보는 최신 커밋된 버전을 반환한다는 점이다. 잠금이 동시 삭제를 막아 준다.- 호출자가 이미 X-lock 을 들고 있을 때는 current version — 잠금 획득 비용을 아끼고, 추가 검사 없이 최신 커밋된 버전을 그대로 읽 는다.
서버의 xlocator_fetch 는 fetch_version_type 을 보고 그에 맞는
MVCC_SNAPSHOT 을 만들어 heap 계층으로 넘긴다.
force 패밀리 — 서버 측 fan-in
섹션 제목: “force 패밀리 — 서버 측 fan-in”force 패밀리에서 locator 는 더 이상 전송 계층이 아니라 지휘자 가
된다. 정식 진입점은 locator_attribute_info_force 다.
// locator_attribute_info_force — src/transaction/locator_sr.c (signature)intlocator_attribute_info_force (THREAD_ENTRY *thread_p, const HFID *hfid, OID *oid, HEAP_CACHE_ATTRINFO *attr_info, ATTR_ID *att_id, int n_att_id, LC_COPYAREA_OPERATION operation, int op_type, HEAP_SCANCACHE *scan_cache, int *force_count, bool not_check_fk, REPL_INFO_TYPE repl_info, int pruning_type, PRUNING_CONTEXT *pcontext, FUNC_PRED_UNPACK_INFO *func_preds, MVCC_REEV_DATA *mvcc_reev_data, UPDATE_INPLACE_STYLE force_update_inplace, RECDES *rec_descriptor, bool need_locking);시그니처만 봐도 책임이 드러난다. 이 함수는 attr-info 묶음
(HEAP_CACHE_ATTRINFO. cubrid-heap-manager.md 의 AttrInfo cache
참고)과 LC_COPYAREA_OPERATION 을 받아서 연산값에 따라 디스
패치 한다. 본문은 switch (operation) 하나다.
// locator_attribute_info_force — body sketchswitch (operation) { case LC_FLUSH_UPDATE: case LC_FLUSH_UPDATE_PRUNE: case LC_FLUSH_UPDATE_PRUNE_VERIFY: /* (1) Read the existing row using the right MVCC discipline */ if (HEAP_IS_UPDATE_INPLACE (force_update_inplace) || !need_locking) scan = heap_get_last_version (thread_p, &context); else scan = locator_lock_and_get_object (thread_p, oid, &class_oid, ©_recdes, scan_cache, X_LOCK, COPY, NULL_CHN, LOG_ERROR_IF_DELETED); old_recdes = ©_recdes; /* fallthrough */
case LC_FLUSH_INSERT: case LC_FLUSH_INSERT_PRUNE: case LC_FLUSH_INSERT_PRUNE_VERIFY: /* (2) Encode attr_info + (for UPDATE) old_recdes into a new RECDES */ copyarea = locator_allocate_copy_area_by_attr_info (thread_p, attr_info, old_recdes, &new_recdes, -1, LOB_FLAG_INCLUDE_LOB); if (LC_IS_FLUSH_INSERT (operation)) error_code = locator_insert_force (thread_p, &class_hfid, &class_oid, oid, &new_recdes, true, op_type, scan_cache, force_count, pruning_type, pcontext, func_preds, UPDATE_INPLACE_NONE, NULL, false, false); else /* LC_FLUSH_UPDATE */ error_code = locator_update_force (thread_p, &class_hfid, &class_oid, oid, old_recdes, &new_recdes, has_index, att_id, n_att_id, op_type, scan_cache, force_count, not_check_fk, repl_info, pruning_type, pcontext, mvcc_reev_data, force_update_inplace, need_locking); break;
case LC_FLUSH_DELETE: error_code = locator_delete_force (thread_p, &class_hfid, oid, true, op_type, scan_cache, force_count, mvcc_reev_data, need_locking); break; }내재화해 둘 만한 세 가지 사실이 있다.
-
UPDATE 경로가 INSERT 경로로 흘러 들어간다. 소스에서 보이는 C 의
[[fallthrough]]가 그것이다. UPDATE 는 “old 읽고 + new 인코딩 + 적용 이고, INSERT 는 그 중 new 인코딩 + 적용” 만이라 는 점이다. 인코딩 단계를 공유함으로써 두 경로의 의미가 어긋나 지 않는다. -
lock 은 heap 이 아니라 여기서 잡힌다. 행이 X-lock 을 받느 냐 마느냐는
need_locking과force_update_inplace가 결정한 다는 점이다. 일반적인 실행기 주도 UPDATE / DELETE 에서는 행이 이미 SELECT 단계에서 X-lock 잡혀 있다(실행기가S_DELETE/S_UPDATE를locator_lock_and_get_object를X_LOCK으 로 부른다). 그래서 force 경로는 잠금을 건너뛴다. 워크스페이스 주도 경로(서버가 X-lock 잡힌 적 없는 객체를 클라이언트가 flush 한 경우)에서는 force 안의locator_lock_and_get_object가 잠금 을 직접 잡는다는 점이다. lock manager 분석 문서 (cubrid-lock-manager.md)가 lock 은 locator 로 흐른다 라 고 적은 설계상의 이유가 여기에 있다. -
MVCC 환경에서 UPDATE/DELETE 는 스냅샷을 참조하지만 INSERT 는 참조하지 않는다. INSERT 에는 보일 수 있는 기존 버전이 없기 때문이다. fall-through 배치 자체가 이 사실을 조건부가 아니라 구조적으로 표현해 준다.
locator_insert_force — INSERT 가 손대는 것들
섹션 제목: “locator_insert_force — INSERT 가 손대는 것들”// locator_insert_force — src/transaction/locator_sr.c (skeleton)static intlocator_insert_force (THREAD_ENTRY *thread_p, HFID *hfid, OID *class_oid, OID *oid, RECDES *recdes, int has_index, int op_type, HEAP_SCANCACHE *scan_cache, int *force_count, int pruning_type, PRUNING_CONTEXT *pcontext, FUNC_PRED_UNPACK_INFO *func_preds, UPDATE_INPLACE_STYLE force_in_place, PGBUF_WATCHER *home_hint_p, bool has_BU_lock, bool dont_check_fk, bool use_bulk_logging){ /* (1) Partition pruning — if the class is partitioned, choose * the actual partition that will receive the row. */ if (pruning_type != DB_NOT_PARTITIONED_CLASS) partition_prune_insert (...);
/* (2) Heap insert. The OID is decided by the slot the heap chose; * the row is now physically present. */ recdes->type = REC_HOME; heap_create_insert_context (&context, &real_hfid, &real_class_oid, recdes, local_scan_cache); context.update_in_place = force_in_place; context.is_bulk_op = has_BU_lock; context.use_bulk_logging = use_bulk_logging;
heap_insert_logical (thread_p, &context, home_hint_p); COPY_OID (oid, &context.res_oid);
/* (3) Index updates — for every B-tree on the class, encode the * key out of the new record and insert the (key, oid) pair. * locator_add_or_remove_index does the per-index loop. */ if (has_index) locator_add_or_remove_index (thread_p, recdes, oid, &real_class_oid, /*is_insert=*/true, op_type, scan_cache, /*datayn=*/true, /*need_replication=*/true, &real_hfid, func_preds, has_BU_lock, dont_check_fk);
/* (4) Foreign key checks — for every FK whose referencing column is * an attribute of this class, look up the parent in the parent * B-tree; error if not found. (locator_check_foreign_key does * the per-FK loop.) */ if (!dont_check_fk) locator_check_foreign_key (...);
/* (5) HA replication record (if HA enabled). * (6) WAL log entry — implicit, written by heap_insert_logical / * btree_insert as those primitives flush their own redo/undo. */}3번 단계에 unique 검사 가 들어 있다는 점이다. unique B-tree 에 키
가 이미 존재하면 btree_insert 가 ER_BTREE_UNIQUE_FAILED 를 반환
하고, locator_add_or_remove_index_internal 이 그 오류를 위로 전파
한다. 4번 단계가 FK 존재성 검사 자리다. 부모가 없으면 서버는
ER_FK_INVALID 를 반환한다는 점이다.
locator_update_force — diff 기반 인덱스 갱신
섹션 제목: “locator_update_force — diff 기반 인덱스 갱신”UPDATE 가 더 흥미로운 이유는 모든 인덱스를 건드릴 필요가 없을 수
있기 때문이다. 사용자가 c5 한 컬럼만 갱신했고 c5 를 덮는 B-tree
가 하나뿐이라면, 나머지 인덱스는 손대지 않는 게 옳다는 점이다.
// locator_update_force flow (paraphrased; see locator_sr.c)//// (1) Read the existing record (already done by attr_info_force; old_recdes in hand).// (2) Build new_recdes from attr_info.// (3) Decide policy:// - REC_HOME stays REC_HOME if size fits → in-place update (heap_update_logical).// - Otherwise → may relocate or move to overflow (heap manager decides).// (4) Index update loop:// locator_update_index (new_recdes, old_recdes, att_id[], n_att_id, ...)// For each btree on the class:// if no att_id from att_id[] is part of this btree's key → SKIP// else extract old_key from old_recdes, new_key from new_recdes// if old_key != new_key:// btree_delete (old_key, oid)// btree_insert (new_key, oid) /* unique-check happens here */// (5) FK checks for changed referencing keys.// (6) HA replication + WAL.att_id[] / n_att_id 인자가 핵심이다. 이 인자들은 실행기가 어느
컬럼을 갱신했는지 를 locator 에 알려 주는 역할을 한다. locator 는
이 정보로 어느 B-tree 만 건드리면 되는지 를 가려낸다는 점이다.
이 정보가 없으면 모든 UPDATE 가 모든 인덱스를 재평가해야 한다. 인덱
스가 많고 갱신이 좁은 폭의 컬럼에 집중되는 wide table 에서는 적지
않은 절약이 된다.
locator_delete_force — 대칭
섹션 제목: “locator_delete_force — 대칭”// locator_delete_force — src/transaction/locator_sr.cintlocator_delete_force (THREAD_ENTRY *thread_p, HFID *hfid, OID *oid, int has_index, int op_type, HEAP_SCANCACHE *scan_cache, int *force_count, MVCC_REEV_DATA *mvcc_reev_data, bool need_locking){ return locator_delete_force_internal (thread_p, hfid, oid, has_index, op_type, scan_cache, force_count, mvcc_reev_data, FOR_INSERT_OR_DELETE, NULL, NULL, need_locking);}for_moving 변형(locator_delete_force_for_moving) 은 파티션 테이
블의 사례를 위한 것이다. 파티션 A 에 있던 행이 UPDATE 의 결과로 파
티션 B 로 옮겨가야 한다면, 그 동작은 delete from A + insert into B 로 구현된다. delete 쪽에 새 OID 와 새 파티션 클래스를 함
께 실어 보냄으로써 HA 복제와 트리거 로직이 이 것이 진짜 삭제가 아
니라 이동임을 알아챌 수 있게 해 준다는 점이다. 두 변형 모두
locator_delete_force_internal 을 공유한다. 내부 동작은 (1) 행을
읽어 키를 확인하고, (2) locator_add_or_remove_index 를
is_insert=false 로 호출해 모든 B-tree 에서 키를 제거하고, (3)
heap_delete_logical 이 mvcc_del_id 를 찍는다 — 물리 삭제는 아
직 아니다(자세한 의미는 cubrid-mvcc.md 참고). (4) HA 복제 레코드
를 쓰고, (5) WAL 엔트리는 heap/btree primitive 안에서 암묵적으로
기록된다.
제약(constraint) 오케스트레이션
섹션 제목: “제약(constraint) 오케스트레이션”// locator_sr.c — index orchestration entriesextern int locator_add_or_remove_index (THREAD_ENTRY *thread_p, RECDES *recdes, OID *inst_oid, OID *class_oid, int is_insert, int op_type, HEAP_SCANCACHE *scan_cache, bool datayn, bool need_replication, HFID *hfid, FUNC_PRED_UNPACK_INFO *func_preds, bool has_BU_lock, bool skip_checking_fk);
extern int locator_update_index (THREAD_ENTRY *thread_p, RECDES *new_recdes, RECDES *old_recdes, ATTR_ID *att_id, int n_att_id, OID *oid, OID *class_oid, int op_type, HEAP_SCANCACHE *scan_cache, REPL_INFO *repl_info);둘 다 locator_add_or_remove_index_internal 로 들어간다. 그 내부에
서는 HEAP_CACHE_ATTRINFO index_attrinfo(per-index 디코더 캐시.
cubrid-heap-manager.md 의 AttrInfo cache 참고)를 할당한 뒤,
클래스 representation 에서 파싱된 B-tree 목록 or_classrep->indexes[]
을 순회한다.
// locator_add_or_remove_index_internal — sketchheap_attrinfo_start_with_index (thread_p, class_oid, NULL, &index_attrinfo, &idx_info);num_btids = idx_info.num_btids;
for (i = 0; i < num_btids; i++) { index = &index_attrinfo.last_classrepr->indexes[i]; btid = index->btid;
/* Skip indexes that are functional / partial / where filter excludes this row. */ if (or_pred && !pred_eval (or_pred, recdes)) continue;
/* Compute the key. heap_attrvalue_get_key extracts the key columns out of recdes using attr_info, encoding multi-column keys via tp_value_string_to_key_value. */ key_dbvalue = heap_attrvalue_get_key (thread_p, i, &index_attrinfo, recdes, btid, &dbvalue, ...);
/* Insert or delete the (key, inst_oid) pair. */ if (is_insert) btree_insert (thread_p, btid, key_dbvalue, class_oid, inst_oid, ..., &unique_pk, ...); else btree_delete (thread_p, btid, key_dbvalue, class_oid, inst_oid, ..., &unique_pk, ...); }locator_check_unique_btree_entries 는 같은 패밀리의 무결성 검사
변형이다. CHECKDB 와 복원 후 일관성 sweep 이 사용한다는 점이다.
모든 클래스의 모든 B-tree 를 돌며, leaf entry 마다 대응하는 heap
행이 있는지, 같은 unique 키가 두 개의 OID 로 매핑되는 일이 없는지
를 확인한다.
외래 키 오케스트레이션은 locator_check_foreign_key 가 맡는다.
// locator_check_foreign_key — src/transaction/locator_sr.c (signature)static intlocator_check_foreign_key (THREAD_ENTRY *thread_p, HFID *hfid, OID *class_oid, OID *inst_oid, RECDES *recdes, RECDES *new_recdes, bool *is_cached, LC_COPYAREA **cache_attr_copyarea);이 함수는 클래스 representation 의 FK 리스트를 따라가며, 참조 컬
럼 키를 추출해서 부모 클래스의 PK B-tree 에 btree_keyoid_checks
로 검색한다. miss 가 나오면 행 INSERT/UPDATE 는 ER_FK_INVALID 로
거절된다는 점이다.
클래스 fetch 와 카탈로그 캐시
섹션 제목: “클래스 fetch 와 카탈로그 캐시”카탈로그 자체에 닿는 길도 locator 다. root class 는 그 자체로
하나의 OID(oid_Root_class_oid) 이고, 모든 사용자 클래스는 root
class 의 heap 안의 OID 다. 실행기가 T1 의 레이아웃을 알고 싶을
때, T1 의 클래스 OID 를 얻어 한 레코드처럼 읽는다는 점이다.
// xlocator_find_class_oid — src/transaction/locator_sr.cextern LC_FIND_CLASSNAME xlocator_find_class_oid (THREAD_ENTRY *thread_p, const char *classname, OID *class_oid, LOCK lock);이것이 카탈로그 룩업이다. 반환값은 LC_CLASSNAME_EXIST /
LC_CLASSNAME_DELETED / LC_CLASSNAME_ERROR 중 하나다. 매핑 자체는
locator_sr.c 의 파일 스코프 메모리 해시 locator_Mht_classnames
에 들어 있고, 키는 클래스 이름이다.
그 위에 두 가지 transient-name 메커니즘이 더 얹힌다.
- 예약(reservation) —
xlocator_reserve_class_names/xlocator_reserve_class_name. 트랜잭션 안에서 CREATE TABLE 을 돌릴 때, 이름은 예약 만 되고 클래스는 아직 커밋되지 않는다. 같은 이름으로 들어오는 두 번째 CREATE TABLE 은 이 예약을 발견 하고 실패하거나 대기한다는 점이다. - commit/abort 시 transient drop —
locator_drop_transient_class_name_entries/locator_savepoint_transient_class_name_entries가 트랜잭션 경 계와 savepoint rollback 시점에 예약 집합과 영구 해시를 화해시 킨다.
클라이언트 측에서는 locator_fetch_class 와
locator_fetch_class_with_classmop 이 인스턴스 fetch 와 동일한
LC_COPYAREA 메커니즘으로 클래스 객체를 워크스페이스로 끌어 온
다. 클래스에 대응하는 워크스페이스의 MOP 이 실행기가 이후 작업에
사용하는 영속 핸들이며, 그 위에 쌓이는 스키마 캐시가 저장 계층의
HEAP_CLASSREPR_CACHE 를 떠받친다는 점이다.
스냅샷 인지 읽기 진입점
섹션 제목: “스냅샷 인지 읽기 진입점”// locator_sr.c — snapshot-aware readsextern SCAN_CODE locator_get_object (THREAD_ENTRY *thread_p, const OID *oid, OID *class_oid, RECDES *recdes, HEAP_SCANCACHE *scan_cache, SCAN_OPERATION_TYPE op_type, LOCK lock_mode, int ispeeking, int chn);
extern SCAN_CODE locator_lock_and_get_object (THREAD_ENTRY *thread_p, const OID *oid, OID *class_oid, RECDES *recdes, HEAP_SCANCACHE *scan_cache, LOCK lock, int ispeeking, int old_chn, NON_EXISTENT_HANDLING handling);
extern SCAN_CODE locator_lock_and_get_object_with_evaluation ( THREAD_ENTRY *thread_p, OID *oid, OID *class_oid, RECDES *recdes, HEAP_SCANCACHE *scan_cache, SCAN_OPERATION_TYPE op_type, LOCK lock, int ispeeking, int chn, MVCC_REEV_DATA *mvcc_reev_data, UPDATE_INPLACE_STYLE inplace);locator_get_object 는 force 패밀리의 읽기 짝이다. 실행기가
스캔 도중 부르고, force 함수가 UPDATE / DELETE 의 old image 를
읽을 때도 이 함수를 부른다는 점이다. 본문은 op_type 과 클래스
의 MVCC 활성 여부를 보고 lock mode 를 자동으로 결정한다.
// locator_get_object — body sketch (src/transaction/locator_sr.c)if (!OID_IS_ROOTOID (class_oid)) { if (op_type == S_SELECT && !mvcc_is_mvcc_disabled_class (class_oid)) lock_mode = NULL_LOCK; /* MVCC: no lock */ else if (op_type == S_DELETE || op_type == S_UPDATE) lock_mode = X_LOCK; else lock_mode = S_LOCK; /* SELECT FOR UPDATE / non-MVCC */ }
if (op_type == S_SELECT && lock_mode == NULL_LOCK) scan_code = heap_get_visible_version_internal (thread_p, &context, false);else scan_code = locator_lock_and_get_object_internal (thread_p, &context, lock_mode);cubrid-mvcc.md 의 MVCC 헤더는 locator_* 흐름이 찍는다 라는 주
장이 현실로 환금되는 곳이 여기다. heap 계층 위쪽의 모든 읽기는 이
함수를 통과하며, 이 함수가 언제 잠금을 잡는지 (op_type 기반),
언제 스냅샷을 참조하는지 (MVCC 비활성 클래스 검사), 어떤 scan
code 의미론을 반환하는지 (S_SUCCESS / S_DOESNT_EXIST /
S_SNAPSHOT_NOT_SATISFIED / S_ERROR)를 모두 안다는 점이다.
locator_lock_and_get_object_with_evaluation 은 잠금을 잡은 뒤
predicate 를 재평가 하는 변형이다. SERIALIZABLE / REPEATABLE
READ 에서, 동시 UPDATE 가 커밋된 후 행이 더 이상 WHERE 절을 만족하
지 않는 상황 — MVCC 의 내가 잡은 행을 잃은 것은 아닌가? 검사 —
을 잡아낸다는 점이다. 잠긴 레코드를 WHERE 절을 다시 평가하고,
V_FALSE 가 나오면 그 행은 건너뛴다.
벌크 전송 + 와이어 패킹
섹션 제목: “벌크 전송 + 와이어 패킹”src/transaction/locator.c 는 배관(plumbing) 이다.
LC_COPYAREA, LC_LOCKSET, LC_LOCKHINT, LC_OIDSET 의 직렬화
헬퍼와 영역 객체의 공유 free-list(locator_initialize_areas /
locator_free_areas) 가 들어 있다는 점이다.
// locator.c — packing entriesextern char *locator_pack_copy_area_descriptor (int num_objs, LC_COPYAREA *, char *desc, int desc_len);extern char *locator_unpack_copy_area_descriptor (int num_objs, LC_COPYAREA *, char *desc, int packed_size);
extern int locator_pack_lockset (LC_LOCKSET *, bool pack_classes, bool pack_objects);extern int locator_unpack_lockset (LC_LOCKSET *, bool unpack_classes, bool unpack_objects);
extern int locator_pack_lockhint (LC_LOCKHINT *, bool pack_classes);extern int locator_unpack_lockhint (LC_LOCKHINT *, bool unpack_classes);
extern char *locator_pack_oid_set (char *buffer, LC_OIDSET *);extern LC_OIDSET *locator_unpack_oid_set_to_new (THREAD_ENTRY *, char *buffer);패킹은 본문과 분리 되어 있다. LC_COPYAREA.mem 은 행 본문(이미
상위 인코더가 network-endian 으로 만들어 둔 것)을 운반하고,
pack_copy_area_descriptor 는 디스크립터(LC_COPYAREA_MANYOBJS +
N × LC_COPYAREA_ONEOBJ)만을 호출자가 제공한 byte 배열에 패킹한다
는 점이다. 그 결과 네트워크 계층은 두 부분을 별도의 버퍼로 보낼 수
있어 복사 한 번을 아낀다. locator.h 의 LC_AREA_ONEOBJ_PACKED_SIZE
매크로가 디스크립터의 고정된 packed size(int 4개 + HFID 1개 + OID 2
개) 를 계산해 준다.
flowchart TB
subgraph CL["클라이언트 측"]
WS["워크스페이스 dirty 리스트"]
MF["LOCATOR_MFLUSH_CACHE\n(스테이징)"]
CA["LC_COPYAREA"]
WS --> MF --> CA
end
subgraph WIRE["와이어"]
PD["패킹된 디스크립터\n(network-endian)"]
PB["패킹된 본문\n(이미 인코딩됨)"]
CA -- "pack_copy_area_descriptor" --> PD
CA -- "(zero-copy)" --> PB
end
subgraph SR["서버 측"]
UCA["LC_COPYAREA 재구성\n(unpack_copy_area_descriptor)"]
XF["xlocator_force"]
LOOP["객체별 디스패치:\nONEOBJ 마다\n switch operation\n locator_∗_force"]
PD --> UCA
PB --> UCA
UCA --> XF --> LOOP
end
끝에서 끝까지의 전체 그림
섹션 제목: “끝에서 끝까지의 전체 그림”sequenceDiagram
participant APP as 애플리케이션
participant WS as 워크스페이스
participant LCL as locator_cl
participant NET as 와이어
participant XS as xlocator_force
participant LSR as locator_sr
participant HM as heap_manager
participant BT as btree
participant LK as lock_manager
participant LG as log_manager
participant HA as repl_log
APP->>WS: db_create / db_get / set_attr
WS->>WS: MOP 을 dirty 표시 (operation = INSERT/UPDATE)
APP->>WS: commit
WS->>LCL: locator_all_flush
LCL->>LCL: locator_mflush_initialize
LCL->>LCL: ws_map_dirty(locator_mflush)
loop dirty MOP 마다
LCL->>LCL: 객체 인코딩 → RECDES
LCL->>LCL: LC_COPYAREA_ONEOBJ 추가
end
LCL->>NET: locator_force(copy_area)
NET->>XS: xlocator_force
XS->>XS: tran_server_start_topop (원자성)
loop copy_area 안의 ONEOBJ 마다
XS->>LSR: locator_attribute_info_force
LSR->>LK: lock_object (X_LOCK)
LSR->>HM: heap_insert_logical / heap_update_logical / heap_delete_logical
HM->>LG: log_append (redo/undo)
LSR->>BT: locator_add_or_remove_index / locator_update_index
BT->>LG: log_append (btree 의 redo/undo)
LSR->>LSR: locator_check_foreign_key (FK 가 있으면)
LSR->>HA: repl_log_insert (HA 활성 시)
end
XS->>XS: tran_server_end_topop
XS->>NET: LC_COPYAREA 응답 (영구 OID 포함)
NET->>LCL: 수신
LCL->>WS: ws_update_oid_and_class (temp → perm)
WS->>APP: commit 반환
HA 복제 단계에는 한 가지 짚어 둘 만한 사실이 있다.
locator_attribute_info_force 가 복제의 정식 통로이며, 그래서
cubrid-ha-replication.md 가 locator_attribute_info_force → heap_*_logical → btree_update → repl_log_insert 경로를 따라간다는
점이다. 복제 레코드는 locator 의 RECDES (이미 인코딩된 것)와 OID,
클래스 OID 로부터 빌드된다. repl_info.repl_info_type 이 형식을
선택한다는 점이다(row 기반 복제는 REPL_INFO_TYPE_RBR_NORMAL). 모
든 DML 이 이 함수 한 곳을 통과하기 때문에 복제 커버리지가 구조적으
로 완전하다는 점이다. 이 코드 경로의 복제를 깜빡했다 와 같은
실패 모드는 누군가 locator 를 우회하는 새로운 DML 연산을 도입하기
전까지는 발생하지 않는다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”심볼 이름 을 앵커로 삼으라. 줄 번호가 아니다. CUBRID 소스는 움직인다. 함수 이름(또는 struct/enum 태그)은 안정적인 핸들이며, 줄 번호는 누군가 헤더 한 줄을 끼워 넣는 순간 어긋나기 시작한다.
git grep -n '<symbol>' src/transaction/으로 현재 위치를 확인한 다. 이 절 끝의 위치 힌트 표는 문서의updated:시점에 관찰된 위 치이며, 빠른 참조용일 뿐이다.
타입 정의 (src/transaction/locator.h)
섹션 제목: “타입 정의 (src/transaction/locator.h)”enum LC_COPYAREA_OPERATION— 11개 값을 가진 enum:LC_FETCH,LC_FETCH_DELETED,LC_FETCH_DECACHE_LOCK,LC_FLUSH_INSERT,LC_FLUSH_INSERT_PRUNE,LC_FLUSH_INSERT_PRUNE_VERIFY,LC_FLUSH_DELETE,LC_FLUSH_UPDATE,LC_FLUSH_UPDATE_PRUNE,LC_FLUSH_UPDATE_PRUNE_VERIFY,LC_FETCH_VERIFY_CHN._PRUNE접미사는 partition pruning 변형을 표시한다.enum LC_FETCH_VERSION_TYPE— 4개 값:LC_FETCH_CURRENT_VERSION,LC_FETCH_MVCC_VERSION,LC_FETCH_DIRTY_VERSION,LC_FETCH_CURRENT_VERSION_NO_CHECK.struct lc_copyarea_oneobj(LC_COPYAREA_ONEOBJ) — 객체별 디스 크립터: operation, flag, hfid, class_oid, oid, length, offset.struct lc_copyarea_manyobjs(LC_COPYAREA_MANYOBJS) — 디스크립 터 배열의 헤더 (objs 첫 번째, multi_update_flags, num_objs).struct lc_copy_area(LC_COPYAREA) —(mem, length)— 버퍼.struct lc_lock_set(LC_LOCKSET) — 벌크 fetch 요청:num_reqobjs,LC_LOCKSET_REQOBJ *objects,LC_LOCKSET_CLASSOF *classes, 인스턴스/클래스 lock mode.struct lc_lock_hint(LC_LOCKHINT) — lockhint 영역: prefetch 대상 클래스 목록과 그 잠금.struct lc_oidset/lc_class_oidset/lc_oidmap— 영구 OID 할당 요청.enum lc_prefetch_flags—LC_PREF_FLAG_LOCK,LC_PREF_FLAG_COUNT_OPTIM.enum MULTI_UPDATE_FLAG—IS_MULTI_UPDATE,START_MULTI_UPDATE,END_MULTI_UPDATE.LC_FLAG_HAS_INDEX,LC_FLAG_UPDATED_BY_ME,LC_FLAG_HAS_UNIQUE_INDEX,LC_FLAG_TRIGGER_INVOLVED— 객체별 flag 비트.
워크스페이스 — 클라이언트 측 (src/transaction/locator_cl.c)
섹션 제목: “워크스페이스 — 클라이언트 측 (src/transaction/locator_cl.c)”struct locator_mflush_cache— 벌크 flush 의 스테이징 버퍼.struct locator_mflush_temp_oid— temp-OID 리스트 노드.struct locator_cache_lock— fetch 별 lock context.locator_is_root/locator_is_class— MOP 의 타입 술어.locator_fetch_object/_class/_class_of_instance/_instance/_set/_nested— fetch 진입점 (locator_lock/locator_lock_set/locator_lock_nested로 매핑).locator_lock/locator_lock_set/locator_lock_nested—LC_LOCKSET빌드 후 서버 왕복.locator_cache_lock/locator_cache_lock_set— 워크스페이스 측 잠금 캐시.locator_cache/locator_cache_object_class/_cache_object_instance/_cache_have_object/_cache_not_have_object— 서버 응답LC_COPYAREA를 워크스페 이스로 풀어 넣는다.locator_flush_class/_instance/_all_instances/_for_multi_update/_all_flush— 공개 flush 진입점.locator_flush_and_decache_instance— flush + 캐시에서 제거.locator_mflush— MOP 별 인코드/패킹 (ws_map_dirty콜백).locator_mflush_initialize/_reset/_end/_reallocate_copy_area— 스테이징 라이프사이클.locator_mflush_force— 스테이징 버퍼를 서버로 비워내고, temp→perm OID 를 화해시키며, flush 별 상태를 해제.locator_mem_to_disk/locator_class_to_disk— 인코더(인스 턴스 / 클래스).locator_add_class/_add_instance/_add_root— 워크스페 이스 추가.locator_remove_class/_remove_instance— 워크스페이스 삭제.locator_update_instance/_update_class/_update_tree_classes— dirty 표시를 동반한 워크스페이스 갱신.locator_prepare_rename_class— ALTER TABLE RENAME 시 이름 예약 핸드셰이크.locator_force— 와이어 송신 (net_client_request_recv_copyarea호출).locator_repl_*— 복제 측 flush 변형들 (locator_repl_mflush_force,locator_repl_flush_all).
서버 측 — DML fan-in (src/transaction/locator_sr.c)
섹션 제목: “서버 측 — DML fan-in (src/transaction/locator_sr.c)”force 패밀리 (DML 정식 진입점)
섹션 제목: “force 패밀리 (DML 정식 진입점)”locator_attribute_info_force— 최상위 진입점;LC_COPYAREA_OPERATION에 대한 switch.locator_insert_force— heap insert + 인덱스 insert + FK 검사.locator_update_force(static) — heap update + diff 기반 인덱스 갱신 + FK 검사 + 복제.locator_delete_force/locator_delete_force_for_moving/locator_delete_force_internal— heap delete + 인덱스 delete + FK cascade + 복제.locator_move_record— 파티션 이동 헬퍼(파티션 A 의 delete + 파티션 B 의 insert 와 그 연결).locator_force_for_multi_update— 트리거 인지 순서를 갖는 multi-update 경로.xlocator_force— 와이어 진입점;LC_COPYAREA_ONEOBJ들을 순회 하며 op 별 force 함수를 top-op 안에서 호출.xlocator_force_repl_update— HA-applier 변형.
제약 오케스트레이션
섹션 제목: “제약 오케스트레이션”locator_add_or_remove_index(extern) /locator_add_or_remove_index_for_moving(static) /locator_add_or_remove_index_internal(static) — INSERT/DELETE 의 인덱스별 루프.locator_update_index— UPDATE 의 인덱스별 루프 (att_id[]기반 diff 구동).locator_check_foreign_key(static) — FK 존재성 probe.locator_check_unique_btree_entries— CHECKDB 무결성 sweep.locator_check_btree_entries/locator_check_class/locator_check_by_class_oid/locator_check_all_entries_of_all_btrees— 무결성 검사 패밀리 의 나머지.locator_was_index_already_applied— 공유 B-tree(PK ↔ FK 겹침) 의 이중 적용 가드.xlocator_check_fk_validity— 와이어 호출 가능한 FK 검증 (ALTER TABLE ADD CONSTRAINT 시 사용).
OID 라이프사이클
섹션 제목: “OID 라이프사이클”xlocator_assign_oid— 단일 영구 OID 사전 발급 (heap_assign_address+ 이름 바인딩).xlocator_assign_oid_batch—LC_OIDSET을 위한 배치 변형.xlocator_find_class_oid— 클래스명 → 클래스 OID (잠금 동반).locator_permoid_class_name— 새로 발급된 OID 를 예약된 클래스 이름에 바인딩.xlocator_reserve_class_names/xlocator_reserve_class_name/xlocator_get_reserved_class_name_oid— CREATE TABLE 의 이름 예약 프로토콜.xlocator_delete_class_name/xlocator_rename_class_name— 카탈로그 이름 유지.locator_drop_transient_class_name_entries/locator_savepoint_transient_class_name_entries— commit / abort / savepoint 시점에 transient name 집합을 화해시킴.locator_check_class_names/locator_dump_class_names— 이름 해시 진단.
스냅샷 인지 읽기
섹션 제목: “스냅샷 인지 읽기”locator_get_object—op_type에 따라 lock mode 를 결정;heap_get_visible_version_internal(잠금 없음) 또는locator_lock_and_get_object_internal(잠금 동반) 으로 디스패치.locator_lock_and_get_object— 명시적 잠금 진입점.locator_lock_and_get_object_with_evaluation— 잠금 획득 후 predicate 재평가 (SERIALIZABLE / RR 의미론).locator_lock_and_get_object_internal(static) — 공유 본문.
벌크 fetch (벌크 flush 의 와이어 측 쌍)
섹션 제목: “벌크 fetch (벌크 flush 의 와이어 측 쌍)”xlocator_fetch— 단일 OID 서버 fetch (한LC_COPYAREA반환).xlocator_fetch_all— heap 스캔이 페이지 배치 당 하나의LC_COPYAREA를 반환 (boot 시 캐시 채울 때 사용).xlocator_fetch_lockset—LC_LOCKSET요청의 벌크 fetch.xlocator_fetch_all_reference_lockset— 추이적 폐포 fetch (참조된 클래스/인스턴스).xlocator_fetch_lockhint_classes—LC_LOCKHINT로부터 클래스 prefetch.locator_lock_and_return_object(static) — 벌크 fetch 경로의 OID 별 본문.locator_return_object_assign(static) — OID 하나를 응답LC_COPYAREA에 패킹.locator_all_reference_lockset(static) — OID 의 전체 참조 폐포 빌드.locator_find_lockset_missing_class_oids(static) — 호출자가 알지 못했던 객체의 클래스 인덱스 채움 (LC_LOCKSET_REQOBJ.class_index).locator_guess_sub_classes(static) —LC_LOCKHINT안의 서브 클래스 참조 확장.xlc_fetch_allrefslockset— 참조 폐포 경로의 와이어 진입점.
라이프사이클과 모듈 상태 (src/transaction/locator_sr.c)
섹션 제목: “라이프사이클과 모듈 상태 (src/transaction/locator_sr.c)”locator_initialize/locator_finalize—locator_Mht_classnames해시 생성/소멸.locator_Pseudo_pageid_first/_Pseudo_pageid_last/_Pseudo_pageid_crt— transient name 처리 시 사용되는 pseudo-pageid 범위.locator_Mht_classnames— 클래스명 → 캐시된 클래스 OID + 예약 상태로의 모듈 스코프 해시.
특수/복제
섹션 제목: “특수/복제”locator_repl_prepare_force(static) — HA-applier flush 의 사 전 검사(키 해석, 기존 OID 가져오기).locator_repl_get_key_value(static) — 복제 레코드에서 키 컬럼 추출.locator_repl_add_error_to_copyarea(static) — 객체별 오류 결 과를 응답 패킹.xlocator_redistribute_partition_data—ALTER TABLE ... REORGANIZE PARTITION후 파티션 데이터 재배치.locator_rv_redo_rename— 클래스 rename 의 recovery hook.
전송 (src/transaction/locator.c)
섹션 제목: “전송 (src/transaction/locator.c)”locator_allocate_copy_area_by_length/locator_reallocate_copy_area_by_length/locator_free_copy_area— 버퍼 라이프사이클.locator_pack_copy_area_descriptor/locator_unpack_copy_area_descriptor.locator_send_copy_area—(contents, descriptor)로 분할해 네트워크 계층에 넘김.locator_recv_allocate_copyarea— 서버 측 거울.locator_allocate_lockset/_reallocate_lockset/_free_lockset/locator_pack_lockset/locator_unpack_lockset/locator_allocate_and_unpack_lockset.locator_allocate_lockhint/_reallocate_lockhint/_free_lockhint/locator_pack_lockhint/locator_unpack_lockhint/locator_allocate_and_unpack_lockhint.locator_initialize_areas/locator_free_areas— 모듈 와이드 free list.locator_make_oid_set/_clear_oid_set/_free_oid_set/_add_oid_set/_get_packed_oid_set_size/_pack_oid_set/_unpack_oid_set_to_new/_unpack_oid_set_to_exist.locator_manyobj_flag_is_set/_remove/_set—LC_COPYAREA_MANYOBJS의 multi-update flag 조작.
이 리비전 시점의 위치 힌트
섹션 제목: “이 리비전 시점의 위치 힌트”이 표의 줄 번호는 문서가 마지막으로 updated: 된 시점에 관찰된
값이며, 시간이 지날수록 정확도가 떨어진다는 점이다. 정의가 다른 위
치에 있다면 심볼 이름이 정답이다. 발견하는 김에 표를 갱신해 두면
다음 사람이 고마워한다.
| 심볼 | 파일 | 줄 |
|---|---|---|
enum LC_COPYAREA_OPERATION | locator.h | 107 |
enum LC_FETCH_VERSION_TYPE | locator.h | 179 |
struct lc_copyarea_oneobj | locator.h | 224 |
struct lc_copyarea_manyobjs | locator.h | 243 |
struct lc_copy_area | locator.h | 252 |
struct lc_lock_set | locator.h | 286 |
struct lc_lock_hint | locator.h | 329 |
struct lc_oidset | locator.h | 395 |
struct locator_mflush_cache | locator_cl.c | 69 |
struct locator_mflush_temp_oid | locator_cl.c | 61 |
locator_fetch_object | locator_cl.c | (varies) |
locator_fetch_class | locator_cl.c | (varies) |
locator_fetch_set | locator_cl.c | (varies) |
locator_mflush | locator_cl.c | 4435 |
locator_mflush_initialize | locator_cl.c | 3802 |
locator_mflush_force | locator_cl.c | 3995 |
locator_flush_class | locator_cl.c | 4890 |
locator_flush_instance | locator_cl.c | 5058 |
locator_all_flush | locator_cl.c | 5279 |
locator_initialize | locator_sr.c | 246 |
locator_finalize | locator_sr.c | 364 |
xlocator_reserve_class_names | locator_sr.c | 409 |
xlocator_find_class_oid | locator_sr.c | 1033 |
xlocator_assign_oid | locator_sr.c | 2043 |
xlocator_fetch | locator_sr.c | 2374 |
xlocator_fetch_all | locator_sr.c | 2772 |
xlocator_fetch_lockset | locator_sr.c | 3052 |
xlocator_fetch_all_reference_lockset | locator_sr.c | 3818 |
locator_check_foreign_key | locator_sr.c | 4023 |
locator_insert_force | locator_sr.c | 4938 |
locator_delete_force | locator_sr.c | 6116 |
locator_delete_force_internal | locator_sr.c | 6172 |
locator_force_for_multi_update | locator_sr.c | 6543 |
xlocator_force | locator_sr.c | 7129 |
locator_attribute_info_force | locator_sr.c | 7461 |
locator_add_or_remove_index | locator_sr.c | 7695 |
locator_add_or_remove_index_internal | locator_sr.c | 7760 |
locator_update_index | locator_sr.c | 8260 |
locator_check_unique_btree_entries | locator_sr.c | 9768 |
xlocator_fetch_lockhint_classes | locator_sr.c | 11356 |
xlocator_assign_oid_batch | locator_sr.c | 11577 |
xlocator_check_fk_validity | locator_sr.c | 11754 |
locator_lock_and_get_object_internal | locator_sr.c | 12936 |
locator_lock_and_get_object_with_evaluation | locator_sr.c | 13100 |
locator_get_object | locator_sr.c | 13241 |
locator_lock_and_get_object | locator_sr.c | 13352 |
locator_allocate_copy_area_by_length | locator.c | (varies) |
locator_pack_copy_area_descriptor | locator.c | (varies) |
locator_pack_lockset | locator.c | (varies) |
locator_pack_lockhint | locator.c | (varies) |
locator_pack_oid_set | locator.c | (varies) |
locator_sr.c 는 약 14 000 줄, locator_cl.c 는 약 7 100 줄이다.
실제 위치를 찾을 때는 심볼 단위 git grep 이 가장 빠르다.
교차 검토 노트
섹션 제목: “교차 검토 노트”이 문서는 raw 분석 자료 없이 작성되었기에, 아래의 교차 검토는 이 지식 베이스의 다른 CUBRID 코드 분석 문서들이 locator 에 관해 적 어 둔 주장을 이루어진다. 각 항목은 그 문서들의 주장을 풀어 적은 뒤, 이번 읽기에서 무엇을 확인했고 어디를 정제했는지를 적는다.
-
cubrid-heap-manager.md가 “locator_insert/update/delete_force가heap_*_logical의 주된 호출자다” 라고 한다. 확인됨.locator_insert_force가heap_create_insert_context다음에heap_insert_logical을 호출하고,locator_delete_force_internal이heap_delete_logical을,locator_update_force가heap_update_logical을 부른다. 다른 호출 지점은 거의 없으며, 있는 것들(복구 시 재실행, vacuum)도 의도적으로 특수한 경로다. -
cubrid-ha-replication.md가locator_attribute_info_force → heap_*_logical → btree_update → repl_log_insert를 따라간다. 구조적으로 확인됨. 복제 레코드는locator_insert_force/locator_update_force/locator_delete_force_internal안에서, heap 연산 이후 그리고 인덱스 갱신 이후 빌드된다는 점이다. 따라서 복제 레코드는 늘 후 상태(post-state)를 기술한다.repl_info인자는locator_attribute_info_force를 관통하며, 형식 선택점이 된다.REPL_INFO_TYPE_RBR_NORMAL은 row 기반,REPL_INFO_TYPE_STMT_NORMAL은 statement 기반(이제는 드물다),REPL_INFO_TYPE_RBR_AT_LEAST_ONE_RECORD는 다행 사례다. -
cubrid-lock-manager.md가 “잠금은 locator 경로로 잡힌 다” 라고 한다. 정제됨. 잠금이 어디서 잡히는지는 누가 연산을 시작했느냐에 따라 다르다는 점이다.- 실행기 주도 경로 (예: UPDATE WHERE) — X-lock 은 SELECT-with-
WHERE 단계에서
scan_manager.c가 부르는locator_lock_and_get_object가 잡고, 이후locator_attribute_info_force는need_locking=false로 호출된 다. - 워크스페이스 주도 경로 (서버가 X-lock 잡은 적 없는 객체를
클라이언트가 flush 한 경우) — X-lock 은
locator_attribute_info_force안의LC_FLUSH_UPDATE가지에서locator_lock_and_get_object가need_locking=true로 잡는다는 점이다. - force-update-in-place 경로 (카탈로그 업데이트, 복구) —
force_update_inplace가UPDATE_INPLACE_OLD_MVCCID같은 값이 며, 함수는 MVCC 를 참조하지 않고 잠금도 잡지 않는heap_get_last_version을 사용한다. 상위 잠금은 호출자가 책임 진다. 어느 경로든 행 잠금은 locator 라는 렌즈로 일어난다는 점은 공통이다.
- 실행기 주도 경로 (예: UPDATE WHERE) — X-lock 은 SELECT-with-
WHERE 단계에서
-
cubrid-mvcc.md가 MVCC 헤더는locator_*흐름이 찍는다 라 고 한다. 확인됨. MVCC 헤더(mvcc_rec_header)는 heap 의heap_insert_logical/heap_update_logical/heap_delete_logical내부 에서 찍힌다. 그리고 이 함수들은 DML 경로에서 오직 locator 의 force 함수만 부른다는 점이다. locator 가 직접 손대는 MVCC 필드는 새로 할당된 heap 행의 inserter MVCCID 하나뿐이다.mvcc_rec_header[2]배열로locator_add_or_remove_index_internal로 내려가, B-tree 가 inserter 를 정확히 보고 unique 검사를 하도록 해 준다. 교차 참조는 정밀하 다. -
cubrid-page-buffer-manager.md가 다중 페이지 latch 순서 문맥 에서PGBUF_WATCHER를 언급한다. locator 는 그 계약을 지킨다. force 함수들은PGBUF_WATCHER *home_hint_p를 인자로 받아heap_insert_logical로 그대로 전달한다. heap 은 이 watcher 로 슬롯 검색 사이에 home page 를 latch 잡힌 채로 유지한다는 점이다. page-buffer 측에서 본 벌크 insert 경로가 빠른 이유 가 여기에 있다. locator 가 호출마다 페이지를 다시 fix 하지 않고 watcher 를 forwarding 하기 때문이다.
Open Questions
섹션 제목: “Open Questions”-
양방향
LC_COPYAREA레이아웃의 실제 비용은 얼마인가? 행 본문은 앞쪽으로, 디스크립터는 뒤쪽으로 자라며 가운데에서 만난 다. 충돌이 발생하면locator_mflush_reallocate_copy_area가 작 동한다. 일반적인 OLTP 워크로드에서 “재할당해야 했다 vs 들어맞 았다” 의 분포는 실제로 어떻게 되는가? 조사 방향: 큰 쓰기 워크로 드 아래에서 재할당 횟수를 계측하고 기본COPYAREA크기를 재고 한다는 점이다. -
모든 flush 연산에
*_PRUNE과*_PRUNE_VERIFY변형이 있는 이유는 무엇인가?LC_FLUSH_INSERT_PRUNE은 서버에서 partition pruning 을 트리거하고,_PRUNE_VERIFY는 “정말 올바른 파티션에 안착했는가?” 라는 추가 검사를 더한다는 점이다. verify 변형은 클 라이언트/서버 스키마 버전 불일치를 방어하는 인상이다. 조사 방 향: 클라이언트(locator_mem_to_disk와 친구들)에서 각 변형이 어디서 만들어지는지 추적해 트리거 조건을 문서화한다. -
multi-update flag 의미.
IS_MULTI_UPDATE,START_MULTI_UPDATE,END_MULTI_UPDATE는LC_COPYAREA_MANYOBJS헤더에 실린다. 서버의locator_force_for_multi_update는IS_MULTI_UPDATE로 분기하 는데, START/END 쌍은 같은 논리적 UPDATE 의 여러LC_COPYAREA에 걸친 unique 통계 수집 을 묶어 주는 것으로 보인다. 조사 방 향:btree_unique_stats경로에서START_MULTI_UPDATE의 producer 와 consumer 를 짚어낸다는 점이다. -
locator_Mht_classnames는 모듈 스코프이며 사실상 싱글톤이 다. DDL 이 폭주하는 환경에서의 리사이즈 의미는 어떠한가? 조 사 방향: 해시가 크기 한도를 가지는지, eviction 정책이 있다면 무엇인지 확인한다. -
LC_FETCH_VERSION_TYPE은 4개 값을 가지지만,LC_FETCH_CURRENT_VERSION_NO_CHECK는 코드 주석에 “skip server-side checks” 라고만 적혀 있다. 어떤 검사를 건너뛰는가?xlocator_fetch를 따라가 보면skip_fetch_version_type_check = true로 설정한 뒤_NO_CHECK를_CURRENT와 동일하게 다룬다는 점이다. 건너뛰는 검사는assert (lock_get_object_lock(...) != NULL_LOCK)전제 — 즉, 호출자가 이미 잠금을 들고 있다 는 단언으로 보인다. 모든 소비자 를 좀 더 꼼꼼히 감사해 볼 만하다. -
locator_force(클라이언트) 와xlocator_force(서버) 의 네이밍 컨벤션. 패턴은 일관된다.locator_*는 클라이언트 측 또는 공유,xlocator_*는 서버에서 호출 가능한 와이어 진입점 (x접두사가 CUBRID 컨벤션에서 외부/와이어 진입점 을 뜻한다) 이다. 이것이 유일한 접두사 규칙인지 확정해 둘 만하다. -
워크스페이스 decache eviction 정책.
LOCATOR_MFLUSH_CACHE의decache플래그와locator_flush_and_decache_instance는 flush 와 함께 MOP 을 캐시에서 제거 할 수 있게 한다. 상위 계층 은 언제 이 기능을 사용하는가? 캐시 압력에 의한 eviction 은work_space.c안에 살고 있는 듯하다. 호출 지점을 따라가 두면 계약이 분명해질 것이다. -
xlocator_fetch_all_reference_lockset의 참조 폐포 경로. 참조를 추이적으로 따라가는 일은 넓은 스키마에서는 폭발할 위험 이 있다. 무엇이 그것을 막는가? 조사 방향:locator_all_reference_lockset을 정독해prune_level과quit_on_errors의 상호작용을 파악한다는 점이다.
CUBRID 소스 (/data/hgryoo/references/cubrid/ 아래)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/ 아래)”src/transaction/locator.h— 공개 타입과 매크로 (LC_COPYAREA,LC_COPYAREA_ONEOBJ,LC_COPYAREA_MANYOBJS,LC_LOCKSET,LC_LOCKHINT,LC_OIDSET,LC_COPYAREA_OPERATION,LC_FETCH_VERSION_TYPE).src/transaction/locator.c— 와이어 패킹 (locator_pack_copy_area_descriptor,locator_pack_lockset,locator_pack_lockhint,locator_pack_oid_set), 영역 객체 할당과 free-list 관리.src/transaction/locator_cl.h— 클라이언트 측 공개 API (locator_fetch_*,locator_flush_*,locator_add_*,locator_remove_*,locator_update_*).src/transaction/locator_cl.c— 워크스페이스 다리 (LOCATOR_MFLUSH_CACHE,locator_mflush*,locator_cache*,locator_lock*,locator_force).src/transaction/locator_sr.h— 서버 측 공개 API (locator_attribute_info_force,locator_get_object,locator_lock_and_get_object,locator_add_or_remove_index,locator_update_index,locator_check_class*).src/transaction/locator_sr.c— 서버 측 fan-in (xlocator_force,xlocator_fetch*, force 패밀리, 제약 오케스트레이션, OID 라이프사이클, classnames 해시, 참조 폐포 fetch).
이 지식 베이스의 교차 참조 문서
섹션 제목: “이 지식 베이스의 교차 참조 문서”cubrid-heap-manager.md— heap_insert/update/delete_logical 과 locator 가 구동하는 슬롯 페이지 기반.cubrid-mvcc.md—mvcc_rec_header스탬핑과 locator 의 읽기 가 참조하는 가시성 술어.cubrid-lock-manager.md—locator_lock_and_get_object를 관통하는 잠금 모드와 획득 지점.cubrid-page-buffer-manager.md— force 패밀리가 페이지 사이를 가로질러 보존하는PGBUF_WATCHER체인.cubrid-btree.md—locator_add_or_remove_index_internal이 호출하는 B-tree primitive.cubrid-ha-replication.md—locator_attribute_info_force안에서 만들어지는 HA 복제 레코드.cubrid-catalog-manager.md— classnames 해시가 떠받치는 root class 와 클래스별 카탈로그.
텍스트북 챕터 (knowledge/research/dbms-general/ 아래)
섹션 제목: “텍스트북 챕터 (knowledge/research/dbms-general/ 아래)”- Database Internals (Petrov), 3장 File Formats 와 4장 Implementing B-Trees — RID/OID/TID 의미론, 컴팩션 하의 정체성.
- Database Systems: The Complete Book (Garcia-Molina, Ullman, Widom), §10.6 “Object-Oriented Database Systems” — 워크스페이스 패턴, MOP 대 영속 정체성.
- Database System Concepts (Silberschatz, Korth, Sudarshan, 6판), 13장 Storage and File Structure 와 17장 Database System Architectures — 클라이언트/서버 경계, 벌크 fetch 의 동기.