콘텐츠로 이동

(KO) CUBRID unloaddb — 스키마와 데이터 export, 4-파일 출력 레이아웃, per-class 멀티스레드 · 멀티프로세스 driver

Logical exporter 는 카탈로그를 walk 하고 스키마와 데이터를 텍스트 또는 거의-텍스트 표현으로 써내는 데이터베이스 유틸리티 이다 — 엔진의 loader 가 다른 데이터베이스(같은 엔진, 가능하면 다른 버전, 또는 cross-version 마이그레이션 / DR 훈련 / 테스트 fixture 빌드를 위한 새 빈 데이터베이스)로 다시 import 하기에 적합한 형태로. 모든 관계형 엔진이 하나씩 가지고 있다는 점이다 — pg_dump, mysqldump, expdp, mongodump — 그리고 모든 구현 이 같은 디자인 선택을 한다.

세 가지 축이 지배한다:

  1. Schema-first vs data-first 순서. 거의 모든 loader 가 데이터 가 insert 되기 전에 스키마가 존재해야 하므로, exporter 는 스키마 를 먼저, 데이터를 나중에 emit 한다는 뜻이다. 흥미로운 질문은 무엇이 스키마로 치는가이다 — 표 정의는 명백하고, 뷰는 표에 의존하므로 다음에, 트리거는 표를 참조하고 (그리고 서로 참조할 수 있고), 인덱스는 이미 로드된 데이터에서 빌드 가능하므로 미룰 수 있다. 순서 선택은 — 그리고 어떤 artefact 가 post- load pass 로 미뤄지는지가 — exporter 와 loader 사이의 계약이다.

  2. 단일 파일 vs 다중 파일. 단일 export 파일은 ship 하고 replay 하기 (cat dump.sql | psql) 단순하다. 다중 파일 (단계당 하나 또는 클래스당 하나)은 export 와 import 모두를 병렬화해주고, 운영자가 부분을 선택적으로 reload 할 수 있게 한다. 대가는 운영적이다 — 다중 파일은 exporter 프로세스가 출력 stream 을 조율해야 하고, importer 가 파일명 규약을 알아야 한다는 뜻이다.

  3. 스냅샷 의미. 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 스냅샷 토글.

엔진단계 순서파일 레이아웃동시성스냅샷
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 mysqldumpDDL 이 각 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 먼저 둘 다 emitdirectory object 안의 한 개 또는 다중 .dmp dumpfile; format 은 binaryPARALLEL=N worker 프로세스FLASHBACK_TIME / FLASHBACK_SCN
MongoDB mongodump컬렉션당 metadata .metadata.json + BSON .bson데이터베이스당 한 디렉토리, 컬렉션당 한 파일 쌍암묵적 per-collection 병렬기본 없음; collection-scan consistency 에 --snapshot
CUBRID unloaddbschema → 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 텍스트 직렬화를.

기본 unloaddb mydb 는 출력 디렉토리에 4 개의 파일을 생성한다는 점이다:

파일내용loaddb 가 로드
<prefix>_schema표에는 CREATE CLASS ..., 뷰에는 CREATE VCLASS ..., topologically 올바른 순서로; 컬럼 정의, primary key, do_auth = 1 일 때 owner / auth grantloaddb phase 1 (스키마)
<prefix>_trigger모든 트리거에 대한 CREATE TRIGGER ... 문; 트리거가 클래스를 참조하므로 스키마 후에 emitloaddb phase 2 (트리거)
<prefix>_indexes모든 secondary 인덱스에 대한 CREATE INDEX ... 문; 마지막에 emit 되어 인덱스를 fully populated heap 위에서 post-load 로 빌드할 수 있게 한다 (데이터 insert 동안의 per-row 인덱스 유지 비용 회피)loaddb phase 4 (데이터 후)
<prefix>_objectsCUBRID 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.cunloaddb 함수는 통합 cubrid admin CLI (cubrid-cub-admin.md §데이터베이스-admin arm 참조)가 로드하는 SA/CS 이중 진입점이다. UTIL_FUNCTION_ARG * 를 받아 ~25 개의 옵션을 다섯 패밀리로 풀어낸다는 점이다:

// unloaddb — unloaddb.c
input_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.

옵션 파싱과 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/M partition 의 프로세스 1 에서만 돌아간다. 프로세스 카운트와 무관하게 _schema / _trigger / _indexes 파일은 하나뿐이다 — 모든 프로세스에서 emit 하면 파일 시스템을 race 하게 된다는 뜻이다. 프로세스 1 이 emit 하고, 프로세스 2..N 은 건너뛰고 데이터 절반으로 직진한다.
  • 데이터 절반 주변의 auth 토글. AU_SAVE_AND_ENABLE / AU_RESTORE 가 데이터 추출을 bracket 해, caller 가 auth 가 비활성화된 채 (예: 내부 스크립트에서) 도착했더라도, 실제 행 단위 read 가 연결 사용자의 authorisation 을 강제한다. 스키마 절반은 이 처리가 필요 없다 — 그 emitter 가 명시적으로 extract_contextis_dba_user / is_dba_group_member 를 검사하기 때문이다.

extract_context (extract_schema.hpp 에 선언)가 모든 emitter 함수에 thread 되는 bag 이다:

필드목적
output_dirname파일이 가는 곳
output_prefix데이터베이스별 파일명 prefix
exec_nameunloaddb — 에러 메시지용
login_user연결 사용자
is_dba_user / is_dba_group_memberstartup 에 해석된 authorisation 플래그
do_authGRANT 문을 emit 할지 여부 (1 = yes)
storage_orderFOLLOW_STORAGE_ORDER (디스크상의 컬럼 순서) vs FOLLOW_ATTRIBUTE_ORDER (카탈로그의 선언 순서); CREATE CLASS 와 데이터 파일의 컬럼 순서에 영향
classesexport 할 클래스 객체의 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 nameGRANT ... TO .... storage_order 설정이 두 컬럼-iteration 도우미 사이에서 고른다 — attribute-order 가 카탈로그 선언 순서 (사용자의 멘탈 모델과 일치), storage-order 가 on-disk slot 레이아웃과 일치 (cross-platform binary-tier 복제에 중요하다).

