콘텐츠로 이동

(KO) CUBRID 카탈로그 매니저 — disk representation, 시스템 클래스, 통계

목차

카탈로그는 데이터베이스의 자기 기술 서술서 다. 다른 모든 서브 시스템 — 파서, 옵티마이저, MVCC, lock 매니저, vacuum, CDC — 이 카탈로그에 묻는 질문이 정해져 있다. “클래스 C에는 어떤 attribute 가 있는가?, 그 heap 파일은 어디인가?, 어떤 인덱스가 그것을 대상으로 하는가?”, 행 개수는 얼마인가? Database Internals (Petrov) 1장 §Database storage 와 7장 §Storage Engines 가 이를 데이터베이스의 두 가지 보편 불변식 중 하나라고 정리한다 — 스키마와 저장 레이아웃은 디스크 위 바이트만으로 다시 만들어 낼 수 있어야 한다. out-of-band 지식 없이.

이 모델 위에서 모든 실제 엔진은 두 가지 구현 결정을 내려야 한다. 두 결정이 본 문서 골격을 만든다.

  1. 부트스트랩 root 가 어디에 사는가. 카탈로그는 시작점이 필요하다 — OID 하나, 고정된 페이지 id, 또는 파일 id. 모든 CUBRID 데이터베이스 파일에서 같은 자리에 있어야 한다. 그렇지 않으면 카탈로그를 여는 일조차 카탈로그를 필요로 하는 닭과 달걀 문제에 빠진다. 엔진이 고르는 답은 — root class 의 고정 OID (CUBRID, OODB 스타일), 시스템 tablespace 의 고정 페이지 (Oracle의 부트스트랩 segment), 또는 하드코딩된 스키마 를 가진 고정 테이블 OID (PostgreSQL의 pg_class).
  2. 저장 레이아웃과 사용자 가시 스키마가 같은 구조인가. 어떤 엔진은 둘을 통합한다. PostgreSQL의 pg_class그 자체로 테이블 카탈로그이며, 사용자 테이블과 같은 heap+index 기계로 접근된다. 다른 엔진은 둘을 분리한다 — 내부 catalog manager 가 엔진 hot path 용 컴팩트한 disk-representation 레코드를 저장하고, 그 옆에 SQL이 스키마를 들여다 볼 수 있도록 사용자 가시 시스템 클래스 (_db_class, _db_attribute, …) 를 따로 둔다. CUBRID은 분리 설계 를 고른다 — 내부 system_catalog 와 사용자 가시 catcls_* 테이블이 공존하며, 사용자 측 테이블 은 내부 레코드로부터 구동된다.

이 두 결정의 답이 보이고 나면, 본 문서의 모든 CUBRID 구조는 그 답 중 하나를 구현하거나 그 답에 빠르게 닿기 위해 존재한다는 점이 분명해진다.

모든 관계형 엔진은 카탈로그 저장과 부트스트랩 주변에서 비슷한 패턴을 채택한다.

자기 자신 위에 사는 자기 기술 저장소

섹션 제목: “자기 자신 위에 사는 자기 기술 저장소”

카탈로그는 다른 클래스와 똑같이 heap 파일에 저장된다. 그러나 heap 매니저가 페이지를 해석하려면 카탈로그 레코드가 필요하다. 이 닭과 달걀이 풀리는 자리가 부트스트랩 클래스 다. 엔진이 카탈로그를 보지 않고도 해석할 수 있는 하드코딩된 스키마를 가진 클래스다. CUBRID의 root class가 그 씨앗이다. root class 로부터 엔진이 _db_class 를 배우고, _db_class 로부터 _db_attribute 를 배우는 식이다.

엔진은 물리 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 명칭
카탈로그 식별자 (boot anchor)CTID { vfid, xhid, hpgid } (system_catalog.h); 글로벌 catalog_Id
한 클래스의 disk representationDISK_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 heapcatalog_Id.vfid — 카탈로그 레코드를 담는 파일
카탈로그 디렉토리 hashcatalog_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

카탈로그에는 네 개의 이동 부품이 있다. 카탈로그 식별자와 그 세 권 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.h
struct 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_REPRCLS_INFO 레코드를 담는 heap 파일이다. heap 매니저가 일반 heap처럼 다룬다 (cubrid-heap-manager.md). 단 한 가지 추가 불변식이 있다 — 어떤 트랜잭션이 그 클래스를 여전히 볼 수 있는 동안에는 카탈로그 레코드가 vacuum되어 사라 져서는 안 된다.
  • xhidclass_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.h
