콘텐츠로 이동

(KO) CUBRID 부팅 — 서버 기동, 최초 생성, 재시작-복구 디스패치, 클라이언트 접속

목차

프로세스가 시작되는 시점의 관계형 DBMS 는 서브시스템 의존성에 대한 엄격한 위상 정렬이다. 각 서브시스템이 그 직전 서브시스템을 요구하는 방식은, 운영체제 커널이 부팅 도중 자기보다 먼저 깨워야 할 모듈을 요구하는 방식과 정확히 같다. 그리고 그 순서를 어긋 나게 만들었을 때의 치명성도 같다. 증상은 폭포처럼 번지고, 정작 근본 원인은 부팅 코드를 이미 읽은 사람이 아니면 보이지 않는다.

디스크 상주 엔진이라면 거의 모두 재현하는 의존성 그래프는 다음 과 같다.

  1. 에러 보고와 메시지 카탈로그. 이 둘이 살아 있기 전까지는 다른 어떤 모듈도 실패를 사람이 읽을 수 있는 형태로 보고할 수 없다. 의존성은 없다.
  2. 시스템 파라미터 로더. 설정 파일을 읽는다. 에러 보고기가 먼저 살아 있어야 한다. 그렇지 않으면 파싱 실패가 침묵으로 사라진다는 점이다.
  3. 메모리/area 할당기. 풀 크기가 설정 가능한 값이므로 파라 미터 로더에 묶인다.
  4. 로케일, 타임존, 캐릭터셋. 거의 무동작 모듈이다. 다만 실패 보고를 위해 메시지 카탈로그가 필요하다.
  5. 스레드/워커 풀 매니저. 이 지점부터는 진단 출력이나 시스템 트랜잭션을 위해 어떤 모듈이든 THREAD_ENTRY 가 필요할 수 있다.
  6. 파일 I/O 계층. 디스크립터 단위로 볼륨을 마운트한다. OS 계층과 에러 보고 외에는 의존하지 않는다.
  7. 페이지 버퍼 풀. 디스크 페이지를 캐시한다. 파일 I/O 와 파라미터 (캐시 크기, 교체 정책) 에 의존한다.
  8. 로그 매니저. WAL 을 읽고 쓴다. 데이터 측은 페이지 버퍼, 로그 자체는 직접 파일 I/O 가 필요하다.
  9. 복구 매니저. 로그를 따라 걸으며 데이터베이스를 충돌-일관 상태로 되돌린다. 로그 매니저, 페이지 버퍼, 디스크 매니저가 모두 필요하다.
  10. 카탈로그 (시스템 테이블). heap 파일, B+Tree, 파일 트래커에 의존한다. 그런데 이 셋의 페이지가 복구로 복원되기 전까지는 어떤 질의에도 답할 수 없다.
  11. 락과 MVCC. 락 매니저는 자원 식별을 위해 카탈로그 OID 가 필요하다. MVCC 는 복구 매니저가 다시 만들어 둔 active 트 랜잭션 테이블이 필요하다.
  12. 질의 옵티마이저/플랜 캐시. 카탈로그가 있어야 한다.
  13. 네트워크 리스너. 가장 마지막에 깨어난다. 카탈로그가 로딩 되기 전에 클라이언트를 받아 들이면, 첫 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) 인 그래프를 그린다.

디스크 상주 관계형 엔진은 부팅을 똑같은 몇 가지 관습을 공유한다. 이 관습이 문헌에 규범으로 적혀 있는 것은 아니다. 위 의존성 그래프에서 자연스럽게 흘러나온 결과다.

두 바이너리로 나뉘는 부트스트랩

