(KO) CUBRID 카탈로그 매니저 — disk representation, 시스템 클래스, 통계
목차
학술적 배경
섹션 제목: “학술적 배경”카탈로그는 데이터베이스의 자기 기술 서술서 다. 다른 모든 서브 시스템 — 파서, 옵티마이저, MVCC, lock 매니저, vacuum, CDC — 이 카탈로그에 묻는 질문이 정해져 있다. “클래스 C에는 어떤 attribute 가 있는가?, 그 heap 파일은 어디인가?, 어떤 인덱스가 그것을 대상으로 하는가?”, 행 개수는 얼마인가? Database Internals (Petrov) 1장 §Database storage 와 7장 §Storage Engines 가 이를 데이터베이스의 두 가지 보편 불변식 중 하나라고 정리한다 — 스키마와 저장 레이아웃은 디스크 위 바이트만으로 다시 만들어 낼 수 있어야 한다. out-of-band 지식 없이.
이 모델 위에서 모든 실제 엔진은 두 가지 구현 결정을 내려야 한다. 두 결정이 본 문서 골격을 만든다.
- 부트스트랩 root 가 어디에 사는가. 카탈로그는 시작점이
필요하다 — OID 하나, 고정된 페이지 id, 또는 파일 id. 모든
CUBRID 데이터베이스 파일에서 같은 자리에 있어야 한다. 그렇지
않으면 카탈로그를 여는 일조차 카탈로그를 필요로 하는 닭과
달걀 문제에 빠진다. 엔진이 고르는 답은 — root class 의
고정 OID (CUBRID, OODB 스타일), 시스템 tablespace 의 고정
페이지 (Oracle의 부트스트랩 segment), 또는 하드코딩된 스키마
를 가진 고정 테이블 OID (PostgreSQL의
pg_class). - 저장 레이아웃과 사용자 가시 스키마가 같은 구조인가. 어떤
엔진은 둘을 통합한다. PostgreSQL의
pg_class는 그 자체로 테이블 카탈로그이며, 사용자 테이블과 같은 heap+index 기계로 접근된다. 다른 엔진은 둘을 분리한다 — 내부 catalog manager 가 엔진 hot path 용 컴팩트한 disk-representation 레코드를 저장하고, 그 옆에 SQL이 스키마를 들여다 볼 수 있도록 사용자 가시 시스템 클래스 (_db_class,_db_attribute, …) 를 따로 둔다. CUBRID은 분리 설계 를 고른다 — 내부system_catalog와 사용자 가시catcls_*테이블이 공존하며, 사용자 측 테이블 은 내부 레코드로부터 구동된다.
이 두 결정의 답이 보이고 나면, 본 문서의 모든 CUBRID 구조는 그 답 중 하나를 구현하거나 그 답에 빠르게 닿기 위해 존재한다는 점이 분명해진다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”모든 관계형 엔진은 카탈로그 저장과 부트스트랩 주변에서 비슷한 패턴을 채택한다.
자기 자신 위에 사는 자기 기술 저장소
섹션 제목: “자기 자신 위에 사는 자기 기술 저장소”카탈로그는 다른 클래스와 똑같이 heap 파일에 저장된다. 그러나
heap 매니저가 페이지를 해석하려면 카탈로그 레코드가 필요하다.
이 닭과 달걀이 풀리는 자리가 부트스트랩 클래스 다. 엔진이
카탈로그를 보지 않고도 해석할 수 있는 하드코딩된 스키마를 가진
클래스다. CUBRID의 root class가 그 씨앗이다. root class 로부터
엔진이 _db_class 를 배우고, _db_class 로부터
_db_attribute 를 배우는 식이다.
Disk representation vs. 논리 스키마
섹션 제목: “Disk representation vs. 논리 스키마”엔진은 물리 disk representation (attribute의 바이트 순서,
fixed vs. variable, padding) 과 논리 스키마 (컬럼 이름, 타입,
제약) 를 구분한다. disk representation 은 기존 행을 무효화하지
않고도 바뀔 수 있다. 버전 관리 덕분이다 — 클래스마다 REPR_ID
로 색인된 representation 리스트를 두고, 모든 heap 행은 자기가
쓰여진 시점의 REPR_ID 를 함께 들고 다닌다. ALTER TABLE은
representation을 bump하고, 옛 행은 옛 representation으로 디코딩
되며, 새 행은 새 representation으로 디코딩된다.
별도의, 자주 변하는 통계 레코드
섹션 제목: “별도의, 자주 변하는 통계 레코드”통계는 옵티마이저의 입력이지 스키마의 일부가 아니다. 끊임없이
변한다. 그래서 엔진은 통계를 카탈로그 옆에 두면서도 갱신 주기를
다르게 잡는다. PostgreSQL은 pg_statistic, InnoDB은
mysql.innodb_index_stats, CUBRID은 CLS_INFO 안에 클래스별
통계와 함께 인덱스별 BTREE_STATS 를 둔다.
카디널리티 추정의 세 단위
섹션 제목: “카디널리티 추정의 세 단위”옵티마이저가 묻는 카디널리티는 세 단위다. 클래스 단위
(heap_num_objects, heap_num_pages), attribute 단위 (n_distinct_values),
인덱스 단위 (B+Tree key count, leaf count, height, 컴파운드
인덱스를 위한 partial-key 카디널리티). CUBRID의 CLASS_STATS,
ATTR_STATS, BTREE_STATS 가 이 세 단위에 일대일로 대응한다.
두 가지 접근 흐름 — server-side와 client-side
섹션 제목: “두 가지 접근 흐름 — server-side와 client-side”서버는 query 실행 도중 카탈로그를 읽는다. 클라이언트는 DDL
파싱과 스키마 introspection 시점에 읽는다. CUBRID은 statistics
를 *_sr.c (서버) 와 *_cl.c (클라이언트) 소스를 병행해서
출하한다. _sr 측이 권위 있는 쪽이다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 | CUBRID 명칭 |
|---|---|
| 카탈로그 식별자 (boot anchor) | CTID { vfid, xhid, hpgid } (system_catalog.h); 글로벌 catalog_Id |
| 한 클래스의 disk representation | DISK_REPR { id, n_fixed, fixed[], n_variable, variable[] } (system_catalog.h) |
| Attribute별 disk 정보 | DISK_ATTR { id, location, type, val_length, value, position, classoid, n_btstats, bt_stats, ndv } |
| 클래스별 카탈로그 정보 | CLS_INFO { ci_hfid, ci_tot_pages, ci_tot_objects, ci_time_stamp, ci_rep_dir } |
| 접근 시점의 transient 상태 | CATALOG_ACCESS_INFO { class_oid, dir_oid, class_name, is_update, … } |
| 사용자 가시 시스템 클래스 | _db_class, _db_attribute, _db_index, _db_domain, _db_method, … |
| 카탈로그 클래스 진입점 패밀리 | catcls_* 함수들 (catalog_class.c) |
| 카탈로그 primary heap | catalog_Id.vfid — 카탈로그 레코드를 담는 파일 |
| 카탈로그 디렉토리 hash | catalog_Id.xhid — extendible-hash 인덱스 class_oid → dir_oid |
| 카탈로그 헤더 페이지 | catalog_Id.hpgid — 카탈로그 메타데이터를 담는 고정 페이지 id |
| 클래스 통계 | CLASS_STATS { time_stamp, heap_num_objects, heap_num_pages, n_attrs, attr_stats[] } (statistics.h) |
| Attribute별 통계 | ATTR_STATS { id, type, n_btstats, bt_stats[], ndv } |
| 인덱스별 통계 | BTREE_STATS { btid, leafs, pages, height, keys, has_function, key_type, pkeys[] } |
| 카탈로그 복구 함수 | catalog_rv_new_page_redo, catalog_rv_insert_{redo,undo}, catalog_rv_delete_{redo,undo}, catalog_rv_update, catalog_rv_ovf_page_logical_insert_undo |
| 서버 부트 | boot_restart_server (boot_sr.c) |
| 카탈로그 access 시작 | catalog_start_access_with_dir_oid |
| 카탈로그 access 종료 | catalog_end_access_with_dir_oid |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”카탈로그에는 네 개의 이동 부품이 있다. 카탈로그 식별자와 그 세 권 layout, 클래스별 저장을 잡아 두는 disk-representation 레코드, 같은 데이터를 SQL로 노출하는 사용자 가시 시스템 클 래스, 그리고 스키마와는 다른 갱신 주기를 가진 통계 레코드. 이 순서로 본다.
전체 구조
섹션 제목: “전체 구조”flowchart LR
subgraph BOOT["부트스트랩"]
ROOT["Root class (고정 OID)"]
BOOTFN["boot_restart_server"]
end
subgraph CTID["catalog_Id (CTID)"]
VFID["vfid: 카탈로그 heap 파일"]
XHID["xhid: extendible hash class_oid → dir_oid"]
HPGID["hpgid: 카탈로그 헤더 페이지"]
end
subgraph CR["카탈로그 레코드 (system_catalog.c)"]
DR1["DISK_REPR — 클래스 A repr 1"]
DR2["DISK_REPR — 클래스 A repr 2"]
CLI["CLS_INFO — 클래스 A"]
CR1["..."]
end
subgraph SC["사용자 가시 시스템 클래스 (catcls)"]
DBC["_db_class"]
DBA["_db_attribute"]
DBI["_db_index"]
DBD["_db_domain"]
DBT["_db_data_type"]
end
subgraph ST["통계"]
CS["CLASS_STATS"]
AS["ATTR_STATS"]
BS["BTREE_STATS"]
end
BOOTFN --> ROOT
BOOTFN --> CTID
CTID --> CR
ROOT --> SC
SC -.동기화됨.-> CR
CR -.카디널리티 출처.-> ST
ST --> CS --> AS --> BS
이 그림이 보여 주는 세 경계가 있다. 첫째, boot vs runtime.
부트 시점에 미리 알고 있는 OID는 root class 하나뿐이다. 그
이후의 모든 자리는 root에서 도달 가능하다. 둘째, 내부 vs 사용자
가시. catalog_Id 아래의 카탈로그 레코드는 컴팩트한 disk
representation이며 엔진이 hot path에서 읽는다. _db_* 아래의
시스템 클래스는 SQL이 스키마를 들여다 보는 표면이다. 셋째,
스키마 vs 통계. representation은 모양을 기술하고 자주 변하지
않는다. 통계는 크기를 기술하고 매 analyze마다 변한다.
CTID — 카탈로그의 세 권 식별자
섹션 제목: “CTID — 카탈로그의 세 권 식별자”카탈로그 자체는 작은 자료 구조다. 세 포인터를 가진다.
// CTID — src/storage/system_catalog.hstruct ctid{ VFID vfid; /* catalog volume identifier — heap file holding catalog records */ EHID xhid; /* extendible hash index identifier — class_oid → dir_oid map */ PAGEID hpgid; /* catalog header page identifier */};typedef struct ctid CTID;
extern CTID catalog_Id; /* global catalog identifier */세 컴포넌트는 디스크 위 세 객체에 대응한다.
vfid— 카탈로그의DISK_REPR와CLS_INFO레코드를 담는 heap 파일이다. heap 매니저가 일반 heap처럼 다룬다 (cubrid-heap-manager.md). 단 한 가지 추가 불변식이 있다 — 어떤 트랜잭션이 그 클래스를 여전히 볼 수 있는 동안에는 카탈로그 레코드가 vacuum되어 사라 져서는 안 된다.xhid—class_oid를 키로 하는 extendible-hash 인덱스 다. 그 클래스의 directory 레코드 OID를 가리킨다. directory 레코드는 다시 그 클래스의 모든DISK_REPR레코드 OID 리스트 를 들고 있다 (representation 하나당 하나, 그리고CLS_INFO하나).hpgid— 카탈로그 헤더 페이지다. 글로벌 카탈로그 메타데이 터 (버전, 마지막으로 할당된 representation id 등) 를 들고 있다. 페이지 id는 boot 시점에 정해지고 그 이후로는 변하지 않는다.
catalog_initialize (system_catalog.c) 가 boot 동안
글로벌 catalog_Id 를 이 세 값으로 채운다. 그 시점부터 모든
카탈로그 access는 catalog_Id 에서 출발해 세 컴포넌트 중 하나로
내려간다.
Disk representation — attribute 레이아웃당 한 레코드
섹션 제목: “Disk representation — attribute 레이아웃당 한 레코드”클래스별, representation별 레코드는 다음과 같다.
// DISK_REPR — src/storage/system_catalog.hstruct disk_representation{ REPR_ID id; /* representation identifier */ int n_fixed; /* number of fixed-length attributes */ struct disk_attribute *fixed; /* fixed attribute structures */ int fixed_length; /* total length of fixed attrs */ int n_variable; /* number of variable-length attrs */ struct disk_attribute *variable; /* variable attribute structures */};fixed 와 variable 의 분리가 디스크 레이아웃의 결정이다. 고정
길이 attribute는 attribute별 offset overhead 없이 빽빽이 채워
지고, 가변 길이 attribute는 행 앞쪽의 offset 표를 함께 들고 다닌
다. disk_representation::fixed[] 와 ::variable[] 을 차례로
순회하는 것이 곧 heap 매니저가 행을 해석하는 순서다.
각 attribute 가 들고 다니는 정보는 다음과 같다.
// DISK_ATTR — src/storage/system_catalog.hstruct disk_attribute{ ATTR_ID id; /* attribute identifier */ int location; /* fixed: exact offset; variable: index into the offset table */ DB_TYPE type; /* int / varchar / float / … */ int val_length; /* default value length, ≥ 0 */ void *value; /* default value (no default expression) */ int position; /* storage position (fixed only) */ OID classoid; /* source class — for inherited attrs */ int n_btstats; /* number of B+tree statistics */ BTREE_STATS *bt_stats; /* per-index stats array */ INT64 ndv; /* Number of Distinct Values */};짚을 두 필드가 있다. classoid — 상속된 attribute의 경우, 이
값이 상속 사슬 어디에서 attribute가 처음 정의되었는지를 식별한
다. 옵티마이저가 클래스 카디널리티를 계산할 때 상속된 attribute
를 두 번 세지 않게 하기 위함이다. bt_stats[] 와 ndv —
통계가 attribute 레코드 안에 산다. 별도 파일이 아니다. 절충
은 분명하다 — ALTER STATS 가 attribute 레코드 전체를 다시 쓴다.
별도 통계 테이블보다 무겁다. 그러나 이득은 옵티마이저가 disk
representation을 한 번 fetch하면 모든 정보를 한꺼번에 얻는다
는 점이다.
클래스별 정보 — heap 포인터와 대략적 카디널리티
섹션 제목: “클래스별 정보 — heap 포인터와 대략적 카디널리티”CLS_INFO 가 클래스별 요약 레코드다.
// CLS_INFO — src/storage/system_catalog.hstruct cls_info{ HFID ci_hfid; /* heap file identifier for the class */ int ci_tot_pages; /* total pages in the heap file */ int ci_tot_objects; /* total live objects */ unsigned int ci_time_stamp; /* timestamp of last update */ OID ci_rep_dir; /* representation directory record OID */};ci_hfid 가 가장 자주 읽히는 필드다. 클래스를 스캔하는 모든
query 가 그 시작점에 클래스의 CLS_INFO 를 카탈로그에서 가져오고,
ci_hfid 를 읽고, 그 heap 파일을 scan 매니저에 넘긴다.
ci_rep_dir 은 CLS_INFO에서 directory 레코드로 가는 back-pointer
다. directory는 이 클래스의 모든 representation을 나열한다. 표준
lookup 사슬은 class_oid → xhid → dir_oid → DISK_REPR 다.
역방향 traversal — 즉 CLS_INFO::ci_rep_dir → DISK_REPR — 은
ALTER 도중에 쓰인다.
ci_time_stamp 는 캐시 검증 토큰이다. 옵티마이저는 CLS_INFO를
프로세스 메모리에 캐시하고, ci_time_stamp 가 전진하면 캐시를
무효화한다.
카탈로그 access — 접근 시점 상태
섹션 제목: “카탈로그 access — 접근 시점 상태”카탈로그 read나 write는 모두 CATALOG_ACCESS_INFO 세션을
거친다.
// CATALOG_ACCESS_INFO — src/storage/system_catalog.hstruct catalog_access_info{ OID *class_oid; OID *dir_oid; /* cached after first xhid lookup */ char *class_name; bool is_update; /* update access — needs X locks */ bool need_unlock; /* unlock at end-access time */ bool access_started; /* guard against double-start */ bool need_free_class_name;#if !defined (NDEBUG) bool is_systemop_started;#endif};세션은 catalog_start_access_with_dir_oid 로 열고
catalog_end_access_with_dir_oid 로 닫는다. 그 사이에서 호출자
는 directory OID를 캐시한 채로, 클래스 lock을 잡은 채로 (read
는 S, update는 X), 그리고 (update 세션에서는) 부분 카탈로그
갱신이 한 logical 단위로 rollback될 수 있도록 system-op 괄호를
열어 둔 채로 동작한다. debug-only is_systemop_started 필드가
이 규율을 assert로 검사한다.
카탈로그-클래스 기계 — 사용자 가시 스키마
섹션 제목: “카탈로그-클래스 기계 — 사용자 가시 스키마”catalog_Id 위에 뿌리내린 레코드 옆에서, CUBRID은 같은 데이터를
SQL로 노출하는 사용자 가시 클래스 집합을 유지한다. 그 클래스들
은 관습적으로 _db_* 라고 명명된다.
_db_class— 클래스마다 한 행. 이름, OID, owner, 종류 (테이블 / view / partition), 생성 시각._db_attribute— 클래스의 attribute마다 한 행._db_index— 인덱스마다 한 행._db_domain— 타입 도메인마다 한 행 (compound 도메인에 사용)._db_data_type— 시스템 데이터 타입 카탈로그._db_method,_db_meth_arg,_db_meth_file,_db_method_sig— OODB 메소드용._db_partition— 파티셔닝 정보._db_trigger— 트리거._db_serial— 시퀀스._db_collation— collation 카탈로그._db_charset,_db_servers,_db_user,_db_auth,_db_password,_db_synonym, … — 부속.
catalog_class.c 의 catcls_* 패밀리가 다리 역할을 한다.
// catalog_class.h — src/storage/catalog_class.hextern bool catcls_Enable;
int catcls_compile_catalog_classes (THREAD_ENTRY *thread_p);int catcls_insert_catalog_classes (THREAD_ENTRY *thread_p, RECDES *record);int catcls_delete_catalog_classes (THREAD_ENTRY *thread_p, const char *name, OID *class_oid);int catcls_update_catalog_classes (THREAD_ENTRY *thread_p, const char *name, RECDES *record, OID *class_oid_p, UPDATE_INPLACE_STYLE force_in_place);int catcls_finalize_class_oid_to_oid_hash_table (THREAD_ENTRY *thread_p);int catcls_remove_entry (THREAD_ENTRY *thread_p, OID *class_oid);int catcls_get_server_compat_info (THREAD_ENTRY *thread_p, INTL_CODESET *charset_id_p, char *lang_buf, const int lang_buf_size, char *timezone_checksum);int catcls_get_db_collation (THREAD_ENTRY *thread_p, LANG_COLL_COMPAT **db_collations, int *coll_cnt);int catcls_update_class_stats (THREAD_ENTRY *thread_p, const char *class_name, unsigned int ci_time_stamp, bool with_fullscan);DDL이 클래스를 생성하거나 변경할 때, 엔진은 카탈로그에 내부
DISK_REPR 을 쓰는 동시에 _db_class 에 행 하나를 (그리고
관련 행들을 _db_attribute, _db_index, … 에) insert한다. 두
쓰기는 한 트랜잭션에 묶여 함께 commit된다. 분기가 발생하면 (예 —
두 쓰기 사이에 충돌) 복구 패스가 부분 작업을 한 단위로 rollback
한다. lock-mode escalation 하에서 동시 reader가 두 표면을 어긋난
상태로 관찰할 수 있는지는 미해결이다 (Open Q3 참조).
catcls_compile_catalog_classes 가 처음에 시스템 클래스를
하드코딩된 스키마에서 구축한다. 이 경로는 install 시점에 동작
한다. (이름이 비슷한 catcls_insert_catalog_classes 는 DDL
시점에 행을 insert하는 진입점이지, install-time 컴파일러가
아니다.) 스키마 소스는 src/object/schema_system_catalog_install.cpp
에 산다 (CUBRID AGENTS.md 가 그 자리의 엄격한 포맷 규칙을 언급
한다).
부트스트랩 — boot_restart_server와 root class
섹션 제목: “부트스트랩 — boot_restart_server와 root class”boot_restart_server (boot_sr.c) 가 복구 후 카탈로그를
온라인으로 가져오는 진입점이다.
- 로그 초기화 + 복구.
log_initialize가 세 패스 재시작을 돌린다 (cubrid-recovery-manager.md). - disk와 file 매니저 초기화.
catalog_Id읽기. 데이터베이스 파라미터 파일 (boot_DB_parm레코드) 과 디스크 헤더로부터.(vfid, xhid, hpgid)가 손에 들어온다.catalog_initialize (catalog_Id). 인메모리 구조 설정.- root class 바인딩. root class의 OID는 boot 파라미터에
고정 저장되어 있다. 엔진이 그 OID로부터 카탈로그에서
DISK_REPR을 읽어 와 메타클래스 캐시를 prime한다. - 시스템 클래스 바인딩. root class에서 출발해
_db_class,_db_attribute등이 로드된다. 클래스 이름 ↔ OID 매핑이catcls_class_oid_to_oid_hash_table에 캐시된다. - 통계 캐시 초기화.
- vacuum master + workers 시작 (cubrid-vacuum.md).
7단계가 끝나면 서버는 query를 받을 수 있다. boot_DB_parm 은
디스크 위 파라미터 레코드다. 갱신은 catcls_update_catalog_classes
를 거쳐 흘러 가, 내구성을 갖는다.
통계 — 다른 갱신 주기, 다른 access
섹션 제목: “통계 — 다른 갱신 주기, 다른 access”통계는 같은 DISK_ATTR 레코드의 일부지만 갱신 주기가 다르다.
UPDATE STATISTICS나 fullscan이 일어날 때마다 갱신된다. 통계 구
조체는 다음과 같다.
// statistics.h — src/storage/statistics.hstruct btree_stats{ BTID btid; int leafs; /* leaf pages including overflow */ int pages; /* total pages */ int height; /* tree depth */ int keys; /* distinct keys */ int has_function; /* function index? */ TP_DOMAIN *key_type; int pkeys_size; /* compound-key partial-cardinality array */ int *pkeys; /* pkeys[k] = NDV of first k+1 components */ int dedup_idx; /* SUPPORT_DEDUPLICATE_KEY_MODE */};
struct attr_stats{ int id; DB_TYPE type; int n_btstats; BTREE_STATS *bt_stats; INT64 ndv; /* Number of Distinct Values */};
struct class_stats{ unsigned int time_stamp; int heap_num_objects; int heap_num_pages; int n_attrs; ATTR_STATS *attr_stats;};pkeys[] 배열 이 짚어 둘 만하다. compound 인덱스 (a, b, ..., x) 에서 pkeys_size = k 라고 하자. pkeys[i] 는 처음
i+1 개 컬럼의 카디널리티다. 옵티마이저가 인덱스의 prefix만
사용하는 query의 selectivity를 추정할 때 이 값을 쓴다. 이 배열이
없다면 매 prefix query가 컬럼 분포의 독립성을 가정해야 했을
것이다.
STATS_SAMPLING_THRESHOLD = 5000 과 NUMBER_OF_SAMPLING_PAGES = 5000 (statistics.h 에 선언) 이 샘플링 기본값이다. fullscan
모드 (STATS_WITH_FULLSCAN) 가 그 대안이다.
stats_adjust_sampling_weight inline (statistics.h 안) 이
샘플링 NDV 가 기대값의 1% 이하로
나오면 차등 weight를 적용한다. 가정은 명료하다 — 샘플 데이터에
중복이 많으면 전체 데이터에도 중복이 많을 것이다.
서버 측 통계 access는 statistics_sr.c 를 거치고, 클라이언트 측
(SQL 인터페이스) 은 statistics_cl.c 와 stats_get_statistics
(statistics.h 에 선언, SERVER_MODE 에서는 비활성) 를
거친다.
한 번의 ALTER, 처음부터 끝까지
섹션 제목: “한 번의 ALTER, 처음부터 끝까지”sequenceDiagram participant CL as DDL parser participant CAT as catalog (system_catalog) participant CC as catalog_class (catcls_*) participant LM as log_manager (sysop) participant LCK as lock_manager CL->>LCK: X-lock class_oid CL->>LM: log_sysop_start (DDL atomic) CL->>CAT: catalog_start_access_with_dir_oid (X) CL->>CAT: catalog_add_representation (new DISK_REPR with new REPR_ID) CL->>CAT: catalog_update_class_info (CLS_INFO ci_time_stamp bumped) CL->>CC: catcls_update_catalog_classes (_db_class row) CL->>CC: catcls_update_catalog_classes (_db_attribute rows) CL->>CAT: catalog_end_access_with_dir_oid CL->>LM: log_sysop_commit CL->>LCK: X-lock release at commit
두 카탈로그 표면 — 내부 레코드와 사용자 가시 시스템 클래스 — 이 같은 system op 안에서 갱신된다. 그래서 갱신 도중 충돌이 나면 둘이 함께 rollback되거나 둘이 함께 유지된다. DDL 트랜잭션은 ~10개의 다른 파일을 만지지만, 단일 atomic 단위로 commit된다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”anchor는 심볼명 이다. 라인은 흘러간다.
헤더와 타입
섹션 제목: “헤더와 타입”CTID(system_catalog.h) — 카탈로그 식별자.DISK_REPR/DISK_ATTR(system_catalog.h) — disk representation 레코드.CLS_INFO(system_catalog.h) — 클래스별 요약.CATALOG_ACCESS_INFO(system_catalog.h) — 접근별 세션.CATALOG_DIR_REPR_KEY = -2매크로 (system_catalog.h) — directory 키 sentinel.BTREE_STATS/ATTR_STATS/CLASS_STATS(statistics.h).BTREE_STATS_PKEYS_NUM = 8매크로 (statistics.h) — compound-key 배열 한도.STATS_SAMPLING_THRESHOLD/NUMBER_OF_SAMPLING_PAGES(statistics.h).
Lifecycle
섹션 제목: “Lifecycle”catalog_initialize(system_catalog.c).catalog_finalize(system_catalog.c).catalog_create(system_catalog.c) — 최초 설정. root만 호출.catalog_destroy(system_catalog.c) — 카탈로그 drop.catalog_reclaim_space(system_catalog.c) — 단편화된 카탈 로그 레코드를 압축.
Read access
섹션 제목: “Read access”catalog_get_class_info(system_catalog.c).catalog_get_representation(system_catalog.c).catalog_get_representation_directory(system_catalog.c).catalog_get_last_representation_id(system_catalog.c).catalog_get_class_info_from_record(system_catalog.c) — heap 레코드에서 CLS_INFO 디코딩.catalog_get_dir_oid_from_cache(system_catalog.c) — 캐시 lookup.
Write access
섹션 제목: “Write access”catalog_add_representation(system_catalog.c).catalog_add_class_info(system_catalog.c).catalog_update_class_info(system_catalog.c).catalog_drop_old_representations(system_catalog.c).catalog_insert/catalog_update/catalog_delete(system_catalog.c) — generic 레코드 단위 op.
세션 괄호
섹션 제목: “세션 괄호”catalog_start_access_with_dir_oid(system_catalog.c).catalog_end_access_with_dir_oid(system_catalog.c).
복구 함수
섹션 제목: “복구 함수”catalog_rv_new_page_redo,catalog_rv_insert_redo/_undo,catalog_rv_delete_redo/_undo,catalog_rv_update,catalog_rv_ovf_page_logical_insert_undo(system_catalog.h에 선언,system_catalog.c에 정의).
카디널리티
섹션 제목: “카디널리티”catalog_get_cardinality(system_catalog.c).catalog_get_cardinality_by_name(system_catalog.c).
사용자 가시 시스템 클래스
섹션 제목: “사용자 가시 시스템 클래스”catcls_compile_catalog_classes(catalog_class.c) — install 시 스키마 빌드.catcls_insert_catalog_classes(catalog_class.c).catcls_update_catalog_classes(catalog_class.c).catcls_delete_catalog_classes(catalog_class.c).catcls_remove_entry(catalog_class.c).catcls_get_server_compat_info(catalog_class.c) — boot 시점의 charset / locale / timezone 호환성 검사.catcls_get_db_collation(catalog_class.c).catcls_update_class_stats(catalog_class.c).catcls_finalize_class_oid_to_oid_hash_table(catalog_class.c).catcls_find_and_set_cached_class_oid(catalog_class.c).
Boot
섹션 제목: “Boot”boot_restart_server(boot_sr.c) — 메인 boot 진입점.- boot 파라미터 레코드 (
boot_DB_parm) — 카탈로그 OID를 포함한 디스크 상주 데이터베이스 파라미터.
Statistics
섹션 제목: “Statistics”stats_get_statistics(statistics.h,statistics_cl.c에 정의) — 클라이언트 측 fetch.stats_dump/stats_ndv_dump(statistics.h) — 디버깅.stats_make_select_list_for_ndv(statistics.h).stats_get_ndv_by_query(statistics.h).stats_adjust_sampling_weight(statistics.h, inline).
이 개정 시점의 위치 힌트 (2026-04-30)
섹션 제목: “이 개정 시점의 위치 힌트 (2026-04-30)”| 심볼 | 파일 | 라인 |
|---|---|---|
CTID (struct) | system_catalog.h | 45 |
DISK_REPR (struct) | system_catalog.h | 63 |
DISK_ATTR (struct) | system_catalog.h | 80 |
CLS_INFO (struct) | system_catalog.h | 96 |
CATALOG_ACCESS_INFO (struct) | system_catalog.h | 106 |
catalog_Id (extern global) | system_catalog.h | 153 |
BTREE_STATS (struct) | statistics.h | 61 |
ATTR_STATS (struct) | statistics.h | 82 |
CLASS_STATS (struct) | statistics.h | 93 |
stats_adjust_sampling_weight (inline) | statistics.h | 135 |
catalog_initialize | system_catalog.c | 2577 |
catalog_finalize | system_catalog.c | 2607 |
catalog_get_class_info_from_record | system_catalog.c | 504 |
catalog_initialize_max_space | system_catalog.c | 549 |
catalog_initialize_new_page | system_catalog.c | 598 |
catalog_add_representation | system_catalog.c | 2815 |
catalog_add_class_info | system_catalog.c | 3029 |
catalog_update_class_info | system_catalog.c | 3172 |
catalog_get_class_info | system_catalog.c | 4113 |
catcls_insert_catalog_classes | catalog_class.c | 4310 |
boot_restart_server | boot_sr.c | 1969 |
소스 검증 (2026-04-30 기준)
섹션 제목: “소스 검증 (2026-04-30 기준)”검증된 사실
섹션 제목: “검증된 사실”-
카탈로그 소스 파일은
system_catalog.{c,h}와catalog_class.{c,h}다 —catalog_manager.{c,h}가 아니다.find -name 'catalog*'로 검증. 이 doc의references:는 초안 시점에 그에 맞게 수정 되었다 (skeleton은 vendor decks 의 catalog manager 라는 prose 명명에 따라catalog_manager.{c,h}를 가정했었다). -
카탈로그 식별자
CTID는(vfid, xhid, hpgid)의 셋 묶음 이다.system_catalog.h의CTID구조체를 읽고 검증. 셋은 각각 heap 파일 (카탈로그 레코드), extendible-hash 인덱스 (class_oid → dir_oid), 그리고 고정 헤더 페이지에 대응한다. -
catalog_Id는 글로벌이다 — thread별 상태가 아니다.system_catalog.h의extern CTID catalog_Id선언으로 검증. 식별자는 boot 시점 (catalog_initialize) 에 한 번 설정되며, 서버 라이프타임 동안 변하지 않는다. -
DISK_REPR 은 attribute 를 fixed 와 variable 두 배열로 가른다 — 단일 정렬 리스트가 아니다.
system_catalog.h의disk_representation구조체로 검증 (별도n_fixed/fixed[]와n_variable/variable[]필드). 함의 — 행 디코딩이 fixed attribute는 정확한 offset으로, variable attribute는 행별 offset 표로 읽는다. -
통계는 attribute 레코드에 inline으로 산다 — 별도 파일이 아니다.
system_catalog.h의DISK_ATTR위n_btstats,bt_stats,ndv필드로 검증. 비용 — 통계 갱신이 attribute 레코드 전체를 다시 쓴다. 이득 — 옵티마이저가 스키마와 통계를 한 fetch로 읽는다. -
CATALOG_ACCESS_INFO는 debug-onlyis_systemop_started필드를 가진다.system_catalog.h의catalog_access_info구조체에 NDEBUG-conditional 필드로 검증. 그 목적은 update-mode 카탈로그 access 가 system op 으로 적절히 감싸졌는지 assert로 검사하는 것이다. -
사용자 가시 시스템 클래스는
catalog_class.c의catcls_*패밀리로 채워진다 —system_catalog.c의 내부catalog_*와 분리되어 있다.catalog_class.h(헤더 전체가 ~50줄 이내, 작은 표면) 를 읽고catalog_class.c에서catcls_insert_catalog_classes를 grep으로 찾아 검증. 두 표면은 같은 트랜잭션을 공유한다. DDL이 두 표면 모두에서 atomic이다. -
catcls_Enable은 카탈로그-클래스 유지를 켜고 끄는 글로벌 토글이다.catalog_class.h의extern bool catcls_Enable선언으로 검증. false 일 때 시스템 클래스가 동기화되지 않는다 — install과 마이그레이션 동안 쓰인다. -
CLS_INFO::ci_time_stamp가 캐시 검증 토큰이다.system_catalog.h의cls_info구조체 위ci_time_stamp필드로 검증. 옵티마이저가 CLS_INFO를 프로세스 메모리에 캐시 하고, 캐시 무효화는 저장된 timestamp와 현재 timestamp의 비교로 결정한다. -
카탈로그 로그 레코드를 다루는 복구 함수가 일곱 개 있다.
system_catalog.h의 복구 함수 선언으로 검증 —catalog_rv_new_page_redo,catalog_rv_insert_redo,catalog_rv_insert_undo,catalog_rv_delete_redo,catalog_rv_delete_undo,catalog_rv_update,catalog_rv_ovf_page_logical_insert_undo. 마지막 것이 짚어 둘 만하다 — overflow 페이지 insertion이 logical undo를 가진 다. redo는 페이지 할당을 그대로 재생하지만, logical undo는 file 매니저로 페이지를 de-allocate 한다. -
샘플링 기본 페이지 수는 5000이며, 샘플링 임계도 5000이다.
statistics.h의 샘플링 상수들로 검증.STATS_SAMPLING_THRESHOLD = 5000이 시도 횟수,NUMBER_OF_SAMPLING_PAGES = 5000이 페이지 budget,EXPECTED_ROWS_PER_PAGE = 20이 fan-out 가정. -
compound-key partial 카디널리티 배열
pkeys[]의 기본 크기 는 8이다.statistics.h의BTREE_STATS_PKEYS_NUM = 8매크로로 검증. 컬럼이 8개를 넘는 compound 인덱스는 8번째 너머의 prefix selectivity 추적을 잃는다.
미해결 질문
섹션 제목: “미해결 질문”-
root class의 OID가 디스크 어디에 사는가.
boot_DB_parm레코드가 boot 파라미터를 들고 있고, root class OID는 그 안의 특정 필드 하나다. 추적 경로 —boot_sr.c의 1969행 부근 (boot_restart_server) 을 읽고 root-class OID를 어디서 로드하는지 추적. -
카탈로그 overflow-page logical-undo 규율.
catalog_rv_ovf_page_logical_insert_undo가 logical 이지만, overflow 페이지가 깨끗하게 돌아오도록 보장하는 file-manager 호출 시퀀스가 무엇인가? 추적 경로 — 그 함수 본문을 읽고file_dealloc_page호출을 따라가기. -
내부 카탈로그와 사용자 시스템 클래스 사이의 동기화. DDL 은 두 표면을 lockstep으로 갱신해야 한다. 다른 reader가 내부 카탈로그는 갱신된 상태인데
_db_class는 아직 갱신되지 않은 상태를 보지 않게 무엇이 막아 주는가? 추적 경로 — DDL 경로 에서 lock 획득 순서를 추적. 카탈로그 X-lock이 두 표면을 모두 덮는지 확인. -
catcls_update_class_stats의 갱신 주기. 통계 갱신이 이 함수로 흐른다. SQLUPDATE STATISTICS명령과 동기적 인가, 아니면 백그라운드 sweep이 있는가? 추적 경로 — 호출자를 grep하고 daemon 등록을 확인. -
HA 환경에서의 카탈로그-클래스 캐시 무효화. slave 서버에서 master DDL이 replay될 때, slave는 자기 카탈로그-클래스 hash 테이블을 무효화하는가? 추적 경로 — cubrid-cdc.md 와
catcls_finalize_class_oid_to_oid_hash_table. -
catalog_reclaim_space의 갱신 주기와 트리거. 카탈로그 압축은 드물게 일어날 것이다. 그러나 트리거가 헤더에 명시되어 있지 않다. 추적 경로 — 호출자를 grep하고boot_restart_server또는 백그라운드 daemon에서 사용되는지 확인.
CUBRID 너머 — 비교 설계와 연구 동향
섹션 제목: “CUBRID 너머 — 비교 설계와 연구 동향”분석이 아닌 포인터(pointers).
-
PostgreSQL
pg_class— 단일 카탈로그 테이블이다. 정상 heap+index 기계로 접근된다. 부트스트랩은genbki.pl스크립트 와pg_*_d.h매크로로 한다. CUBRID의 분리 설계가 통합을 포기하는 대신 옵티마이저가 직접 읽는 더 컴팩트한 내부 레코드 형식을 얻는다. -
MySQL data dictionary (8.0+) — 8.0 이후 InnoDB 테이블이 되었다. 그 이전에는 FRM 파일이었다. CUBRID의 분리 설계는 시기적으로 8.0 이전이며, 개념적으로 8.0 이전 MySQL과 비슷 하다 (SQL 표면과 별도의 바이너리 구조).
-
Oracle의 부트스트랩 segment — instance 시작 시 읽히는 단일 행
obj$씨앗. CUBRID의boot_DB_parm+ root-class OID 는 같은 아이디어를 두 anchor로 푼 것이다. -
REPR_ID 기반 스키마 버저닝은 PG의
pg_attribute.atttypid버저닝과 비슷하다 — 두 엔진 모두 행을 자기 저장 시점의 representation으로 디코드한다. 모든 행을 즉시 다시 쓰지 않고 도 온라인 ALTER TABLE이 가능해진다. 차이는 어디에 버전이 사는지다 — CUBRID은 행별 (행 헤더의REPR_ID), PG는 테이블 버전별 (DDL이 새pg_class행을 받음). -
InnoDB의
mysql.innodb_index_stats— 인덱스별 통계가 별도 테이블이다. CUBRID의 inlinebt_stats[]와 비교하면, query는 비싸지만 갱신은 가볍다. -
HyPer / Vectorwise compressed catalog — in-memory 캐시를 위해 카탈로그 구조를 압축하는 연구 엔진들. CUBRID의
DISK_REPR는 이미 컴팩트하다. in-memory 변종은 더 줄일 수 있을 것이다.
원본 분석 (raw/code-analysis/cubrid/storage/catalog_manager/)
섹션 제목: “원본 분석 (raw/code-analysis/cubrid/storage/catalog_manager/)”1._Catalog_Overview.pdf2._Root_Class.pdf3._System_Catalog_n_Statistics.pdf4._Catalog_Classes_n_boot_DB_parm.pdfcls_info_rec.pptxCUBRID Catalog Access.pptx
형제 문서
섹션 제목: “형제 문서”knowledge/code-analysis/cubrid/cubrid-heap-manager.md— 카탈로그 레코드가 사는 heap 파일.knowledge/code-analysis/cubrid/cubrid-btree.md—BTREE_STATS의 소비자이자 인덱스 통계의 출처.knowledge/code-analysis/cubrid/cubrid-recovery-manager.md—RV_fun[]안의 카탈로그catalog_rv_*함수.knowledge/code-analysis/cubrid/cubrid-log-manager.md— DDL 이 사용하는 system-op 괄호 규율.knowledge/code-analysis/cubrid/cubrid-cdc.md— 카탈로그 변경에서 surface된 DDL 이벤트. 같은 배치에서 진행 중.
교재 챕터 (knowledge/research/dbms-general/)
섹션 제목: “교재 챕터 (knowledge/research/dbms-general/)”- Database Internals (Petrov), 1장 §Database storage (boot anchor), 7장 §Storage Engines (메타데이터로서의 카탈로그).
CUBRID 소스 (/data/hgryoo/references/cubrid/)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”src/storage/system_catalog.{c,h}src/storage/catalog_class.{c,h}src/storage/statistics.h,statistics_{cl,sr}.{c,h}src/transaction/boot_sr.{c,h}src/object/schema_system_catalog_install.cpp— install 시점 시스템 클래스용 하드코딩된 스키마 (CUBRID AGENTS.md §“Add info schema view”).