(KO) CUBRID DDL 실행 — 파스 트리에서 카탈로그와 클래스 객체 캐시 무효화까지의 스키마 변경 파이프라인
목차
학술적 배경
섹션 제목: “학술적 배경”데이터 정의 언어(DDL)는 SQL에서 행을 옮기지 않는 부분이며, 행을
해석하는 모양을 옮긴다. SQL의 DDL 문 — CREATE TABLE,
ALTER TABLE, DROP TABLE, CREATE INDEX, RENAME, TRUNCATE
와 그 객체 관계형, 파티셔닝 확장 — 이 함께 모든 후속
SELECT/INSERT/UPDATE 플랜이 컴파일되는 스키마 그래프를 빌드한다.
Database System Concepts(Silberschatz 외, 7판, §5.2와 §15.5)이
DDL 구현의 교과서적 구조를 다섯 동작으로 잡아낸다. DDL을 정의
트리로 파싱하고, 기존 카탈로그에 비추어 검증하고, 디스크 산출물
(heap, B-tree, foreign-key 디스크립터)을 머터리얼라이즈하고, 일치
하는 카탈로그 행을 insert/update/delete하고, 옛 모양을 참조하던
캐시가 다음 플랜 컴파일 전에 비워지도록 무효화 신호를 뿌린다.
엔진이 어떤 종류인지에 따라 강제되는 설계 결정이 몇 가지 있다.
-
트랜잭셔널 DDL vs 비트랜잭셔널 DDL. SQL Server, DB2, Postgres, MySQL 8.0은 DDL을 DML과 같은 WAL/복구 프레임워크로 감싼다.
CREATE TABLE+ROLLBACK은CREATE TABLE이 없었던 것과 동일하다. 옛 MySQL 릴리스와 Oracle은 DDL을 암묵적 commit 으로 다룬다. 트랜잭셔널 엔진은 모든 카탈로그 변경을 사용자 데이터와 같은 redo/undo 로그로 저널링해야 하고, 암묵적 commit 엔진은 카탈로그 행이 언젠가 도달하기만 하면 된다. -
스키마 변경 vs 데이터 이동. 순수 메타데이터 DDL (
CREATE TABLE,DROP INDEX)은 짧은 배타 lock을 잡고 빠진다. 데이터를 건드리는 DDL(ALTER … ADD COLUMN c NOT NULL DEFAULT 0, 파티션 reorg,TRUNCATE)은 같은 긴 DDL 아래에서 시간 단위로 돌 수 있는 cursor 스타일 scan이 필요하다. -
DDL fence와 캐시 무효화. 변경된 클래스를 참조하던 캐시 플랜 은 새 플랜이 새 모양으로 컴파일되기 전에 무효화되어야 한다. 표준 규율은 단조 증가하는 스키마 버전이다. Oracle의 Library Cache Lock, Postgres의
CommandCounterIncrement+ relcache 무효화 메시지, CUBRID의sm_bump_local_schema_version더하기 모든 MOP 위의 클래스별chn. -
Staging. 살아 있는 클래스를 변경하면 동시 reader에게 부분 상태가 노출된다. 표준 답은 엔진이 자유롭게 변경하다가 문장 끝에 배타 lock 아래 atomic하게 swap-in하는 인메모리 템플릿이다. Postgres은
AccessExclusiveLock아래Relation을 in-place로 다시 쓴다. InnoDB은 ghost copy + row-log를 갖는 online DDL이 있다. Oracle은DBMS_REDEFINITION을 쓴다. CUBRID의 답은SM_TEMPLATE이다.DB_CTMPL핸들 안의SM_CLASS작업 사본이며sm_finish_class → install_new_representation이 설치한다. -
카탈로그 표현. 튜플 스타일 엔진(Postgres
pg_class/pg_attribute, MySQL 8.0mysql.tables, Oracledba_objects)은 객체마다 한 행을 두고 DDL을 권한 있는 시스템 테이블에 대한 INSERT/UPDATE/DELETE로 다룬다. 객체 스타일 엔진은 각 클래스를 메타 클래스(_db_class,_db_attribute)의 인스턴스로 다룬다. CUBRID이 두 번째 스타일이다. 카탈로그는_db_접두 클래스의 집합이며src/storage/catalog_class.c의catcls_*이SM_CLASS과 heap 행 표현 사이를 매개한다.
이 계층화의 동기를 부여하는 책 챕터들은 Database System Concepts §5.2(DDL 구문), §15.5(System Catalog), §17(Recovery와 DDL을 위한 WAL의 역할)이다. Petrov의 Database Internals 4장이 같은 기계를 단일 노드 엔진의 시각에서 프레임한다. 같은 B-tree 가 튜플을 들고 있을 때 디스크에 쓰여진 스키마 상태는 자동으로 durability, locking, 크래시 복구를 상속한다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”Postgres의 DDL 파이프라인이 유용한 기준점이다. CREATE TABLE은
CreateStmt 노드로 파싱된다. src/backend/tcop/utility.c의
ProcessUtility switch가 DefineRelation로 디스패치한다.
DefineRelation이 TupleDesc(SM_TEMPLATE에 정신적으로 등가)을
빌드하고, heap_create_with_catalog을 불러 relfilenode, pg_class
행, pg_attribute 행을 할당하고, 관련 제약을 등록한다. 두 동작이
캐시를 추가로 묶는다. 모든 카탈로그 INSERT은 CatalogTupleInsert
을 거치며, 이 함수가 relfilenode와 relid에 대한 무효화 메시지
를 기록한다. 문장 끝의 CommandCounterIncrement이 그 무효화를
같은 트랜잭션의 후속 문장에 보이게 만든다. relcache은 다음 액세스
시 lazy하게 재구성된다. DDL은 완전 트랜잭셔널이다. ROLLBACK이
카탈로그 행과 파일시스템 동작 모두를 pendingDeletes로 되돌린다.
MySQL 8.0의 atomic DDL은 데이터 dictionary의 완전한 재작성을
이끌었다. 모든 DDL은 새 mysql.tables / mysql.columns 시스템
테이블에 대한 단일 InnoDB 트랜잭션이며, 파일시스템 동작은
DDL log로 큐잉되어 크래시 복구가 DDL 전체를 commit하거나
rollback한다. 옛 MySQL 버전은 그러지 못했다. CREATE TABLE이
rollback에서도 .frm 파일을 남길 수 있었다. 교훈은 스토리지
엔진과 dictionary가 트랜잭셔널 substrate를 공유해야 한다는 점
이다. 그렇지 않으면 atomic DDL은 닿을 수 없다.
Oracle은 반대 길을 간다. 모든 DDL이 암묵적 commit으로 둘러싸인
다. CREATE TABLE이 먼저 pending된 사용자 트랜잭션을 commit하고,
자기 트랜잭션에서 데이터 dictionary 위에 돌고, 다시 commit한다.
공유 cursor 캐시는 library cache lock(해시 파티션 latch)으로
캐시된 플랜을 fence한다. DDL이 lock을 배타 모드로 잡고 의존
cursor를 invalid로 표시한 뒤 lock을 푼다. 후속 컴파일은 lock을
공유 모드로 잡고, 의존성이 표시되어 있으면 다시 파싱한다.
SQL Server과 DB2는 정신적으로 Postgres에 가깝다. 카탈로그 행이 평범한 시스템 테이블에 살고, DDL은 완전 트랜잭셔널이고, 데이터 베이스별 스키마 버전 더하기 문장별 재컴파일이 캐시 무효화를 처리한다.
CUBRID은 이 메뉴에서 여러 특성을 결합한다. Postgres과 MySQL 8.0
처럼 완전 트랜잭셔널이다. 모든 DDL이 진입에서 시스템
savepoint를 잡고 어떤 오류 경로에서도 그 savepoint로 rollback
하므로, 부분 ALTER TABLE이 새는 일이 없다. Oracle처럼 commit
이전에 새 모양을 인메모리 템플릿에 stage한다. CUBRID의
SM_TEMPLATE은 Oracle의 데이터 dictionary 캐시 작업 영역의 등가
물이지만 공유 라이브러리 캐시 안이 아니라 워크스페이스의
클라이언트 측에 산다. Postgres처럼 단조 증가하는 버전 번호를
bump해 플랜 캐시를 무효화한다. sm_bump_local_schema_version이
CommandCounterIncrement의 직접 등가다. 카탈로그는 평범한
heap_insert이 아니라 catcls_*로 쓰여진다. CUBRID 카탈로그가
평범한 테이블이 아니라 시스템 클래스의 집합이기 때문이다. 템플릿이 finalise된 뒤에야 SM_CLASS이 _db_class의 heap 행이
된다.
이어지는 walk은 이 조각들이 어떻게 단일 파이프라인으로 배치되는지, CUBRID 특화 선택 — 워크스페이스 MOP, locator 매개 heap 생성, 두 단계 파티셔닝 — 이 어디에 들어맞는지 보여 준다.
CUBRID의 구현
섹션 제목: “CUBRID의 구현”flowchart TB SQL[SQL text] SQL --> Parse[csql_grammar.y<br/>PT_CREATE_ENTITY / PT_ALTER / PT_DROP] Parse --> Sem[name_resolution<br/>· semantic_check] Sem --> DoStmt[do_statement / do_execute_statement<br/>switch on PT_NODE_TYPE] DoStmt --> CE[do_create_entity] DoStmt --> AL[do_alter] DoStmt --> DR[do_drop] DoStmt --> CI[do_create_index] DoStmt --> TR[do_create_trigger] DoStmt --> SE[do_create_serial] DoStmt --> US[do_create_user / do_grant] CE --> Tmpl[dbt_create_class<br/>= smt_def_class] AL --> Tmpl2[dbt_edit_class<br/>= smt_edit_class_mop] Tmpl --> Local[do_create_local<br/>do_add_attributes / do_add_constraints] Tmpl2 --> Local Local --> Finish[dbt_finish_class<br/>= sm_finish_class -> update_class] Finish --> Install[install_new_representation<br/>· allocate_disk_structures] Install --> Locator[locator_add_class<br/>locator_create_heap_if_needed] Locator --> Catcls[catcls_insert_catalog_classes<br/>via locator flush] Catcls --> Bump[sm_bump_local_schema_version<br/>· XASL cache invalidation on next access] DR --> DropPath[db_drop_class_ex<br/>-> sm_delete_class_mop] DropPath --> Locator
이 파이프라인이 DDL 구현의 척추다. 모든 구체 DDL 문이 같은 경로의 specialisation이다. 이 절의 나머지는 각 단계를 자세히 걷는다. 코드 인용은 그대로 보존해서 읽는 사람이 소스 트리를 따라가지 않아도 되도록 한다.
최상위 디스패치 — do_statement과 DDL switch
섹션 제목: “최상위 디스패치 — do_statement과 DDL switch”단일 문 진입점은 execute_statement.c의 do_statement이다. 파싱된
PT_NODE마다 한 번씩 호출된다. 함수가 순서대로 세 가지를 한다. read-fetch instance 버전을 평가(DDL은 LC_FETCH_DIRTY_VERSION이
필요하다. 카탈로그를 곧 쓸 것이므로), 그 다음 거대한
switch (statement->node_type)이 각 파스 트리 종류를 자기 핸들러
로 라우팅, 마지막으로 HA용 복제와 보조 로그 캡처를 트리거하는
꼬리.
// do_statement — execute_statement.c (DDL slice of the switch)case PT_CREATE_ENTITY: error = do_check_internal_statements (parser, statement, do_create_entity); break;case PT_ALTER: error = do_check_internal_statements (parser, statement, do_alter); break;case PT_DROP: (void) do_reserve_classinfo (parser, statement, cls_info); error = do_check_internal_statements (parser, statement, do_drop); break;case PT_CREATE_INDEX: error = do_create_index (parser, statement); break;case PT_DROP_INDEX: error = do_drop_index (parser, statement); break;case PT_ALTER_INDEX: error = do_alter_index (parser, statement); break;case PT_RENAME: error = do_rename (parser, statement); break;case PT_TRUNCATE: error = do_truncate (parser, statement); break;case PT_CREATE_TRIGGER: error = do_create_trigger (parser, statement); break;case PT_DROP_TRIGGER: error = do_drop_trigger (parser, statement); break;case PT_ALTER_TRIGGER: error = do_alter_trigger (parser, statement); break;case PT_CREATE_SERIAL: error = do_create_serial (parser, statement); break;case PT_ALTER_SERIAL: error = do_alter_serial (parser, statement); break;case PT_DROP_SERIAL: error = do_drop_serial (parser, statement); break;case PT_CREATE_USER: error = do_create_user (parser, statement); break;case PT_DROP_USER: error = do_drop_user (parser, statement); break;case PT_GRANT: error = do_grant (parser, statement); break;case PT_REVOKE: error = do_revoke (parser, statement); break;case PT_CREATE_STORED_PROCEDURE: error = jsp_create_stored_procedure (parser, statement); break;래퍼 do_check_internal_statements은 현재 do_func (parser, statement)로 가는 얇은 pass-through이다. 현재 #if 0로 비활성
화된 텍스트 도메인 내부 문을 호스팅하기 위해 존재한다. 멘탈 모델
은 이 디스패치 줄이 바로 DDL switch이다, 래퍼는 자동 생성된
companion 문을 위한 흔적 훅이다.
핸들러가 반환된 뒤, 같은 do_statement 본체가 DDL을 HA 복제에
연결한다.
// do_statement — execute_statement.c (post-handler tail)if (need_stmt_replication) { int repl_error = NO_ERROR; if (error >= 0) { repl_error = locator_all_flush (); } suppress_repl_error = db_set_suppress_repl_on_transaction (false); if (error >= 0 && repl_error == NO_ERROR && suppress_repl_error == NO_ERROR) { repl_error = do_replicate_statement (parser, statement); } /* ... */ }
if (prm_get_integer_value (PRM_ID_SUPPLEMENTAL_LOG) > 0) { (void) do_supplemental_statement (parser, statement, cls_info, reserved_oid); }locator_all_flush ()이 결정적인 호출이다. DDL 도중 만들어진
임시 워크스페이스 MOP이 스키마 복제 레코드가 쓰여지기 전에
디스크로 플러시되어야 한다. 그렇지 않으면 슬레이브의 로그 applier
가 heap 파일 없는 클래스를 가리키는 카탈로그 행을 보게 된다.
CREATE TABLE — SM_TEMPLATE을 끝에서 끝까지 빌드
섹션 제목: “CREATE TABLE — SM_TEMPLATE을 끝에서 끝까지 빌드”CREATE TABLE 파스 트리 노드는 entity_type = PT_CLASS, 이름,
컬럼 리스트, 제약 리스트, 선택적 LIKE 소스 클래스, 선택적
AS SELECT 쿼리, 선택적 파티션 정보, 테이블 옵션(charset,
collation, comment, REUSE_OID, ENCRYPT)을 나른다. 핸들러
do_create_entity(execute_schema.c)가 고정 시퀀스를 따른다. 시스템 savepoint, 템플릿 빌드, do_create_local로 채움, 템플릿
finish, 그리고 post-create 단계.
sequenceDiagram
participant Parser
participant DoCE as do_create_entity
participant Smt as smt_def_class<br/>(schema_template.c)
participant Loc as locator_reserve_class_name<br/>(locator_cl.c)
participant DCL as do_create_local
participant SmF as sm_finish_class<br/>(schema_manager.c)
participant SmU as update_class<br/>install_new_representation
participant LAdd as locator_add_class
participant Heap as locator_create_heap_if_needed<br/>· heap_create
participant Cat as catcls_insert_catalog_classes<br/>(catalog_class.c)
Parser->>DoCE: PT_NODE { create_entity }
DoCE->>DoCE: tran_system_savepoint("cREATEeNTITY")
DoCE->>Smt: dbt_create_class(name)
Smt->>Loc: locator_reserve_class_name(name)
Loc-->>Smt: pseudo-OID, SCH_M_LOCK
Smt-->>DoCE: SM_TEMPLATE
DoCE->>DCL: do_create_local(parser, ctemplate, node)
DCL->>DCL: do_add_attributes
DCL->>DCL: do_add_constraints
DCL->>DCL: do_check_fk_constraints
DCL-->>DoCE: NO_ERROR
DoCE->>SmF: dbt_finish_class(ctemplate)
SmF->>SmU: update_class(template, &classmop, ...)
SmU->>SmU: lockhint_subclasses<br/>flatten_template
SmU->>LAdd: locator_add_class(SM_CLASS, name)
LAdd-->>SmU: classmop (cached in workspace)
SmU->>SmU: install_new_representation
SmU->>SmU: allocate_disk_structures (B-trees)
SmU-->>DoCE: classmop
DoCE->>Heap: locator_create_heap_if_needed(class_obj, reuse_oid)
Heap->>Heap: heap_create(hfid, oid, reuse_oid)
Heap-->>DoCE: hfid set on SM_CLASS
DoCE->>DoCE: do_create_partition (if partitioned)
DoCE->>DoCE: do_create_index (per CREATE INDEX clause)
DoCE->>DoCE: locator_flush_class -> Cat
Cat-->>DoCE: row in _db_class
DoCE-->>Parser: NO_ERROR
do_create_entity 머리의 savepoint는
UNIQUE_SAVEPOINT_CREATE_ENTITY = cREATEeNTITY이며
CREATE TABLE 실패를 복구 가능하게 만드는 유일한 장치다. 이후
어떤 단계라도 실패하면 tran_abort_upto_system_savepoint이 카탈
로그 변경 이전까지 DDL 전체를 되돌린다.
// do_create_entity — execute_schema.c (skeleton)class_name = node->info.create_entity.entity_name->info.name.original;/* ... super-class partitioning checks, table option parsing ... */
error = tran_system_savepoint (UNIQUE_SAVEPOINT_CREATE_ENTITY);do_rollback_on_error = true;
ctemplate = create_like ? dbt_copy_class (class_name, create_like, &source_class) : dbt_create_class (class_name);if (!create_like) error = do_create_local (parser, ctemplate, node, query_columns);
class_obj = dbt_finish_class (ctemplate);
if (locator_create_heap_if_needed (class_obj, reuse_oid) == NULL) { error = er_errid (); break; }
if (node->info.create_entity.partition_info != NULL) error = do_create_partition (parser, node, &info);
for (create_index = node->info.create_entity.create_index; create_index; create_index = create_index->next) error = do_create_index (parser, create_index);dbt_create_class은 src/compat/db_temp.c에 있다.
smt_def_class을 감싼 얇은 래퍼이며, locator_reserve_class_name
경유로 서버에 클래스 이름을 예약하기도 한다.
// dbt_create_class + dbt_reserve_name — db_temp.c (condensed)def = smt_def_class (name); /* allocate SM_TEMPLATE */if (def != NULL) def = dbt_reserve_name (def, name);return def;
// dbt_reserve_name:reserved = locator_reserve_class_name (def->name, &class_oid); /* server hash insert */if (reserved != LC_CLASSNAME_RESERVED) { /* LC_CLASSNAME_EXIST -> ER_LC_CLASSNAME_EXIST */ smt_quit (def); return NULL; }return def;이름을 템플릿이 채워지기 전에 예약하면 두 동시 CREATE TABLE이
이름을 두고 경합할 때 결정적인 패자를 보게 된다. 두 번째 쪽이
LC_CLASSNAME_EXIST을 받고 abort한다, 어느 쪽이 실제로 템플릿을
먼저 마쳤는지와 무관하게. 이는 Postgres의
RangeVarGetAndCheckCreationNamespace이 잡는 namespace 레벨
lock의 CUBRID 등가물이다.
do_create_local은 컬럼·제약 배관이다. do_add_* 도우미의 직선
시퀀스이며, 각각이 템플릿을 변경한다.
// do_create_local — execute_schema.c (template-population sequence)error = do_add_attributes (parser, ctemplate, attr_def_list, constraint_list, query_columns);error = do_add_attributes (parser, ctemplate, class_attr_def_list, NULL, NULL);error = do_add_constraints (ctemplate, constraint_list);error = do_check_fk_constraints (ctemplate, constraint_list);error = do_add_methods (parser, ctemplate, method_def_list);error = do_add_method_files (parser, ctemplate, method_file_list);error = do_add_resolutions (parser, ctemplate, resolution_list);error = do_add_supers (parser, ctemplate, supclass_list);error = do_add_queries (parser, ctemplate, as_query_list);error = do_set_object_id (parser, ctemplate, object_id_list);결과는 실행기 워크스페이스에만 사는 완전히 채워진
SM_TEMPLATE이다. 카탈로그는 아직 건드려지지 않았다. 서버 측에
있는 것은 locator_reserve_class_name의 예약된 이름 항목뿐이다.
dbt_finish_class은 sm_finish_class을 부르고, 그것이 다시
update_class을 부른다. 중심 템플릿 설치 루틴.
// sm_finish_class — schema_manager.cintsm_finish_class (SM_TEMPLATE * template_, MOP * classmop){ return update_class (template_, classmop, 0, AU_ALTER, true);}update_class이 DDL 파이프라인의 심장이다. 순서대로 다음을 한다. 로컬 스키마 버전 bump, ALTER이라면 기존 SM_CLASS fetch(CREATE
이면 NULL), 서브클래스 격자와 슈퍼클래스 사전 lock, 템플릿 평탄화
(상속된 컴포넌트 병합), 서브클래스 write lock, 서브클래스 평탄화,
새로 만드는 거라면 locator_add_class로 영구 클래스 객체 할당,
새 representation 설치, 디스크 B-tree 할당, 마지막으로 슈퍼·서브
클래스 리스트 갱신.
// update_class — schema_manager.c (condensed)sm_bump_local_schema_version ();error = tran_system_savepoint (SM_ADD_UNIQUE_CONSTRAINT_SAVEPOINT_NAME);if ((error == NO_ERROR) && (template_->op != NULL)) error = au_fetch_class (template_->op, &class_, AU_FETCH_UPDATE, auth);
if (needs_hierarchy_lock) { error = lockhint_subclasses (template_, class_); error = lock_supers (template_, class_ ? class_->inheritance : NULL, &oldsupers, &newsupers); }
if (class_ != NULL) class_->new_ = template_;error = flatten_template (template_, NULL, &flat, auto_res);if (needs_hierarchy_lock) error = lock_subclasses (template_, newsupers, class_ ? class_->users : NULL, &newsubs);class_->new_ = flat;error = flatten_subclasses (newsubs, NULL);
if (class_ == NULL) /* fresh class -> new MOP */ { class_ = classobj_make_class (template_->name); /* ... owner assignment ... */ template_->op = locator_add_class ((MOBJ) class_, sm_ch_name ((MOBJ) class_)); }
flat->partition_parent_atts = template_->partition_parent_atts;error = install_new_representation (template_->op, class_, flat);num_indexes = allocate_disk_structures (template_->op, class_, newsubs, template_);error = update_supers (template_->op, oldsupers, newsupers);error = update_subclasses (newsubs);template_->op = locator_add_class (...) 한 줄이 새
SM_CLASS이 워크스페이스 MOP(클래스 객체의 클라이언트 측 핸들)
이 되는 순간이다. locator_reserve_class_name이 돌려준 임시
OID에 묶인다. 영구 OID는 flush 시점에 서버가 부여한다.
install_new_representation이 구조 변경 단계다. 자기 참조 도메인
을 고치고, build_storage_order을 돌려 attribute ID를 부여하고,
변경이 새 디스크 representation을 강제하는지 결정한다(CUBRID
특화 개념: SM_CLASS은 옛 heap 행이 여전히 파싱될 수 있도록 여러
역사적 representation을 들고 다닐 수 있다). 강제한다면 클래스의
모든 캐시된 인스턴스가 플러시되고 decache된다.
// install_new_representation — schema_manager.c (condensed)fixup_component_classes (classop, flat);fixup_self_reference_domains (classop, flat);check_inherited_attributes (classop, class_, flat);needrep = build_storage_order (class_, flat);for (a = flat->shared_attributes; a != NULL; a = a->header.next) assign_attribute_id (class_, a, 0);for (a = flat->class_attributes; a != NULL; a = a->header.next) assign_attribute_id (class_, a, 1);
if (needrep && !classop->no_objects) { locator_flush_all_instances (classop, DECACHE); locator_update_class (classop); newrep = 1; WS_SET_NO_OBJECTS (classop); }
error = transfer_disk_structures (classop, class_, flat);invalidate_unused_triggers (classop, class_, flat);sm_reset_descriptors (classop);error = classobj_install_template (class_, flat, newrep);locator_update_class (classop);여기서 표시할 두 조각은 transfer_disk_structures — 옛
SM_CLASS_CONSTRAINT 리스트와 새 평탄 리스트를 화해시키고 각
B-tree를 보존하거나 deallocate_index한다. 와
classobj_install_template — 실제 swap을 한다. 옛 SM_CLASS을
필드별로 떨어뜨리고 평탄화된 템플릿을 살아 있는 클래스 객체에
복사한다. 이제 클래스 MOP이 권위적이다. fetch하는 다음 DML
컴파일이 새 모양을 보게 된다.
do_create_entity로 돌아오면, dbt_finish_class이 반환된 뒤 heap
이 lazy하게 만들어진다. heap 할당이 locator_create_heap_if_needed
로 미루어져, vclass(view)은 heap 비용 없이 끝낼 수 있고, 평범한
클래스는 한 번만 비용을 치른다.
// locator_create_heap_if_needed — locator_cl.c (condensed)class_obj = locator_fetch_class (class_mop, DB_FETCH_CLREAD_INSTWRITE);hfid = sm_ch_heap (class_obj);if (HFID_IS_NULL (hfid)) { class_obj = locator_fetch_class (class_mop, DB_FETCH_WRITE); oid = ws_oid (class_mop); if (OID_ISTEMP (oid)) { locator_flush_class (class_mop); oid = ws_oid (class_mop); } heap_create (hfid, oid, reuse_oid); /* lob processing, dirty, flush */ }OID가 여전히 임시라면(클래스가 서버에 한 번도 플러시되지 않았다면)
locator_flush_class이 먼저 호출된다. 임시 OID를 영구 OID로
실현하고 서버 측에서 catcls_insert_catalog_classes을 트리거하는
지점이다. 따라서 CREATE TABLE은 보통 카탈로그 쓰기를 여기서
한다, 문장 끝에서가 아니라.
마지막으로, 파티셔닝과 같은 문 안의 CREATE INDEX 절이 디스패치
된다. 인라인 CREATE TABLE … AS SELECT은 do_create_entity
바닥에서 INSERT INTO target SELECT …로 desugar되어 do_statement
로 다시 들어간다. 작지만 우아한 트릭으로, 엔진이 모든 DML을 재
사용하게 한다.
ALTER TABLE — 다절 경로와 단절 경로
섹션 제목: “ALTER TABLE — 다절 경로와 단절 경로”do_alter이 체인을 걸어가며(ALTER은 여러 절을 나를 수 있다) 단일
UNIQUE_SAVEPOINT_MULTIPLE_ALTER savepoint 아래에서 각 절을
실행한다. 절 N에서 오류가 나면 1..N-1 절이 rollback되도록.
stateDiagram-v2
[*] --> Savepoint: tran_system_savepoint("mULTIPLEaLTER")
Savepoint --> Loop: for each crt_clause
Loop --> SemCheck: pt_compile (re-check 2nd+ clause)
SemCheck --> Code: switch(alter_code)
Code --> Rename: PT_RENAME_ENTITY
Code --> AddIdx: PT_ADD_INDEX_CLAUSE
Code --> DropIdx: PT_DROP_INDEX_CLAUSE
Code --> ChgAI: PT_CHANGE_AUTO_INCREMENT
Code --> ChgAttr: PT_CHANGE_ATTR
Code --> Owner: PT_CHANGE_OWNER
Code --> Coll: PT_CHANGE_COLLATION
Code --> TblComm: PT_CHANGE_TABLE_COMMENT
Code --> ColComm: PT_CHANGE_COLUMN_COMMENT
Code --> WithTmpl: default -> do_alter_one_clause_with_template
Rename --> Loop
AddIdx --> Loop
DropIdx --> Loop
ChgAI --> Loop
ChgAttr --> Loop
Owner --> Loop
Coll --> Loop
TblComm --> Loop
ColComm --> Loop
WithTmpl --> Loop
Loop --> [*]: NO_ERROR
Loop --> Rollback: error -> tran_abort_upto_system_savepoint
Rollback --> [*]
// do_alter — execute_schema.c (condensed)error_code = tran_system_savepoint (UNIQUE_SAVEPOINT_MULTIPLE_ALTER);for (crt_clause = alter; crt_clause != NULL; crt_clause = crt_clause->next) { if (do_semantic_checks) { /* re-pt_compile 2nd+ clauses against the freshly mutated class */ } switch (crt_clause->info.alter.code) { case PT_RENAME_ENTITY: error_code = do_alter_clause_rename_entity (parser, crt_clause); break; case PT_ADD_INDEX_CLAUSE: error_code = do_alter_clause_add_index (parser, crt_clause); break; case PT_DROP_INDEX_CLAUSE: error_code = do_alter_clause_drop_index (parser, crt_clause); break; case PT_CHANGE_AUTO_INCREMENT: error_code = do_alter_change_auto_increment (parser, crt_clause); break; case PT_CHANGE_ATTR: error_code = do_alter_clause_change_attribute (parser, crt_clause); break; case PT_CHANGE_OWNER: error_code = do_alter_change_owner (parser, crt_clause); break; case PT_CHANGE_COLLATION: error_code = do_alter_change_default_cs_coll (parser, crt_clause); break; case PT_CHANGE_TABLE_COMMENT: error_code = do_alter_change_tbl_comment (parser, crt_clause); break; case PT_CHANGE_COLUMN_COMMENT: error_code = do_alter_change_col_comment (parser, crt_clause); break; default: error_code = do_alter_one_clause_with_template (parser, crt_clause); /* template-mutating clauses */ } if (error_code != NO_ERROR) goto error_exit; do_semantic_checks = true; }catch-all do_alter_one_clause_with_template이 살아 있는
SM_CLASS을 찌르지 않고 SM_TEMPLATE을 변경하는 절을 처리한다. PT_ADD_QUERY, PT_DROP_QUERY, PT_MODIFY_QUERY,
PT_ADD_ATTR_MTHD, PT_DROP_ATTR_MTHD, PT_MODIFY_ATTR_MTHD,
PT_RESET_QUERY, 그리고 모든 파티션 변경 코드(PT_APPLY_PARTITION,
PT_REMOVE_PARTITION, PT_ADD_PARTITION,
PT_ADD_HASHPARTITION, PT_REORG_PARTITION,
PT_COALESCE_PARTITION, PT_ANALYZE_PARTITION,
PT_PROMOTE_PARTITION).
템플릿을 만지는 모든 절의 골격은 같다. edit, mutate, finish,
필요하면 제약 추가를 위해 edit-again, finish-again. 한 문에서 컬럼
과 unique 제약을 함께 추가할 때 발생하는 전형적 두-finish
패턴을 가장 잘 보여 주는 것이 PT_ADD_ATTR_MTHD 사례다.
// do_alter_one_clause_with_template / PT_ADD_ATTR_MTHD — execute_schema.c (condensed)error = tran_system_savepoint (UNIQUE_SAVEPOINT_ADD_ATTR_MTHD);error = do_add_attributes (parser, ctemplate, attr_def_list, constraint_list, NULL);vclass = dbt_finish_class (ctemplate); /* first finish: column added */ctemplate = dbt_edit_class (vclass); /* re-edit so constraints can reference new column */error = do_add_constraints (ctemplate, constraint_list);error = do_check_fk_constraints (ctemplate, constraint_list);if (mthd_def_list != NULL) error = do_add_methods (parser, ctemplate, mthd_def_list);/* second dbt_finish_class happens in the common tail */분리 이유는 제약이 새 컬럼을 참조할 수 있으나 컬럼은 템플릿이 finish되고 카탈로그 행이 쓰여지기 전까지 서버에 존재하지 않기 때문이다. 그래서 컬럼이 commit되고, 새 컬럼을 포함하는 새 템플릿으로 클래스가 다시 edit되고, 제약이 attach된다.
PT_CHANGE_ATTR 경로(do_alter_clause_change_attribute)은 더 정교
하다. attribute 변경이 새 도메인이 옛 도메인의 자명한 superset이
아닐 때 인스턴스 레벨 갱신(do_run_update_query_for_class,
do_run_upgrade_instances_domain)을 요구할 수 있기 때문이다.
DROP TABLE — sm_delete_class_mop과 cascade
섹션 제목: “DROP TABLE — sm_delete_class_mop과 cascade”do_drop은 더 짧다. drop 리스트의 entity마다 파티션 sanity 검사,
그 다음 drop_class_name → db_drop_class_ex → sm_delete_class_mop.
// do_drop / drop_class_name — execute_schema.cerror = tran_system_savepoint (UNIQUE_SAVEPOINT_DROP_ENTITY);
for (entity_spec = entity_spec_list; entity_spec; entity_spec = entity_spec->next) for (entity = entity_spec->info.spec.flat_entity_list; entity; entity = entity->next) { error = drop_class_name (entity->info.name.original, statement->info.drop.is_cascade_constraints); if (error != NO_ERROR) goto error_exit; }
/* drop_class_name */class_mop = db_find_class (name);if (class_mop) return db_drop_class_ex (class_mop, is_cascade_constraints);sm_delete_class_mop은 CREATE 파이프라인의 역이다. 파티셔닝된
클래스는 do_drop_partitioned_class로 분기해 각 파티션에 재귀
하고, steady-state 경로는 tran_system_savepoint (SM_DROP_CLASS_MOP_SAVEPOINT_NAME),
sm_bump_local_schema_version,
au_fetch_class (..., AU_FETCH_WRITE, AU_ALTER),
lockhint_subclasses, FK referrer 검사(cascade-drop 또는
ER_FK_CANT_DROP_PK_REFERRED), auto-increment SERIAL 객체 제거,
그 다음 classobj_make_template (NULL, op, class_)이 클래스 사라
짐을 표현하는 null 템플릿을 만든다. 서브·슈퍼 클래스 lock은
lock_supers_drop / lock_subclasses / flatten_subclasses로
잡힌다. 모든 캐시된 인스턴스는 heap 파괴 이전에
ws_mark_instances_deleted 처리 후 DECACHE로 플러시된다(그렇지
않으면 워크스페이스가 free된 메모리를 dereference한다).
update_supers_drop과 update_subclasses이 스키마 그래프를 다시
모양 잡고, transfer_disk_structures (op, class_, NULL)이 모든
B-tree를 deallocate한다(flat = NULL이 migrate가 아니라 drop
하라는 신호다). remove_class_triggers이 물리적 트리거 삭제를
수행한다. 카탈로그 행은 같은 MVCC 트랜잭션 아래 flush 시점에
서버 측에서 catcls_delete_catalog_classes로 삭제된다.
성공한 DROP TABLE은 따라서 클라이언트 측 변경의 시리즈가 flush
시점에 카탈로그 delete + heap destroy로 수렴하는 모양이다.
Index DDL — create_or_drop_index_helper
섹션 제목: “Index DDL — create_or_drop_index_helper”do_create_index(PT_CREATE_INDEX)와 do_drop_index
(PT_DROP_INDEX) 둘 다 공유 도우미로 위임한다.
// create_or_drop_index_helper — execute_schema.c (signature)static intcreate_or_drop_index_helper (PARSER_CONTEXT *parser, const char *const constraint_name, const bool is_reverse, const bool is_unique, const PT_INDEX_INFO *idx_info, DB_OBJECT *const obj, DO_INDEX do_index);도우미는 파티션 위의 index DDL을 거절하고, (is_reverse, is_unique)에서 DB_CONSTRAINT_TYPE을 계산하고, filter 술어
(pt_to_pred_with_context, xts_map_filter_pred_to_stream)와
function-index 표현식(pt_node_to_function_index)을 stream된
SM_PREDICATE_INFO과 SM_FUNCTION_INFO로 머터리얼라이즈하고,
sm_produce_constraint_name으로 표준 이름을 유도하고, 마지막으로
sm_add_constraint(B-tree를 allocate_disk_structures로 할당) 또는
sm_drop_constraint(B-tree free)을 호출한다.
PT_ALTER_INDEX은 do_alter_index을 거쳐
do_alter_index_rebuild(drop+create), do_alter_index_rename,
do_alter_index_comment, 그리고 VISIBLE/INVISIBLE 토글
do_alter_index_status로 갈라진다.
Partition DDL — pre/post 분리와 자식 클래스 fanout
섹션 제목: “Partition DDL — pre/post 분리와 자식 클래스 fanout”CUBRID 파티션 DDL은 부모 클래스의 루트 템플릿 변경 더하기 자식
파티션마다 do_create_local 호출 fanout으로 구현된다. 드라이버
do_alter_partitioning_pre이 부모 템플릿 finish 이전에 돌고,
do_alter_partitioning_post이 이후에 돈다. 자식이 머터리얼
라이즈되기 전에 파티션 컬럼과 제약이 자리를 잡도록.
// do_alter_partitioning_pre — execute_schema.cswitch (alter_op) { case PT_APPLY_PARTITION: /* set SM_ATTFLAG_PARTITION_KEY on parent template */ case PT_ADD_PARTITION: case PT_ADD_HASHPARTITION: error = do_create_partition (parser, alter, pinfo); break; case PT_REMOVE_PARTITION: error = do_remove_partition_pre (parser, alter, pinfo); break; case PT_COALESCE_PARTITION: error = do_coalesce_partition_pre (parser, alter, pinfo); break; case PT_REORG_PARTITION: error = do_reorganize_partition_pre (parser, alter, pinfo); break; /* ... */ }do_create_partition 자신이 자식 파티션마다 PT_CREATE_ENTITY
파스 트리를 합성해 PARTITIONED_SUB_CLASS_TAG(__p__) 접미사로
이름 짓고, 각각마다 do_create_local로 재귀한다. 자식 템플릿
은 부모를 상속하고(supclass_list), pt_node_to_partition_info이
채운 partition 필드를 나른다. 해시 파티션은 hashsize 자식을
만든다. range와 list 파티션은 PT_PARTS 원소마다 한 자식을 만든다.
해시 파티션은 스키마에 base_class__p__p0, base_class__p__p1,
… 식으로 등장하며, 모두 base의 서브클래스다. 각 자식이 자기 heap
과 B-tree를 가진다. 부모 heap은 비어 있다. DML이 올바른 자식으로
라우팅되는 일은 나중에 일어난다. pt_resolve_partition_spec(파서)
과 btree_find_root_with_key(실행기).
상호 참조 — 라우팅 측 자세한 내용은 전용 cubrid-partition 분석 참조.
Trigger DDL — 짧은 세 핸들러
섹션 제목: “Trigger DDL — 짧은 세 핸들러”do_create_trigger, do_drop_trigger, do_alter_trigger,
do_rename_trigger, do_remove_trigger이 모두
execute_statement.c에 있다. src/object/trigger_manager.c의
tr_create_trigger, tr_drop_trigger 등을 감싼 얇은 래퍼다.
// do_create_trigger — execute_statement.cclass_ = db_find_class (PT_TR_TARGET_CLASS (target));/* ... extract event/condition/action, time, priority, comment ... */trigger = tr_create_trigger (name, status, priority, event, class_, attribute, cond_time, cond_source, action_time, action_type, action_source, comment);
if (smclass != NULL && smclass->users != NULL && TM_TRAN_ISOLATION () < TRAN_REP_READ) error = locator_all_flush ();워크스페이스 전체 플러시는 CUBRID 특이점이다. 트리거 메타데이터
가 클래스와 같은 heap 행(SM_CLASS::triggers)을 거쳐 흐르기 때문
에, 서브클래스가 임시 객체인 클래스에 트리거를 설치하려면 트리거
가 계층에 걸쳐 보이도록 전체 플러시가 필요하다. repeatable-read
가드는 격리가 이미 일관성을 제공할 때 플러시를 short-circuit한다.
상호 참조 — 본 폴더의 cubrid-trigger 분석이 tr_* 내부를 다룬다.
Locator 통합 — DDL은 DML과 같은 MOP 경로를 거친다
섹션 제목: “Locator 통합 — DDL은 DML과 같은 MOP 경로를 거친다”DDL은 locator를 절대 우회하지 않는다. 모든 카탈로그 변경은 다음 중 하나로 서버에 도달한다.
locator_reserve_class_name— classname-to-OID 테이블에 대한 서버 측 해시 insert. 임시 OID를 돌려준다.locator_add_class— 임시 OID로 워크스페이스 캐시 install. 클래스 MOP에SCH_M_LOCK세팅.locator_flush_class— 클래스 인스턴스를 서버에 push. 거기서xlocator_force이 그것을catcls_insert_catalog_classes/catcls_update_catalog_classes로 번역한다.locator_remove_class— 인스턴스 decache, heap 파괴, classname delete.locator_create_heap_if_needed—HFID이 세팅되도록 보장. 서버에서heap_create호출.locator_all_flush— 복제가 DDL을 기록하기 전 모든 dirty MOP drain.
결정적 성질은 이 가운데 어느 것도 특별 DDL 오피코드가 아니라는
점이다. 클래스는 _db_class의 인스턴스일 뿐이고, 그것을 서버에
플러시하는 일은 다른 dirty MOP을 플러시하는 일과 같은 동작이다.
카탈로그 쓰기는 클래스 MOP의 목적지 heap이 바로 카탈로그 heap
이기 때문에 일어난다.
// locator_add_class — locator_cl.c (condensed)class_mop = ws_find_class (classname); /* deleted-and-resurrected case is also handled */locator_get_reserved_class_name_oid (classname, &class_temp_oid);
/* Convert root-class lock to IX_LOCK so the new class can be IX-locked under it */lock = ws_get_lock (sm_Root_class_mop);if (lock != NULL_LOCK) ws_set_lock (sm_Root_class_mop, lock_conv (lock, IX_LOCK));else locator_lock (sm_Root_class_mop, LC_CLASS, IX_LOCK, LC_FETCH_CURRENT_VERSION);
class_mop = ws_cache_with_oid (class_obj, &class_temp_oid, sm_Root_class_mop);if (class_mop != NULL) { ws_dirty (class_mop); ws_set_lock (class_mop, SCH_M_LOCK); }return class_mop;부각할 만한 두 설계점.
- 클래스 MOP이 루트 클래스 MOP(
sm_Root_class_mop) 아래 install된다. 루트는 DDL이 도는 동안IX_LOCK을 들고 있다. 이것이 글로벌 스키마 lock 없이 동시 DDL을 locator로 직렬화하는 방식이다. - 새 클래스는 flush될 때까지 자기 임시 OID 아래 캐시된다.
flush 시점에 서버가 영구 OID를 부여하고
ws_update_oid이 워크스페이스 핸들을 다시 쓴다.
상호 참조 — cubrid-locator 분석이 classname 해시 테이블,
xlocator_assign_oid_batch, 임시→영구 OID 재작성을 자세히 다룬다.
카탈로그 쓰기 — catcls_insert_catalog_classes
섹션 제목: “카탈로그 쓰기 — catcls_insert_catalog_classes”CREATE TABLE flush의 서버 측은 catcls_insert_catalog_classes
(src/storage/catalog_class.c)에 떨어진다.
// catcls_insert_catalog_classes — catalog_class.c (condensed)value_p = catcls_get_or_value_from_class_record (thread_p, record_p);class_oid_p = &ct_Class.cc_classoid; /* OID of _db_class */cls_info_p = catalog_get_class_info (thread_p, class_oid_p, NULL);hfid_p = &cls_info_p->ci_hfid;heap_scancache_start_modify (thread_p, &scan, hfid_p, class_oid_p, SINGLE_ROW_UPDATE, NULL);catcls_insert_instance (thread_p, value_p, &oid, &root_oid, class_oid_p, hfid_p, &scan);heap_scancache_end_modify (thread_p, &scan);경로는 다음과 같다. 클래스 레코드를 OR_VALUE 트리로 deserialise
하고, 잘 알려진 OID(ct_Class.cc_classoid)로 카탈로그 _db_class
을 찾고, modify 모드로 heap scancache을 열고,
catcls_insert_instance을 호출 — 그 함수가 실제 heap insert과
모든 카탈로그 인덱스에 대한 B-tree 갱신을 수행한다. 쓰기는
단일 MVCC 트랜잭션이다. 사용자 테이블을 보호하는 같은 MVCC
기계가 카탈로그를 보호한다.
catcls_update_catalog_classes은 같지만 ALTER용이다. 편리한
fall-through에 주목 — 클래스 이름을 찾지 못하면 update가 insert로
승격된다.
// catcls_update_catalog_classes — catalog_class.c (condensed)catcls_find_oid_by_class_name (thread_p, name_p, &oid);if (OID_ISNULL (&oid)) /* upgrade UPDATE to INSERT */ return catcls_insert_catalog_classes (thread_p, record_p);value_p = catcls_get_or_value_from_class_record (thread_p, record_p);/* open scancache; heap_update_logical inside catcls_update_instance */catcls_delete_catalog_classes은 대칭이다. 이름으로 OID 찾기,
SINGLE_ROW_DELETE용 heap scancache 열기, catcls_delete_instance
호출, 그 다음 catcls_remove_entry로 인메모리 클래스 캐시에서
evict. *MVCC에서는 행이 물리적으로 제거되지 않는다. 소스 주석이
명시적이다. /* in MVCC, do not physically remove the row */ —
대신 논리적으로 삭제되고 VACUUM이 회수한다.
캐시 무효화 — 스키마 버전, 클래스 객체, XASL
섹션 제목: “캐시 무효화 — 스키마 버전, 클래스 객체, XASL”세 캐시가 클래스 변경을 알아야 한다.
flowchart LR Bump[sm_bump_local_schema_version<br/>schema_manager.c] Bump --> Inst[install_new_representation<br/>classobj_install_template] Inst --> WS[Workspace MOP cache<br/>SM_CLASS swap] Bump --> XASL[XASL cache miss on next compile<br/>see cubrid-xasl-cache] Inst --> Repr[representation chain<br/>old reps preserved for legacy heap rows] WS --> Reset[sm_reset_descriptors<br/>per-MOP attribute cache]
- 워크스페이스 클래스 객체 캐시. MOP 자신이
SM_CLASS포인터 를 소유한다.classobj_install_template이 모든 필드를 걸어가며 평탄 템플릿에서 살아 있는 클래스로 복사한다. 이 시점 이후 모든au_fetch_class (mop, ..., AU_FETCH_READ, ...)이 새 모양을 돌려 준다. 문장별 캐시 무효화 메시지는 없다. swap 자체가 무효화이며, stale 포인터를 든 실행기는 같은 시점에 lock을 잃었을 것이다. - Attribute과 method 디스크립터 캐시.
sm_reset_descriptors이 클래스별 attribute 디스크립터 캐시를 비운다(db_get_attribute_descriptor과 fast-path attribute getter가 사용). 옛 디스크립터 핸들은 다음 사용 시 free된 메모리를 dereference하는 대신 실패한다. - XASL 캐시. CUBRID 플랜 캐시는 각 항목을
(SHA1(SQL), schema_version)쌍으로 키 잡는다.sm_bump_local_schema_version이 로컬 카운터를 증가시키고, 변경된 클래스를 참조하는 다음 플랜 컴파일이 miss를 본다. XASL 캐시가 적극적으로 purge되지는 않는 다. 자연스럽게 노화된다. - 트리거 캐시.
invalidate_unused_triggers이install_new_representation에서 호출되어, 더 이상 존재하지 않는 attribute과 연관된TR_TRIGGER항목을 deleted로 표시한다. 권위 있는 트리거 리스트는 다음 액세스에서_db_trigger로부터 재구성된다. - Representation 체인.
build_storage_order이 변경이 클래스의new representation을 강제하는지 결정한다. 옛 representation 은 카탈로그에 보존되어, 스키마 버전 스탬프가 변경 이전인 역사적 heap 행을 여전히 읽을 수 있게 한다. 실행기는or_class_rep_id을 거쳐 올바른 representation을 참조한다. 이는 Postgres의attnum+ drop-column-marker 스킴의 CUBRID 등가물이다.
상호 참조 — cubrid-class-object이 SM_CLASS / SM_TEMPLATE
데이터 구조를 다루고, cubrid-xasl-cache이 플랜 캐시 무효화 정책
을 다루고, cubrid-catalog-manager이 catcls_*이 쓰는 디스크
카탈로그 레이아웃을 다룬다.
트랜잭셔널 의미 — 곳곳의 savepoint
섹션 제목: “트랜잭셔널 의미 — 곳곳의 savepoint”CUBRID DDL은 완전 트랜잭셔널이며, 그것을 가능하게 하는 규율은 시스템 savepoint다. 모든 DDL 핸들러가 진입에서 고유 savepoint를 잡고 어떤 오류 경로에서도 그것으로 rollback한다.
// savepoint identifiers — execute_schema.c (selected; full list lives at the top of the file)#define UNIQUE_SAVEPOINT_CREATE_ENTITY "cREATEeNTITY"#define UNIQUE_SAVEPOINT_DROP_ENTITY "dROPeNTITY"#define UNIQUE_SAVEPOINT_MULTIPLE_ALTER "mULTIPLEaLTER"#define UNIQUE_SAVEPOINT_ADD_ATTR_MTHD "aDDaTTRmTHD"#define UNIQUE_SAVEPOINT_CHANGE_ATTR "cHANGEaTTR"#define UNIQUE_SAVEPOINT_RENAME "rENAME"#define UNIQUE_SAVEPOINT_TRUNCATE "tRUnCATE"#define UNIQUE_SAVEPOINT_REPLACE_VIEW "rEPlACE"#define UNIQUE_SAVEPOINT_ALTER_INDEX "aLTERiNDEX"/* plus the user/grant/revoke variants and partition savepoints */이상한 대소문자(cREATEeNTITY)는 의도적이다. 사용자 이름의
savepoint과 충돌할 가능성을 천문학적으로 작게 만든다. savepoint은
tran_system_savepoint로 잡히며, 함수가 CLR(compensation log
record)을 WAL에 쓴다. rollback 시 복구가 abort 레코드에서
savepoint LSN까지 WAL을 backward로 걸어 그 사이의 모든 것을 undo
한다.
CREATE TABLE … AS SELECT이 INSERT 도중 절반쯤에서 실패하면
cREATEeNTITY로 rollback된다. 카탈로그 행, heap, 모든 B-tree,
어떤 파티션 자식도 전부 undo된다. 성공한 CREATE TABLE 뒤의
ROLLBACK도 마찬가지로 깨끗하게 create를 undo한다. 이것이
CUBRID이 atomic DDL(MySQL 8.0 의미)을 주장하게 해 주는 계약이다.
복제 꼬리는 do_replicate_statement 호출로 스키마 변경을
LOG_REPLICATION_DDL 항목으로 기록한다. 보조 로그는 영향받은 클래스
OID와 파싱된 문 텍스트를 CDC용으로 캡처한다. 둘 다 로컬 DDL이
성공하고 locator_all_flush ()이 모든 dirty MOP을 실현한 뒤에
지연되어 일어난다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”최상위 디스패치 — execute_statement.c
섹션 제목: “최상위 디스패치 — execute_statement.c”do_statement (parser, statement)— 단일 문 진입점. 거대한switch (statement->node_type)이 DDL 라우터.do_execute_statement— 재준비 변종. 같은 switch을 걷지만 적용 가능할 때 prepared XASL로 디스패치.do_check_internal_statements (parser, stmt, do_func)— 현재do_func로 가는 pass-through. legacy 텍스트 도메인 내부 문 훅이#if 0로 가드.do_reserve_classinfo,do_reserve_oidinfo— DROP / DROP_SERIAL 이전에 영향받은 클래스 OID를 기록해 카탈로그 행이 사라진 뒤에도 보조 로그가 이름을 부를 수 있도록.do_supplemental_statement— CDC용 보조 로그 레코드 발행.do_replicate_statement— HA용 스키마 복제 레코드 발행.do_create_serial,do_alter_serial,do_drop_serial— serial DDL._db_serial로 호출.do_create_trigger,do_drop_trigger,do_alter_trigger,do_rename_trigger,do_remove_trigger,do_set_trigger,do_get_trigger,do_execute_trigger— 트리거 DDL.trigger_manager.c의tr_*로 위임.
CREATE TABLE / DROP / RENAME — execute_schema.c
섹션 제목: “CREATE TABLE / DROP / RENAME — execute_schema.c”do_create_entity—CREATE TABLE/CREATE VIEW드라이버.do_create_local— 템플릿의 컬럼 / 제약 / method / 슈퍼 클래스 채움.execute_create_select_query—CREATE TABLE … AS SELECT의 desugar.PT_INSERT을 합성하고do_statement로 다시 들어감.create_select_to_insert_into—PT_INSERT파스 트리 합성.do_drop—DROP TABLE드라이버.drop_class_name— 이름 → MOP →db_drop_class_ex.truncate_class_name— 이름 → MOP →db_truncate_class.do_truncate—TRUNCATE드라이버.do_rename—RENAME드라이버.acquire_locks_for_multiple_rename이 chained rename에서 atomic하게 lock하고 이름을 예약.do_rename_internal— 실제sm_rename_class수행.update_locksets_for_multiple_rename,acquire_locks_for_multiple_rename—RENAME a TO b, b TO c, c TO a을 위한 lock-and-reserve 댄스.do_recreate_renamed_class_indexes,do_copy_indexes— RENAME과CREATE LIKE이후 인덱스 재생성.
ALTER TABLE — execute_schema.c
섹션 제목: “ALTER TABLE — execute_schema.c”do_alter,do_alter_one_clause_with_template— 다절 드라이버 와 ADD/DROP 컬럼, query op, 파티션 op이 쓰는 단절 템플릿 경로.do_alter_clause_rename_entity,do_alter_clause_add_index,do_alter_clause_drop_index,do_alter_change_auto_increment,do_alter_clause_change_attribute,do_alter_change_owner,do_alter_change_default_cs_coll,do_alter_change_tbl_comment,do_alter_change_col_comment— catch-all 템플릿 경로를 우회하는PT_ALTER_CODE마다 하나씩.do_change_att_schema_only— 스키마만의 컬럼 재정의.do_run_update_query_for_new_notnull_fields,do_run_update_query_for_new_default_expression_fields,do_update_new_notnull_cols_without_default,do_update_new_cols_with_default_expression,do_run_upgrade_instances_domain— NOT NULL / DEFAULT / 도메인 확장이 있는 ALTER ADD COLUMN의 인스턴스 레벨 fixup.do_drop_att_constraints,do_recreate_att_constraints,do_save_all_indexes,do_drop_saved_indexes,do_recreate_saved_indexes— CHANGE COLUMN 주변의 제약과 인덱스 보존.do_alter_index_status— ALTER INDEX … VISIBLE/INVISIBLE.
Index DDL — execute_schema.c
섹션 제목: “Index DDL — execute_schema.c”do_create_index— PT_CREATE_INDEX 드라이버.do_drop_index— PT_DROP_INDEX 드라이버.do_alter_index— rebuild/rename/comment/status 디스패치.do_alter_index_rebuild,do_alter_index_rename,do_alter_index_comment.create_or_drop_index_helper— 공유 본체.get_reverse_unique_index_type,get_index_type_qualifiers— PT 플래그 → DB_CONSTRAINT_TYPE 어댑터.
Partition DDL — execute_schema.c
섹션 제목: “Partition DDL — execute_schema.c”do_create_partition이 자식마다PT_CREATE_ENTITY을 합성하고do_create_local로 재귀.do_alter_partitioning_pre/do_alter_partitioning_post— 두 단계 ALTER … PARTITION 디스패처. op별 짝은do_remove_partition_pre/_post,do_coalesce_partition_pre/_post,do_reorganize_partition_pre/_post, 그리고do_promote_partition_list,do_promote_partition_by_name,do_promote_partition,do_analyze_partition.- 파티션 인지 도우미 —
do_check_partitioned_class,do_get_partition_parent,do_is_partitioned_subclass,do_drop_partitioned_class,do_rename_partition,do_get_partition_size,do_get_partition_keycol,do_drop_partition_list,do_create_partition_constraints,do_create_partition_constraint,do_redistribute_partitions_data.
사용자와 권한 DDL — execute_schema.c
섹션 제목: “사용자와 권한 DDL — execute_schema.c”do_grant,do_revoke— 권한 관리.do_create_user,do_drop_user,do_alter_user— 사용자 DDL (멤버 셋, 비밀번호, 코멘트).
트리거와 serial DDL — execute_statement.c
섹션 제목: “트리거와 serial DDL — execute_statement.c”do_create_trigger—tr_create_trigger래핑.do_drop_trigger— spec 리스트마다tr_drop_trigger래핑.do_alter_trigger—tr_set_priority/tr_set_status래핑.do_rename_trigger—tr_rename_trigger래핑.do_create_serial,do_alter_serial,do_drop_serial—_db_serial시스템 클래스 위 CRUD, 그리고do_get_serial_obj_idlookup 도우미.
템플릿 기계 — db_temp.c, schema_template.c, schema_manager.c
섹션 제목: “템플릿 기계 — db_temp.c, schema_template.c, schema_manager.c”- 공개
dbt_*API:dbt_create_class,dbt_create_vclass,dbt_edit_class,dbt_copy_class,dbt_finish_class,dbt_abort_class, 그리고 컴포넌트별dbt_add_attribute/dbt_add_constraint/dbt_drop_*/dbt_*_query_spec변경기. - 내부 factory:
smt_def_class,smt_def_typed_class,smt_edit_class_mop,smt_copy_class_mop,smt_copy_class,smt_quit,def_class_internal. SM_TEMPLATE라이프사이클(class_object.c):classobj_make_template,classobj_make_template_like,classobj_install_template,classobj_free_template.- 상속 병합과 물리 레이아웃:
flatten_template,flatten_subclasses,build_storage_order,assign_attribute_id,assign_method_id. - 스키마 그래프 프리미티브:
update_class,update_supers,update_supers_drop,update_subclasses,lock_supers,lock_supers_drop,lock_subclasses,lockhint_subclasses. - 물리 변경:
install_new_representation,transfer_disk_structures,allocate_disk_structures,deallocate_index,rem_class_from_index. - 공개 클래스 갱신 엔드포인트:
sm_finish_class,sm_update_class,sm_update_class_auto,sm_update_class_with_auth. drop 기계:sm_delete_class_mop,remove_class_triggers,sm_drop_cascade_foreign_key,do_drop_partitioned_class. - DDL fence 카운터:
sm_bump_local_schema_version,sm_local_schema_version.
Locator와 카탈로그 — locator_cl.c, locator_sr.c, catalog_class.c
섹션 제목: “Locator와 카탈로그 — locator_cl.c, locator_sr.c, catalog_class.c”- Classname 해시:
locator_reserve_class_name,locator_delete_class_name,locator_get_reserved_class_name_oid,locator_force_drop_class_name_entry. - 클라이언트 측 클래스 MOP 라이프사이클:
locator_add_class,locator_remove_class,locator_update_class,locator_flush_class,locator_create_heap_if_needed,locator_prepare_rename_class. 워크스페이스 drain:locator_all_flush,locator_flush_all_instances. - 서버 측:
xlocator_force이 flush를 디스패치하고 카탈로그 클래스 인스턴스를catcls_*로 라우팅.xlocator_remove_class_from_index과xlocator_assign_oid_batch이 다중 클래스 인덱스 제거와 임시→ 영구 OID 재작성을 처리. - 카탈로그 쓰기 API:
catcls_insert_catalog_classes,catcls_update_catalog_classes,catcls_delete_catalog_classes,catcls_update_class_stats,catcls_remove_entry,catcls_compile_catalog_classes. OR_VALUE↔ heap 행 marshalling:catcls_get_or_value_from_class,catcls_get_or_value_from_class_record,catcls_put_or_value_into_record,catcls_insert_instance,catcls_delete_instance,catcls_update_instance. ALTER/DROP 의 이름 해석:catcls_find_oid_by_class_name.
위치 힌트 (이 리비전 기준)
섹션 제목: “위치 힌트 (이 리비전 기준)”| 심볼 | 파일 | 줄 |
|---|---|---|
do_statement (DDL switch) | src/query/execute_statement.c | 3244 |
do_check_internal_statements | src/query/execute_statement.c | 4270 |
do_create_trigger | src/query/execute_statement.c | 6661 |
do_drop_trigger | src/query/execute_statement.c | 6801 |
do_alter_trigger | src/query/execute_statement.c | 6860 |
do_create_serial | src/query/execute_statement.c | 1406 |
do_replicate_statement | src/query/execute_statement.c | 16136 |
do_supplemental_statement | src/query/execute_statement.c | 15515 |
do_alter | src/query/execute_schema.c | 1770 |
do_alter_one_clause_with_template | src/query/execute_schema.c | 409 |
do_alter_clause_rename_entity | src/query/execute_schema.c | 1558 |
do_alter_clause_change_attribute | src/query/execute_schema.c | 9942 |
drop_class_name | src/query/execute_schema.c | 2582 |
do_drop | src/query/execute_schema.c | 2607 |
acquire_locks_for_multiple_rename | src/query/execute_schema.c | 2737 |
do_rename | src/query/execute_schema.c | 2890 |
create_or_drop_index_helper | src/query/execute_schema.c | 3027 |
do_create_index | src/query/execute_schema.c | 3330 |
do_drop_index | src/query/execute_schema.c | 3370 |
do_alter_index | src/query/execute_schema.c | 3995 |
do_create_partition | src/query/execute_schema.c | 4037 |
do_alter_partitioning_pre | src/query/execute_schema.c | 6017 |
do_alter_partitioning_post | src/query/execute_schema.c | 6165 |
do_add_attributes | src/query/execute_schema.c | 7735 |
do_add_constraints | src/query/execute_schema.c | 7950 |
do_check_fk_constraints | src/query/execute_schema.c | 8303 |
do_create_local | src/query/execute_schema.c | 8766 |
execute_create_select_query | src/query/execute_schema.c | 8946 |
do_create_entity | src/query/execute_schema.c | 9025 |
truncate_class_name | src/query/execute_schema.c | 9855 |
do_truncate | src/query/execute_schema.c | 9880 |
do_grant | src/query/execute_schema.c | 1888 |
do_create_user | src/query/execute_schema.c | 2114 |
dbt_create_class | src/compat/db_temp.c | 76 |
dbt_edit_class | src/compat/db_temp.c | 132 |
dbt_finish_class | src/compat/db_temp.c | 225 |
smt_def_class | src/object/schema_template.c | 735 |
smt_edit_class_mop | src/object/schema_template.c | 753 |
update_class | src/object/schema_manager.c | 13148 |
sm_finish_class | src/object/schema_manager.c | 13431 |
sm_delete_class_mop | src/object/schema_manager.c | 13584 |
install_new_representation | src/object/schema_manager.c | 12366 |
transfer_disk_structures | src/object/schema_manager.c | 11924 |
sm_bump_local_schema_version | src/object/schema_manager.c | 6717 |
locator_add_class | src/transaction/locator_cl.c | 5378 |
locator_create_heap_if_needed | src/transaction/locator_cl.c | 5472 |
locator_remove_class | src/transaction/locator_cl.c | 5652 |
locator_flush_class | src/transaction/locator_cl.c | 4890 |
xlocator_force | src/transaction/locator_sr.c | 7129 |
catcls_insert_catalog_classes | src/storage/catalog_class.c | 4310 |
catcls_update_catalog_classes | src/storage/catalog_class.c | 4573 |
catcls_delete_catalog_classes | src/storage/catalog_class.c | 4379 |
catcls_get_or_value_from_class_record | src/storage/catalog_class.c | 3552 |
Cross-check 노트
섹션 제목: “Cross-check 노트”- cubrid-class-object이
SM_CLASS과SM_TEMPLATE을 데이터 구조 로 묘사한다. 본 문서는 템플릿이 클래스가 되는 워크플로우를 묘사한다.classobj_install_template이 구조 계약이고, representation 체인(new_,old_,representations)은 여기서build_storage_order이 채우고 거기서or_class_rep_id이 소비 한다. - cubrid-catalog-manager이
_db_class/_db_attribute/_db_index의 디스크 레이아웃을 다룬다. 본 문서는catcls_insert_catalog_classes/catcls_update_catalog_classes/catcls_delete_catalog_classes로 가는 호출 경로를 다룬다. marshalling은 거기 묘사된OR_VALUE트리와 일대일이다. - cubrid-locator이 classname 해시 테이블,
sm_Root_class_mop아래의 lock 변환, flush 시점의 임시→영구 OID 재작성을 소유한다. 본 문서는 호출 사이트(locator_add_class,locator_remove_class,locator_create_heap_if_needed,locator_flush_class,xlocator_force)를 명명한다. - cubrid-trigger이
tr_create_trigger/tr_drop_trigger과_db_trigger카탈로그 행을 소유한다. 가로지르는 관심사는install_new_representation안의invalidate_unused_triggers과sm_delete_class_mop안의remove_class_triggers다. - cubrid-btree과 cubrid-partition — 인덱스 DDL은
sm_add_constraint/sm_drop_constraint로 위임하고 궁극적 으로btree_create_index/btree_delete_index로 간다. 파티션 DDL은 자식 클래스 생성으로 fan out하고, 라우팅은 파티션 문서에 있다. - cubrid-xasl-cache이 bump된
sm_local_schema_version이 플랜 캐시 키와 prepared statement 상태와 어떻게 상호작용하는지 다룬다. - Drift watch. 위치 힌트 표의 줄 번호는 2025년 말 워크트리
상태 기준으로 안정적이다. 새
PT_NODE_TYPE코드를 위한execute_statement.c편집은 DDL switch을 아래로 밀어내는 경향이 있다. 심볼 이름에 닻을 내려라 — 11.0 이후 릴리스에 걸쳐 안정적이다.
풀리지 않은 질문
섹션 제목: “풀리지 않은 질문”- Online schema change.
ALTER TABLE이 전체 동안SCH_M_LOCK을 잡는다. InnoDB 식 row-log / ghost-copy의 online DDL이라면 긴 ALTER(자명하지 않은 DEFAULT를 가진 ADD COLUMN, 도메인 확장) 도중 동시 DML이 진행할 수 있다. 자연스러운 통합 지점은do_run_update_query_for_new_notnull_fields과do_run_upgrade_instances_domain이다. - Disjoint 클래스에 대한 동시 DDL. 다른 클래스에 대한 두
CREATE TABLE이sm_Root_class_mop위IX_LOCK과 classname 해시 insert로 직렬화된다. 더 세밀한 스키마 lock이 실무에서 도움이 될지는 소스에 없는 경합 측정에 달려 있다. - 파티션별 online add column.
do_add_attribute이 부모 템플릿 위에서 한 번 돌고flatten_subclasses로 전파된다. representation 체인이 클래스별 representation ID를 나르므로 파티션별 병렬 마이그레이션은 생각해 볼 만하지만 구현되어 있지 않다. - HA 가로지르는 트리거 DDL의 atomicity.
do_create_trigger안의locator_all_flush ()이 로컬 일관성 을 다루지만 스키마 복제 레코드는 바깥do_replicate_statement이 쓴다. 계층적 트리거에 대한 슬레이브 측 크래시 복구 거동은 완전히 문서화되어 있지 않다. do_check_internal_statements재활성화. 래퍼는 TEXT 도메인 보조 문을 위한 substantial한#if 0블록을 호스팅한다. TEXT가 돌아오면 savepoint 규율은 디스패치된 핸들러가 이미 잡은 DDL별 savepoint와 화해되어야 한다.- 카탈로그 행의 MVCC delete.
catcls_delete_catalog_classes은 행을 물리적으로 제거하지 않는다. 카탈로그 heap 위 VACUUM이 특별 취급되는지(예: 모든 DROP 후 강제) 여부는execute_schema.c만으로는 분명하지 않다.
위치 힌트 표가 기록한 리비전의 CUBRID 소스 트리에서 합성됨.
소비된 코드 경로.
src/query/execute_schema.c—do_create_entity,do_alter,do_drop,do_truncate,do_rename,do_create_index/do_drop_index/do_alter_index*,do_create_partition,do_alter_partitioning_pre/_post,do_grant/do_revoke,do_create_user/do_drop_user/do_alter_user, 그리고do_add_*템플릿 변경 도우미.src/query/execute_statement.c—do_statementswitch, 복제 와 보조 로그 꼬리, 트리거 / serial / 저장 프로시저 DDL 핸들러.src/object/schema_manager.c—update_class,sm_finish_class,sm_delete_class_mop,install_new_representation,transfer_disk_structures,sm_bump_local_schema_version.src/object/schema_template.c—smt_def_class,smt_edit_class_mop,smt_copy_class_mop,smt_quit.src/object/class_object.c—classobj_make_template,classobj_install_template,classobj_make_class,classobj_free_template.src/object/object_template.c—obt_quit,obt_assign,obt_apply_assignments,obt_update.src/compat/db_temp.c— 공개dbt_*템플릿 API.src/transaction/locator_cl.c—locator_reserve_class_name,locator_add_class,locator_create_heap_if_needed,locator_remove_class,locator_flush_class,locator_update_class,locator_all_flush.src/transaction/locator_sr.c—xlocator_force,xlocator_remove_class_from_index,xlocator_assign_oid_batch,locator_force_drop_class_name_entry.src/storage/catalog_class.c—catcls_insert_catalog_classes,catcls_update_catalog_classes,catcls_delete_catalog_classes,catcls_get_or_value_from_class_record,catcls_compile_catalog_classes.src/parser/csql_grammar.y—PT_CREATE_ENTITY,PT_ALTER,PT_DROP,PT_CREATE_INDEX,PT_ALTER_INDEX,PT_RENAME,PT_TRUNCATE,PT_CREATE_TRIGGER,PT_CREATE_SERIAL,PT_CREATE_USER을 만들어 내는 DDL 문법.
학술적 참조.
- Silberschatz 외, Database System Concepts, 7판, §5.2(DDL 구문), §15.5(System Catalog), §17(Recovery와 DDL의 atomicity).
- Petrov, Database Internals, 4장(B-tree 갱신의 특수 사례로서 스키마 관리).
- Postgres 소스:
src/backend/commands/tablecmds.c(DefineRelation,ATExecAddColumn),src/backend/utils/cache/relcache.c(relcache 무효화),src/backend/utils/cache/inval.c(CommandCounterIncrement). - MySQL 8.0 Atomic DDL 설계 노트(
mysql.tables,dd::cache, DDL log). - Oracle Concepts Guide(Library Cache Locks와 Pin. DDL 암묵적 commit).