섹션 제목: “두 바이너리로 나뉘는 부트스트랩”

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_initializehdr.is_shutdown == falselog_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 명칭
프로세스 진입점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-offlog_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-clientxboot_register_client (boot_sr.c)
클라이언트 측 register-client RPCboot_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_mastermain() in src/executables/master.c
마스터 heartbeat 초기화hb_master_init — 마스터 main() 안에서 호출
서버 종료xboot_shutdown_server (boot_sr.c)
서버 finalizeboot_server_all_finalize (boot_sr.c)
클라이언트 종료boot_shutdown_client (boot_cl.c)
클라이언트 finalizeboot_client_all_finalize (boot_cl.c)

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_installboot_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)
int
xboot_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 int
boot_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.c
static int
install_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.cnet_server_start 가 호출하고, 그 위는 cub_servermain() (server.c) 다. 부팅 서브시스템 안에서 가장 긴 함수다. 라인이 아니라 페이즈 단위로 본다.

// boot_restart_server — src/transaction/boot_sr.c (condensed, annotated by phase)
int
boot_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

두 흐름이 같은 모듈 안에 있고, 그 둘 사이의 선택은 어느 실행 파일/유틸리티가 들어 오느냐로 결정된다. 결정은 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_orderpreferred_hosts 의 설정 여부에 따라 preferred-then-default 또는 순차-then-random 이다.

// boot_register_client — src/communication/network_interface_cl.c (CS_MODE branch)
int
boot_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_CREDENTIALclientids (in client_credentials.hpp) 를 상속한다.

// boot_client_credential — src/transaction/client_credentials.hpp
struct 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.hBOOT_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 로 들어 간다. 락 매니저가 락 획득 시 사용자 멤버십을 검사하고, 스키마 매니저가 카탈로그 접근 시 가시성을 검사한다.

cub_mastercub_server 는 별개의 프로세스다. 마스터가 heartbeat (hb_master_init) 를 돌리고, 다중 호스트 토폴로지를 유지하며, 클러스터의 권위 있는 HA 상태가 살아 있는 자리다. 재시 작 중인 cub_server 는 마스터를 두 군데서 참조한다.

  1. boot_make_session_server_key — Phase A 단계. time() 과 호스트 IP로 프로세스별 세션 키를 만들고, 이를 마스터를 향한 서버 신원으로 사용한다.
  2. css_change_ha_server_state — Phase N. PRM_ID_HA_SERVER_STATE 파라미터에서 (기본 HA_SERVER_STATE_IDLE) 읽어 들인 요청 HA 상태를 마스터로 전송하고 확인을 기다린다. 전이는 server_support.ccss_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 상태일 때 정상 클라이언트의 접속을 거절할 수 있는 곳 이다.

// xboot_shutdown_server — src/transaction/boot_sr.c (condensed)
bool
xboot_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)
void
boot_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_clientboot_client_all_finalize 에 있다.

// boot_client_all_finalize — src/transaction/boot_cl.c (condensed)
void
boot_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 는 부팅 이야기의 세 번째 실행 파일이다. 서버 부팅 보다 훨씬 짧지만, 모든 클라이언트 접속 경로 위에 놓인다. 마스 터가 (database_name, host) 를 살아 있는 서버 프로세스에 매핑 하는 레지스트리이기 때문이다.