struct 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 */
};

fixedvariable 의 분리가 디스크 레이아웃의 결정이다. 고정 길이 attribute는 attribute별 offset overhead 없이 빽빽이 채워 지고, 가변 길이 attribute는 행 앞쪽의 offset 표를 함께 들고 다닌 다. disk_representation::fixed[]::variable[] 을 차례로 순회하는 것이 곧 heap 매니저가 행을 해석하는 순서다.

각 attribute 가 들고 다니는 정보는 다음과 같다.

// DISK_ATTR — src/storage/system_catalog.h
struct 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.h
struct 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.h
struct 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.ccatcls_* 패밀리가 다리 역할을 한다.

// catalog_class.h — src/storage/catalog_class.h
extern 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) 가 복구 후 카탈로그를 온라인으로 가져오는 진입점이다.

  1. 로그 초기화 + 복구. log_initialize 가 세 패스 재시작을 돌린다 (cubrid-recovery-manager.md).
  2. disk와 file 매니저 초기화.
  3. catalog_Id 읽기. 데이터베이스 파라미터 파일 (boot_DB_parm 레코드) 과 디스크 헤더로부터. (vfid, xhid, hpgid) 가 손에 들어온다.
  4. catalog_initialize (catalog_Id). 인메모리 구조 설정.
  5. root class 바인딩. root class의 OID는 boot 파라미터에 고정 저장되어 있다. 엔진이 그 OID로부터 카탈로그에서 DISK_REPR 을 읽어 와 메타클래스 캐시를 prime한다.
  6. 시스템 클래스 바인딩. root class에서 출발해 _db_class, _db_attribute 등이 로드된다. 클래스 이름 ↔ OID 매핑이 catcls_class_oid_to_oid_hash_table 에 캐시된다.
  7. 통계 캐시 초기화.
  8. 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.h
struct 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 = 5000NUMBER_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.cstats_get_statistics (statistics.h 에 선언, SERVER_MODE 에서는 비활성) 를 거친다.

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).
  • 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) — 단편화된 카탈 로그 레코드를 압축.
  • 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.
  • 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_restart_server (boot_sr.c) — 메인 boot 진입점.
  • boot 파라미터 레코드 (boot_DB_parm) — 카탈로그 OID를 포함한 디스크 상주 데이터베이스 파라미터.
  • 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.h45
