콘텐츠로 이동

(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는 관계형 엔진이지만 이름에는 객체/관계 하이브리드 시스템 으로 출발했던 흔적이 남아 있다는 점이다.

이 그림을 완성하는 두 가지 교과서적 재료가 더 있다.

  1. 워크스페이스 패턴. 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 매핑을 들고 다니는 이유가 여기에 있다는 점이다. 모던 관계형 클라이언트는 더 이상 이런 워크스페이스를 들고 다니지 않는다는 점에서 이는 차별화된 선택이다.

  2. 벌크 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(PostgreSQL COPY, MySQL LOAD DATA), 그리고 commit 직전에 session.flush() 를 부르는 ORM에서도 동일하게 등장한다.

이 문서는 CUBRID가 두 부분 — 객체 워크스페이스(locator_cl.c)와 서 버 측 fan-in(locator_sr.c) — 을 어떻게 구현했는지, 그리고 그 둘 을 묶는 와이어 모양(locator.cLC_COPYAREA)이 어떻게 생겼는지 를 따라간다.

교과서가 모델을 준다면, 이 절은 거의 모든 행 지향 엔진이 어떤 형 태로든 채택하는 공학적 관행 들을 호명한다는 점이다. 다음 절 ## CUBRID의 구현 의 구체적 선택은 이 공유 설계 공간 안에서 어느 다이얼을 어디에 맞췄는가로 읽는 편이 정확하다.

모든 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_deleteExecInsertIndexTuples, ON-CONFLICT 기계로 흩어 져 있다. 실행기가 이들을 직접 호출하며, 모던(post-Berkeley) PostgreSQL 에는 클라이언트 측 워크스페이스가 없다는 점이다. 클라 이언트가 보낸 튜플은 Bind/Execute 메시지의 ASCII/binary 매개변수로 서버에 전달되고, 서버가 그것을 buffer pool 안에서 HeapTuple 로 실체화한다. 즉, PostgreSQL 의 실행기 자체가 locator 역할을 하며, 디스패치는 암묵적으로 일어난다.

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.cforce 패밀리 다. locator_attribute_info_force 가 정식 진입점이며, 실행기(qexec_execute_*), 트리거 기계, 스키마 조작기, 타입 검사 기, 파티션 프루너 가 결국 모두 이 함수로 모인다. 이 함수가 locator_insert_force / locator_update_force / locator_delete_force 로 디스패치하고, 그 함수들 이 heap, btree, FK, unique 검사, 복제, log 를 올바른 순서로 몰고 간다는 점이다.

이론 (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 별 forcelocator_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 의 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 ... SELECTUPDATE ... 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 entries
extern 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.c
struct 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 의 패킹 루프는 다음과 같이 돌아간다.

  1. dirty MOP 마다 LC_COPYAREA_ONEOBJ 디스크립터를 채운다. operationLC_FLUSH_INSERT / LC_FLUSH_UPDATE / LC_FLUSH_DELETE 중 하나, flagLC_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 은 행 본문이 버퍼 안 어디에 있는지를 가리 킨다.
  2. 행 본문은 locator_mem_to_disk(인스턴스) 또는 locator_class_to_disk (클래스)가 인코딩한다. 둘 다 스키마 / primitive 계층을 호출해 인메모리 객체를 원시 RECDES 로 직렬화한다는 점이다.
  3. 버퍼가 넘치면 그 자리에서 locator_mflush_force 를 호출해 현재 내용을 서버로 비워낸 뒤, 버퍼를 리셋하고 넘쳤던 객체부터 루프를 이어 간다.
  4. MOP 이 임시 OID 가 있으면 mop_toids 에 기록해 둔다. flush 결과로 서버가 부여한 영구 OID 가 응답으로 돌아오면, 그 시점에 ws_update_oid_and_class 가 워크스페이스를 패치한다 는 점이다.

locator.h 에 정의된 와이어 모양:

// LC_COPYAREA — src/transaction/locator.h
struct lc_copy_area
{
char *mem; /* the buffer */
int length; /* size */
};
// LC_COPYAREA_MANYOBJS — at the END of the buffer, growing backward
struct 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 end
struct 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
// locator_cl.h — flush entries
extern 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_updateUPDATE 한 문에서 행 당 여러 번의 update 가 발생할 수 있는 경우(트리거, FK cascade)를 위한 특별 경로다. LC_COPYAREA_MANYOBJS.multi_update_flagsSTART_MULTI_UPDATE / END_MULTI_UPDATE 마커를 함께 실어 보낸다.

내부적으로는 모두 locator_mflush_initializews_map_dirty(locator_mflush, mflush)locator_mflush_forcelocator_force (와이어 송신)로 모인다. 마지막 단계에서 net_client_request_recv_copyarea 가 서버의 xlocator_force 를 호출한다.

OID 의 생애에는 세 단계가 있다. 임시(temp) — 워크스페이스가 내부에서 발급한 sentinel 로, 서버는 아직 모른다는 점이다. 할당(assigned) — 서버가 heap slot 에 OID 를 묶었다는 점이다. 해석(resolved) — 서버가 행 본문까지 확인했다는 점이다.

db_create 가 새 인스턴스를 만들 때 워크스페이스는 임시 OID 를 하나 찍는다. OID_ISTEMP 가 true 인 sentinel 값으로, 실제 (volid, pageid, slotid) 튜플이 아니다. MOP 은 LC_FLUSH_INSERT 연산으로 dirty 리스트에 들어간다. 이 시점에는 서버와의 통신이 없다는 점이다.

flush 시점에 서버의 locator_insert_forceheap_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.c
int
xlocator_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_addressREC_ASSIGN_ADDRESS placeholder 만 들어 있는 슬롯을 할당한다(자세한 동작은 cubrid-heap-manager.md). OID 는 존재하고, 행 본문은 나중에 채워진다는 점이다.

배치 처리 — CREATE TABLE 한 번이 카탈로그 행 여럿을 한꺼번에 만드는 경우 — 에는 xlocator_assign_oid_batch 가 같은 일을 다중 OID 를 한 번의 왕복으로 처리한다. LC_OIDSET / LC_CLASS_OIDSET (locator.h)이 그 요청 자료구조다.

// LC_FETCH_VERSION_TYPE — src/transaction/locator.h
typedef 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_fetchfetch_version_type 을 보고 그에 맞는 MVCC_SNAPSHOT 을 만들어 heap 계층으로 넘긴다.

force 패밀리에서 locator 는 더 이상 전송 계층이 아니라 지휘자 가 된다. 정식 진입점은 locator_attribute_info_force 다.

// locator_attribute_info_force — src/transaction/locator_sr.c (signature)
int
locator_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 sketch
switch (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,
&copy_recdes, scan_cache, X_LOCK,
COPY, NULL_CHN, LOG_ERROR_IF_DELETED);
old_recdes = &copy_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;
}

내재화해 둘 만한 세 가지 사실이 있다.

  1. UPDATE 경로가 INSERT 경로로 흘러 들어간다. 소스에서 보이는 C 의 [[fallthrough]] 가 그것이다. UPDATE 는 “old 읽고 + new 인코딩 + 적용 이고, INSERT 는 그 중 new 인코딩 + 적용” 만이라 는 점이다. 인코딩 단계를 공유함으로써 두 경로의 의미가 어긋나 지 않는다.

  2. lock 은 heap 이 아니라 여기서 잡힌다. 행이 X-lock 을 받느 냐 마느냐는 need_lockingforce_update_inplace 가 결정한 다는 점이다. 일반적인 실행기 주도 UPDATE / DELETE 에서는 행이 이미 SELECT 단계에서 X-lock 잡혀 있다(실행기가 S_DELETE / S_UPDATElocator_lock_and_get_objectX_LOCK 으 로 부른다). 그래서 force 경로는 잠금을 건너뛴다. 워크스페이스 주도 경로(서버가 X-lock 잡힌 적 없는 객체를 클라이언트가 flush 한 경우)에서는 force 안의 locator_lock_and_get_object 가 잠금 을 직접 잡는다는 점이다. lock manager 분석 문서 (cubrid-lock-manager.md)가 lock 은 locator 로 흐른다 라 고 적은 설계상의 이유가 여기에 있다.

  3. 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 int
locator_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_insertER_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 — src/transaction/locator_sr.c
int
locator_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_indexis_insert=false 로 호출해 모든 B-tree 에서 키를 제거하고, (3) heap_delete_logicalmvcc_del_id 를 찍는다 — 물리 삭제는 아 직 아니다(자세한 의미는 cubrid-mvcc.md 참고). (4) HA 복제 레코드 를 쓰고, (5) WAL 엔트리는 heap/btree primitive 안에서 암묵적으로 기록된다.

// locator_sr.c — index orchestration entries
extern 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 — sketch
heap_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 int
locator_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 로 거절된다는 점이다.

카탈로그 자체에 닿는 길도 locator 다. root class 는 그 자체로 하나의 OID(oid_Root_class_oid) 이고, 모든 사용자 클래스는 root class 의 heap 안의 OID 다. 실행기가 T1 의 레이아웃을 알고 싶을 때, T1 의 클래스 OID 를 얻어 한 레코드처럼 읽는다는 점이다.

// xlocator_find_class_oid — src/transaction/locator_sr.c
extern 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_classlocator_fetch_class_with_classmop 이 인스턴스 fetch 와 동일한 LC_COPYAREA 메커니즘으로 클래스 객체를 워크스페이스로 끌어 온 다. 클래스에 대응하는 워크스페이스의 MOP 이 실행기가 이후 작업에 사용하는 영속 핸들이며, 그 위에 쌓이는 스키마 캐시가 저장 계층의 HEAP_CLASSREPR_CACHE 를 떠받친다는 점이다.

// locator_sr.c — snapshot-aware reads
extern 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 entries
extern 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.hLC_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.mdlocator_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: 시점에 관찰된 위 치이며, 빠른 참조용일 뿐이다.

  • 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_flagsLC_PREF_FLAG_LOCK, LC_PREF_FLAG_COUNT_OPTIM.
  • enum MULTI_UPDATE_FLAGIS_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_nestedLC_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)”
  • 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 시 사용).
  • xlocator_assign_oid — 단일 영구 OID 사전 발급 (heap_assign_address + 이름 바인딩).
  • xlocator_assign_oid_batchLC_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_objectop_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_locksetLC_LOCKSET 요청의 벌크 fetch.
  • xlocator_fetch_all_reference_lockset — 추이적 폐포 fetch (참조된 클래스/인스턴스).
  • xlocator_fetch_lockhint_classesLC_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_finalizelocator_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_dataALTER TABLE ... REORGANIZE PARTITION 후 파티션 데이터 재배치.
  • locator_rv_redo_rename — 클래스 rename 의 recovery hook.
  • 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 / _setLC_COPYAREA_MANYOBJS 의 multi-update flag 조작.