// master.c::main — src/executables/master.c (condensed)
int
main (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 가 자세히 다룬다.

마지막으로, Phase A 부터 Phase O 까지 묶어 주는 실행 파일이다.

// server.c::main — src/executables/server.c (condensed)
int
main (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_startboot_restart_serverlog_initializelog_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_serverfrom_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 연산이 사용한다.
  • 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_init (schema_system_catalog_install.cpp) — 시스템 클래스/뷰 정의의 정적 clist / vclist 를 채운다.
  • catcls_install (schema_system_catalog_install.cpp) — clist 의 각 엔트리를 catalog_builder::create_and_mark_system_classcatalog_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.
  • main in src/executables/server.ccub_server 진입점. 시그널 핸들러 다음에 net_server_start (argv[1]).
  • main in src/executables/master.ccub_master 진입점. utility init, master 소켓 페어, css_master_init, hb_master_init, css_master_loop.
심볼파일라인
xboot_initialize_serversrc/transaction/boot_sr.c1385
boot_restart_serversrc/transaction/boot_sr.c1969
xboot_restart_from_backupsrc/transaction/boot_sr.c2808
xboot_shutdown_serversrc/transaction/boot_sr.c3044
xboot_get_server_session_keysrc/transaction/boot_sr.c3121
xboot_register_clientsrc/transaction/boot_sr.c3149
xboot_unregister_clientsrc/transaction/boot_sr.c3313
xboot_notify_unregister_clientsrc/transaction/boot_sr.c3413
boot_server_all_finalizesrc/transaction/boot_sr.c3841
boot_create_all_volumessrc/transaction/boot_sr.c4859
boot_remove_all_volumessrc/transaction/boot_sr.c5161
boot_get_db_charset_from_headersrc/transaction/boot_sr.c5882
boot_client_type_to_stringsrc/transaction/boot_sr.c5914
boot_get_new_volume_name_and_idsrc/transaction/boot_sr.c5969
boot_dbparm_save_volumesrc/transaction/boot_sr.c6043
boot_get_db_parmsrc/transaction/boot_sr.c295
boot_find_root_heapsrc/transaction/boot_sr.c325
boot_reset_db_parmsrc/transaction/boot_sr.c447
boot_db_namesrc/transaction/boot_sr.c459
boot_db_full_namesrc/transaction/boot_sr.c470
boot_get_lob_pathsrc/transaction/boot_sr.c479
boot_check_permanent_volumessrc/transaction/boot_sr.c1193
boot_mountsrc/transaction/boot_sr.c1264
boot_ctrl_c_in_init_serversrc/transaction/boot_sr.c1313
boot_make_session_server_keysrc/transaction/boot_sr.c1931
boot_server_statussrc/transaction/boot_sr.c227
boot_shutdown_server_at_exitsrc/transaction/boot_sr.c255
boot_donot_shutdown_server_at_exitsrc/transaction/boot_sr.c275
BOOT_DB_PARM structsrc/transaction/boot_sr.c117
BOOT_SERVER_STATUS enumsrc/transaction/boot_sr.h67
BOOT_SERVER_SHUTDOWN_MODE enumsrc/transaction/boot_sr.h72
BO_RESTART_ARG structsrc/transaction/boot_sr.h112
BOOT_DB_PATH_INFO structsrc/transaction/boot.h117
BOOT_SERVER_CREDENTIAL structsrc/transaction/boot.h152
HA_SERVER_STATE enumsrc/transaction/boot.h131
BOOT_NORMAL_CLIENT_TYPE macrosrc/transaction/boot.h34
boot_initialize_clientsrc/transaction/boot_cl.c275
boot_restart_clientsrc/transaction/boot_cl.c690
boot_shutdown_clientsrc/transaction/boot_cl.c1352
boot_shutdown_client_at_exitsrc/transaction/boot_cl.c1413
boot_server_die_or_changedsrc/transaction/boot_cl.c1458
boot_client_all_finalizesrc/transaction/boot_cl.c1493
boot_client_initialize_csssrc/transaction/boot_cl.c1590
install_system_metadatasrc/transaction/boot_cl.c204
boot_clientsrc/transaction/boot_cl.c187
boot_check_localessrc/transaction/boot_cl.c2047
boot_check_timezone_checksumsrc/transaction/boot_cl.c2115
boot_client_find_and_cache_class_oidssrc/transaction/boot_cl.c2147
boot_build_catalog_classessrc/transaction/boot_cl.c1769
boot_destroy_catalog_classessrc/transaction/boot_cl.c1835
boot_get_host_connectedsrc/transaction/boot_cl.c1971
boot_get_lob_path (cl)src/transaction/boot_cl.c1988
boot_get_host_namesrc/transaction/boot_cl.c2006
boot_get_ipsrc/transaction/boot_cl.c2021
boot_Server_credentialsrc/transaction/boot_cl.c123
boot_register_client (network wrapper)src/communication/network_interface_cl.c3957
boot_unregister_client (network wrapper)src/communication/network_interface_cl.c4037
net_server_startsrc/communication/network_sr.c1058
net_server_initsrc/communication/network_sr.c75
css_initsrc/connection/server_support.c554
css_initialize_server_interfacessrc/connection/server_support.c516
css_change_ha_server_statesrc/connection/server_support.c(HA section)
css_transit_ha_server_statesrc/connection/server_support.c1643
clientids structsrc/transaction/client_credentials.hpp38
boot_client_credential structsrc/transaction/client_credentials.hpp85
clientids::set_idssrc/transaction/client_credentials.cpp90
clientids::set_system_internalsrc/transaction/client_credentials.cpp165
boot_client_credential::packsrc/transaction/client_credentials.cpp263
catcls_initsrc/object/schema_system_catalog_install.cpp257
catcls_installsrc/object/schema_system_catalog_install.cpp315
catcls_compile_catalog_classessrc/storage/catalog_class.c4737
catcls_get_server_compat_infosrc/storage/catalog_class.c4865
catcls_find_and_set_cached_class_oidsrc/storage/catalog_class.c5728
catcls_initialize_class_oid_to_oid_hash_tablesrc/storage/catalog_class.c260
catcls_finalize_class_oid_to_oid_hash_tablesrc/storage/catalog_class.c286
catcls_Enablesrc/storage/catalog_class.c108
main (cub_server)src/executables/server.c276
main (cub_master)src/executables/master.c1207
css_master_initsrc/executables/master.c259
css_master_loopsrc/executables/master.c1159

본 절은 본 문서의 주장을, 이 지식 베이스에 이미 들어 있는 복구 매니저 / heartbeat 문서들과 교차 점검한다.

복구 hand-off 는 cubrid-recovery-manager.md 와 일치한다. 그 문서가 log_recoverylog_manager.clog_initialize 가 active 로그 헤더에서 데이터베이스가 깨끗하게 종료되지 않았다 (is_shutdown == false) 고 판단할 때 호출된다” 고 적어 둔 것 과, 본 문서에서 부팅 측이 log_initialize 를 정확히 한 번 — boot_restart_server 의 Phase G — 페이지 버퍼 / DWB 데몬 init (Phase F) 와 카탈로그 캐시 재수화 (Phase I) 사이 에 호출한다는 것이 같은 진술의 양면이다. 이 순서가 교과서적인 세 패스를 안전 하게 만든다. 버퍼 풀이 살아 있어 redo 가 페이지 fix-and-apply 를 할 수 있고, 카탈로그 캐시는 비어 있어 복구 도중 무효화해야 할 인메모리 상태가 없다는 점이다. 핵심 불변식은 log_recoverycatcls_find_and_set_cached_class_oidcatcls_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.mdmaster_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_parmcubrid-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).

  1. 복구 병렬화 설정. log_recovery_redo_parallel.{cpp,hpp}log_initializelog_recoverylog_recovery_redo 경로에서 투명하게 호출된다. 부팅은 그 worker pool 의 크기를 어디에서도 세팅하지 않는다. 조사: log_recovery_redo_parallel.cpp 의 생성자를 읽고, system_parameter.{c,h} 에서 PRM_ID_LOG_RECOVERY_* 계열 파라미터가 있는지 확인. cubrid-recovery-manager.md 의 미해결 질문 #1 과 교차한다.

  2. 재시작을 통째로 막는 HA 상태 vs. degraded 동작을 허용하는 상태. server_support.ccss_transit_ha_server_state 테이블이 현재 상태와 HA 정책에 따라 요청을 받거나 거절한다. boot_restart_server Phase N 만 봐서는 HA_SERVER_STATE_TO_BE_STANDBY 가 마스터를 기다리는 정지점인 지, Phase O 이전에 STANDBY 로 흘러가는 transient 상태인지를 판별할 수 없다. 조사: heartbeat 코드 안에서 상태 전이를 추적하고 cubrid-heartbeat.md 와 교차 참조.

  3. 복구 도중의 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 게이트에 걸려 조용히 건너 뛰어진 서브시스템이 있는가?

  4. 재시작 초기의 PL 서버 init. Phase B 가 pl_server_init (db_name) 을 부르는데, 이는 로그가 열리기 이다 (Phase C). 소스의 주석은 fork() 와의 상호 작용으 로 정당화한다. log_initialize 동안 큰 메모리 할당이 일어 나면 fork() 가 실패할 수 있다는 것이다. PL 서버는 부모가 작을 때 일찍 fork되어 둔다. 미해결: SA 모드 (fork 가 없는 환경) 에는 어떤 의미가 있는가? 그리고 일찍 init된 PL 서버가 redo 이전에 만들어져 있어, redo 가 적용한 데이터베이스 상태 변경을 놓칠 가능성이 없는가?

  5. 카탈로그-클래스 호환성 검사. catcls_compile_catalog_classes 는 Phase L 에서 돌고, boot_set_skip_check_ct_classes 가 세팅하는 skip_to_check_ct_classes_for_rebuild 플래그로 보호된다. 이 플래그는 카탈로그 재구축 (synccolldb, 마이그레이션 유틸리 티) 동안에만 true 다. 카탈로그가 재시작 시점에 진짜로 손 상되어 이 검사가 실패한다면 복구 의미론은 어떠한가? 사용 가 능한 에러를 내는가, 아니면 fatal 종료인가?

  6. 마스터 / 서버 접속 실패 윈도우. cub_server 가 마스터에 css_change_ha_server_state 를 보내는 Phase N 과 listening 소켓을 여는 css_init 사이에는, 마스터는 서버의 HA 상태를 알지만 클라이언트는 아직 접속할 수 없는 짧은 윈도우가 있다. 이 윈도우 동안 도착한 클라이언트에게 마스터는 무엇을 보고하 는가?

  7. 부분 실패 시의 대칭 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.ccatcls_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.cppcatcls_init, catcls_install (테이블별 카탈로그 빌더).
  • src/communication/network_sr.cnet_server_start. boot_restart_server 의 얇은 래퍼다.
  • src/communication/network_interface_cl.cboot_register_client (CS-mode RPC 래퍼).
  • src/connection/server_support.ccss_init, css_change_ha_server_state, css_transit_ha_server_state.
  • src/executables/server.ccub_server main.
  • src/executables/master.ccub_master main.

같은 지식 베이스 안의 자매 문서

섹션 제목: “같은 지식 베이스 안의 자매 문서”
  • knowledge/code-analysis/cubrid/cubrid-recovery-manager.md — 세 패스 재시작. 본 문서 Phase G 의 log_initialize 가 호출하는 그 모듈.
  • knowledge/code-analysis/cubrid/cubrid-heartbeat.mdcub_master 안의 heartbeat 스레드. Phase N 이 참조하는 HA 상태 전이.
  • knowledge/code-analysis/cubrid/cubrid-page-buffer-manager.md Phase F 에서 데몬이 시작되는 버퍼 풀.
  • knowledge/code-analysis/cubrid/cubrid-log-manager.md — 부팅 이 부르는 log_initialize 의 WAL.
  • knowledge/code-analysis/cubrid/cubrid-catalog-manager.mdcatcls_install 이 부트스트랩하고 catcls_compile_catalog_classes 가 적재하는 카탈로그.
  • knowledge/code-analysis/cubrid/cubrid-disk-manager.md — 부팅이 부르는 disk_format_first_volumedisk_get_boot_hfid 의 디스크 매니저.
  • knowledge/code-analysis/cubrid/cubrid-authentication.mdinstall_system_metadata 가 부르는 au_install / au_start 의 인증 서브시스템.
  • knowledge/code-analysis/cubrid/cubrid-transaction.mdxboot_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-측 서술.