extract_triggers_to_fileextract_indexes_to_file 는 같은 패턴을 따른다 — 출력을 열고, 카탈로그를 walk 하고, 객체별 CREATE TRIGGER / CREATE INDEX 를 emit. 트리거는 조건 표현식을 verbatim 으로 emit 되고, 인덱스는 INDEX hint 와 filtered-index WHERE 절을 보존한다는 뜻이다.

데이터 추출 (extract_objectsprocess_class)

섹션 제목: “데이터 추출 (extract_objects → process_class)”

extract_objects (unload_object.c:699 에 정의)가 데이터 절반의 진입점이다. 다음을 한다는 점이다:

  1. 카탈로그 (또는 -i 가 주어진 경우 입력 클래스 파일)을 다시 walk 해서 클래스 list 를 빌드.
  2. --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 파일).
  3. 이 프로세스에 할당된 각 클래스마다 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-image on. 다른 클래스가 추출된 시점과 무관하게 각 행의 가장 최근 commit 된 버전을 fetch. 더 빠르다 (스냅샷 추적 없음)이지만, export 실행 동안 commit 된 트랜잭션을 반영 하는 export 를 생산한다 — 일관된 스냅샷을 기대하는 downstream consumer 를 놀라게 한다.

-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 된 클래스에서 참조되더라도 건너뛴다는 뜻이다.

기본으로 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 이다.

--enhanced-estimates)

--sampling-test Nextract_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 로그를 더럽힐 수 있기 때문이다.

스키마 절반이 수십 또는 수백 개의 CREATE / GRANT 문을 만들 때, --split-schema-files 는 한 큰 <prefix>_schema 대신 클래스당 한 스키마 파일을 쓴다. per-class unit 위에서 동작하길 원하는 downstream 도구 (diff, PR 리뷰, 부분 reload)에 유용하다는 점이다. 트리거와 인덱스 파일은 split 되지 않는다.

--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) 를 같이 주면 거절된다.

심볼역할
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 를 받지 않는 도우미에 옵션 값 운반
심볼역할
extract_classes_to_file<prefix>_schema 열기; 카탈로그 walk; emit_schema(EXTRACT_CLASS) 그 다음 emit_schema(EXTRACT_VCLASS) 호출
emit_schemaper-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_indexesper-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
심볼역할
extract_objects데이터 절반 진입; --mt-process 별로 클래스 list partition; process_class 호출하면서 클래스 위에 루프
process_classper-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_workspacectxt.classes 와 per-half scratch 상태 free
심볼경로
unloaddb (진입)src/executables/unloaddb.c:115
unload_usagesrc/executables/unloaddb.c:99
옵션-unpack 블록src/executables/unloaddb.c:144–227
스키마 절반 driversrc/executables/unloaddb.c:404–455
데이터 절반 driversrc/executables/unloaddb.c:457–492
extract_classes_to_filesrc/executables/unload_schema.c:1378
extract_triggers_to_filesrc/executables/unload_schema.c:1534
extract_indexes_to_filesrc/executables/unload_schema.c:1606
emit_schemasrc/executables/unload_schema.c:1708
emit_indexessrc/executables/unload_schema.c:1671
extract_objectssrc/executables/unload_object.c:699
process_classsrc/executables/unload_object.c:1773
process_class 선언src/executables/unload_object.c:394
extract_context (struct)src/executables/extract_schema.hpp

심볼 이름이 정규 anchor 이고, 라인 번호는 updated: 날짜에 스코프된 힌트이다.

  • 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 을 억제하는 플래그는 없다 — 스키마 파일은 연결 사용자가 볼 수 있는 한 항상 OWNERGRANT 절을 포함한다.
  • --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.conf thread_count--thread-count 보다 낮은 서버에서 export 가 서버 capacity 에서 병목된다는 뜻이다. 상호작용을 클라이언트가 경고하지 않는다.
  • 재개 가능성. 실패한 export 는 부분 _objects 파일을 남긴다 — resume 모드는 없다. 운영자는 처음부터 재실행한다. 일부 엔진 (Oracle Data Pump)은 RESUME job 을 지원한다. CUBRID 는 안 한다.
  • 압축. 빌트인 압축 없음 — 운영자가 출력을 사후에 gzip 으로 pipe 한다. mongodump 와 pg_dump 의 custom format 둘 다 inline 압축을 지원한다.
  • _objects 형식의 cross-version 호환성. loaddb 형식은 CUBRID 버전 너머로 안정적이라고 문서화되어 있지만, 정확한 호환성 매트릭스 (특히 JSON Tables, TDE-encrypted 클래스 같은 새 데이터 타입 주변)는 기록되지 않았다.
  • src/executables/unloaddb.c — 진입, 옵션 파싱, 두 절반 orchestration, 멀티프로세스 partition 로직
  • src/executables/unloaddb.h — 옵션 문자열 상수, 공유 타입
  • src/executables/unload_schema.c — 스키마 / 트리거 / 인덱스 emitter; per-domain 과 per-constraint renderer
  • src/executables/unload_object.c — 데이터 추출 driver 와 per-class 루프
  • src/executables/unload_object_file.{c,h} — 출력 파일 plumbing, per-thread buffering, 파일명 builder
  • src/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 심볼을 로드하는 통합 cubrid admin 진입; --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 의 기반인 스냅샷 의미)