이 표의 줄 번호는 문서가 마지막으로 updated: 된 시점에 관찰된 값이며, 시간이 지날수록 정확도가 떨어진다는 점이다. 정의가 다른 위 치에 있다면 심볼 이름이 정답이다. 발견하는 김에 표를 갱신해 두면 다음 사람이 고마워한다.

심볼파일
enum LC_COPYAREA_OPERATIONlocator.h107
enum LC_FETCH_VERSION_TYPElocator.h179
struct lc_copyarea_oneobjlocator.h224
struct lc_copyarea_manyobjslocator.h243
struct lc_copy_arealocator.h252
struct lc_lock_setlocator.h286
struct lc_lock_hintlocator.h329
struct lc_oidsetlocator.h395
struct locator_mflush_cachelocator_cl.c69
struct locator_mflush_temp_oidlocator_cl.c61
locator_fetch_objectlocator_cl.c(varies)
locator_fetch_classlocator_cl.c(varies)
locator_fetch_setlocator_cl.c(varies)
locator_mflushlocator_cl.c4435
locator_mflush_initializelocator_cl.c3802
locator_mflush_forcelocator_cl.c3995
locator_flush_classlocator_cl.c4890
locator_flush_instancelocator_cl.c5058
locator_all_flushlocator_cl.c5279
locator_initializelocator_sr.c246
locator_finalizelocator_sr.c364
xlocator_reserve_class_nameslocator_sr.c409
xlocator_find_class_oidlocator_sr.c1033
xlocator_assign_oidlocator_sr.c2043
xlocator_fetchlocator_sr.c2374
xlocator_fetch_alllocator_sr.c2772
xlocator_fetch_locksetlocator_sr.c3052
xlocator_fetch_all_reference_locksetlocator_sr.c3818
locator_check_foreign_keylocator_sr.c4023
locator_insert_forcelocator_sr.c4938
locator_delete_forcelocator_sr.c6116
locator_delete_force_internallocator_sr.c6172
locator_force_for_multi_updatelocator_sr.c6543
xlocator_forcelocator_sr.c7129
locator_attribute_info_forcelocator_sr.c7461
locator_add_or_remove_indexlocator_sr.c7695
locator_add_or_remove_index_internallocator_sr.c7760
locator_update_indexlocator_sr.c8260
locator_check_unique_btree_entrieslocator_sr.c9768
xlocator_fetch_lockhint_classeslocator_sr.c11356
xlocator_assign_oid_batchlocator_sr.c11577
xlocator_check_fk_validitylocator_sr.c11754
locator_lock_and_get_object_internallocator_sr.c12936
locator_lock_and_get_object_with_evaluationlocator_sr.c13100
locator_get_objectlocator_sr.c13241
locator_lock_and_get_objectlocator_sr.c13352
locator_allocate_copy_area_by_lengthlocator.c(varies)
locator_pack_copy_area_descriptorlocator.c(varies)
locator_pack_locksetlocator.c(varies)
locator_pack_lockhintlocator.c(varies)
locator_pack_oid_setlocator.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_forceheap_*_logical 의 주된 호출자다” 라고 한다. 확인됨. locator_insert_forceheap_create_insert_context 다음에 heap_insert_logical 을 호출하고, locator_delete_force_internalheap_delete_logical 을, locator_update_forceheap_update_logical 을 부른다. 다른 호출 지점은 거의 없으며, 있는 것들(복구 시 재실행, vacuum)도 의도적으로 특수한 경로다.

  • cubrid-ha-replication.mdlocator_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_forceneed_locking=false 로 호출된 다.
    • 워크스페이스 주도 경로 (서버가 X-lock 잡은 적 없는 객체를 클라이언트가 flush 한 경우) — X-lock 은 locator_attribute_info_force 안의 LC_FLUSH_UPDATE 가지에서 locator_lock_and_get_objectneed_locking=true 로 잡는다는 점이다.
    • force-update-in-place 경로 (카탈로그 업데이트, 복구) — force_update_inplaceUPDATE_INPLACE_OLD_MVCCID 같은 값이 며, 함수는 MVCC 를 참조하지 않고 잠금도 잡지 않는 heap_get_last_version 을 사용한다. 상위 잠금은 호출자가 책임 진다. 어느 경로든 행 잠금은 locator 라는 렌즈로 일어난다는 점은 공통이다.
  • 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 하기 때문이다.

  1. 양방향 LC_COPYAREA 레이아웃의 실제 비용은 얼마인가? 행 본문은 앞쪽으로, 디스크립터는 뒤쪽으로 자라며 가운데에서 만난 다. 충돌이 발생하면 locator_mflush_reallocate_copy_area 가 작 동한다. 일반적인 OLTP 워크로드에서 “재할당해야 했다 vs 들어맞 았다” 의 분포는 실제로 어떻게 되는가? 조사 방향: 큰 쓰기 워크로 드 아래에서 재할당 횟수를 계측하고 기본 COPYAREA 크기를 재고 한다는 점이다.

  2. 모든 flush 연산에 *_PRUNE*_PRUNE_VERIFY 변형이 있는 이유는 무엇인가? LC_FLUSH_INSERT_PRUNE 은 서버에서 partition pruning 을 트리거하고, _PRUNE_VERIFY 는 “정말 올바른 파티션에 안착했는가?” 라는 추가 검사를 더한다는 점이다. verify 변형은 클 라이언트/서버 스키마 버전 불일치를 방어하는 인상이다. 조사 방 향: 클라이언트(locator_mem_to_disk 와 친구들)에서 각 변형이 어디서 만들어지는지 추적해 트리거 조건을 문서화한다.

  3. multi-update flag 의미. IS_MULTI_UPDATE, START_MULTI_UPDATE, END_MULTI_UPDATELC_COPYAREA_MANYOBJS 헤더에 실린다. 서버의 locator_force_for_multi_updateIS_MULTI_UPDATE 로 분기하 는데, START/END 쌍은 같은 논리적 UPDATE 의 여러 LC_COPYAREA 에 걸친 unique 통계 수집 을 묶어 주는 것으로 보인다. 조사 방 향: btree_unique_stats 경로에서 START_MULTI_UPDATE 의 producer 와 consumer 를 짚어낸다는 점이다.

  4. locator_Mht_classnames 는 모듈 스코프이며 사실상 싱글톤이 다. DDL 이 폭주하는 환경에서의 리사이즈 의미는 어떠한가? 조 사 방향: 해시가 크기 한도를 가지는지, eviction 정책이 있다면 무엇인지 확인한다.

  5. 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) 전제 — 즉, 호출자가 이미 잠금을 들고 있다 는 단언으로 보인다. 모든 소비자 를 좀 더 꼼꼼히 감사해 볼 만하다.

  6. locator_force (클라이언트) 와 xlocator_force (서버) 의 네이밍 컨벤션. 패턴은 일관된다. locator_* 는 클라이언트 측 또는 공유, xlocator_* 는 서버에서 호출 가능한 와이어 진입점 (x 접두사가 CUBRID 컨벤션에서 외부/와이어 진입점 을 뜻한다) 이다. 이것이 유일한 접두사 규칙인지 확정해 둘 만하다.

  7. 워크스페이스 decache eviction 정책. LOCATOR_MFLUSH_CACHEdecache 플래그와 locator_flush_and_decache_instance 는 flush 와 함께 MOP 을 캐시에서 제거 할 수 있게 한다. 상위 계층 은 언제 이 기능을 사용하는가? 캐시 압력에 의한 eviction 은 work_space.c 안에 살고 있는 듯하다. 호출 지점을 따라가 두면 계약이 분명해질 것이다.

  8. xlocator_fetch_all_reference_lockset 의 참조 폐포 경로. 참조를 추이적으로 따라가는 일은 넓은 스키마에서는 폭발할 위험 이 있다. 무엇이 그것을 막는가? 조사 방향: locator_all_reference_lockset 을 정독해 prune_levelquit_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.mdmvcc_rec_header 스탬핑과 locator 의 읽기 가 참조하는 가시성 술어.
  • cubrid-lock-manager.mdlocator_lock_and_get_object 를 관통하는 잠금 모드와 획득 지점.
  • cubrid-page-buffer-manager.md — force 패밀리가 페이지 사이를 가로질러 보존하는 PGBUF_WATCHER 체인.
  • cubrid-btree.mdlocator_add_or_remove_index_internal 이 호출하는 B-tree primitive.
  • cubrid-ha-replication.mdlocator_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 의 동기.