(KO) CUBRID unloaddb — 스키마와 데이터 export, 4-파일 출력 레이아웃, per-class 멀티스레드 · 멀티프로세스 driver
이론적 배경
섹션 제목: “이론적 배경”Logical exporter 는 카탈로그를 walk 하고 스키마와 데이터를
텍스트 또는 거의-텍스트 표현으로 써내는 데이터베이스 유틸리티
이다 — 엔진의 loader 가 다른 데이터베이스(같은 엔진, 가능하면
다른 버전, 또는 cross-version 마이그레이션 / DR 훈련 / 테스트
fixture 빌드를 위한 새 빈 데이터베이스)로 다시 import 하기에
적합한 형태로. 모든 관계형 엔진이 하나씩 가지고 있다는 점이다 —
pg_dump, mysqldump, expdp, mongodump — 그리고 모든 구현
이 같은 디자인 선택을 한다.
세 가지 축이 지배한다:
-
Schema-first vs data-first 순서. 거의 모든 loader 가 데이터 가 insert 되기 전에 스키마가 존재해야 하므로, exporter 는 스키마 를 먼저, 데이터를 나중에 emit 한다는 뜻이다. 흥미로운 질문은 무엇이 스키마로 치는가이다 — 표 정의는 명백하고, 뷰는 표에 의존하므로 다음에, 트리거는 표를 참조하고 (그리고 서로 참조할 수 있고), 인덱스는 이미 로드된 데이터에서 빌드 가능하므로 미룰 수 있다. 순서 선택은 — 그리고 어떤 artefact 가 post- load pass 로 미뤄지는지가 — exporter 와 loader 사이의 계약이다.
-
단일 파일 vs 다중 파일. 단일 export 파일은 ship 하고 replay 하기 (
cat dump.sql | psql) 단순하다. 다중 파일 (단계당 하나 또는 클래스당 하나)은 export 와 import 모두를 병렬화해주고, 운영자가 부분을 선택적으로 reload 할 수 있게 한다. 대가는 운영적이다 — 다중 파일은 exporter 프로세스가 출력 stream 을 조율해야 하고, importer 가 파일명 규약을 알아야 한다는 뜻이다. -
스냅샷 의미. export 의 순간에 무엇이 데이터베이스 로 치는가? 순진한 read 는 일찍 추출된 vs 늦게 추출된 클래스를 commit 된-후-변경된 행을 다르게 본다. MVCC 가 있는 엔진은 exporter 가 단일 스냅샷을 pin 할 수 있게 한다 (PostgreSQL 의
pg_dump는 repeatable-read 를 쓴다; CUBRID 는 마지막 commit 된 image vs export 시작의 스냅샷 사이에서 고를 수 있는--latest-image토글을 노출한다). 스냅샷을 pin 하지 않는 exporter 는 운영자가 쓰기를 중단하는 데 의존한다 — cold backup 에는 받아들일 만하지만 online 에는 절대 안 된다.
unloaddb 도구가 세 축 모두를 다룬다는 점이다 — 순서에는
표-driven, 고정 선택 (schema → triggers → indexes → data) 과
configurable split (기본은 단계당 하나; 옵션으로 클래스당 하나)
플러스 명시적 --latest-image 스냅샷 토글.
일반 DBMS 디자인
섹션 제목: “일반 DBMS 디자인”| 엔진 | 단계 순서 | 파일 레이아웃 | 동시성 | 스냅샷 |
|---|---|---|---|---|
| PostgreSQL pg_dump | 데이터가 --section=pre-data (스키마), --section=data, --section=post-data (인덱스, FK, 트리거)로 bracket 됨 | 한 파일 (text/custom/directory format); directory format → relation 당 한 파일 | --jobs N (directory format 만) — per-table COPY 병렬 | 시작 시점에 pin 된 repeatable-read 스냅샷 |
| MySQL mysqldump | DDL 이 각 INSERT 앞에 inline (--single-transaction 으로 pin) | 기본 한 파일; --tab 으로 per-table .sql + .txt 쌍 | mysqldump 자체에는 없음; mysqlpump 가 병렬 변형 | --single-transaction 이 REPEATABLE READ 로 pin; 그 외에는 스냅샷 없음 |
| Oracle expdp (Data Pump) | METADATA_ONLY / DATA_ONLY 단계; full export 는 dumpfile 안에 metadata 먼저 둘 다 emit | directory object 안의 한 개 또는 다중 .dmp dumpfile; format 은 binary | PARALLEL=N worker 프로세스 | FLASHBACK_TIME / FLASHBACK_SCN |
| MongoDB mongodump | 컬렉션당 metadata .metadata.json + BSON .bson | 데이터베이스당 한 디렉토리, 컬렉션당 한 파일 쌍 | 암묵적 per-collection 병렬 | 기본 없음; collection-scan consistency 에 --snapshot |
| CUBRID unloaddb | schema → triggers → indexes → data (loaddb 가 이 순서로 replay) | 기본 4 파일 (_schema/_trigger/_indexes/_objects); --datafile-per-class 가 클래스당 데이터 파일 추가 | --thread-count N (per-class fetch 병렬); --mt-process N/M (클래스로 whole-process partition) | --latest-image 토글: 마지막 commit vs export 시작 |
CUBRID 의 차별점은 두 축 동시성 이다 — 한 프로세스 안의
스레드 풀 (--thread-count)로 네트워크-bound fetch 병렬, 또는
다중 프로세스 (--mt-process N/M)로 각 프로세스가 클래스 list
의 disjoint subset 을 export 하고 per-process 파일 prefix 로
쓰기. 두 모드는 호출 시점에 상호 배타적이지만 다른 병목을
다룬다는 점이다 — 스레드는 네트워크 round-trip 지연시간을,
프로세스는 CPU-bound 텍스트 직렬화를.
CUBRID 의 접근
섹션 제목: “CUBRID 의 접근”4-파일 출력 계약
섹션 제목: “4-파일 출력 계약”기본 unloaddb mydb 는 출력 디렉토리에 4 개의 파일을 생성한다는
점이다:
| 파일 | 내용 | loaddb 가 로드 |
|---|---|---|
<prefix>_schema | 표에는 CREATE CLASS ..., 뷰에는 CREATE VCLASS ..., topologically 올바른 순서로; 컬럼 정의, primary key, do_auth = 1 일 때 owner / auth grant | loaddb phase 1 (스키마) |
<prefix>_trigger | 모든 트리거에 대한 CREATE TRIGGER ... 문; 트리거가 클래스를 참조하므로 스키마 후에 emit | loaddb phase 2 (트리거) |
<prefix>_indexes | 모든 secondary 인덱스에 대한 CREATE INDEX ... 문; 마지막에 emit 되어 인덱스를 fully populated heap 위에서 post-load 로 빌드할 수 있게 한다 (데이터 insert 동안의 per-row 인덱스 유지 비용 회피) | loaddb phase 4 (데이터 후) |
<prefix>_objects | CUBRID object-loader 형식 — 행당 한 record, schema-aware 텍스트 인코딩 | loaddb phase 3 (데이터) |
<prefix> 는 기본 데이터베이스 이름이며 --output-prefix / -p
로 override 된다. --output-path / -od 가 출력 디렉토리를
선택한다는 뜻이다.
export 의 단계 순서 (schema → trigger → index → data)는 load
순서가 아니다. loaddb 는 다음과 같이 소비한다 — 스키마 먼저
(어떤 데이터에도 존재해야), 그 다음 트리거 (데이터 동안 AFTER
트리거가 올바로 발사되도록 데이터 전에 존재해야 — 트리거가
--no-trigger 로 명시적으로 비활성화되지 않은 한), 그 다음 데이터,
그 다음 인덱스 (이제 채워진 heap 위에서 한 batch 로 빌드 — per-
row 유지보다 훨씬 빠르다는 점이다).
진입점과 옵션 분류 체계
섹션 제목: “진입점과 옵션 분류 체계”unloaddb.c 의 unloaddb 함수는 통합 cubrid admin CLI
(cubrid-cub-admin.md §데이터베이스-admin arm 참조)가 로드하는
SA/CS 이중 진입점이다. UTIL_FUNCTION_ARG * 를 받아 ~25 개의
옵션을 다섯 패밀리로 풀어낸다는 점이다:
// unloaddb — unloaddb.cinput_filename = utility_get_option_string_value (arg_map, UNLOAD_INPUT_CLASS_FILE_S, 0);include_references = utility_get_option_bool_value (arg_map, UNLOAD_INCLUDE_REFERENCE_S);required_class_only = utility_get_option_bool_value (arg_map, UNLOAD_INPUT_CLASS_ONLY_S);datafile_per_class = utility_get_option_bool_value (arg_map, UNLOAD_DATAFILE_PER_CLASS_S);est_size = utility_get_option_int_value (arg_map, UNLOAD_ESTIMATED_SIZE_S);cached_pages = utility_get_option_int_value (arg_map, UNLOAD_CACHED_PAGES_S);output_dirname = utility_get_option_string_value (arg_map, UNLOAD_OUTPUT_PATH_S, 0);do_schema = utility_get_option_bool_value (arg_map, UNLOAD_SCHEMA_ONLY_S);do_objects = utility_get_option_bool_value (arg_map, UNLOAD_DATA_ONLY_S);latest_image_flag = utility_get_option_bool_value (arg_map, UNLOAD_LATEST_IMAGE_S);output_prefix = utility_get_option_string_value (arg_map, UNLOAD_OUTPUT_PREFIX_S, 0);hash_filename = utility_get_option_string_value (arg_map, UNLOAD_HASH_FILE_S, 0);verbose_flag = utility_get_option_bool_value (arg_map, UNLOAD_VERBOSE_S);order = utility_get_option_bool_value (arg_map, UNLOAD_KEEP_STORAGE_ORDER_S) ? FOLLOW_STORAGE_ORDER : FOLLOW_ATTRIBUTE_ORDER;split_schema_files = utility_get_option_string_value (arg_map, UNLOAD_SPLIT_SCHEMA_FILES_S, 0);is_as_dba = utility_get_option_string_value (arg_map, UNLOAD_AS_DBA_S, 0);g_pre_alloc_varchar_size = utility_get_option_int_value (arg_map, UNLOAD_STRING_BUFFER_SIZE_S);g_request_pages = utility_get_option_int_value (arg_map, UNLOAD_REQUEST_PAGES_S);thread_count = utility_get_option_int_value (arg_map, UNLOAD_THREAD_COUNT_S);sampling_records = utility_get_option_int_value (arg_map, UNLOAD_SAMPLING_TEST_S);/* --mt-process N/M 은 "process_idx/process_total" 로 파싱 */다섯 패밀리:
- 무엇을 추출.
--schema-only/--data-only,--input-class-file,--input-class-only,--include-reference. - 어디에 둘 것인가.
--output-path,--output-prefix,--datafile-per-class,--split-schema-files. - 어떻게 fetch.
--cached-pages,--request-pages,--string-buffer-size,--latest-image,--keep-storage-order. - 어떻게 병렬화.
--thread-count또는--mt-process N/M. - Auth / verbose.
--user,--password,--as-dba,--verbose,--enhanced-estimates,--sampling-test,--estimated-size,--hash-file.
두 driver 경로
섹션 제목: “두 driver 경로”옵션 파싱과 db_restart_ex 후, unloaddb 함수가 do_schema /
do_objects 로 게이트된 두 절반으로 돌아간다는 점이다:
// unloaddb (의역)if (!status && (do_schema || !do_objects)) { // 스키마 절반 if (g_parallel_process_cnt <= 1 || g_parallel_process_idx == 1) { create_filename_trigger (output_dirname, output_prefix, trigger_output_filename, ...); create_filename_indexes (output_dirname, output_prefix, indexes_output_filename, ...);
unload_context.do_auth = 1; unload_context.storage_order = order; unload_context.exec_name = exec_name; unload_context.login_user = user; unload_context.output_prefix = output_prefix;
extract_classes_to_file (unload_context); // _schema extract_triggers_to_file (unload_context, trigger_output_filename); // _trigger extract_indexes_to_file (unload_context, indexes_output_filename); // _indexes
unload_context.clear_schema_workspace (); }}
AU_SAVE_AND_ENABLE (au_save);if (!status && (do_objects || !do_schema)) { // 데이터 절반 extract_objects (unload_context, output_dirname, thread_count, sampling_records, enhanced_estimates);}AU_RESTORE (au_save);
db_shutdown ();두 가지 중요한 조건:
- 스키마 절반은
--mt-process N/Mpartition 의 프로세스 1 에서만 돌아간다. 프로세스 카운트와 무관하게_schema/_trigger/_indexes파일은 하나뿐이다 — 모든 프로세스에서 emit 하면 파일 시스템을 race 하게 된다는 뜻이다. 프로세스 1 이 emit 하고, 프로세스 2..N 은 건너뛰고 데이터 절반으로 직진한다. - 데이터 절반 주변의 auth 토글.
AU_SAVE_AND_ENABLE/AU_RESTORE가 데이터 추출을 bracket 해, caller 가 auth 가 비활성화된 채 (예: 내부 스크립트에서) 도착했더라도, 실제 행 단위 read 가 연결 사용자의 authorisation 을 강제한다. 스키마 절반은 이 처리가 필요 없다 — 그 emitter 가 명시적으로extract_context로is_dba_user/is_dba_group_member를 검사하기 때문이다.
extract_context — carrier 구조체
섹션 제목: “extract_context — carrier 구조체”extract_context (extract_schema.hpp 에 선언)가 모든 emitter
함수에 thread 되는 bag 이다:
| 필드 | 목적 |
|---|---|
output_dirname | 파일이 가는 곳 |
output_prefix | 데이터베이스별 파일명 prefix |
exec_name | unloaddb — 에러 메시지용 |
login_user | 연결 사용자 |
is_dba_user / is_dba_group_member | startup 에 해석된 authorisation 플래그 |
do_auth | GRANT 문을 emit 할지 여부 (1 = yes) |
storage_order | FOLLOW_STORAGE_ORDER (디스크상의 컬럼 순서) vs FOLLOW_ATTRIBUTE_ORDER (카탈로그의 선언 순서); CREATE CLASS 와 데이터 파일의 컬럼 순서에 영향 |
classes | export 할 클래스 객체의 DB_OBJLIST — 스키마 절반이 extract_classes_to_file 에서 채우고, 트리거/인덱스에 재사용 |
has_indexes | 스키마 추출 동안 설정된 캐시 플래그 |
vclass_list_has_using_index | 뷰 수준 USING INDEX tracker |
clear_schema_workspace() | per-half workspace 를 free |
구조체의 역할은 emitter 함수들을 클래스 사이에서 stateless 로 유지하는 것이다 — 모든 호출이 컨텍스트를 전달하고, 컨텍스트가 모든 정책을 운반한다.
스키마 추출 (extract_classes_to_file →
섹션 제목: “스키마 추출 (extract_classes_to_file →”emit_schema)
extract_classes_to_file (unload_schema.c:1378 에 정의)가
<prefix>_schema 를 열고, 카탈로그에서 ctxt.classes 를 채우고,
per-user-class 헤더를 emit 하고, emit_schema(ctxt, output, EXTRACT_CLASS) 를 호출해서 표를 쓴다 — 그리고 두 번째 호출은
EXTRACT_VCLASS 로 뷰를 쓴다. 두 pass 분리가 표 전, 뷰 후
를 강제하는 부분이다 — 어떤 뷰가 아직 emit 안 된 표를 참조하면,
두 번째 pass 가 명시적 에러로 실패하고 replay 불가능한 파일을
만들지 않는다는 점이다.
emit_schema 자체가 ctxt.classes 를 iterate 하고 클래스마다
전체 CREATE CLASS 문을 생산한다 — CHAR(N) / NUMERIC(p,s)
등의 도메인 renderer; 제약 (PRIMARY KEY, UNIQUE, NOT NULL inline;
FOREIGN KEY 끝에); inheritance / partition 절; do_auth 일 때
OWNER name 과 GRANT ... TO .... storage_order 설정이 두
컬럼-iteration 도우미 사이에서 고른다 — attribute-order 가
카탈로그 선언 순서 (사용자의 멘탈 모델과 일치), storage-order 가
on-disk slot 레이아웃과 일치 (cross-platform binary-tier 복제에
중요하다).
extract_triggers_to_file 와 extract_indexes_to_file 는 같은
패턴을 따른다 — 출력을 열고, 카탈로그를 walk 하고, 객체별
CREATE TRIGGER / CREATE INDEX 를 emit. 트리거는 조건 표현식을
verbatim 으로 emit 되고, 인덱스는 INDEX hint 와 filtered-index
WHERE 절을 보존한다는 뜻이다.
데이터 추출 (extract_objects → process_class)
섹션 제목: “데이터 추출 (extract_objects → process_class)”extract_objects (unload_object.c:699 에 정의)가 데이터 절반의
진입점이다. 다음을 한다는 점이다:
- 카탈로그 (또는
-i가 주어진 경우 입력 클래스 파일)을 다시 walk 해서 클래스 list 를 빌드. --mt-process N/M인 경우 클래스 list 를 partition — 클래스i를 프로세스(i mod N) + 1가 처리. partition 은 클래스 list 위에서 일어나므로 각 프로세스가 disjoint 한 표 subset 을 건드린다는 뜻이다. 결합된 출력은 per-process<prefix>_objects파일들의 union 이다 (--datafile-per-class도 켜져 있으면 per-prefix-and-class 파일).- 이 프로세스에 할당된 각 클래스마다
process_class호출.
process_class (라인 1773)이 per-class 루프이다. 다음을 한다:
--cached-pages(기본 100)와--request-pages(기본 100)으로 배치된 클래스의 heap 페이지를db_get_all_objects/ cursor API 로 서버측 fetch 를 발사한다.- 각 fetch 된 객체를
storage_order에 따라 클래스의 attribute list 를 walk 하고 loader 의 텍스트 형식으로_objects파일에 한 record 를 쓴다 —%id|attr1=val1, attr2=val2, ..., OID reference, set / sequence / multiset, JSON, LOB locator, MVCC visibility metadata 에 대한 type-specific encoder. --datafile-per-class면 per-class 출력 stream 이 별도 파일<prefix>_<class>_objects이고, 그 외에는 모든 것이 단일_objects파일에 클래스 헤더로 절을 분리하면서 append 된다는 점이다.
--thread-count > 1 일 때 process_class 는 per-class fetch 를
설정된 worker thread 수에 걸쳐 분할한다 — 각 thread 가 disjoint
OID 범위를 fetch 하고 per-thread 버퍼로 직렬화한 다음, 버퍼들이
출력 파일로 flush 되기 전에 클래스 수준 lock 아래 concat 된다.
이는 네트워크 round-trip 을 병렬화한다 — 원격 데이터베이스 export
의 지배 비용이다.
--latest-image 가 fetch 의 두 visibility 모드 사이에서 토글
한다:
- 기본 (off).
extract_objects시작에 잡힌 스냅샷으로 fetch — 모든 클래스가 시작-시점-commit-된 데이터의 일관된 view 를 본다. --latest-imageon. 다른 클래스가 추출된 시점과 무관하게 각 행의 가장 최근 commit 된 버전을 fetch. 더 빠르다 (스냅샷 추적 없음)이지만, export 실행 동안 commit 된 트랜잭션을 반영 하는 export 를 생산한다 — 일관된 스냅샷을 기대하는 downstream consumer 를 놀라게 한다.
선택적 export (-i input_classfile,
섹션 제목: “선택적 export (-i input_classfile,”-io input-class-only, -ir include-reference)
-i <file> 이 export 를 파일에서 읽힌 클래스 이름 list (한 줄에
하나)로 제한한다. -io (input-class-only)가 더 나아가 오직
그 클래스만으로 제한한다 — 기본 동작은 list 된 클래스에서 참조
된 클래스 (foreign key, set element domain, view base class)도
재귀적으로 closure 까지 끌어온다는 점이다.
-ir (include-reference)가 reference crawl 의 명시적 형식이다 —
-i 를 전혀 안 쓰고도 reference-following 동작을 원할 때 사용
한다 (즉, 다른 곳에서 derived 된 클래스 list 위에서).
required_class_only = true (-io 에서 설정)가
extract_classes_to_file 안의 카탈로그 walk 에서 검사된다 —
입력 list 에 없는 클래스는, list 된 클래스에서 참조되더라도
건너뛴다는 뜻이다.
--as-dba cross-owner extract
섹션 제목: “--as-dba cross-owner extract”기본으로 unloaddb 는 연결 사용자가 소유한 클래스만 추출한다
(또는 사용자가 DBA 면 모든 클래스). --as-dba 가 cross-owner
추출을 요청한다 — non-DBA admin 이 디버깅이나 마이그레이션을
위해 다른 사용자의 스키마를 dump 하고 싶을 때 유용하다.
플래그가 게이트된다:
// unloaddb (의역)if (is_as_dba) { unload_context.is_dba_group_member = au_is_dba_group_member (Au_user); if (!unload_context.is_dba_group_member) { fprintf (stderr, "\n--%s is an option available only when " "the user is a DBA Group.\n", UNLOAD_AS_DBA_L); goto end; }} else { unload_context.is_dba_user = ws_is_same_object (Au_dba_user, Au_user);}DBA 그룹에 없는 사용자가 --as-dba 를 invoke 하면 어떤 추출
전에 거절된다 — 옵션이 조용히 degrade 되는 게 아니라 명시적
운영자 opt-in 이다.
Sampling 과 estimate (--sampling-test,
섹션 제목: “Sampling 과 estimate (--sampling-test,”--enhanced-estimates)
--sampling-test N 이 extract_objects 에게 클래스당 처음 N
record 만 fetch 하라고 지시한다 — full-extraction 비용을 안 내고
export-pipeline 타이밍과 디스크-budget 추정에 사용. 정상
_objects 출력이 생산되며, byte 수만 원하는 downstream 도구는
이걸 쓰고 클래스 cardinality 로 나눠 full-export 크기를 추정할
수 있다는 뜻이다.
--enhanced-estimates (--verbose 와만 의미가 있다)가 클래스당
fine-grained 타이밍 breakdown 을 추가한다 — 카탈로그 read 시간,
per-row 직렬화 시간, write 시간, lock 시간. 약 5% 오버헤드가
들고 --verbose 뒤에 게이트된다 — 추가 행이 깨끗한 export
로그를 더럽힐 수 있기 때문이다.
--split-schema-files
섹션 제목: “--split-schema-files”스키마 절반이 수십 또는 수백 개의 CREATE / GRANT 문을 만들
때, --split-schema-files 는 한 큰 <prefix>_schema 대신
클래스당 한 스키마 파일을 쓴다. per-class unit 위에서 동작하길
원하는 downstream 도구 (diff, PR 리뷰, 부분 reload)에 유용하다는
점이다. 트리거와 인덱스 파일은 split 되지 않는다.
--mt-process N/M 의미
섹션 제목: “--mt-process N/M 의미”--mt-process N/M 는 “이 프로세스가 총 M 중 partition N,
데이터 절반만 한다” 는 뜻이다. 예:
unloaddb --mt-process 1/4 mydb— 4 중 프로세스 1; 스키마 절반 emit (프로세스 1 이므로) 그리고 partition 1 에 할당된 클래스에 대한 데이터 절반.unloaddb --mt-process 2/4 mydb— 4 중 프로세스 2; 스키마 절반 건너뜀 (프로세스 1 만 emit); partition 2 에 할당된 클래스에 대한 데이터 절반.
각 프로세스가 자기 <prefix>_objects (또는 --datafile-per-class
면 per-class)를 prefix 안에 partition 인덱스가 명명된 채로 가진다.
downstream loaddb 가 이들을 concat 하거나 순차적으로 로드한다는
점이다.
per-class 할당은 카탈로그 walk 가 돌려준 클래스 list 순서를
결정적이다 — 클래스 인덱스 i 는 partition (i mod M) + 1
로 간다. 즉 두 unloaddb 실행 사이에 카탈로그에 추가된 클래스
는 두 번째에 다른 partition 에 들어갈 수 있다는 뜻이다 — 운영자
는 실행들 너머로 per-partition 클래스 멤버십에 의존하면 안 된다.
MAX_PROCESS_COUNT = 36 한도는 hard-coded 이고, --thread-count
한도는 MAX_THREAD_COUNT = 127. 두 플래그는 호출 시점에 상호
배타적이다 — --thread-count > 1 와 --mt-process N/M (N > 1)
를 같이 주면 거절된다.
소스 워크스루
섹션 제목: “소스 워크스루”진입과 orchestration (unloaddb.c)
섹션 제목: “진입과 orchestration (unloaddb.c)”| 심볼 | 역할 |
|---|---|
unloaddb | 라이브러리 진입; 옵션 파싱; auth setup; 스키마 절반 그 다음 데이터 절반 orchestration; 끝에서 db_shutdown 호출 |
unload_usage | 메시지 카탈로그에서 usage 출력 |
do_multi_processing (MULTI_PROCESSING_UNLOADDB_WITH_FORK 로 게이트) | fork-mode 멀티프로세스에서 N 자식의 옵션 fork-spawn; 기본 코드 경로는 아님 |
output_prefix, output_dirname 등 (file-scope global) | context 를 받지 않는 도우미에 옵션 값 운반 |
스키마 export (unload_schema.c)
섹션 제목: “스키마 export (unload_schema.c)”| 심볼 | 역할 |
|---|---|
extract_classes_to_file | <prefix>_schema 열기; 카탈로그 walk; emit_schema(EXTRACT_CLASS) 그 다음 emit_schema(EXTRACT_VCLASS) 호출 |
emit_schema | per-class emitter; 컬럼, 제약, owner, grant 와 함께 CREATE CLASS/CREATE VCLASS 렌더 |
extract_triggers_to_file | <prefix>_trigger 열기; per-trigger CREATE TRIGGER emit |
extract_indexes_to_file | <prefix>_indexes 열기; emit_indexes 호출 |
emit_indexes | per-class 인덱스 iteration; hint / filtered 절 보존된 CREATE INDEX emit |
Per-domain renderer (emit_domain, emit_attribute_def, emit_constraint_def, emit_partition_def 등) | emit_schema 가 사용하는 type-specific emitter |
데이터 export (unload_object.c)
섹션 제목: “데이터 export (unload_object.c)”| 심볼 | 역할 |
|---|---|
extract_objects | 데이터 절반 진입; --mt-process 별로 클래스 list partition; process_class 호출하면서 클래스 위에 루프 |
process_class | per-class fetch + 직렬화 루프; --cached-pages, --request-pages, --thread-count, --latest-image 존중 |
| Per-DB_TYPE writer (object/set/sequence/multiset/JSON/OID/LOB) | loader 형식으로 들어가는 type-specific 텍스트 encoder |
출력 파일 plumbing (unload_object_file.{c,h})
섹션 제목: “출력 파일 plumbing (unload_object_file.{c,h})”| 심볼 | 역할 |
|---|---|
| 출력 파일 open / close / flush 도우미 | 데이터 절반 writer 가 사용; per-thread buffering 과 결국 <prefix>_objects (또는 per-class 파일)로의 write 를 추상화 |
파일명 builder (create_filename_*) | <prefix>_schema, <prefix>_trigger, <prefix>_indexes, <prefix>_objects, 그리고 --datafile-per-class 일 때 per-class 변형 |
Carrier 구조체 (extract_schema.{cpp,hpp})
섹션 제목: “Carrier 구조체 (extract_schema.{cpp,hpp})”| 심볼 | 역할 |
|---|---|
extract_context (struct) | 모든 emitter 에 thread 되는 bag; 위에서 기술한 필드 |
extract_context::clear_schema_workspace | ctxt.classes 와 per-half scratch 상태 free |
위치 힌트 (2026-05-05 기준)
섹션 제목: “위치 힌트 (2026-05-05 기준)”| 심볼 | 경로 |
|---|---|
unloaddb (진입) | src/executables/unloaddb.c:115 |
unload_usage | src/executables/unloaddb.c:99 |
| 옵션-unpack 블록 | src/executables/unloaddb.c:144–227 |
| 스키마 절반 driver | src/executables/unloaddb.c:404–455 |
| 데이터 절반 driver | src/executables/unloaddb.c:457–492 |
extract_classes_to_file | src/executables/unload_schema.c:1378 |
extract_triggers_to_file | src/executables/unload_schema.c:1534 |
extract_indexes_to_file | src/executables/unload_schema.c:1606 |
emit_schema | src/executables/unload_schema.c:1708 |
emit_indexes | src/executables/unload_schema.c:1671 |
extract_objects | src/executables/unload_object.c:699 |
process_class | src/executables/unload_object.c:1773 |
process_class 선언 | src/executables/unload_object.c:394 |
extract_context (struct) | src/executables/extract_schema.hpp |
심볼 이름이 정규 anchor 이고, 라인 번호는 updated: 날짜에
스코프된 힌트이다.
Cross-check 노트
섹션 제목: “Cross-check 노트”- Loaddb 의 load 순서가 unloaddb 의 emit 순서가 아니다. unloaddb 는 schema → trigger → index → data 로 emit 하고, loaddb 는 schema → trigger → data → index 로 소비한다는 뜻 이다. 인덱스 생성이 load 시점에 미뤄져 per-row 유지를 회피 하는데, 그래서 인덱스 파일이 unload 에서는 셋째 이지만 load 에서는 넷째 이다.
- Schema-only 와 data-only 가 엄격히 역방향 술어가 아니다.
--schema-only는 스키마 절반을 돌리고 데이터 절반을 건너뛰고,--data-only는 데이터 절반을 돌리고 스키마 절반을 건너뛴다 — 둘 다 안 set 하면 둘 다 돌린다는 점이다. 기본은 둘 다. schema-and-trigger-but-no-index 를 표현하는 것은 직접적으로 사용 불가 — 운영자가 redirect 하거나 사후에 파일을 split 해야 한다. --mt-process가 실제로 사용되는 유일한 멀티프로세스 모드 이다. 한 호출 안에서 fork-spawning 을 했던 더 오래된MULTI_PROCESSING_UNLOADDB_WITH_FORK코드 경로가 소스에 (빌드 define 으로 게이트되어) 존재하지만 기본 빌드에는 없다. 모던 경로는 다른--mt-process N/M값으로 바이너리를 여러 번 호출 하는 것이다 — 각각이 per-partition 출력을 생산하고 외부 스크 립트가 조율한다는 뜻이다.--use-delimiter는 deprecated 이다. 옛 스크립트와의 backward 호환을 위해 여전히 파싱된다 (값이 읽혀서 버려진다). 효과 없음.- Owner / grant emit 은
do_auth = 1에 게이트되어 있다 — 스키마 절반 driver 에 hard-coded. 스키마 export 에서 authorization 을 억제하는 플래그는 없다 — 스키마 파일은 연결 사용자가 볼 수 있는 한 항상OWNER와GRANT절을 포함한다. --latest-image와--mt-process가 비직관적으로 상호작용 한다.--latest-image없이는 각 프로세스가 자기 스냅샷을 pin 하므로, 동일 wall-clock 순간에 시작하도록 스케줄되지 않는 한 partition 들이 데이터베이스의 다른 스냅샷-상태를 볼 수 있다.--latest-image와 함께면 모든 프로세스가 마지막 commit 을 본다 — 멀티프로세스 export 를 일관성은 어느 쪽이든 운영자 의 문제이다.storage_order = FOLLOW_STORAGE_ORDER가 스키마 컬럼 선언 순서 그리고 데이터 파일의 attribute-list 순서 둘 다를 바꾼다 — loader 가 컬럼을 위치적으로 읽으므로 둘이 일치해야 한다는 뜻 이다. 일치하지 않으면 (예: 수동 편집된 스키마 파일에서) loaddb 가 값을 조용히 잘못 할당한다. 그래서 플래그가 실제로는 거의 사용되지 않는다 — on-disk 순서가 중요하고 스키마 파일이 재 생성되는 cross-platform binary-tier 복제용이다.- 데이터 절반 주변의
AU_SAVE_AND_ENABLE가 startup 에 잡힌Au_user의 스냅샷을 사용한다 — half-boundary 에서Au_user가 무엇이든.--as-dba로 돌렸던 스키마 절반이 데이터 절반을 re-elevate 하지 않는다 — 두 절반이 같은Au_user로 돌고, 데이터 절반의 authorisation 만 강제로 활성화된다.
열린 질문
섹션 제목: “열린 질문”--mt-process+--datafile-per-class의 per-class 데이터- 파일 명명. partition-index-prefixed 명명과 per-class-suffixed 명명 사이의 충돌 규칙이 문서화되지 않았다 — 실제로는 출력이<prefix>_<partidx>_<class>_objects이지만 파일명 builder 가AGENTS.md에서 호출되지 않았다.--thread-count와의 per-class fetch 병렬 이 서버측 스레드를 얼마나 사용하는가? 클라이언트가 N 개의 병렬 cursor 를 연다. 각 cursor 가 서버 자원을 소비한다 (fetch 당 thread-pool worker).cubrid.confthread_count가--thread-count보다 낮은 서버에서 export 가 서버 capacity 에서 병목된다는 뜻이다. 상호작용을 클라이언트가 경고하지 않는다.- 재개 가능성. 실패한 export 는 부분
_objects파일을 남긴다 — resume 모드는 없다. 운영자는 처음부터 재실행한다. 일부 엔진 (Oracle Data Pump)은RESUMEjob 을 지원한다. CUBRID 는 안 한다. - 압축. 빌트인 압축 없음 — 운영자가 출력을 사후에
gzip으로 pipe 한다. mongodump 와 pg_dump 의 custom format 둘 다 inline 압축을 지원한다. _objects형식의 cross-version 호환성. loaddb 형식은 CUBRID 버전 너머로 안정적이라고 문서화되어 있지만, 정확한 호환성 매트릭스 (특히 JSON Tables, TDE-encrypted 클래스 같은 새 데이터 타입 주변)는 기록되지 않았다.
Sources
섹션 제목: “Sources”src/executables/unloaddb.c— 진입, 옵션 파싱, 두 절반 orchestration, 멀티프로세스 partition 로직src/executables/unloaddb.h— 옵션 문자열 상수, 공유 타입src/executables/unload_schema.c— 스키마 / 트리거 / 인덱스 emitter; per-domain 과 per-constraint renderersrc/executables/unload_object.c— 데이터 추출 driver 와 per-class 루프src/executables/unload_object_file.{c,h}— 출력 파일 plumbing, per-thread buffering, 파일명 buildersrc/executables/extract_schema.{cpp,hpp}—extract_context구조체 정의와 도우미src/executables/AGENTS.md— agent 가이드; binary→source 매핑- 인접 문서:
cubrid-loaddb.md(역방향 — loaddb 가 unloaddb 가 생산하는 모든 파일의 consumer 이고 그 phase 순서가 unload 의 emit 순서를 결정한다),cubrid-cub-admin.md(unloaddb 심볼을 로드하는 통합cubridadmin 진입;--SA-mode/--CS-mode에 따른 SA_CS 라우팅),cubrid-csql.md(unloaddb 가 생산하는 같은 loader 형식으로 데이터를 emit 하게 해주는--loaddb-output모드 — unloaddb 호출 없이 ad-hoc 부분 export 에 유용),cubrid-class-object.md(스키마 추출이 의존하는 카탈로그 walk),cubrid-locator.md(데이터 추출이 의존하는 OID materialisation),cubrid-mvcc.md(--latest-image의 기반인 스냅샷 의미)