(KO) CUBRID 부팅 — 서버 기동, 최초 생성, 재시작-복구 디스패치, 클라이언트 접속
목차
학술적 배경
섹션 제목: “학술적 배경”프로세스가 시작되는 시점의 관계형 DBMS 는 서브시스템 의존성에 대한 엄격한 위상 정렬이다. 각 서브시스템이 그 직전 서브시스템을 요구하는 방식은, 운영체제 커널이 부팅 도중 자기보다 먼저 깨워야 할 모듈을 요구하는 방식과 정확히 같다. 그리고 그 순서를 어긋 나게 만들었을 때의 치명성도 같다. 증상은 폭포처럼 번지고, 정작 근본 원인은 부팅 코드를 이미 읽은 사람이 아니면 보이지 않는다.
디스크 상주 엔진이라면 거의 모두 재현하는 의존성 그래프는 다음 과 같다.
- 에러 보고와 메시지 카탈로그. 이 둘이 살아 있기 전까지는 다른 어떤 모듈도 실패를 사람이 읽을 수 있는 형태로 보고할 수 없다. 의존성은 없다.
- 시스템 파라미터 로더. 설정 파일을 읽는다. 에러 보고기가 먼저 살아 있어야 한다. 그렇지 않으면 파싱 실패가 침묵으로 사라진다는 점이다.
- 메모리/area 할당기. 풀 크기가 설정 가능한 값이므로 파라 미터 로더에 묶인다.
- 로케일, 타임존, 캐릭터셋. 거의 무동작 모듈이다. 다만 실패 보고를 위해 메시지 카탈로그가 필요하다.
- 스레드/워커 풀 매니저. 이 지점부터는 진단 출력이나 시스템
트랜잭션을 위해 어떤 모듈이든
THREAD_ENTRY가 필요할 수 있다. - 파일 I/O 계층. 디스크립터 단위로 볼륨을 마운트한다. OS 계층과 에러 보고 외에는 의존하지 않는다.
- 페이지 버퍼 풀. 디스크 페이지를 캐시한다. 파일 I/O 와 파라미터 (캐시 크기, 교체 정책) 에 의존한다.
- 로그 매니저. WAL 을 읽고 쓴다. 데이터 측은 페이지 버퍼, 로그 자체는 직접 파일 I/O 가 필요하다.
- 복구 매니저. 로그를 따라 걸으며 데이터베이스를 충돌-일관 상태로 되돌린다. 로그 매니저, 페이지 버퍼, 디스크 매니저가 모두 필요하다.
- 카탈로그 (시스템 테이블). heap 파일, B+Tree, 파일 트래커에 의존한다. 그런데 이 셋의 페이지가 복구로 복원되기 전까지는 어떤 질의에도 답할 수 없다.
- 락과 MVCC. 락 매니저는 자원 식별을 위해 카탈로그 OID 가 필요하다. MVCC 는 복구 매니저가 다시 만들어 둔 active 트 랜잭션 테이블이 필요하다.
- 질의 옵티마이저/플랜 캐시. 카탈로그가 있어야 한다.
- 네트워크 리스너. 가장 마지막에 깨어난다. 카탈로그가 로딩 되기 전에 클라이언트를 받아 들이면, 첫 SQL 요청이 빈 스키마 를 본다는 뜻이기 때문이다.
이 그래프는 디스크 상태에 따라 두 갈래의 실행 흐름으로 갈라
진다. 부트스트랩 흐름은 데이터베이스 생애에 단 한 번 — createdb
시점 — 만 돌고, 카탈로그 테이블, root-class 객체, 데이터베이스
헤더를 만들어 내는 유일한 경로다. 재시작 흐름은 그 외 모든
서버 기동 때마다 돈다. 포맷-부트스트랩 단계를 건너뛰고, 그 자리
에서 충돌 치유를 위해 복구로 제어를 넘긴다.
부팅 서브시스템은 두 흐름의 계약 보유자다. 이 모듈이 짊어지는 책임은 다음 네 가지다.
- 의존성 그래프를 어떤 순서로 따라 갈지.
- 부트스트랩 / 재시작 중 어느 흐름을 실행할지의 결정.
- 새 데이터베이스와 기존 데이터베이스를 구분하는 영구 헤더의 소유.
- 서브시스템 실패를 깔끔한 프로세스 종료로 변환하는 일 (대칭 적 tear-down).
오케스트레이터가 단일 모듈이어야 하는 이유는, 의존성 그래프가 가까이서 보면 순환 이 있기 때문이다. 복구 매니저는 페이지 버퍼가 필요하고, 페이지 버퍼는 디스크 매니저가 필요하며, 디스크 매니저는 테이블스페이스 메타데이터를 찾기 위해 카탈로그 를 원하고, 카탈로그는 복구가 끝나야 한다. 부팅 모듈은 이 순환 을 단계화 (staging) 로 끊는다. 한 서브시스템이 두 페이즈로 깨어난다. 이른 페이즈에서는 복구에 참여하기에 딱 필요한 만큼의 상태만 가지고, 늦은 페이즈에서야 카탈로그 데이터를 소비한다. 이 단계화 없이는 그래프가 만족 불가능하다는 점이다.
기동 오케스트레이션을 교과서가 다루는 자리는 Database Internals (Petrov) 다. 이 책은 WAL/복구 측은 명시적으로 다루 지만, 더 넓은 서브시스템 정렬은 함의로만 짚는다. OS 문헌이 더 직설적이다. Linux Kernel Development (Love) 의 §“The Boot Process”, 그리고 Bovet & Cesati 의 Understanding the Linux Kernel 의 부팅 장. 두 책 모두 DBMS 그래프와 거의 동형 (early text → 콘솔 → 메모리 → VFS → init) 인 그래프를 그린다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”디스크 상주 관계형 엔진은 부팅을 똑같은 몇 가지 관습을 공유한다. 이 관습이 문헌에 규범으로 적혀 있는 것은 아니다. 위 의존성 그래프에서 자연스럽게 흘러나온 결과다.
두 바이너리로 나뉘는 부트스트랩
섹션 제목: “두 바이너리로 나뉘는 부트스트랩”PostgreSQL 은 두 개의 바이너리를 배포한다. 평소 서버인
postgres 와, initdb 가 호출하는 부트스트랩 모드다. 부트스
트랩 바이너리는 BootStrapXLOG, BootstrapModeMain,
BuildBootstrapping 으로 이어지는 별도 코드 경로를 탄다.
pg_xlog 를 포맷하고, 초기 control file 을 쓰고, 시스템 카탈로
그 (pg_class, pg_attribute) 를 부트스트랩한 뒤에야 정상 운영
이 받아 받을 수 있다는 뜻이다. InnoDB 의 srv_start 도 같은
분기 구조다. srv_force_recovery 와 시스템 테이블스페이스의
존재 여부에 따라 갈라진다. Oracle 은 사용자 인터페이스 단계로
이 분리를 노출한다. STARTUP NOMOUNT (공유 메모리와 백그라
운드 프로세스만), STARTUP MOUNT (control file 만 열림),
STARTUP OPEN (데이터 파일 열림, 복구 디스패치).
CUBRID 은 별도의 부트스트랩 바이너리를 두지 않는다. 두 흐름이
모두 boot_sr.c 안에 있고, 실행 파일이 어떤 진입점을 호출하는
가에 따라 갈라진다. 생성은 xboot_initialize_server, 재시작
은 boot_restart_server. 의도된 설계 결정이다. 비용은
boot_sr.c 가 6,178 라인이라는 점이고, 편익은 서브시스템 초기
화 순서가 단 한 벌만 유지된다는 점이다.
별도 진입점을 통한 복구 디스패치
섹션 제목: “별도 진입점을 통한 복구 디스패치”PostgreSQL 의 StartupXLOG 가 복구 드라이버다. PostmasterMain
이 control file 에서 직전 종료가 깨끗하지 않다고 읽었을 때만
호출된다. InnoDB 의 recv_recovery_from_checkpoint_start 도
같은 자리다. CUBRID 의 log_initialize 가 hdr.is_shutdown == false 면 log_recovery 를 호출하는 구조도 같은 자리에 들어
간다.
부팅 모듈의 책임은 복구 드라이버를 기동 시퀀스의 정확한 시점
에서 호출 하는 것이다. 페이지 버퍼와 디스크 매니저가 살아
있을 정도로는 충분히 늦게, 카탈로그가 열리기 전 정도로는 충분
히 일찍. CUBRID 은 boot_restart_server 를 긴 서브시스템 초기
화 시퀀스로 펼쳐 두고, 그 시퀀스의 페이지-버퍼 모듈과 카탈로그
모듈 사이에 정확히 log_initialize 를 끼워 이 조건을 만족시킨
다. 정확한 위치는 소스 코드 가이드 절을 참조.
등록 시점에 이루어지는 서버 측 자격 정보 교환
섹션 제목: “등록 시점에 이루어지는 서버 측 자격 정보 교환”클라이언트가 처음 접속할 때, 서버는 이미 한참 전부터 살아 있다.
카탈로그가 로드되어 있고, HA 상태도 결정된 상태다. 클라이언트
측은 이 중 어떤 것도 모른다. 그래서 register-client RPC 가
패킹된 서버 자격 (server credential) 을 돌려 준다. 페이지
크기, 로그 페이지 크기, root-class OID, 디스크 호환 번호, HA
상태, 캐릭터셋, 언어, 세션 키. 클라이언트는 이 값들로 자기 메
모리 자료 구조를 서버 모양에 맞춘다. CUBRID 의
BOOT_SERVER_CREDENTIAL (boot.h) 가 그 명시적 구조체다.
PostgreSQL 은 같은 정보를 startup-packet 흐름의
ParameterStatus 메시지에 암시적으로 인코딩한다.
대칭적 종료
섹션 제목: “대칭적 종료”모든 엔진이 부팅 순서를 거꾸로 따라 가는 종료 절차를 갖는다. 새 클라이언트 접수 중단, 백그라운드 워커 정지, 더티 페이지
flush, 종료 체크포인트 작성, 헤더에 clean-shutdown 플래그 세
팅, 하위 서브시스템 마무리. 계약은 단순하다. 다음 재시작이 헤
더의 그 플래그를 보면 복구를 건너뛸 수 있다는 것. CUBRID 의
xboot_shutdown_server 가 이를 구현한다. 클라이언트 측 대칭
짝은 boot_shutdown_client 가 같은 방식의 tear-down 을 따라
간다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 | CUBRID 명칭 |
|---|---|
| 프로세스 진입점 | main() in src/executables/server.c (cub_server) |
| 최초 생성 진입 | xboot_initialize_server (boot_sr.c) |
| 재시작 진입 | boot_restart_server (boot_sr.c) |
| 재시작 디스패처 | net_server_start (network_sr.c) — boot_restart_server 호출 후 css_init |
| 복구 hand-off | log_initialize (log_manager.c) → log_recovery (log_recovery.c) |
| 첫 볼륨 포맷터 | boot_create_all_volumes (boot_sr.c) → disk_format_first_volume |
| 데이터베이스 파라미터 영속화 | BOOT_DB_PARM 구조체 + boot_Db_parm 글로벌 (boot_sr.c) |
| 부팅 상태 플래그 | BOOT_SERVER_STATUS { UP, DOWN, MAINTENANCE } + boot_Server_status (boot_sr.h) |
| 서버 자격 | BOOT_SERVER_CREDENTIAL 구조체 (boot.h) |
| 클라이언트 자격 | BOOT_CLIENT_CREDENTIAL extends clientids (client_credentials.hpp) |
| 서버 측 register-client | xboot_register_client (boot_sr.c) |
| 클라이언트 측 register-client RPC | boot_register_client (network_interface_cl.c) |
| 클라이언트 측 재시작 | boot_restart_client (boot_cl.c) |
| 클라이언트 측 최초 생성 | boot_initialize_client (boot_cl.c) |
| 클라이언트 호스트 접속 | boot_client_initialize_css (boot_cl.c) |
| 카탈로그 부트스트랩 (테이블 생성) | catcls_init + catcls_install (schema_system_catalog_install.cpp) |
| 재시작 시 카탈로그 재수화 | catcls_compile_catalog_classes + catcls_find_and_set_cached_class_oid (catalog_class.c) |
| 부팅 시 HA 상태 | css_change_ha_server_state (server_support.c) — boot_restart_server 끝 부근 |
| 마스터 프로세스 | cub_master — main() in src/executables/master.c |
| 마스터 heartbeat 초기화 | hb_master_init — 마스터 main() 안에서 호출 |
| 서버 종료 | xboot_shutdown_server (boot_sr.c) |
| 서버 finalize | boot_server_all_finalize (boot_sr.c) |
| 클라이언트 종료 | boot_shutdown_client (boot_cl.c) |
| 클라이언트 finalize | boot_client_all_finalize (boot_cl.c) |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”CUBRID 부팅 서브시스템에는 네 개의 이동 부품이 있다. 실행 파일 진입점들 — 프로세스 호출을 어느 흐름으로 보낼지 결정한다. 서버 측 first-create 드라이버 — 볼륨을 포맷하고 카탈로그를 부트스트랩한다. 서버 측 재시작 드라이버 — 기존 볼륨을 마운트 하고 복구로 디스패치한다. 그리고 클라이언트 측 흐름 — 접속 하고, 자격을 교환하고, 스키마 캐시를 적재한다. 이 순서로 본다.
전체 구조
섹션 제목: “전체 구조”flowchart TB
subgraph EXES["실행 파일"]
SRV["cub_server (server.c::main)\n→ net_server_start"]
MST["cub_master (master.c::main)\n→ css_master_init + hb_master_init"]
CLI["client (csql / broker / app)\n→ db_restart → boot_restart_client"]
CDB["createdb 유틸리티\n→ boot_initialize_client"]
end
subgraph SR["서버 부팅 (boot_sr.c)"]
XINIT["xboot_initialize_server\n(최초 생성)"]
XRST["boot_restart_server\n(정상 재시작)"]
XSHD["xboot_shutdown_server"]
XREG["xboot_register_client"]
XUNREG["xboot_unregister_client"]
end
subgraph CL["클라이언트 부팅 (boot_cl.c)"]
CINIT["boot_initialize_client\n(최초 생성)"]
CRST["boot_restart_client\n(정상)"]
CSHD["boot_shutdown_client"]
CCSS["boot_client_initialize_css\n(호스트 접속)"]
end
subgraph LR["복구 (log_recovery.c)"]
LRC["log_recovery\nanalysis → redo → undo"]
end
subgraph CAT["카탈로그 부트스트랩"]
CIN["catcls_init"]
CIS["catcls_install"]
CCC["catcls_compile_catalog_classes"]
end
SRV --> XRST
CDB --> CINIT --> CCSS
CINIT --> XINIT
XINIT --> CIN --> CIS
CLI --> CRST --> CCSS --> XREG
XRST -->|"via log_initialize"| LRC
XRST --> CCC
XSHD --> XUNREG
이 그림에는 본 문서 전체에서 반복되는 세 가지 시퀀싱 결정이
들어 있다. 서버 진입점은 둘이지만, 한 프로세스는 그 중 하나
만 부른다. 둘 다 부르는 일도, 같은 진입점을 두 번 부르는 일도
없다. first-create 경로는 카탈로그를 서버 측에서 부트스트
랩하지만, 카탈로그의 내용물 은 클라이언트 측이 채워 넣는다
(catcls_install 은 boot_initialize_server 가 돌아온 뒤에야
boot_initialize_client 안에서 돈다). 시스템 테이블이 일반
클라이언트 측 스키마 API로 채워지기 때문에 갈라 둔 것이라
는 점이다. 클라이언트 측 boot_restart_client 는 네트워크
RPC로 xboot_register_client 를 구동한다 — register-client
응답에 클라이언트가 페이지 크기, OID, HA 상태를 맞추는 데 쓸
서버 자격이 패킹되어 돌아온다.
서버 측 최초 생성
섹션 제목: “서버 측 최초 생성”이 흐름은 데이터베이스마다 단 한 번, createdb (클라이언트 유
틸리티) 가 boot_initialize_client 를 부르고 그것이 다시 네트
워크로 boot_initialize_server 를 부를 때 돈다. 서버 측
핸들러가 xboot_initialize_server (boot_sr.c) 다.
// xboot_initialize_server — src/transaction/boot_sr.c (condensed)intxboot_initialize_server (const BOOT_CLIENT_CREDENTIAL *client_credential, BOOT_DB_PATH_INFO *db_path_info, bool db_overwrite, const char *file_addmore_vols, volatile DKNPAGES db_npages, PGLENGTH db_desired_pagesize, ...){ /* 1. early subsystem init (server-mode only) */ lang_init (); tz_load (); msgcat_init (); sysprm_load_and_init (NULL, NULL, SYSPRM_LOAD_ALL); area_init (); set_area_init (); pr_area_init (); tp_init (); tsc_init ();
/* 2. canonicalise paths */ realpath (db_path_info->db_path, fixed_pathbuf); realpath (db_path_info->log_path, fixed_pathbuf); /* compose boot_Db_full_name = "<db_path>/<db_name>" */
/* 3. shut down any prior server in this process */ if (BO_IS_SERVER_RESTARTED ()) { (void) xboot_shutdown_server (thread_p, ER_ALL_FINAL); }
/* 4. thread manager */ cubthread::initialize (thread_p); cubthread::initialize_thread_entries ();
/* 5. update databases.txt directory file */ cfg_read_directory_ex (...); if (existing && !db_overwrite) goto exit_on_error; if (existing && db_overwrite) boot_remove_all_volumes (...);
/* 6. install SIGINT handler so Ctrl-C aborts the create */ os_set_signal_handler (SIGINT, boot_ctrl_c_in_init_server);
/* 7. THE FORMAT: create volumes, log, root class, catalog */ tran_index = boot_create_all_volumes (thread_p, client_credential, ...);
/* 8. publish DB to databases.txt */ cfg_add_db (&dir, ..., db_path, log_path, lob_path, host); cfg_write_directory_ex (...);
/* 9. fill out the root-class OID/HFID for the caller */ *rootclass_oid = boot_Db_parm->rootclass_oid; boot_find_root_heap (rootclass_hfid);
/* 10. session state, version banner */ session_states_init (thread_p); fprintf (stdout, "<format banner>"); return tran_index;}전체에서 가장 무거운 단계가 7단계다. boot_create_all_volumes
(boot_sr.c) 가 정확히 다음 순서로 진행된다.
// boot_create_all_volumes — src/transaction/boot_sr.c (condensed)static intboot_create_all_volumes (THREAD_ENTRY *thread_p, const BOOT_CLIENT_CREDENTIAL *client_credential, ...){ spage_boot (thread_p); heap_manager_initialize ();
/* (a) create active log + initialise log/recovery managers */ log_create (thread_p, boot_Db_full_name, log_path, log_prefix, log_npages); log_initialize (thread_p, boot_Db_full_name, log_path, log_prefix, false, NULL);
/* (b) assign a transaction index for the create work */ tran_index = logtb_assign_tran_index (thread_p, NULL_TRANID, TRAN_ACTIVE, client_credential, NULL, client_lock_wait, client_isolation);
/* (c) double-write buffer must exist before first volume format */ dwb_create (thread_p, log_path, log_prefix);
/* (d) format the first data volume */ disk_format_first_volume (thread_p, boot_Db_full_name, db_comments, db_npages); logpb_add_volume (NULL, LOG_DBFIRST_VOLID, boot_Db_full_name, DB_PERMANENT_DATA_PURPOSE);
/* (e) initialise the persistent boot_Db_parm structure */ boot_Db_parm->trk_vfid.volid = LOG_DBFIRST_VOLID; boot_Db_parm->hfid.vfid.volid = LOG_DBFIRST_VOLID; /* ... fields for tracker, root class, classname table, catalog, ... */ strncpy (boot_Db_parm->rootclass_name, ROOTCLASS_NAME, ...); boot_Db_parm->nvols = 1; boot_Db_parm->last_volid = LOG_DBFIRST_VOLID;
/* (f) create the persistent files: file tracker, system heap, * root-class heap, TDE-key heap, classname-hash, catalog file */ file_tracker_create (thread_p, &boot_Db_parm->trk_vfid); xheap_create (thread_p, &boot_Db_parm->hfid, NULL, false); xheap_create (thread_p, &boot_Db_parm->rootclass_hfid, NULL, false); xheap_create (thread_p, &boot_Db_parm->tde_keyinfo_hfid, NULL, false); heap_assign_address (thread_p, &boot_Db_parm->rootclass_hfid, NULL, &boot_Db_parm->rootclass_oid, 0); oid_set_root (&boot_Db_parm->rootclass_oid); heap_cache_class_info (thread_p, &boot_Db_parm->rootclass_oid, &boot_Db_parm->rootclass_hfid, FILE_HEAP, boot_Db_parm->rootclass_name); xehash_create (thread_p, &boot_Db_parm->classname_table, ...); catalog_create (thread_p, &boot_Db_parm->ctid);
/* (g) write boot_Db_parm into the system heap as a real heap record */ recdes.data = (char *) boot_Db_parm; recdes.length = DB_SIZEOF (*boot_Db_parm); heap_create_insert_context (&heapop_context, &boot_Db_parm->hfid, &boot_Db_parm->rootclass_oid, &recdes, NULL); heap_insert_logical (thread_p, &heapop_context, NULL); COPY_OID (boot_Db_parm_oid, &heapop_context.res_oid);
/* (h) vacuum data file, dropped-files vacuum tracker */ vacuum_create_file_for_vacuum_data (thread_p, &boot_Db_parm->vacuum_data_vfid); vacuum_create_file_for_dropped_files (thread_p, &boot_Db_parm->dropped_files_vfid); boot_db_parm_update_heap (thread_p);
/* (i) optional add-more-volumes from a control file */ if (file_addmore_vols != NULL) boot_parse_add_volume_extensions (thread_p, file_addmore_vols);
locator_initialize (thread_p); pgbuf_flush_all (thread_p, NULL_VOLID);
/* (j) catalog and query manager init */ oid_set_root (&boot_Db_parm->rootclass_oid); catalog_initialize (&boot_Db_parm->ctid); qmgr_initialize (thread_p); tf_install_meta_classes (); tde_initialize (thread_p, &boot_Db_parm->tde_keyinfo_hfid);
/* (k) flush + checkpoint so the create is durable */ logpb_force_flush_pages (thread_p); pgbuf_flush_all (thread_p, NULL_VOLID); fileio_synchronize_all (thread_p); logpb_checkpoint (thread_p);
boot_server_status (BOOT_SERVER_UP); return tran_index;}(a) 부터 (k) 까지 열한 개의 알파벳 단계가 곧 계약이다. (a) 는
데이터베이스가 생성된 그 순간부터 복구 가능하게 만드는 단계다.
이후의 모든 단계는 모두 로깅된다. (e) 는 단 하나의 OID 를
만들어 두는 단계다. 이후의 모든 재시작이 데이터베이스 나머지를
찾기 위해 읽어 들이는 OID가 이것이다. 이 OID는 boot_sr.c 의
정적 변수 boot_Db_parm_oid 에 저장되며, 데이터베이스 안에서는
찾을 수 없는 유일한 정보다. (j) 가 tf_install_meta_classes 를
호출해 시스템 클래스의 스키마 헤더를 쓰지만, 그 클래스의 내용
물 은 나중에 클라이언트 측 (catcls_install) 이 채운다는 점을
유의한다. 카탈로그 부트스트랩 분할
참조.
카탈로그 부트스트랩 분할
섹션 제목: “카탈로그 부트스트랩 분할”시스템 테이블 (db_class, db_attribute, db_index, …) 의
부트스트랩은 두 반쪽으로 나뉜다. 서버 측이 heap 파일과 OID를 만
들고, 클라이언트 측이 같은 first-create 호출 안에서 행을 채운다.
sequenceDiagram participant App as createdb (client) participant CLB as boot_initialize_client participant SRB as xboot_initialize_server participant BCV as boot_create_all_volumes participant CAT as catcls_init / catcls_install App->>CLB: 이름, 경로로 DB 생성 CLB->>CLB: lang_init, tz_load, msgcat_init, sysprm_load CLB->>CLB: area_init, locator_initialize_areas CLB->>CLB: ws_init? (아직 — 뒤로 미뤄 둠) CLB->>SRB: boot_initialize_server (CS_MODE 면 RPC) SRB->>BCV: boot_create_all_volumes BCV->>BCV: log_create, log_initialize BCV->>BCV: disk_format_first_volume BCV->>BCV: xheap_create root class HFID BCV->>BCV: boot_Db_parm 헤더 레코드 기록 BCV->>BCV: catalog_create (file) BCV->>BCV: tf_install_meta_classes BCV->>BCV: logpb_checkpoint BCV-->>SRB: tran_index SRB-->>CLB: tran_index, rootclass_oid, rootclass_hfid CLB->>CLB: ws_init (workspace) CLB->>CLB: sm_create_root (rootclass_oid) CLB->>CAT: install_system_metadata CAT->>CAT: au_init / au_install / au_start CAT->>CAT: tr_init CAT->>CAT: catcls_init (CT_*_NAME 테이블 목록 정의) CAT->>CAT: catcls_install (각각에 대해 build_class + build_vclass) CAT-->>CLB: error_code CLB->>CLB: tran_commit CLB->>CLB: sp_builtin_install CLB-->>App: NO_ERROR
install_system_metadata 가 그 조인 포인트다. boot_cl.c 안
에 살며, boot_initialize_client 에서 — 서버 측이 볼륨을 포
맷하고 root-class OID를 들고 돌아온 직후 — 호출된다. 본문은
짧고 명확하다.
// install_system_metadata — src/transaction/boot_cl.cstatic intinstall_system_metadata (void){ int error = NO_ERROR; /* Create system classes such as the root and authorization classes */ au_init (); error = au_install (); if (error != NO_ERROR) return error; error = au_start (); if (error != NO_ERROR) return error;
tr_init (); catcls_init (); error = catcls_install (); if (error != NO_ERROR) return error;
return NO_ERROR;}catcls_init (schema_system_catalog_install.cpp) 은 시스템
클래스마다 (name, definition) 한 쌍을 내놓는 정적 테이블 빌
더다. CT_CLASS_NAME, CT_ATTRIBUTE_NAME, CT_DOMAIN_NAME,
CT_INDEX_NAME, CT_TRIGGER_NAME, CT_PARTITION_NAME,
CT_STORED_PROC_NAME, CT_SERIAL_NAME, CT_HA_APPLY_INFO_NAME,
CT_COLLATION_NAME, CT_CHARSET_NAME, CT_DUAL_NAME,
CT_SYNONYM_NAME, CT_SERVER_NAME. 그리고 시스템 뷰 (CTV_*_NAME
가족) 마다도 한 쌍씩. catcls_install 이 그 목록을 돌며 클래
스마다 catalog_builder::build_class 를, 뷰마다 build_vclass
를 차례로 호출한다. 클래스 정의 자체는
cubschema::system_catalog_initializer::get_* 팩토리가 만들며,
컬럼 타입과 PK, 인덱스, 뷰 질의 스펙을 모두 C++ 로 인코딩해 두
었다.
왜 이렇게 갈라 두었는가? 카탈로그 테이블이 진짜 heap-resident 객체이기 때문이다. 정상 스키마 매니저 쓰기 경로로 만들면 사용자 정의 테이블과 같은 로깅, 잠금, 제약 강제 코드를 공유한다. 서버 안에 별도의 스키마 부트스트래퍼를 둘 필요가 없어진다는 점 이다.
서버 측 재시작
섹션 제목: “서버 측 재시작”boot_restart_server (boot_sr.c) 가 정상 기동의 거의 모든
경우가 지나가는 경로다. network_sr.c 의 net_server_start
가 호출하고, 그 위는 cub_server 의 main() (server.c) 다.
부팅 서브시스템 안에서 가장 긴 함수다. 라인이 아니라 페이즈
단위로 본다.
// boot_restart_server — src/transaction/boot_sr.c (condensed, annotated by phase)intboot_restart_server (THREAD_ENTRY *thread_p, bool print_restart, const char *db_name, bool from_backup, CHECK_ARGS *check_coll_and_timezone, BO_RESTART_ARG *r_args, bool skip_vacuum){ /* === Phase A — early infrastructure === */ lang_init (); tz_load (); msgcat_init (); sysprm_load_and_init (db_name, NULL, SYSPRM_LOAD_ALL); common_ha_mode = HA_GET_MODE (); cfg_read_directory (&dir, false); /* find DB in databases.txt */ db = cfg_find_db_list (dir, db_name); GETHOSTNAME (boot_Host_name, ...); COMPOSE_FULL_NAME (boot_Db_full_name, ..., db->pathname, db_name); boot_make_session_server_key (); /* check ha_mode in cubrid.conf vs database section */ msgcat_final (); msgcat_init (); /* reload to pick up MAX_THREADS */ css_init_conn_list (); perfmon_initialize (MAX_NTRANS); thread_p = thread_get_thread_entry_info (); er_init (...); er_clear (); event_log_init (db_name); trace_log_init (db_name); area_init (); set_area_init (); pr_area_init (); locator_initialize_areas (); tp_init (); tsc_init (); cubthread::initialize_thread_entries ();
/* === Phase B — PL server (must precede log_initialize) === */ pl_server_init (db_name);
/* === Phase C — open log + first volume; read boot_Db_parm === */ log_get_io_page_size (thread_p, boot_Db_full_name, log_path, log_prefix); logtb_define_trantable (thread_p, -1, -1); if (from_backup) logpb_restore (thread_p, ...); spage_boot (thread_p); heap_manager_initialize (); boot_mount (thread_p, LOG_DBFIRST_VOLID, boot_Db_full_name, NULL); disk_get_boot_hfid (thread_p, LOG_DBFIRST_VOLID, &boot_Db_parm->hfid); boot_get_db_parm (thread_p, boot_Db_parm, boot_Db_parm_oid); /* read header */ tde_cipher_initialize (thread_p, ...); heap_cache_class_info (thread_p, &boot_Db_parm->rootclass_oid, &boot_Db_parm->rootclass_hfid, FILE_HEAP, boot_Db_parm->rootclass_name);
/* === Phase D — charset cross-check === */ db_charset_db_header = boot_get_db_charset_from_header (thread_p, log_path, log_prefix); lang_set_charset (db_charset_db_header);
/* === Phase E — mount remaining volumes, init disk manager, vacuum === */ boot_find_rest_volumes (thread_p, ..., LOG_DBFIRST_VOLID, boot_mount, NULL); disk_manager_init (thread_p, true); logtb_initialize_global_unique_stats_table (thread_p); file_tracker_load (thread_p, &boot_Db_parm->trk_vfid); catalog_initialize (&boot_Db_parm->ctid); vacuum_initialize (thread_p, ..., &boot_Db_parm->vacuum_data_vfid, &boot_Db_parm->dropped_files_vfid, ...); oid_set_root (&boot_Db_parm->rootclass_oid); dwb_load_and_recover_pages (thread_p, log_path, log_prefix);
/* === Phase F — page-buffer / DWB / parallel-query daemons === */ pgbuf_daemons_init (); dwb_daemons_init (); parallel_query::worker_manager_global::get_manager ().init ();
/* === Phase G — RECOVERY HAND-OFF === */ log_initialize (thread_p, boot_Db_full_name, log_path, log_prefix, from_backup, r_args); /* log_initialize internally calls log_recovery if the header says so. */
boot_after_copydb (thread_p); /* only does work after copydb */
/* === Phase H — flush daemons + vacuum + CDC === */ BO_ENABLE_FLUSH_DAEMONS (); cdc_daemons_init (); vacuum_boot (thread_p);
/* === Phase I — locator, catalog cache, plan cache === */ locator_initialize (thread_p); catcls_find_and_set_cached_class_oid (thread_p); xcache_initialize (thread_p); qmgr_initialize (thread_p); qfile_initialize_list_cache (thread_p); fpcache_initialize (thread_p);
/* === Phase J — read db_root, validate charset/lang, check timezone === */ catcls_get_server_compat_info (thread_p, &db_charset_db_root, db_lang, ...); /* compare db_charset_db_header against db_charset_db_root */ lang_set_language (db_lang); if (check_coll_and_timezone->check_timezone) check_timezone_compat (tzd->checksum, timezone_checksum, "server", "database");
/* === Phase K — short housekeeping transaction === */ tran_index = logtb_assign_tran_index (thread_p, ...); boot_remove_all_temp_volumes (thread_p, REMOVE_TEMP_VOL_DEFAULT_ACTION); if (boot_Lob_path[0] != '\0') es_init (boot_Lob_path); xtran_server_commit (thread_p, false); logtb_free_tran_index (thread_p, tran_index); logtb_set_to_system_tran_index (thread_p);
/* === Phase L — meta-class compile, catalog compatibility check === */ if (!tf_Metaclass_class.mc_n_variable) tf_compile_meta_classes (); if (!skip_to_check_ct_classes_for_rebuild && catcls_Enable != true) catcls_compile_catalog_classes (thread_p); if (check_coll_and_timezone->check_db_coll) catcls_get_db_collation (thread_p, ...);
/* === Phase M — SA-mode vacuum, read-only mode, serial cache === */#if defined (SA_MODE) if (!skip_vacuum) xvacuum (thread_p);#endif if (prm_get_bool_value (PRM_ID_READ_ONLY_MODE)) logtb_disable_update (NULL); serial_initialize_cache_pool (thread_p); pl_server_wait_for_ready (); session_states_init (thread_p);
/* === Phase N — IP control, HA state, partition cache === */#if defined (SERVER_MODE) if (prm_get_bool_value (PRM_ID_ACCESS_IP_CONTROL) && !from_backup) css_set_accessible_ip_info (); css_set_ha_num_of_hosts (db->num_hosts); css_change_ha_server_state (thread_p, (HA_SERVER_STATE) prm_get_integer_value (PRM_ID_HA_SERVER_STATE), false, HA_CHANGE_MODE_IMMEDIATELY, true);#endif partition_cache_init (thread_p);
/* === Phase O — server is up === */ if (boot_Server_status == BOOT_SERVER_DOWN) boot_server_status (BOOT_SERVER_UP); return NO_ERROR;}페이즈 경계는 소스에 주석으로 박혀 있지 않다. 이 함수가 열다섯 가지의 서로 다른 일을 하기 때문에, 의존성 이야기를 읽기 좋게 만들기 위해 본 문서가 부여한 라벨이다. Phase A 가 에러 보고기와 파라미터 로더를 깨우고, Phase C 가 데이터베이스 헤더를 읽어 이후 의 페이즈가 무엇을 다루는지 알게 만든다. Phase F 가 복구가 돌아 가는 동안 이미 살아 있어야 하는 백그라운드 데몬을 띄우고, Phase G 가 복구 hand-off 다. Phase I 가 복구된 페이지로부터 인메모리 카탈로그 캐시를 다시 채우고, Phase N 이 클라이언트의 접속 가부 를 결정할 HA 상태를 마스터에 알린다.
핵심 시퀀싱 사실은 이것이다. Phase G 는 Phase I 보다 먼저 돈다. 복구가 먼저 끝나고, 그 다음에야 카탈로그 캐시가 만들 어진다. 이유는 카탈로그 읽기가 heap fetch 를 발행하는데, heap fetch 는 페이지 LSA 가 로그 LSA 와 맞아야 동작하기 때문이라는 점이다. 그 LSA 정합성을 복원하는 일이 정확히 redo 가 하는 일이 다. 복구 전에 카탈로그를 읽으면 찢어진 데이터베이스가 보인다.
sequenceDiagram
autonumber
participant CS as cub_server::main
participant NS as net_server_start
participant BR as boot_restart_server
participant LM as log_initialize
participant LR as log_recovery
participant CC as catcls_find_and_set_cached_class_oid
participant CSS as css_init
participant CL as Client
CS->>NS: net_server_start (server_name)
NS->>NS: er_init, msgcat_init, tz_load
NS->>NS: sysprm_load_and_init, csect_initialize
NS->>NS: net_server_init, css_initialize_server_interfaces
NS->>BR: boot_restart_server
BR->>BR: phase A — 기본 인프라 (lang, msgcat, sysprm, area, tp, threads)
BR->>BR: phase B — pl_server_init
BR->>BR: phase C — log_get_io_page_size, boot_mount LOG_DBFIRST_VOLID
BR->>BR: boot_get_db_parm — 디스크에서 헤더 읽기
BR->>BR: phase D — charset 교차 점검
BR->>BR: phase E — 나머지 마운트, disk_manager_init, vacuum_initialize
BR->>BR: phase F — pgbuf/dwb 데몬 기동
BR->>LM: log_initialize (복구 hand-off)
LM->>LR: log_recovery (analysis → redo → undo)
LR-->>LM: rcv_phase = LOG_RESTARTED
LM-->>BR: returns
BR->>BR: phase H — flush/cdc 데몬, vacuum_boot
BR->>BR: phase I — locator_initialize
BR->>CC: catcls_find_and_set_cached_class_oid
CC-->>BR: cached
BR->>BR: phase J — db_root 검증, lang_set_language
BR->>BR: phase K — 임시 볼륨 정리, lob es_init
BR->>BR: phase L — tf_compile_meta_classes, catcls_compile_catalog_classes
BR->>BR: phase M — serial 캐시, 세션 상태
BR->>BR: phase N — css_change_ha_server_state, partition_cache_init
BR->>BR: phase O — boot_Server_status = UP
BR-->>NS: NO_ERROR
NS->>CSS: css_init (port_id 열고 접속 수락)
loop forever
CL->>CSS: TCP connect
CSS->>CSS: net_server_request 로 디스패치
end
최초 생성 vs. 재시작 결정
섹션 제목: “최초 생성 vs. 재시작 결정”두 흐름이 같은 모듈 안에 있고, 그 둘 사이의 선택은 어느 실행
파일/유틸리티가 들어 오느냐로 결정된다. 결정은 boot_sr.c 안
에 인코딩되어 있지 않다. boot_sr.c 는 두 진입점을 모두 노출
하고, 그 선택을 호출자에게 맡긴다.
flowchart TB
CALL[프로세스 호출]
Q1{어느 실행 파일?}
CDB["createdb 클라이언트 유틸리티"]
CSV["cub_server (server.c)"]
RDB["restoredb 클라이언트 유틸리티"]
CB["client app (db_restart_client)"]
CIN["boot_initialize_client\n(클라이언트 측)"]
CRT["boot_restart_client\n(클라이언트 측)"]
XINIT["xboot_initialize_server\n(서버 측, 네트워크 또는 in-process)"]
XRST["boot_restart_server"]
XRSTBK["xboot_restart_from_backup → boot_restart_server (from_backup=true)"]
REG["xboot_register_client (접속마다)"]
CALL --> Q1
Q1 -->|createdb| CDB --> CIN --> XINIT
Q1 -->|cub_server| CSV --> XRST
Q1 -->|restoredb| RDB --> XRSTBK
Q1 -->|app connect| CB --> CRT --> REG
핵심은 다음이다. cub_server 는 무조건 boot_restart_server 를
부른다. boot_restart_server 는 다시 무조건 log_initialize 를
부른다. 그리고 log_initialize 가 내부에서 복구 디스패치 여부
를 결정한다. 서버는 복구가 필요한지 컴파일 시점에 알지 못한다. 그 지식은 로그 헤더 (hdr.is_shutdown) 에 있다.
클라이언트 측 접속 흐름
섹션 제목: “클라이언트 측 접속 흐름”sequenceDiagram
autonumber
participant App as Client app
participant BRC as boot_restart_client
participant CCSS as boot_client_initialize_css
participant Net as net_client_init
participant SR as Server (xboot_register_client)
participant WS as ws_init / sm_init
participant AU as au_start
participant Cache as boot_client_find_and_cache_class_oids
App->>BRC: boot_restart_client (BOOT_CLIENT_CREDENTIAL)
BRC->>BRC: 이전에 restart 되어 있다면 shutdown_client
BRC->>BRC: lang_init, tz_load, msgcat_init, sysprm_load
BRC->>BRC: er_init, area_init, locator_initialize_areas
BRC->>BRC: perfmon_initialize, cfg_find_db
BRC->>BRC: db_user, login_name, host_name, process_id, ip 채우기
BRC->>BRC: dl_initiate_module
loop preferred_hosts → db->hosts (최대 2회 재시도)
BRC->>CCSS: boot_client_initialize_css
CCSS->>Net: net_client_init (db_name, host)
Net->>Net: ping_server_with_handshake (capability 확인)
alt 접속 성공
Net-->>CCSS: NO_ERROR
else
Net-->>CCSS: ER_NET_*
end
end
BRC->>BRC: er_set BO_CONNECTED_TO
BRC->>BRC: tp_init, tsc_init
BRC->>WS: ws_init
BRC->>SR: boot_register_client (NET_SERVER_BO_REGISTER_CLIENT RPC)
SR-->>BRC: tran_index, BOOT_SERVER_CREDENTIAL
BRC->>BRC: lang_set_charset (server.charset)
BRC->>BRC: lang_set_language (server.lang)
BRC->>BRC: db_set_page_size (server.page_size)
BRC->>BRC: rel_set_disk_compatible (server.disk_compatibility)
BRC->>BRC: boot_client (트랜잭션 설정 캐시)
BRC->>WS: sm_init (root_class_oid, root_class_hfid)
BRC->>AU: au_init, au_start
BRC->>Cache: boot_client_find_and_cache_class_oids
BRC->>BRC: db_find_or_create_session
BRC->>BRC: boot_check_locales, boot_check_timezone_checksum
BRC->>BRC: tr_init (트리거 매니저)
BRC->>BRC: es_init (서버 자격에서 받은 lob 경로)
BRC->>BRC: tran_commit
BRC->>BRC: tran_reset_isolation, tran_reset_wait_times
BRC->>BRC: showstmt_metadata_init
BRC-->>App: NO_ERROR
호스트 선택 루프는 두 단계 전략이다. 1차 패스는
BOOT_CHECK_HA_DELAY_CAP 을 적용한다. 클라이언트는 복제 지연
이 허용 한도 안인 호스트만 받는다. 2차 패스는 그래도 살아 있는
호스트를 못 찾았을 때 캡을 풀어 준다. 각 패스 안에서 호스트 순
서는 client_credential->connect_order 와 preferred_hosts 의
설정 여부에 따라 preferred-then-default 또는 순차-then-random
이다.
// boot_register_client — src/communication/network_interface_cl.c (CS_MODE branch)intboot_register_client (BOOT_CLIENT_CREDENTIAL *client_credential, int client_lock_wait, TRAN_ISOLATION client_isolation, TRAN_STATE *tran_state, BOOT_SERVER_CREDENTIAL *server_credential){#if defined(CS_MODE) /* pack credential as packable_object, send NET_SERVER_BO_REGISTER_CLIENT */ packing_packer packer; cubmem::extensible_block ext_blk; packer.set_buffer_and_pack_all (ext_blk, *client_credential, client_lock_wait, (int) client_isolation);
net_client_request2 (NET_SERVER_BO_REGISTER_CLIENT, ext_blk.get_ptr (), packer.get_current_size (), reply, OR_ALIGNED_BUF_SIZE (a_reply), NULL, 0, &area, &area_size);
/* unpack server reply: tran_index, tran_state, db_full_name, host_name, lob_path, process_id, root_class_oid, root_class_hfid, page_size, log_page_size, disk_compatibility, ha_server_state, db_charset, db_lang */ ... return tran_index;#else /* SA_MODE */ enter_server_no_thread_entry (); tran_index = xboot_register_client (NULL, client_credential, client_lock_wait, client_isolation, tran_state, server_credential); exit_server_no_thread_entry (); return tran_index;#endif}SA / CS 두 모드 분기가 결국 xboot_register_client 한 곳에서
만난다. SA 는 직접 호출, CS 는 네트워크 디스패처를 거친다
(NET_SERVER_BO_REGISTER_CLIENT 가 네트워크 테이블에서 서버의
sboot_register_client 핸들러로 묶여 있고, 그 핸들러가 자격을
unpack해 xboot_register_client 를 부른다).
자격 정보와 로그인 흐름
섹션 제목: “자격 정보와 로그인 흐름”BOOT_CLIENT_CREDENTIAL 은 clientids (in
client_credentials.hpp) 를 상속한다.
// boot_client_credential — src/transaction/client_credentials.hppstruct boot_client_credential : public clientids{ std::string db_name; /* DB_MAX_IDENTIFIER_LENGTH */ std::string db_password; /* DB_MAX_PASSWORD_LENGTH */ char *preferred_hosts; /* LINE_MAX */ int connect_order; /* ... pack / unpack methods ... */};
struct clientids : public cubpacking::packable_object{ db_client_type client_type; std::string client_info; std::string db_user; std::string program_name; std::string login_name; std::string host_name; std::string client_ip_addr; int process_id; /* ... */};이 구조체는 packable_object 다. 즉, 자기 자신을 네트워크용으
로 직렬화할 줄 안다. 13개의 클라이언트 타입
(DB_CLIENT_TYPE_DEFAULT, DB_CLIENT_TYPE_CSQL,
DB_CLIENT_TYPE_BROKER, DB_CLIENT_TYPE_LOG_APPLIER,
DB_CLIENT_TYPE_ADMIN_UTILITY 등 — db_client_type.hpp 에 모두
열거되어 있다) 이 HA 상태 점검까지 내려가는 동작을 매개변수화
한다. boot.h 의 BOOT_NORMAL_CLIENT_TYPE 외 매크로들이 그 타
입 공간을 슬라이스한다.
// BOOT_*_CLIENT_TYPE macros — src/transaction/boot.h#define BOOT_NORMAL_CLIENT_TYPE(t) /* default, csql, broker, loaddb-utility, ... */#define BOOT_READ_ONLY_CLIENT_TYPE(t) /* read-only csql/broker/replica */#define BOOT_ADMIN_CLIENT_TYPE(t) /* admin csql / utility */#define BOOT_LOG_REPLICATOR_TYPE(t) /* log copier / applier */#define BOOT_CSQL_CLIENT_TYPE(t)#define BOOT_BROKER_AND_DEFAULT_CLIENT_TYPE(t)#define BOOT_REPLICA_ONLY_BROKER_CLIENT_TYPE(t)#define BOOT_WRITE_ON_STANDY_CLIENT_TYPE(t)인증 결합부는 xboot_register_client 안에 있다.
// xboot_register_client — src/transaction/boot_sr.c (auth + HA snippet)db_user_save = client_credential->get_db_user ();if (!client_credential->db_user.empty ()) { intl_identifier_upper (client_credential->get_db_user (), db_user_upper); client_credential->db_user = db_user_upper;}
tran_index = logtb_assign_tran_index (thread_p, NULL_TRANID, TRAN_ACTIVE, client_credential, tran_state, client_lock_wait, client_isolation);/* ... fill server_credential ... */
if (BOOT_NORMAL_CLIENT_TYPE (client_credential->client_type)) { if (css_check_ha_server_state_for_client (thread_p, 1) != NO_ERROR) { logtb_release_tran_index (thread_p, tran_index); *tran_state = TRAN_UNACTIVE_UNKNOWN; return NULL_TRAN_INDEX; }}if (client_credential->client_type == DB_CLIENT_TYPE_LOG_APPLIER) { css_notify_ha_log_applier_state (thread_p, HA_LOG_APPLIER_STATE_UNREGISTERED);}실제 사용자 인증 — 비밀번호 검증, 역할 멤버십, 스키마 가시성
확인 — 은 더 늦게, 그것도 클라이언트 측 에서 일어난다.
boot_restart_client 가 (이미 대문자화된) 사용자 이름을 들고
au_start 를 부르는 시점이다. 서버가 register 시점에 하는 일
은 자격을 트랜잭션 디스크립터에 기록해 두는 것 뿐이다. 인증
강제는 read-side 로 들어 간다. 락 매니저가 락 획득 시 사용자
멤버십을 검사하고, 스키마 매니저가 카탈로그 접근 시 가시성을
검사한다.
부팅 시점의 HA 상태
섹션 제목: “부팅 시점의 HA 상태”cub_master 와 cub_server 는 별개의 프로세스다. 마스터가
heartbeat (hb_master_init) 를 돌리고, 다중 호스트 토폴로지를
유지하며, 클러스터의 권위 있는 HA 상태가 살아 있는 자리다. 재시
작 중인 cub_server 는 마스터를 두 군데서 참조한다.
boot_make_session_server_key— Phase A 단계.time()과 호스트 IP로 프로세스별 세션 키를 만들고, 이를 마스터를 향한 서버 신원으로 사용한다.css_change_ha_server_state— Phase N.PRM_ID_HA_SERVER_STATE파라미터에서 (기본HA_SERVER_STATE_IDLE) 읽어 들인 요청 HA 상태를 마스터로 전송하고 확인을 기다린다. 전이는server_support.c의css_transit_ha_server_state에 있는 상태 머신 테이블에 의해 게이팅된다.IDLE → ACTIVE같은 요 청은 즉시 통하고,IDLE → STANDBY → TO_BE_STANDBY류는 마스 터가 peer 의 가용성을 확인한 뒤에야 통할 수 있다.
서버가 자기 HA 상태를 받고 나면, css_check_ha_server_state_for_client
질의에 답할 수 있는 상태가 된다. 위에서 본
xboot_register_client 안의 그 호출이, 서버가 MAINTENANCE
또는 DEAD 상태일 때 정상 클라이언트의 접속을 거절할 수 있는 곳
이다.
종료 — 대칭적 tear-down
섹션 제목: “종료 — 대칭적 tear-down”// xboot_shutdown_server — src/transaction/boot_sr.c (condensed)boolxboot_shutdown_server (REFPTR (THREAD_ENTRY, thread_p), ER_FINAL_CODE is_er_final){ if (!BO_IS_SERVER_RESTARTED ()) return true;
/* phase 1: abort all live transactions, stop vacuum workers */ logtb_set_to_system_tran_index (thread_p); log_abort_all_active_transaction (thread_p); vacuum_stop_workers (thread_p);
/* phase 2: drain caches before removing temp volumes */ logtb_reflect_global_unique_stats_to_btree (thread_p); qfile_finalize_list_cache (thread_p); xcache_finalize (thread_p); fpcache_finalize (thread_p); session_states_finalize (thread_p);
/* phase 3: remove temp volumes, lob temp dir */ boot_remove_all_temp_volumes (thread_p, REMOVE_TEMP_VOL_DEFAULT_ACTION); fileio_lob_remove_matching_dir (BOOT_LOB_TEMP_DIR_KEYWORD);
/* phase 4: stop HA delay registration, then vacuum master */ log_stop_ha_delay_registration (); vacuum_stop_master (thread_p);
/* phase 5: stop daemons */#if defined(SERVER_MODE) pgbuf_daemons_destroy (); cdc_daemons_destroy (); pl_server_destroy (); parallel_query::worker_manager_global::get_manager ().destroy ();#endif
/* phase 6: log_final writes the shutdown checkpoint, sets is_shutdown=true */ log_final (thread_p); dwb_destroy (thread_p);
/* phase 7: release subsystems via boot_server_all_finalize */ boot_server_all_finalize (thread_p, is_er_final, BOOT_SHUTDOWN_EXCEPT_COMMON_MODULES);
#if defined (SA_MODE) cubthread::finalize (); thread_p = NULL;#endif return true;}Phase 6 의 log_final 이 짐을 짊어진 호출이다. 마지막
LOG_END_CHKPT 레코드를 쓰고, 로그 헤더의
hdr.is_shutdown = true 를 세팅한다. 다음 재시작이 헤더에서 그
플래그가 켜져 있는 것을 보면 log_recovery 를 통째로 건너 뛸
수 있다는 뜻이다.
boot_server_all_finalize 가 사실상 부팅의 역순으로 모든 것을
풀어 준다.
// boot_server_all_finalize — src/transaction/boot_sr.c (condensed)voidboot_server_all_finalize (THREAD_ENTRY *thread_p, ER_FINAL_CODE is_er_final, BOOT_SERVER_SHUTDOWN_MODE shutdown_common_modules){ logtb_finalize_global_unique_stats_table (thread_p); locator_finalize (thread_p); spage_finalize (thread_p); catalog_finalize (); qmgr_finalize (thread_p); heap_manager_finalize (); fileio_dismount_all (thread_p); disk_manager_final (); boot_server_status (BOOT_SERVER_DOWN);
catcls_finalize_class_oid_to_oid_hash_table (thread_p); serial_finalize_cache_pool (); partition_cache_finalize (thread_p);
thread_return_lock_free_transaction_entries (); lf_destroy_transaction_systems (); perfmon_finalize ();
#if defined(SERVER_MODE) shutdown_common_modules = BOOT_SHUTDOWN_ALL_MODULES;#endif
if (shutdown_common_modules == BOOT_SHUTDOWN_ALL_MODULES) { es_final (); tp_final (); locator_free_areas (); set_final (); sysprm_final (); area_final (); msgcat_final (); if (is_er_final == ER_ALL_FINAL) er_final (ER_ALL_FINAL); lang_final (); tz_unload (); }
#if defined(SERVER_MODE) css_free_accessible_ip_info (); event_log_final (); trace_log_final ();#endif}거울 속성 — 부팅 경로 안의 모든 *_init / *_initialize /
*_load / area_init 호출이 여기에서 짝이 되는 *_final /
*_finalize / *_unload 를 갖는다. 이 짝짓기는 유지보수성을
위한 접착제다. 새 서브시스템을 추가하는 개발자는 양쪽 절반을
모두 추가해야 하고, 그렇지 않으면 종료 시점에 누수가 생긴다는
점이다. BOOT_SHUTDOWN_* enum 은 공통 모듈 (locale, sysprm,
msgcat, er) 까지 풀 것인지 서버 전용 모듈만 풀 것인지를 게이팅
한다. 공통 모듈을 살려 두는 게 의미 있는 자리는 SA 모드다. 같은
프로세스가 클라이언트 역할도 같이 하면서 여러 차례의 SA-모드
접속 사이클을 가로질러 살아 남고 싶을 수 있기 때문이다.
클라이언트 측 거울은 boot_shutdown_client →
boot_client_all_finalize 에 있다.
// boot_client_all_finalize — src/transaction/boot_cl.c (condensed)voidboot_client_all_finalize (int final_level){ /* signal handlers ignored during finalize */ signal (SIGTERM, SIG_IGN); signal (SIGABRT, SIG_IGN); signal (SIGINT, SIG_IGN);
if (BOOT_IS_CLIENT_RESTARTED () || boot_Is_client_all_final == false) { /* free server-credential strings */ db_private_free_and_init (NULL, boot_Server_credential.db_full_name); db_private_free_and_init (NULL, boot_Server_credential.host_name); /* ... */
showstmt_metadata_final (); tran_free_savepoint_list (); sm_flush_static_methods (); set_final (); parser_final ();
if (final_level != OPTIONAL_FINALIZATION) { tr_final (); /* trigger manager */ au_final (); /* authorization */ sm_final (); /* schema manager */ method_callback_final (); ws_final (); /* workspace */ es_final (); /* external storage */ tp_final (); /* type / domain */ }
locator_free_areas (); sysprm_final (); perfmon_finalize (); area_final (); msgcat_final (); if (final_level != EXCEPT_ER_FINALIZATION) er_final (ER_ALL_FINAL); lang_final (); tz_unload ();
/* clear server credential, mark client as inactive */ memset (&boot_Server_credential, 0, sizeof (boot_Server_credential)); memset (boot_Server_credential.server_session_key, 0xFF, SERVER_SESSION_KEY_SIZE); boot_client (NULL_TRAN_INDEX, TRAN_LOCK_INFINITE_WAIT, TRAN_DEFAULT_ISOLATION_LEVEL ()); boot_Is_client_all_final = true; }}final_level enum (ALL_FINALIZATION, EXCEPT_ER_FINALIZATION,
OPTIONAL_FINALIZATION) 이 tear-down 의 공격성을 조절한다.
EXCEPT_ER_FINALIZATION 은 에러 매니저를 살려 두므로 그 다음
fatal-error 경로가 여전히 보고를 낼 수 있다. OPTIONAL_FINALIZATION
은 스키마/auth/트리거 finalizer 를 건너 뛴다. 이들이 애초에
세팅된 적이 없는 경우 (부팅이 실패한 직후) 를 위한 옵션이다.
cub_master 부팅
섹션 제목: “cub_master 부팅”cub_master 는 부팅 이야기의 세 번째 실행 파일이다. 서버 부팅
보다 훨씬 짧지만, 모든 클라이언트 접속 경로 위에 놓인다. 마스
터가 (database_name, host) 를 살아 있는 서버 프로세스에 매핑
하는 레지스트리이기 때문이다.
// master.c::main — src/executables/master.c (condensed)intmain (int argc, char **argv){ utility_initialize ();#if defined(WINDOWS) css_windows_startup ();#endif master_util_config_startup (argv[1], &port_id); GETHOSTNAME (hostname, ...); er_init (errlog, ER_NEVER_EXIT);
if (__gv_cvar.css_does_master_exist (port_id)) return EXIT_FAILURE; /* duplicate master refused */
msgcat_final (); er_final (ER_ALL_FINAL);#if !defined(WINDOWS) if (envvar_get ("NO_DAEMON") == NULL) css_daemon_start ();#endif utility_initialize (); er_init (errlog, ER_NEVER_EXIT); time (&css_Start_time);
css_master_init (port_id, css_Master_socket_fd); /* signal handlers, sockets */
if (envvar_get ("NO_DAEMON") != NULL) os_set_signal_handler (SIGINT, css_master_cleanup);
#if !defined(WINDOWS) if (!HA_DISABLED ()) hb_master_init (); /* heartbeat */ auto_Restart_server = prm_get_bool_value (PRM_ID_AUTO_RESTART_SERVER); if (auto_Restart_server) master_Server_monitor.reset (new server_monitor ());#endif
/* register the two master sockets with the request queue */ conn = __gv_cvar.css_make_conn (css_Master_socket_fd[0]); css_add_request_to_socket_queue (conn, false, NULL, ...); conn = __gv_cvar.css_make_conn (css_Master_socket_fd[1]); css_add_request_to_socket_queue (conn, false, NULL, ...);
css_master_loop (); /* never returns under normal operation */ css_master_cleanup (SIGINT); ...}마스터는 boot_restart_server 를 부르지 않는다. 마스터의 일은
순수히 디스패처가 되는 것이다. 클라이언트가 마스터로 접속하면,
마스터가 그 클라이언트를 명명된 데이터베이스에 묶인 cub_server
로 포워딩한다. Heartbeat 는 HA 가 비활성화되지 않은 경우에만
돈다. hb_master_init 가 peer 마스터들과 health ping 을 주고
받는 heartbeat 스레드를 띄운다. heartbeat 자체는
cubrid-heartbeat.md 가 자세히 다룬다.
cub_server main 결합부
섹션 제목: “cub_server main 결합부”마지막으로, Phase A 부터 Phase O 까지 묶어 주는 실행 파일이다.
// server.c::main — src/executables/server.c (condensed)intmain (int argc, char **argv){#if !defined(WINDOWS)#if !defined (NDEBUG) register_abort_signal_handler (SIGABRT);#else register_fatal_signal_handler (SIGABRT);#endif register_fatal_signal_handler (SIGILL); register_fatal_signal_handler (SIGFPE); register_fatal_signal_handler (SIGBUS); register_fatal_signal_handler (SIGSEGV); register_fatal_signal_handler (SIGSYS);#endif
if (argc < 2) { PRINT_AND_LOG_ERR_MSG ("Usage: server databasename\n"); return 1; }
fprintf (stdout, "\nThis may take a long time depending on the amount " "of recovery works to do.\n"); binary_name = basename (argv[0]); envvar_bindir_file (executable_path, PATH_MAX, binary_name); database_name = argv[1];
#if !defined(WINDOWS) hb_set_exec_path (executable_path); hb_set_argv (argv); css_set_exec_path (executable_path); css_set_argv (argv); setsid (); /* new process group, decoupled from terminal */#endif
return net_server_start (database_name);}main 의 본질적 라인 수는 40 이다. 어려운 일은 모두
net_server_start → boot_restart_server → log_initialize →
log_recovery 사슬을 거쳐 Phase H..O 로 다시 흘러 들어 간다.
맨 위에서 설치되는 시그널 핸들러는 fatal 시그널을 잡아
er_print_crash_callstack 으로 크래시 콜스택을 찍는다. 이 핸들
러들이 어떤 서브시스템보다도 먼저 설치되기 때문에, Phase A 의
실패라도 의미 있는 크래시 로그를 만든다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”닻은 심볼 이름 이다. 라인 번호는 본 절 끝의 position-hint 표에만 둔다.
서버 부팅 — 진입/재시작/종료
섹션 제목: “서버 부팅 — 진입/재시작/종료”xboot_initialize_server(boot_sr.c) — first-create 드라이버.boot_restart_server(boot_sr.c) — 재시작 드라이버. 긴 쪽이다.xboot_restart_from_backup(boot_sr.c) —boot_restart_server를from_backup=true로 감싼 래퍼.boot_create_all_volumes(boot_sr.c) — 첫 볼륨 포맷, 로그 초기화, root-class heap 생성,boot_Db_parm기록, 초기 체크 포인트 발행.boot_after_copydb(boot_sr.c) — copydb 직후의 reseed 보정 (applylogdb LSA 들을 클리어).boot_remove_all_volumes(boot_sr.c) — overwrite-on-create 에 쓰는 파괴적 볼륨 정리.xboot_shutdown_server(boot_sr.c) — 종료 드라이버.boot_server_all_finalize(boot_sr.c) — 모든 서버 모듈 해제.boot_donot_shutdown_server_at_exit(boot_sr.c) — fatal 실패 경로용 atexit 훅 비활성화.boot_shutdown_server_at_exit(boot_sr.c) —boot_server_status (BOOT_SERVER_UP)시점에 등록되는 atexit 훅.boot_get_db_parm/boot_db_parm_update_heap(boot_sr.c) — 영속 헤더 레코드의 read/write.boot_make_session_server_key(boot_sr.c) —time()+ IP 로 프로세스별 서버 신원을 합성.boot_mount(boot_sr.c) —boot_find_rest_volumes의 콜백으로 쓰이는fileio_mount래퍼.boot_find_rest_permanent_volumes(boot_sr.c) — 첫 볼륨 이후의 모든 볼륨을 마운트하기 위해 디스크 상의 볼륨 목록을 걷는다.boot_remove_all_temp_volumes(boot_sr.c) — Phase K 정리.boot_get_db_charset_from_header(boot_sr.c) — Phase D 에서 로그 헤더의 charset 을 읽는다.boot_ctrl_c_in_init_server(boot_sr.c) — 생성의 cancel 경 로로 longjmp 하는 SIGINT 핸들러.boot_dbparm_save_volume/boot_get_new_volume_name_and_id(boot_sr.c) — add-volume 연산이 사용한다.
서버 부팅 — register/unregister
섹션 제목: “서버 부팅 — register/unregister”xboot_register_client(boot_sr.c) — tran-index 할당, 서버 자격 채움, HA 상태 검사.xboot_unregister_client(boot_sr.c) — 접속 종료 시 tran-index 와 conn-entry 해제.xboot_notify_unregister_client(boot_sr.c) — server-mode 에서 클라이언트 conn 이 닫히고 있다는 신호. conn 상태를CONN_CLOSING으로 뒤집는다.xboot_get_server_session_key(boot_sr.c).
서버 부팅 — 글로벌
섹션 제목: “서버 부팅 — 글로벌”boot_Server_status(boot_sr.c) —BOOT_SERVER_UP / DOWN / MAINTENANCE.boot_Db_parm/boot_Struct_db_parm/boot_Db_parm_oid(boot_sr.c) — 영속 헤더.boot_Db_full_name(boot_sr.c) —<db_path>/<db_name>.boot_Lob_path(boot_sr.c) — 외부 저장소 prefix.boot_Server_session_key(boot_sr.c) — 8 바이트 프로세스별 키.boot_Host_name(boot_sr.c) —GETHOSTNAME으로 채움.boot_Init_server_jmpbuf/boot_Init_server_is_canceled(boot_sr.c) — Ctrl-C 취소 스캐폴딩.boot_Enabled_flush_daemons(boot_sr.c) —BO_ENABLE_FLUSH_DAEMONS/BO_DISABLE_FLUSH_DAEMONS의 게이트 플래그.
클라이언트 부팅 — 진입/재시작/종료
섹션 제목: “클라이언트 부팅 — 진입/재시작/종료”boot_initialize_client(boot_cl.c) — 최초 생성 드라이버.boot_initialize_server(네트워크 또는 in-process) 를 부른 뒤install_system_metadata를 부른다.boot_restart_client(boot_cl.c) — 정상 클라이언트 재시작. 호스트 선택 루프와boot_register_client를 구동한다.boot_shutdown_client(boot_cl.c) — graceful shutdown.boot_shutdown_client_at_exit(boot_cl.c) —boot_client가 등록하는 atexit 훅.boot_donot_shutdown_client_at_exit(boot_cl.c) — fatal 실패용 override.boot_server_die_or_changed(boot_cl.c) — 세션 도중 서버 접속이 끊어졌을 때 호출됨.boot_client_all_finalize(boot_cl.c) — 모든 클라이언트 모 듈 해제.boot_client(boot_cl.c) — 트랜잭션 모듈에 트랜잭션 설정을 캐시하고, atexit 훅을 등록.boot_client_initialize_css(boot_cl.c) — 호스트 선택 +net_client_init+ handshake.install_system_metadata(boot_cl.c) —au_install,tr_init,catcls_init,catcls_install호출.boot_client_find_and_cache_class_oids(boot_cl.c) — 막 적재된 카탈로그로부터 workspace 캐시를 채운다.boot_check_locales/boot_check_timezone_checksum(boot_cl.c).boot_build_catalog_classes/boot_destroy_catalog_classes(boot_cl.c) —synccolldb/ 마이그레이션 유틸리티가 쓰는 SA-mode 카탈로그 재구축 경로.
클라이언트 부팅 — 글로벌
섹션 제목: “클라이언트 부팅 — 글로벌”boot_Server_credential(boot_cl.c) — 서버의NET_SERVER_BO_REGISTER_CLIENT응답에서 채워짐.boot_Host_connected/boot_Host_name/boot_Ip_address(boot_cl.c).boot_User_volid/boot_Volume_label(boot_cl.c).boot_Is_client_all_final/boot_Set_client_at_exit/boot_Process_id(boot_cl.c).
네트워크 결합부
섹션 제목: “네트워크 결합부”boot_register_client(network_interface_cl.c) — CS-mode 에 서 자격을 pack해NET_SERVER_BO_REGISTER_CLIENT를 보내고, 서 버 자격 응답을 unpack한다. SA-mode 는xboot_register_client를 직접 부른다.boot_unregister_client/boot_initialize_server(네트워크 래퍼) —boot_register_client와 같은 패턴.net_server_start(network_sr.c) — 서버 측 부팅 순서의 주인. er_init, msgcat_init, sysprm_load_and_init, sync_initialize_sync_stats, csect_initialize_static_critical_sections, net_server_init, css_initialize_server_interfaces,boot_restart_server,css_init.css_init(server_support.c) — listening 포트를 열고, 서버 요청 루프로 들어 간다. 종료 시에만 돌아온다.css_initialize_server_interfaces(server_support.c) —css_Server_request_handler = net_server_request세팅.css_change_ha_server_state/css_transit_ha_server_state(server_support.c) — 부팅의 HA 마지막 호출.
카탈로그 부트스트랩 (catcls)
섹션 제목: “카탈로그 부트스트랩 (catcls)”catcls_init(schema_system_catalog_install.cpp) — 시스템 클래스/뷰 정의의 정적clist/vclist를 채운다.catcls_install(schema_system_catalog_install.cpp) —clist의 각 엔트리를catalog_builder::create_and_mark_system_class와catalog_builder::build_class를 부른다. vclist 도 마찬가지로build_vclass로.AU_DISABLE상태에서,Au_dba_user로 동작한다.catcls_compile_catalog_classes(catalog_class.c) — 재시작 시 캐시된 카탈로그 메타데이터를 다시 채워 넣는다. 재시작의 Phase L 에서 돈다.catcls_find_and_set_cached_class_oid(catalog_class.c) — collation 조회에 쓰이는 시스템 클래스의 OID 를 캐시한다 (Phase I).catcls_get_server_compat_info(catalog_class.c) —db_root에서 charset, language, timezone checksum 을 읽는다 (Phase J).catcls_initialize_class_oid_to_oid_hash_table/catcls_finalize_class_oid_to_oid_hash_table(catalog_class.c).catcls_Enable(catalog_class.c) — 글로벌 게이트.catcls_compile_catalog_classes가 한 번 성공할 때까지false.
실행 파일
섹션 제목: “실행 파일”maininsrc/executables/server.c—cub_server진입점. 시그널 핸들러 다음에net_server_start (argv[1]).maininsrc/executables/master.c—cub_master진입점. utility init, master 소켓 페어,css_master_init,hb_master_init,css_master_loop.
위치 힌트 (2026-04-30 기준)
섹션 제목: “위치 힌트 (2026-04-30 기준)”| 심볼 | 파일 | 라인 |
|---|---|---|
xboot_initialize_server | src/transaction/boot_sr.c | 1385 |
boot_restart_server | src/transaction/boot_sr.c | 1969 |
xboot_restart_from_backup | src/transaction/boot_sr.c | 2808 |
xboot_shutdown_server | src/transaction/boot_sr.c | 3044 |
xboot_get_server_session_key | src/transaction/boot_sr.c | 3121 |
xboot_register_client | src/transaction/boot_sr.c | 3149 |
xboot_unregister_client | src/transaction/boot_sr.c | 3313 |
xboot_notify_unregister_client | src/transaction/boot_sr.c | 3413 |
boot_server_all_finalize | src/transaction/boot_sr.c | 3841 |
boot_create_all_volumes | src/transaction/boot_sr.c | 4859 |
boot_remove_all_volumes | src/transaction/boot_sr.c | 5161 |
boot_get_db_charset_from_header | src/transaction/boot_sr.c | 5882 |
boot_client_type_to_string | src/transaction/boot_sr.c | 5914 |
boot_get_new_volume_name_and_id | src/transaction/boot_sr.c | 5969 |
boot_dbparm_save_volume | src/transaction/boot_sr.c | 6043 |
boot_get_db_parm | src/transaction/boot_sr.c | 295 |
boot_find_root_heap | src/transaction/boot_sr.c | 325 |
boot_reset_db_parm | src/transaction/boot_sr.c | 447 |
boot_db_name | src/transaction/boot_sr.c | 459 |
boot_db_full_name | src/transaction/boot_sr.c | 470 |
boot_get_lob_path | src/transaction/boot_sr.c | 479 |
boot_check_permanent_volumes | src/transaction/boot_sr.c | 1193 |
boot_mount | src/transaction/boot_sr.c | 1264 |
boot_ctrl_c_in_init_server | src/transaction/boot_sr.c | 1313 |
boot_make_session_server_key | src/transaction/boot_sr.c | 1931 |
boot_server_status | src/transaction/boot_sr.c | 227 |
boot_shutdown_server_at_exit | src/transaction/boot_sr.c | 255 |
boot_donot_shutdown_server_at_exit | src/transaction/boot_sr.c | 275 |
BOOT_DB_PARM struct | src/transaction/boot_sr.c | 117 |
BOOT_SERVER_STATUS enum | src/transaction/boot_sr.h | 67 |
BOOT_SERVER_SHUTDOWN_MODE enum | src/transaction/boot_sr.h | 72 |
BO_RESTART_ARG struct | src/transaction/boot_sr.h | 112 |
BOOT_DB_PATH_INFO struct | src/transaction/boot.h | 117 |
BOOT_SERVER_CREDENTIAL struct | src/transaction/boot.h | 152 |
HA_SERVER_STATE enum | src/transaction/boot.h | 131 |
BOOT_NORMAL_CLIENT_TYPE macro | src/transaction/boot.h | 34 |
boot_initialize_client | src/transaction/boot_cl.c | 275 |
boot_restart_client | src/transaction/boot_cl.c | 690 |
boot_shutdown_client | src/transaction/boot_cl.c | 1352 |
boot_shutdown_client_at_exit | src/transaction/boot_cl.c | 1413 |
boot_server_die_or_changed | src/transaction/boot_cl.c | 1458 |
boot_client_all_finalize | src/transaction/boot_cl.c | 1493 |
boot_client_initialize_css | src/transaction/boot_cl.c | 1590 |
install_system_metadata | src/transaction/boot_cl.c | 204 |
boot_client | src/transaction/boot_cl.c | 187 |
boot_check_locales | src/transaction/boot_cl.c | 2047 |
boot_check_timezone_checksum | src/transaction/boot_cl.c | 2115 |
boot_client_find_and_cache_class_oids | src/transaction/boot_cl.c | 2147 |
boot_build_catalog_classes | src/transaction/boot_cl.c | 1769 |
boot_destroy_catalog_classes | src/transaction/boot_cl.c | 1835 |
boot_get_host_connected | src/transaction/boot_cl.c | 1971 |
boot_get_lob_path (cl) | src/transaction/boot_cl.c | 1988 |
boot_get_host_name | src/transaction/boot_cl.c | 2006 |
boot_get_ip | src/transaction/boot_cl.c | 2021 |
boot_Server_credential | src/transaction/boot_cl.c | 123 |
boot_register_client (network wrapper) | src/communication/network_interface_cl.c | 3957 |
boot_unregister_client (network wrapper) | src/communication/network_interface_cl.c | 4037 |
net_server_start | src/communication/network_sr.c | 1058 |
net_server_init | src/communication/network_sr.c | 75 |
css_init | src/connection/server_support.c | 554 |
css_initialize_server_interfaces | src/connection/server_support.c | 516 |
css_change_ha_server_state | src/connection/server_support.c | (HA section) |
css_transit_ha_server_state | src/connection/server_support.c | 1643 |
clientids struct | src/transaction/client_credentials.hpp | 38 |
boot_client_credential struct | src/transaction/client_credentials.hpp | 85 |
clientids::set_ids | src/transaction/client_credentials.cpp | 90 |
clientids::set_system_internal | src/transaction/client_credentials.cpp | 165 |
boot_client_credential::pack | src/transaction/client_credentials.cpp | 263 |
catcls_init | src/object/schema_system_catalog_install.cpp | 257 |
catcls_install | src/object/schema_system_catalog_install.cpp | 315 |
catcls_compile_catalog_classes | src/storage/catalog_class.c | 4737 |
catcls_get_server_compat_info | src/storage/catalog_class.c | 4865 |
catcls_find_and_set_cached_class_oid | src/storage/catalog_class.c | 5728 |
catcls_initialize_class_oid_to_oid_hash_table | src/storage/catalog_class.c | 260 |
catcls_finalize_class_oid_to_oid_hash_table | src/storage/catalog_class.c | 286 |
catcls_Enable | src/storage/catalog_class.c | 108 |
main (cub_server) | src/executables/server.c | 276 |
main (cub_master) | src/executables/master.c | 1207 |
css_master_init | src/executables/master.c | 259 |
css_master_loop | src/executables/master.c | 1159 |
소스 검증 노트
섹션 제목: “소스 검증 노트”본 절은 본 문서의 주장을, 이 지식 베이스에 이미 들어 있는 복구 매니저 / heartbeat 문서들과 교차 점검한다.
복구 hand-off 는 cubrid-recovery-manager.md 와 일치한다.
그 문서가 “log_recovery 는 log_manager.c 의 log_initialize
가 active 로그 헤더에서 데이터베이스가 깨끗하게 종료되지 않았다
(is_shutdown == false) 고 판단할 때 호출된다” 고 적어 둔 것
과, 본 문서에서 부팅 측이 log_initialize 를 정확히 한 번 —
boot_restart_server 의 Phase G — 페이지 버퍼 / DWB 데몬 init
(Phase F) 와 카탈로그 캐시 재수화 (Phase I) 사이 에 호출한다는
것이 같은 진술의 양면이다. 이 순서가 교과서적인 세 패스를 안전
하게 만든다. 버퍼 풀이 살아 있어 redo 가 페이지 fix-and-apply
를 할 수 있고, 카탈로그 캐시는 비어 있어 복구 도중 무효화해야
할 인메모리 상태가 없다는 점이다. 핵심 불변식은
log_recovery 가 catcls_find_and_set_cached_class_oid 와
catcls_compile_catalog_classes 보다 먼저 돈다는 것이다. 위 소스 코드 가이드 절에서 확인된다. 복구 문서의 세 패스 타임라
인 그림은 본 문서의 Phase G 가 단일한 복구 전부 단계라는 표현
과 일치한다.
다섯 개의 복구 phase. cubrid-recovery-manager.md 가 복구-
phase enum 으로 LOG_RECVPHASE { LOG_RESTARTED, ANALYSIS, REDO, UNDO, FINISH_2PC } 를 든다. 부팅은 이 값들을 직접 쓰지 않는다. log_recovery 자신이 쓴다. 다만 부팅 측은 BO_IS_SERVER_RESTARTED
매크로로 log_Gl.rcv_phase 를 간접적으로 읽는다. 이
매크로는 boot_Server_status 를 키로 하지, rcv_phase 를 키로
하지 않는다. 둘은 독립이다. 부팅 측은 Phase O 에서
boot_server_status (BOOT_SERVER_UP) 가 호출되는 그 순간 (또는
first-create 의 step k) 부터 서버를 up 으로 본다. 그리고 그
시점은 log_recovery 안에서 log_Gl.rcv_phase = LOG_RESTARTED
가 세팅되는 시점보다 엄격히 늦다. 그러므로
BO_IS_SERVER_RESTARTED 가 true 라는 사실은 rcv_phase == LOG_RESTARTED 를 함의하지만, 그 역은 성립하지 않는다.
HA 상태 전이와 cubrid-heartbeat.md.
cubrid-heartbeat.md 가 master_heartbeat.c 안의 heartbeat
스레드를 다룬다. 부팅이 heartbeat 와 만나는 자리는 두 군데다. 마스터 실행 파일이 기동될 때의 hb_master_init 호출, 그리고 서
버가 재시작의 Phase N 에서 부르는 css_change_ha_server_state
호출. 서버의 전이 테이블 (css_transit_ha_server_state) 이 이
부팅/heartbeat 핸드셰이크가 강제되는 지점이다. IDLE → ACTIVE
를 마스터와의 협상 없이 요청한 서버는, 마스터가 이미 peer 를
승격해 둔 상태라면 거절될 수 있다. 복구가 성공한 이후 에 부팅
경로가 실패할 수 있는 유일한 자리가 이곳이다.
xboot_register_client 와 락 매니저.
cubrid-lock-manager.md 가 OID 키로 한 락 자원 조회를 다룬다.
부팅의 역할은 트랜잭션 디스크립터를 할당하는 것
(logtb_assign_tran_index) 이고, 이 디스크립터가 락이 ownership
참조를 위해 들고 다니는 자격-바인딩 구조체다.
xboot_register_client 안에서 db_user 를 돌리는
intl_identifier_upper — 즉, 자격을 대문자로 통일하는 동작 —
때문에 락-소유자 동등성이 대문자 식별자 비교가 된다는 점이 따라
나온다. 미묘한 불변식이다. 이 통일이 없다면 같은 사용자라도
대소문자만 다르면 락 매니저가 다른 사용자로 본다. register 시
점의 대문자화 프로토콜이 그것을 막는다.
boot_Db_parm 과 cubrid-disk-manager.md.
디스크 매니저 문서가 볼륨 포맷팅 (disk_format_*,
disk_get_boot_hfid) 을 다룬다. 부팅은 시스템 헤더의 내용
(BOOT_DB_PARM) 을 소유하지만, 첫 볼륨의 첫 페이지의 형식 은
디스크 매니저가 소유한다. disk_set_boot_hfid /
disk_get_boot_hfid 짝이 모듈 간 경계다. 부팅이 HFID 를 한 번
쓰고 (boot_create_all_volumes 의 step e), 재시작이 그것을 한 번
읽는다 (boot_restart_server 의 Phase C).
미해결 질문
섹션 제목: “미해결 질문”-
복구 병렬화 설정.
log_recovery_redo_parallel.{cpp,hpp}는log_initialize→log_recovery→log_recovery_redo경로에서 투명하게 호출된다. 부팅은 그 worker pool 의 크기를 어디에서도 세팅하지 않는다. 조사:log_recovery_redo_parallel.cpp의 생성자를 읽고,system_parameter.{c,h}에서PRM_ID_LOG_RECOVERY_*계열 파라미터가 있는지 확인.cubrid-recovery-manager.md의 미해결 질문 #1 과 교차한다. -
재시작을 통째로 막는 HA 상태 vs. degraded 동작을 허용하는 상태.
server_support.c의css_transit_ha_server_state테이블이 현재 상태와 HA 정책에 따라 요청을 받거나 거절한다.boot_restart_serverPhase N 만 봐서는HA_SERVER_STATE_TO_BE_STANDBY가 마스터를 기다리는 정지점인 지, Phase O 이전에STANDBY로 흘러가는 transient 상태인지를 판별할 수 없다. 조사: heartbeat 코드 안에서 상태 전이를 추적하고cubrid-heartbeat.md와 교차 참조. -
복구 도중의
BO_IS_SERVER_RESTARTED()동작.boot_server_status (BOOT_SERVER_UP)는 Phase O 에서 — 복구 가 끝난 뒤에야 — 세팅된다. 그러므로 redo 도중에 참조되는 서브시스템 (버퍼 매니저, 락 매니저) 들은 redo 가 페이지 변경 을 재생하는 동안BO_IS_SERVER_RESTARTED == false를 본다. redo 자체에는 문제가 없다 (redo 는 부팅 상태 플래그가 아니라 페이지 LSA 를 본다). 다만 어느 서브시스템이든 방어적으로if (BO_IS_SERVER_RESTARTED ()) ...식으로 게이팅하면, 복구 시점 로직을 안전하게 건너 뛸 수 있는 신뢰할 만한 수단이 된다는 점이다. 미해결: 복구 중에 살아 있어야 했는데BO_IS_SERVER_RESTARTED게이트에 걸려 조용히 건너 뛰어진 서브시스템이 있는가? -
재시작 초기의 PL 서버 init. Phase B 가
pl_server_init (db_name)을 부르는데, 이는 로그가 열리기 전 이다 (Phase C). 소스의 주석은fork()와의 상호 작용으 로 정당화한다.log_initialize동안 큰 메모리 할당이 일어 나면fork()가 실패할 수 있다는 것이다. PL 서버는 부모가 작을 때 일찍 fork되어 둔다. 미해결: SA 모드 (fork 가 없는 환경) 에는 어떤 의미가 있는가? 그리고 일찍 init된 PL 서버가 redo 이전에 만들어져 있어, redo 가 적용한 데이터베이스 상태 변경을 놓칠 가능성이 없는가? -
카탈로그-클래스 호환성 검사.
catcls_compile_catalog_classes는 Phase L 에서 돌고,boot_set_skip_check_ct_classes가 세팅하는skip_to_check_ct_classes_for_rebuild플래그로 보호된다. 이 플래그는 카탈로그 재구축 (synccolldb, 마이그레이션 유틸리 티) 동안에만 true 다. 카탈로그가 재시작 시점에 진짜로 손 상되어 이 검사가 실패한다면 복구 의미론은 어떠한가? 사용 가 능한 에러를 내는가, 아니면 fatal 종료인가? -
마스터 / 서버 접속 실패 윈도우.
cub_server가 마스터에css_change_ha_server_state를 보내는 Phase N 과 listening 소켓을 여는css_init사이에는, 마스터는 서버의 HA 상태를 알지만 클라이언트는 아직 접속할 수 없는 짧은 윈도우가 있다. 이 윈도우 동안 도착한 클라이언트에게 마스터는 무엇을 보고하 는가? -
부분 실패 시의 대칭 finalization.
boot_restart_server안의 에러-회복 분기들이boot_server_all_finalize (..., BOOT_SHUTDOWN_EXCEPT_COMMON_MODULES)를 호출한다. SERVER_MODE 에서는boot_server_all_finalize상단의 매크로가 이를BOOT_SHUTDOWN_ALL_MODULES로 강제 override 한다. SERVER_MODE 부분 실패 재시작에서 이 강제 override 의 결과는? 구체적으로 msgcat / sysprm 까지 풀어 버려서, 그 후 retry 자체를 막는가?
CUBRID 소스 (/data/hgryoo/references/cubrid/)
섹션 제목: “CUBRID 소스 (/data/hgryoo/references/cubrid/)”src/transaction/boot_sr.{c,h}— 서버 부팅.src/transaction/boot_cl.{c,h}— 클라이언트 부팅.src/transaction/boot.h— 공유 타입 (BOOT_DB_PATH_INFO,BOOT_SERVER_CREDENTIAL,HA_SERVER_STATE, 클라이언트 타입 매크로들).src/transaction/client_credentials.{cpp,hpp}— 자격 packable.src/transaction/log_recovery.c— 부팅이 hand-off 하는 복구 드라이버.src/storage/catalog_class.c—catcls_compile_catalog_classes,catcls_get_server_compat_info,catcls_find_and_set_cached_class_oid,catcls_initialize_class_oid_to_oid_hash_table.src/object/schema_system_catalog_install.cpp—catcls_init,catcls_install(테이블별 카탈로그 빌더).src/communication/network_sr.c—net_server_start.boot_restart_server의 얇은 래퍼다.src/communication/network_interface_cl.c—boot_register_client(CS-mode RPC 래퍼).src/connection/server_support.c—css_init,css_change_ha_server_state,css_transit_ha_server_state.src/executables/server.c—cub_servermain.src/executables/master.c—cub_mastermain.
같은 지식 베이스 안의 자매 문서
섹션 제목: “같은 지식 베이스 안의 자매 문서”knowledge/code-analysis/cubrid/cubrid-recovery-manager.md— 세 패스 재시작. 본 문서 Phase G 의log_initialize가 호출하는 그 모듈.knowledge/code-analysis/cubrid/cubrid-heartbeat.md—cub_master안의 heartbeat 스레드. Phase N 이 참조하는 HA 상태 전이.knowledge/code-analysis/cubrid/cubrid-page-buffer-manager.mdPhase F 에서 데몬이 시작되는 버퍼 풀.knowledge/code-analysis/cubrid/cubrid-log-manager.md— 부팅 이 부르는log_initialize의 WAL.knowledge/code-analysis/cubrid/cubrid-catalog-manager.md—catcls_install이 부트스트랩하고catcls_compile_catalog_classes가 적재하는 카탈로그.knowledge/code-analysis/cubrid/cubrid-disk-manager.md— 부팅이 부르는disk_format_first_volume와disk_get_boot_hfid의 디스크 매니저.knowledge/code-analysis/cubrid/cubrid-authentication.md—install_system_metadata가 부르는au_install/au_start의 인증 서브시스템.knowledge/code-analysis/cubrid/cubrid-transaction.md—xboot_register_client가 부르는logtb_assign_tran_index의 TDES.knowledge/code-analysis/cubrid/cubrid-vacuum.md— Phase E 의vacuum_initialize와 Phase H 의vacuum_boot가 속한 vacuum 서브시스템.knowledge/code-analysis/cubrid/cubrid-ha-replication.md— Phase N 의css_change_ha_server_state호출이 끼어 들어 가는 HA 복제 경로.
교과서 챕터 (knowledge/research/dbms-general/ 아래)
섹션 제목: “교과서 챕터 (knowledge/research/dbms-general/ 아래)”- Database Internals (Petrov), Ch. 5 §Recovery Algorithm — 부팅 Phase G 가 디스패치하는 ARIES 재시작.
- Bovet & Cesati, Understanding the Linux Kernel, 부팅 프로세 스 장 — 본 문서 학술적 배경의 의존성 그래프와 동형인 OS-측 서술.