(KO) CUBRID DDL과 스키마 — 섹션 개요
목차
이 섹션의 범위
섹션 제목: “이 섹션의 범위”이 섹션은 CUBRID의 스키마 레이어를 다룬다. 스토리지 엔진(heap, B-tree, MVCC, 락 관리자) 위, 그리고 실행기(XASL, 스캔 프로시저, 쿼리 옵티마이저) 아래에 자리 잡은 띠다. 데이터베이스가 자기 자신을 어떻게 기술하느냐 — self-description — 가 만들어지고, 바뀌고, 읽히고, 통제되는 곳이 바로 이 레이어다.
모든 CREATE/ALTER/DROP은 여기를 통과한다. 모든 DML은 여기서 행의 모양을 읽어 가고, 모든 플랜 컴파일은 여기서 접근 경로와 컬럼 분포를 가져가며, 모든 권한 검사도 여기 안에서 끝난다.
스키마 레이어가 마음을 바꾸면 — 컬럼이 추가되거나, 트리거가 발화하거나, 소유자가 바뀌거나, 통계가 새로 갱신되면 — 그 변화는 XASL 캐시, 워크스페이스 MOP 테이블, 로케이터의 grant 캐시를 차례로 거쳐 다음에 컴파일되는 플랜에까지 닿는다. 이 섹션의 여섯 개 세부 문서가 그 전파를 끝에서 끝까지 따라간다. 이 개요 문서는 그 내용을 다시 적지 않는다. 길과 방향만 가리킨다.
CUBRID 소스 트리는 같은 논리적 클래스를 표현하면서도 책임을 셋으로 분명히 갈라 둔다.
- 파이프라인 — 파스 트리를 적용된 스키마 변경으로 옮기되, 그 중간 상태를 동시에 읽고 있는 다른 세션에게는 보이지 않게 한다(
cubrid-ddl-execution.md). - 디스크 프로젝션 —
SELECT * FROM _db_class가 평범한 테이블처럼 동작한다. 카탈로그 행이 사용자 데이터와 같은 heap 페이지에 들어가기 때문에, 크래시 복구도 같은 WAL을 타고 굴러간다(cubrid-catalog-manager.md). - 인메모리 그래프 — 실행기와 로케이터가 컬럼 하나 찾을 때마다 디스크를 두드리지 않아도 되도록, 메모리 안에 살아 있는 그래프(
cubrid-class-object.md).
스키마 그래프에 매달리는 횡단 기능을 다루는 문서가 셋 더 있다. 트리거는 클래스 위 DML이 떨어질 때 발화하고(cubrid-trigger.md), 권한 검사는 모든 fetch에 자물쇠를 걸며(cubrid-authentication.md), 통계는 카탈로그가 디스크 표현에 사이드 테이블로 매달아 두는 값으로 비용 옵티마이저에 들어간다(cubrid-statistics.md).
스키마 그래프
섹션 제목: “스키마 그래프”세 개의 주된 레이어가 모든 스키마 변경을 엔진 안으로 흘려보낸다.
- DDL 실행 (
cubrid-ddl-execution.md) — PT_CREATE_ENTITY / PT_ALTER / PT_DROP 파스 트리에서 시작해 커밋된 변경에서 끝나는 파이프라인이다. 구현은execute_schema.c의do_create_entity/do_alter/do_drop이 맡는다. 이들이dbt_create_class/smt_def_class로SM_TEMPLATE을 빚어 두고,sm_finish_class → update_class → install_new_representation을 차례로 거쳐 마무리한다. 끝에서sm_bump_local_schema_version을 한 번 더 부르면, 하류 캐시들이 새 모양을 본다. 도중에 트랜잭션이 깨지면, 사용자 데이터를 굴리는 그 WAL이 그대로 롤백을 처리한다. - 디스크 카탈로그 (
cubrid-catalog-manager.md) —SHUTDOWN을 견디고 살아남는 표현이다. CUBRID는 일부러 둘로 쪼갠 카탈로그를 쓴다. 엔진 안쪽용system_catalog는(클래스 OID, repr id)를 키로 들고, 고정 root-class OID에 뿌리를 박는다. 그 옆에 사용자에게 보이는 시스템 클래스(_db_class,_db_attribute,_db_index,_db_method,_db_partition,_db_trigger, …)가 사용자 데이터와 똑같은 heap+B-tree 길로 접근된다. 커밋된 DDL은 두 뷰 모두에 자국을 남긴다. - 인메모리 스키마 (
cubrid-class-object.md) — 실행기가 모든 참조를 풀 때 따라가는 살아 있는 그래프다.SM_CLASS객체 하나가 클라이언트 워크스페이스 메모리에 떠 있고, MOP에 매달려 있다. 안에는SM_ATTRIBUTE체인, ALTER 이전 옛 행도 읽어 낼 수 있게 해 주는SM_REPRESENTATION이력, 파티션 그래프, 제약 목록, 그리고TR_TRIGGER캐시가 들어 있다. 이 그래프와 디스크 카탈로그 사이를 이어 주는 일은catcls_*이 맡는다.
세 횡단 관심사가 위 세 레이어 모두에 걸쳐 있다.
- 트리거 (
cubrid-trigger.md) — 클래스에 붙는 SQL-99 ECA 능동 규칙._db_trigger행에 저장되고,SM_CLASS에 캐시되며, DML 시점에 로케이터에서tr_prepare_class/tr_before_object/tr_after_object로 발화된다. - 권한 (
cubrid-authentication.md) — 사용자, 비밀번호, 클래스별 권한 비트맵이db_user,db_password,_db_auth,db_authorization에 MOP 키로 행이 되어 들어간다. 클래스별AU_CLASS_CACHE가 fetch마다 일어나는 권한 조회를 비트마스크 한 번으로 짓이기고,au_check_authorization이 클래스 fetch 직전에 로케이터 안에서 인라인으로 호출된다. - 통계 (
cubrid-statistics.md) — 서버 쪽xstats_update_statistics가 heap과 B+Tree를 훑어 카디널리티, NDV, 리프 페이지 수, 부분 키 팬아웃을 뽑는다. 이 값들은 최신 디스크 표현 위에 얹혀 보존되고, 와이어로는 패킹된 버퍼로 흘러가, 옵티마이저의qo_iscan_cost/qo_sscan_cost/qo_equal_selectivity/qo_range_selectivity로 들어간다.
flowchart TD
PT["PT_CREATE_ENTITY /<br/>PT_ALTER / PT_DROP<br/>(파스 트리)"] --> DO["do_create_entity /<br/>do_alter / do_drop<br/>(execute_schema.c)"]
DO --> TMPL["SM_TEMPLATE<br/>(클래스의 staging 사본)"]
TMPL --> FIN["sm_finish_class →<br/>update_class →<br/>install_new_representation"]
FIN --> CATCLS["catcls_insert_catalog_classes /<br/>catcls_update_subset<br/>(_db_class, _db_attribute, ...)"]
FIN --> SMCLASS["SM_CLASS 재구성<br/>(워크스페이스 MOP)"]
CATCLS --> SYSCAT["system_catalog<br/>(디스크 repr, repr-id 체인)"]
SMCLASS --> BUMP["sm_bump_local_schema_version"]
BUMP --> XASL["XASL 캐시 무효화<br/>(클래스별 chn 증가)"]
BUMP --> MOPCHN["워크스페이스 MOP 캐시 헤더<br/>갱신"]
SMCLASS -.매달린다.-> TR["TR_TRIGGER 캐시<br/>(cubrid-trigger.md)"]
SMCLASS -.매달린다.-> AU["AU_CLASS_CACHE<br/>(cubrid-authentication.md)"]
SYSCAT -.사이드 테이블.-> ST["통계 버퍼<br/>(cubrid-statistics.md)"]
TR -.로케이터에서 발화.-> DML["DML 경로<br/>(insert / update / delete)"]
AU -.au_check_authorization.-> DML
ST -.흘러든다.-> QO["qo_∗_cost,<br/>qo_∗_selectivity"]
다이어그램은 이 섹션의 등뼈를 보여 준다. 스키마 변경이 위에서 아래로 흐른다. staging, 디스크 기록, 인메모리 재구성, 캐시 무효화 순으로. 점선은 횡단 관심사 셋이 이 등뼈에 걸리는 자리다.
읽는 순서
섹션 제목: “읽는 순서”권하는 순서는 그래프 우선이다. 인메모리 그래프가 다른 모든 문서가 옮겨 적거나 옮겨 받는 기준 구조이기 때문이다.
cubrid-class-object.md— 여기서 시작한다.SM_CLASS그래프,SM_ATTRIBUTE와SM_METHOD체인,SM_REPRESENTATION이력, 파티션 링크, 워크스페이스 MOP에서의 자리잡기까지 정의한다. 다음 모든 문서가 이 그래프의 필드를 가리킨다.cubrid-catalog-manager.md— 두 번째로 읽는다. 같은 그래프를 디스크에 옮긴 그림이다._db_class행이SM_CLASS헤더와 어떻게 맞물리는지,_db_attribute행이 attribute 체인을 어떻게 비추는지, 엔진 안쪽system_catalog가 사용자에게 보이는 시스템 클래스 옆에 어떻게 자리 잡는지를 보여 준다. 인메모리 구조를 머리에 넣고 읽으면, 디스크 레이아웃은 같은 것의 직렬화된 모습으로 자연스럽게 들어온다.cubrid-ddl-execution.md— 세 번째다. (1)과 (2)를 잇는 파이프라인이다. 파스 트리에서SM_TEMPLATE이 어떻게 빚어지는지,sm_finish_class가 카탈로그를 쓰고SM_CLASS를 다시 짜는 흐름이 어떤지, 스키마 버전이 한 단계 올라가면 하류 캐시가 어떻게 깨지는지, 그리고 이 모든 것이 사용자 데이터 DML과 같은 WAL/MVCC/락 틀 안에서 어떻게 감싸지는지를 다룬다.- 횡단 문서는 필요할 때 펼친다.
- 권한 거부나 grant/revoke 흐름을 살필 때 →
cubrid-authentication.md - DML이 추가 일을 끌어들이는 이유, 또는
_db_trigger행이 CREATE TRIGGER에서 런타임 호출까지 어떻게 이어지는지 →cubrid-trigger.md - 옵티마이저 플랜이 의심스럽거나, ANALYZE / UPDATE STATISTICS를 워크로드에 끼우려 할 때 →
cubrid-statistics.md
- 권한 거부나 grant/revoke 흐름을 살필 때 →
이 순서에는 이유가 있다. DDL 실행을 먼저 펴면 SM_TEMPLATE과 catcls_*을 두고 앞뒤로 자꾸 넘기게 된다. 카탈로그 매니저를 먼저 읽으면 디스크 행이 무엇을 비추고 있는지 모른 채 읽게 된다. 인메모리 그래프를 먼저 잡고 가면 나머지 두 문서가 깔끔하게 자리 잡는다.
횡단 관심사
섹션 제목: “횡단 관심사”이 셋은 이 섹션을 가로지르고, 엔진의 다른 영역까지 이어진다.
옵티마이저가 끌어다 쓰는 통계. cubrid-statistics.md가 이 섹션에 속하는 까닭은 단순하다. 클래스별 통계 버퍼가 카탈로그에 들어가고, 클래스의 디스크 표현이 만들어지고 갱신될 때 통계도 같이 만들어지고 갱신된다. 그러나 옵티마이저가 내리는 모든 비용 추정 — 조인 순서, 접근 경로 선택, 선택도 곱셈 — 이 결국 이 버퍼를 다시 읽는다. 세부 문서는 양쪽 끝을 다 추적한다. 만드는 쪽(서버에서 xstats_update_statistics가 heap과 B+Tree를 훑는다)과 쓰는 쪽(클라이언트에서 qo_get_attr_info가 버퍼를 풀어 두면, qo_iscan_cost / qo_sscan_cost와 선택도 추정기들이 플랜을 채점한다). 쿼리 처리 섹션의 cubrid-query-optimizer.md가 옵티마이저가 카탈로그에서 NDV를 읽는다고 적을 때, 그 한 줄 뒤에 깔린 그림이 바로 여기다.
모든 DML이 거치는 권한 검사. au_check_authorization은 로케이터의 클래스 fetch 경로 안에서 인라인으로 호출된다. 그래서 권한 캐시는 세션 시작 시점이 아니라 모든 SELECT, INSERT, UPDATE, DELETE의 핫패스 위에 놓인다. 클래스별 AU_CLASS_CACHE는 이 검사를 캐시된 grant 비트맵에 대한 비트마스크 한 번으로 줄이기 위해 일부러 만들어 둔 장치다. 덕분에 읽기 경로가 fetch마다 _db_auth를 두드리지 않아도 된다. 서버 아키텍처 섹션과 스토리지 엔진 섹션의 로케이터 문서는 이 캐시가 이미 있다는 전제로 적혀 있다. 그 캐시가 어떻게 짜이는지는 cubrid-authentication.md가 답한다.
트리거는 DML 시점에 로케이터에서 발화한다. 트리거 등록은 DDL이다. CREATE TRIGGER가 _db_trigger 행을 적고, 해당 SM_CLASS에 TR_TRIGGER를 매단다. 트리거 실행은 DML이다. 로케이터나 object_template이 INSERT/UPDATE/DELETE를 적용할 때, tr_prepare_class를 한 번 부르고, 그다음 행 또는 문 단위로 tr_before_object / tr_after_object를 부른다. 두 절반이 cubrid-trigger.md에 같이 들어가는 이유는, 흐름이 둘을 묶어 두기 때문이다. 캐시된 TR_TRIGGER가 발화 비용을 낮추고, OID 스택 재귀 가드가 로케이터와 트리거 매니저 사이에 끼어 있다.
개별 문서 요약
섹션 제목: “개별 문서 요약”| 문서 | 모듈 | 역할 | 주요 진입점 |
|---|---|---|---|
| cubrid-ddl-execution.md | ddl-execution | PT_CREATE_ENTITY / PT_ALTER / PT_DROP을 커밋된 카탈로그 기록 + 인메모리 재구성 + 캐시 무효화로 잇는 파이프라인 | do_create_entity, do_alter, do_drop, dbt_create_class, smt_def_class, sm_finish_class, update_class, install_new_representation, sm_bump_local_schema_version, locator_add_class, catcls_insert_catalog_classes |
| cubrid-catalog-manager.md | catalog-manager | 엔진 안쪽 system_catalog(CTID 키 디스크 표현 + 통계)와, 그 옆에 나란히 선 사용자 가시 _db_class/_db_attribute/_db_index/… 시스템 클래스. 고정 root-class OID에서 부트스트랩 | catalog_add_representation, catalog_get_representation, catalog_add_statistics, catalog_get_statistics, catcls_insert_*, boot_create_db_parm, root-class 고정 OID |
| cubrid-class-object.md | class-object | 인메모리 스키마 그래프 — SM_CLASS에 attribute 체인, method 체인, representation 이력, 파티션 그래프, 제약 목록, 트리거 캐시가 매달린다. 클라이언트 워크스페이스 heap에서 산다 | SM_CLASS, SM_ATTRIBUTE, SM_METHOD, SM_REPRESENTATION, SM_CLASS_CONSTRAINT, classobj_*, sm_get_class_with_statistics, catcls_get_class_* (중개자) |
| cubrid-trigger.md | trigger | SQL-99 ECA 규칙 — _db_trigger 행과 SM_CLASS에 매달린 TR_TRIGGER 인메모리 캐시. 로케이터 인접 경로에서 발화. 문 단위 재귀는 per-statement 가드가 아니라 OID 스택으로 통제 | tr_create_trigger, tr_drop_trigger, tr_prepare_class, tr_before_object, tr_after_object, TR_TRIGGER, TR_RECURSION_DECISION |
| cubrid-authentication.md | authentication | 사용자·비밀번호·grant를 db_user, db_password, _db_auth, db_authorization의 MOP 키 행으로 모델링. 클래스별 AU_CLASS_CACHE가 읽기 경로 검사를 비트마스크 하나로 압축 | au_login, au_fetch_class, au_check_authorization, AU_CLASS_CACHE, au_grant, au_revoke, db_user / _db_auth 시스템 클래스 |
| cubrid-statistics.md | statistics | 서버 쪽 heap+B+Tree 훑기로 클래스별 NDV / 카디널리티 / 리프 페이지 / 부분 키 팬아웃을 뽑아, 최신 디스크 표현 위에 얹어 두고, 패킹 버퍼로 클라이언트로 보내, 비용 옵티마이저에 흘려 넣음 | xstats_update_statistics, stats_compile_disk_representation, qo_get_attr_info, qo_iscan_cost, qo_sscan_cost, qo_equal_selectivity, qo_range_selectivity |
인접 섹션
섹션 제목: “인접 섹션”- 스토리지 엔진. 카탈로그 행도 사용자 데이터처럼 heap 페이지 위에 살고, B-tree로 색인된다. 그래서
cubrid-heap-manager.md,cubrid-btree.md,cubrid-page-buffer-manager.md,cubrid-disk-manager.md,cubrid-mvcc.md,cubrid-lock-manager.md가 카탈로그를 읽고 쓸 때마다 같이 굴러간다.cubrid-catalog-manager.md의 둘로 쪼갠 카탈로그가 굴러갈 수 있는 이유도 같다. 엔진 안쪽system_catalog도, 사용자 가시catcls_*테이블도, 결국 같은 heap+B-tree+페이지 버퍼 스택을 타기 때문이다. 아래에서 굴러가는 I/O는 한 가지뿐이다. - 쿼리 처리.
cubrid-statistics.md의 통계가cubrid-query-optimizer.md의 모든 비용 추정으로 흘러든다.cubrid-authentication.md의 권한 검사는 모든 DML과 모든 접근 경로 해석이 로케이터를 거치며 통과해야 하는 문이다.cubrid-trigger.md의 트리거 발화는 옵티마이저가 만든 플랜이 끝나는 바로 그 DML 경로에서 일어난다. 정리하면, 스키마 레이어는 쿼리 시스템에서 데이터 사전 + 접근 정책 + 능동 규칙 절반을 맡고, 쿼리 처리가 나머지 절반을 맡는다. - 서버 아키텍처. DDL 펜스 —
sm_bump_local_schema_version안의 스키마 버전 증가 — 가 XASL 캐시를 무효로 만들고 다음 플랜 컴파일에서 다시 준비를 강제한다. 이것이 스키마 레이어와 세션 사이를 가로지르는 플랜 캐시가 만나는 경계다. XASL 캐시의 모양은 서버 아키텍처 문서에서, 버전 증가 자체는cubrid-ddl-execution.md에서 본다.
스키마 레이어는 잎(leaf) 모듈이 아니다. 이 지식 베이스의 다른 섹션은 모두 이 레이어에서 읽거나(쿼리 처리, 카탈로그 페이지를 다루는 스토리지 엔진, 로케이터의 권한 게이트), 이 레이어를 거쳐 쓴다(DDL 자체). 여섯 개 세부 문서는 한 묶음으로 다뤄야 한다. cubrid-class-object.md를 한 번 훑지 않은 채로 다른 어느 한 문서만 펴서 읽으려 들면, 어딘가에서 막힌다.