DISK_REPR (struct)system_catalog.h63
DISK_ATTR (struct)system_catalog.h80
CLS_INFO (struct)system_catalog.h96
CATALOG_ACCESS_INFO (struct)system_catalog.h106
catalog_Id (extern global)system_catalog.h153
BTREE_STATS (struct)statistics.h61
ATTR_STATS (struct)statistics.h82
CLASS_STATS (struct)statistics.h93
stats_adjust_sampling_weight (inline)statistics.h135
catalog_initializesystem_catalog.c2577
catalog_finalizesystem_catalog.c2607
catalog_get_class_info_from_recordsystem_catalog.c504
catalog_initialize_max_spacesystem_catalog.c549
catalog_initialize_new_pagesystem_catalog.c598
catalog_add_representationsystem_catalog.c2815
catalog_add_class_infosystem_catalog.c3029
catalog_update_class_infosystem_catalog.c3172
catalog_get_class_infosystem_catalog.c4113
catcls_insert_catalog_classescatalog_class.c4310
boot_restart_serverboot_sr.c1969
  • 카탈로그 소스 파일은 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.hCTID 구조체를 읽고 검증. 셋은 각각 heap 파일 (카탈로그 레코드), extendible-hash 인덱스 (class_oid → dir_oid), 그리고 고정 헤더 페이지에 대응한다.

  • catalog_Id 는 글로벌이다 — thread별 상태가 아니다. system_catalog.hextern CTID catalog_Id 선언으로 검증. 식별자는 boot 시점 (catalog_initialize) 에 한 번 설정되며, 서버 라이프타임 동안 변하지 않는다.

  • DISK_REPR 은 attribute 를 fixed 와 variable 두 배열로 가른다 — 단일 정렬 리스트가 아니다. system_catalog.hdisk_representation 구조체로 검증 (별도 n_fixed / fixed[]n_variable / variable[] 필드). 함의 — 행 디코딩이 fixed attribute는 정확한 offset으로, variable attribute는 행별 offset 표로 읽는다.

  • 통계는 attribute 레코드에 inline으로 산다 — 별도 파일이 아니다. system_catalog.hDISK_ATTRn_btstats, bt_stats, ndv 필드로 검증. 비용 — 통계 갱신이 attribute 레코드 전체를 다시 쓴다. 이득 — 옵티마이저가 스키마와 통계를 한 fetch로 읽는다.

  • CATALOG_ACCESS_INFO 는 debug-only is_systemop_started 필드를 가진다. system_catalog.hcatalog_access_info 구조체에 NDEBUG-conditional 필드로 검증. 그 목적은 update-mode 카탈로그 access 가 system op 으로 적절히 감싸졌는지 assert로 검사하는 것이다.

  • 사용자 가시 시스템 클래스는 catalog_class.ccatcls_* 패밀리로 채워진다 — system_catalog.c 의 내부 catalog_* 와 분리되어 있다. catalog_class.h (헤더 전체가 ~50줄 이내, 작은 표면) 를 읽고 catalog_class.c 에서 catcls_insert_catalog_classes 를 grep으로 찾아 검증. 두 표면은 같은 트랜잭션을 공유한다. DDL이 두 표면 모두에서 atomic이다.

  • catcls_Enable 은 카탈로그-클래스 유지를 켜고 끄는 글로벌 토글이다. catalog_class.hextern bool catcls_Enable 선언으로 검증. false 일 때 시스템 클래스가 동기화되지 않는다 — install과 마이그레이션 동안 쓰인다.

  • CLS_INFO::ci_time_stamp 가 캐시 검증 토큰이다. system_catalog.hcls_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.hBTREE_STATS_PKEYS_NUM = 8 매크로로 검증. 컬럼이 8개를 넘는 compound 인덱스는 8번째 너머의 prefix selectivity 추적을 잃는다.

  1. root class의 OID가 디스크 어디에 사는가. boot_DB_parm 레코드가 boot 파라미터를 들고 있고, root class OID는 그 안의 특정 필드 하나다. 추적 경로 — boot_sr.c 의 1969행 부근 (boot_restart_server) 을 읽고 root-class OID를 어디서 로드하는지 추적.

  2. 카탈로그 overflow-page logical-undo 규율. catalog_rv_ovf_page_logical_insert_undo 가 logical 이지만, overflow 페이지가 깨끗하게 돌아오도록 보장하는 file-manager 호출 시퀀스가 무엇인가? 추적 경로 — 그 함수 본문을 읽고 file_dealloc_page 호출을 따라가기.

  3. 내부 카탈로그와 사용자 시스템 클래스 사이의 동기화. DDL 은 두 표면을 lockstep으로 갱신해야 한다. 다른 reader가 내부 카탈로그는 갱신된 상태인데 _db_class 는 아직 갱신되지 않은 상태를 보지 않게 무엇이 막아 주는가? 추적 경로 — DDL 경로 에서 lock 획득 순서를 추적. 카탈로그 X-lock이 두 표면을 모두 덮는지 확인.

  4. catcls_update_class_stats 의 갱신 주기. 통계 갱신이 이 함수로 흐른다. SQL UPDATE STATISTICS 명령과 동기적 인가, 아니면 백그라운드 sweep이 있는가? 추적 경로 — 호출자를 grep하고 daemon 등록을 확인.

  5. HA 환경에서의 카탈로그-클래스 캐시 무효화. slave 서버에서 master DDL이 replay될 때, slave는 자기 카탈로그-클래스 hash 테이블을 무효화하는가? 추적 경로 — cubrid-cdc.md 와 catcls_finalize_class_oid_to_oid_hash_table.

  6. 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의 inline bt_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.pdf
  • 2._Root_Class.pdf
  • 3._System_Catalog_n_Statistics.pdf
  • 4._Catalog_Classes_n_boot_DB_parm.pdf
  • cls_info_rec.pptx
  • CUBRID Catalog Access.pptx
  • knowledge/code-analysis/cubrid/cubrid-heap-manager.md — 카탈로그 레코드가 사는 heap 파일.
  • knowledge/code-analysis/cubrid/cubrid-btree.mdBTREE_STATS 의 소비자이자 인덱스 통계의 출처.
  • knowledge/code-analysis/cubrid/cubrid-recovery-manager.mdRV_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”).