(KO) CUBRID SA vs CS 런타임 — 단독(엔진 내장) 모드와 클라이언트-서버(소켓 통신) 모드
목차
학술적 배경
섹션 제목: “학술적 배경”현대적인 관계형 엔진은 단일 프로그램이 아니다. 클라이언트 측, 서버 측, 그리고 둘을 잇는 와이어 포맷으로 이루어진 프로토콜 경계다. 엔진이 초기 설계 단계에서 — 대개 첫 커밋 때, 거의 번복되지 않는 — 내려야 하는 핵심 선택이 있다. 그 경계를 런타임 경계(두 프로세스, 실제 TCP 소켓, 양방향 직렬화)로 둘 것인가, 아니면 컴파일 타임 경계(단일 프로세스, 함수 호출, memcpy)로 둘 것인가다. 두 방향은 서로 교환 가능하지 않다. 각각은 특정 부류의 연산을 쉽게 만드는 대신 다른 부류를 어렵게 만든다.
두 프로세스 방식 — 대부분의 운영 엔진이 택하는 형태다 — 은 스토리지 매니저, 트랜잭션 매니저, 락 매니저, 버퍼 풀, 복구 매니저를 수명이 긴 데몬 프로세스 안에 넣는다. 클라이언트 도구든, 애플리케이션이든, 관리 유틸리티든 모두 네트워크 프로토콜로 그 데몬에 연결한다. 데몬과 같은 호스트에서 실행되는 도구도 소켓을 통한다. 데몬은 온-디스크 데이터베이스 상태의 유일한 중재자다. 같은 행에 쓰기를 시도하는 두 클라이언트가 일관된 잠금을 보게 되는 이유는, 두 클라이언트 모두 같은 인메모리 락 테이블을 거치기 때문이다. 버퍼 풀이 공유되는 이유는 그것이 데몬의 힙에 살기 때문이다. 충돌 복구는 데몬이 시작될 때 단 한 번 일어난다. 데몬을 거치지 않고 디스크 상의 바이트에 접근하는 경우는 백업이나 파일시스템 수준의 복사뿐이다.
두 프로세스 방식에는 두 가지 비용이 따른다. 첫 번째는 운영상의 비용이다. 다른 무엇도 작동하기 전에 데몬이 먼저 실행 중이어야 한다. 두 번째는 관리상의 비용이다. “처음부터 데이터베이스를 다시 만든다”, 모든 레코드를 물리적으로 옮겨 힙을 압축한다, WAL을 앞으로 재생하다가 멈춘다, “디스크가 쓸 수 있는 최대 속도로 수십억 개의 행을 적재한다” — 이런 종류의 작업들은 데이터 베이스에 대한 독점적 접근을 원한다. 데몬으로 이 작업들을 처리하려면 데몬이 다른 연결을 받지 않도록 설득하고, 작업을 수행 하고, 다시 연결을 받도록 설득하는 과정이 필요하다. 데몬 없이 할 수 있다면 더 빠르고, 더 단순하며, 에러 발생 여지도 적다. 엔진을 유틸리티의 프로세스 안에서 직접 실행할 수 있다면 말이다.
이 지점에서 프로세스 내장 방식이 등장한다. 거의 모든 엔진이 어떤 형태로든 이 방식이 있다. 데몬이 페이지, 로그 레코드, 락, B+Tree를 관리하는 데 쓰는 코드와 동일한 코드를 라이브러리 로 컴파일한다. 단독 유틸리티는 그 라이브러리를 자기 바이너리에 링크해, 데이터베이스 파일을 직접 열고, 필요한 복구를 실행하고, 작업을 수행하고, 페이지를 플러시하고, 종료한다. 소켓도 없고 데몬도 없다. 유틸리티가 실행되는 동안만큼은 유틸리티 자체가 곧 엔진이다. 이 방식의 비용 역시 운영상의 것이다. 유틸리티는 실행 되는 내내 데이터베이스 파일을 독점적으로 잠가야 한다. 두 유틸 리티가 동시에 실행되거나, 유틸리티가 돌면서 데몬도 함께 실행 된다면 데이터베이스가 손상되기 때문이다.
세 번째 축은 파서, 옵티마이저, 질의 실행기를 어떻게 할 것 인가의 문제다. 이 컴포넌트들은 SQL 텍스트를 실행 가능한 플랜으로 변환한다는 의미에서 클라이언트 측에 속하지만, 디스크에 있는 스키마, 통계, 클래스 메타데이터가 있어야 한다. 엔진은 이들을 경계 어디에든 배치할 수 있다. 데몬 안에(모든 것이 집중되고, 와이 어 포맷은 SQL 텍스트), 클라이언트 라이브러리 안에(데몬은 SQL을 보지 않고, 와이어 포맷은 이미 계획된 실행 가능한 바이트코드), 또는 하이브리드(파서는 클라이언트, 옵티마이저는 서버) 방식으로 배치할 수 있다. CUBRID는 클라이언트 측 배치를 선택했다. SQL은 사용자 코드가 실행되는 측에서 파싱되고, 이름이 해석되고, XASL로 변환된다. XASL 스트림이 와이어를 건넌다.
이 두 특성의 조합 — 엔진 전체를 자기 프로세스로 컴파일하는 독점
접근 유틸리티와, 경계 어느 쪽에서 실행되든 항상 클라이언트 측에
있는 파서 — 을 CUBRID는 SA 모드(standalone)라고 부른다.
반대 조합 — 클라이언트 측만 링크하고 별도로 실행 중인 cub_server
에 네트워크 프로토콜로 닿는 유틸리티와 애플리케이션 — 이 CS
모드(client-server)다. 이 선택은 컴파일 타임에 결정되고
(서로 다른 전처리기 심볼, 서로 다른 라이브러리), 런타임에는
유틸리티의 런처가 libcubridsa.so 또는 libcubridcs.so 중 어느
쪽을 dlopen하느냐로 해소된다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”성숙한 엔진은 저마다 같은 질문에 답해야 했다 — “데몬이 없을 때 사용자는 무엇을 실행하는가?” 그 답들은 제각각이며, 그 차이가 흥미롭다.
PostgreSQL은 postgres --single으로 단일-프로세스 모드에
접근할 수 있다. 이른바 단일 사용자 백엔드다. 이 모드는 주로
포스트마스터가 시작되지 않는 충돌 복구 상황에 쓰인다. 사용자가
postgres 바이너리를 직접 실행하면 그 바이너리가 데이터 디렉터리
를 열고, WAL 복구를 실행하고, stdin/stdout에 SQL 프롬프트를
띄운다. 스토리지 위의 모든 계층 — 파서, 플래너, 실행기 — 이 같은
프로세스에서 돈다. 온-디스크 포맷은 멀티-사용자 모드와 동일하다.
차이는 포스트마스터도, 공유 메모리도, 다른 백엔드도 없다는 것뿐
이다. 복구 유틸리티 pg_resetwal은 같은 발상의 다른 갈래다. 어떤
백엔드도 거치지 않고 PostgreSQL의 WAL 파일 포맷을 직접 읽어
컨트롤 파일을 다시 쓰는 바이너리다.
SQLite는 프로세스 내장 방식만 있는 극단적인 사례다. SQLite
에는 데몬이 없다. 라이브러리 libsqlite3.so가 곧 엔진이고, SQLite
를 쓰는 모든 프로그램이 이를 직접 링크한다. 클라이언트 모드가
없는 이유는 서버가 없기 때문이다. 프로세스 간 동시성은 데이터베이
스 파일 자체의 파일 락(POSIX 자문 락, 또는 이후 버전에서는 WAL
모드 공유 메모리)으로 처리된다. 이 설계의 단점은 두 SQLite “클라
이언트”가 버퍼 풀을 공유할 수 없다는 것이다. 각 프로세스가 자체
페이지 캐시를 갖는다. 쓰기는 파일 락으로 직렬화되어야 한다.
장점은 엔진이 마이크로초 단위로 시작되고 운영 부담이 없다는 점
이다.
Oracle은 다른 방식의 분리를 사용한다. 기본 Oracle 바이너리는 실행 중인 인스턴스(섀도 프로세스 모델)에 연결한다. 그러나 Oracle은 SQL*Loader와 Data Pump를 위한 Direct Path API도 제공 한다. 이로 클라이언트 프로세스가 SQL 계층을 우회해 데이터 파일에 블록을 직접 쓸 수 있다. 블록은 사용자 공간에서 포맷되어 서버로 스트리밍되거나(여전히 서버가 디스크 쓰기를 중재), 병렬 다이렉트 패스로 세그먼트 수준에서 적절한 락을 보유한 클라이언트 프로세스가 직접 쓴다. 아키텍처적 교훈은 이것이다. 순수하게 두 프로세스 방식인 엔진조차 클라이언트가 온-디스크 구조를 직접 구성할 수 있는 코드 경로가 필요하다.
MySQL은 역사적으로 libmysqld라는 임베디드 서버 라이브러리
를 제공했다. MySQL 서버 전체를 클라이언트 바이너리에 링크하는
방식이다. 서버 모드와 임베디드 모드 바이너리는 같은 스토리지 및
SQL 코드 경로를 사용하되 전송 방식만 달랐다. 임베디드 모드는 서버
함수를 직접 호출하고, 일반 모드는 클라이언트 프로토콜을 썼다.
이 라이브러리는 MySQL 8에서 사실상 지원 중단되었는데, 같은 코드
베이스에서 두 가지 전송 경로를 유지하는 것이 지속적인 버그 원인
이었기 때문이다. SA-vs-CS 분기는 실제로 반복되는 엔지니어링 비용
이다.
CUBRID는 MySQL 임베디드 모델에 가장 가깝다. 다만 PostgreSQL
의 postgres --single과 공유하는 중요한 개선점이 하나 있다.
SA 모드 유틸리티가 데몬 바이너리의 추가 모드가 아닌, 유틸리
티의 런처가 필요할 때 불러오는 별도의 라이브러리(libcubridsa.so)
이다. 같은 소스 트리가 세 번 컴파일된다 — cub_server
용으로 SERVER_MODE로 한 번, libcubridsa.so용으로 SA_MODE로
한 번, libcubridcs.so용으로 CS_MODE로 한 번. 관리 유틸리티
(cub_admin, csql, cub_compactdb, cub_loaddb)는 유틸리티별
분류 테이블에 따라 런타임에 어느 쪽을 불러올지 결정한다.
CUBRID의 구현
섹션 제목: “CUBRID의 구현”CUBRID의 SA/CS 구분은 층이 나뉘어 있다. 전처리기 플래그, 라이브 러리 구성, 유틸리티별 분류, 런타임 라이브러리 선택, 호출별 디스 패치 분기 — 이렇게 다섯 층의 메커니즘이 있으며, 각 층은 그 아래 층 위에 세워진다.
컴파일 타임 선택
섹션 제목: “컴파일 타임 선택”같은 .c와 .cpp 파일들이 sa/CMakeLists.txt와 cs/CMakeLists.txt
(그리고 많은 경우 cubrid/CMakeLists.txt)에도 등장한다. 그러나
세 CMake 타깃은 서로 다른 target_compile_definitions를 전달해
전처리기가 세 가지 다른 번역 단위를 만들어 내도록 한다. 관련
부분은 다음과 같다.
# cubrid/CMakeLists.txt — cub_server 데몬target_compile_definitions(cubrid PRIVATE SERVER_MODE EnableThreadMonitoring ${COMMON_DEFS})# sa/CMakeLists.txt — 단독 라이브러리target_compile_definitions(cubridsa PRIVATE SA_MODE CUBRID_EXPORTING ${COMMON_DEFS})# cs/CMakeLists.txt — 클라이언트-서버 라이브러리target_compile_definitions(cubridcs PRIVATE CS_MODE CUBRID_EXPORTING ${COMMON_DEFS})세 플래그는 상호 배타적이다. 소스 트리 전체에 걸쳐 모든 조건부
컴파일 블록은 #if defined(CS_MODE), #if defined(SA_MODE), 또는
#if defined(SERVER_MODE) 중 하나로 분기한다. 관례상 SA_MODE와
SERVER_MODE는 함께 서버 측이 이 바이너리 안에 있다는 것을
뜻한다. CS_MODE와 SERVER_MODE는 상호 배타적이다 — 전자는
항상 클라이언트 측이고, 후자는 항상 데몬이다.
라이브러리 구성
섹션 제목: “라이브러리 구성”세 라이브러리의 소스 파일 구성은 서로 달리 아키텍처를 드러낸다.
-
cs/CMakeLists.txt는libcubridcs.so를 클라이언트 측 파일 만으로 빌드한다. 트랜잭션 디렉터리에서는boot_cl.c,transaction_cl.c,locator_cl.c를 가져오지만boot_sr.c,transaction_sr.c,locator_sr.c는 없다. 통신 디렉터리에서는network_cl.c,network_interface_cl.c,network_callback_cl.cpp를 가져온다.network_cl.c에는 65행에#error Does not belong to cs module라는 명시적 가드가 있는데, 이 파일이 잘못된 타깃에 끌려 들어가면 오류가 발생한다. 스토리지 디렉터리에서는 소형 클라이언트 측 스텁(statistics_cl.c,storage_common.c)만 가져온다. CS 빌드에는btree.c도,heap_file.c도,page_buffer.c도 없다. 결과물은 SQL을 파싱하고 계획하는 방법과 서버와 통신하는 방법은 알지만, 데이터베이스 파일을 스스로 열 수는 없는 라이브러리다. -
sa/CMakeLists.txt는libcubridsa.so를 모든 것으로 빌드 한다. 모든 서버 측 파일(boot_sr.c,locator_sr.c,btree.c,heap_file.c,page_buffer.c,log_manager.c,lock_manager.c,query_executor.c,vacuum.c, …)이 모든 클라이언트 측 파일과 함께 컴파일된다. 통신 디렉터리에서는network_interface_cl.c(디스패치 계층)를 가져오지만network_cl.c는 빠진다 — SA 모드에는 소켓 전송이 없다. 결과 라이브러리는 크고(엔진 전체) 자급자족이 가능하다. 소켓을 한 번도 열지 않고도 온-디스크 데이 터베이스를 열고, 복구를 실행하고, 질의를 처리하고, 종료할 수 있다. -
cubrid/CMakeLists.txt는cub_server데몬 바이너리를 빌드한다. 모든 서버 측 파일에network_sr.c와network_interface_sr.cpp(서버 측 전송)를 더한다.*_cl.c파일은 하나도 없다. 파서 가cub_server안에 없다는 뜻이다. 파싱은 클라이언트 측에서 일어난다. 데몬은 컴파일된 XASL을 받아 실행한다.
세 라이브러리는 나란히 배포된다. 일반적인 CUBRID 설치에는
lib/libcubridcs.so, lib/libcubridsa.so, bin/cub_server가 있다.
각 유틸리티 바이너리는 시작 시 둘 중 하나를 선택한다.
유틸리티별 분류
섹션 제목: “유틸리티별 분류”관리 유틸리티의 SA와 CS 선택은 src/executables/util_admin.c에
정적 테이블로 인코딩되어 있다.
// ua_Utility_Map — src/executables/util_admin.cstatic UTIL_MAP ua_Utility_Map[] = { {CREATEDB, SA_ONLY, 2, UTIL_OPTION_CREATEDB, "createdb", ...}, {DELETEDB, SA_ONLY, 1, UTIL_OPTION_DELETEDB, "deletedb", ...}, {BACKUPDB, SA_CS, 1, UTIL_OPTION_BACKUPDB, "backupdb", ...}, {RESTOREDB, SA_ONLY, 1, UTIL_OPTION_RESTOREDB, "restoredb", ...}, {ADDVOLDB, SA_CS, 2, UTIL_OPTION_ADDVOLDB, "addvoldb", ...}, {SPACEDB, SA_CS, 1, UTIL_OPTION_SPACEDB, "spacedb", ...}, {LOCKDB, CS_ONLY, 1, UTIL_OPTION_LOCKDB, "lockdb", ...}, {KILLTRAN, CS_ONLY, 1, UTIL_OPTION_KILLTRAN, "killtran", ...}, {OPTIMIZEDB, SA_ONLY, 1, UTIL_OPTION_OPTIMIZEDB, "optimizedb", ...}, {INSTALLDB, SA_ONLY, 1, UTIL_OPTION_INSTALLDB, "installdb", ...}, {DIAGDB, SA_ONLY, 1, UTIL_OPTION_DIAGDB, "diagdb", ...}, {CHECKDB, SA_CS, 1, UTIL_OPTION_CHECKDB, "checkdb", ...}, {LOADDB, SA_CS, 1, UTIL_OPTION_LOADDB, "loaddb_user", ...}, {UNLOADDB, SA_CS, 1, UTIL_OPTION_UNLOADDB, "unloaddb", ...}, {COMPACTDB, SA_CS, 1, UTIL_OPTION_COMPACTDB, "compactdb", ...}, {STATDUMP, CS_ONLY, 1, UTIL_OPTION_STATDUMP, "statdump", ...}, {CHANGEMODE, CS_ONLY, 1, UTIL_OPTION_CHANGEMODE, "changemode", ...}, {COPYLOGDB, CS_ONLY, 1, UTIL_OPTION_COPYLOGDB, "copylogdb", ...}, {APPLYLOGDB, CS_ONLY, 1, UTIL_OPTION_APPLYLOGDB, "applylogdb", ...}, {VACUUMDB, SA_CS, 1, UTIL_OPTION_VACUUMDB, "vacuumdb", ...}, {CHECKSUMDB, CS_ONLY, 1, UTIL_OPTION_CHECKSUMDB, "checksumdb", ...}, {FLASHBACK, CS_ONLY, 2, UTIL_OPTION_FLASHBACK, "flashback", ...}, // ... 생략 ... {-1, -1, 0, 0, 0, 0, 0}};각 행은 유틸리티를 세 가지 분류 중 하나로 태깅한다.
-
SA_ONLY—cub_server가 붙지 않은 상태에서 실행해야 한다. 온-디스크 상태에 독점적 접근이 필요한 연산들이다.createdb는 데이터베이스가 아직 없어 데몬이 연결할 대상이 없고,deletedb는 데이터베이스가 곧 사라지는 상황이며,restoredb는 백업 이미지로 온-디스크 상태를 덮어쓰는 중이다.optimizedb는 힙 페이지를 직접 스캔해 통계를 재구성하고,installdb는 처음 만들 어진 데이터베이스에 시스템 카탈로그를 설치하며,genlocale/dumplocale은 로케일 데이터 파일을 다시 쓰고,synccolldb는 정렬 규칙 테이블을 다시 쓴다. 이 연산들에 CS 모드를 허용하는 것은 잘못된 것(같은 파일에 두 쓰기 주체)이거나 불가능한 것(데이 터베이스 자체가 없으니 데몬도 아직 없다)이다. -
CS_ONLY—cub_server가 연결된 상태에서 실행해야 한다. 이 연산들은 실행 중인 서버와 상호작용한다.lockdb는 인메모 리 락 테이블을 덤프하고,killtran은 실행 중인 트랜잭션을 강제 종료하며,statdump는 인메모리 성능 카운터를 덤프하고,changemode는 HA 상태 전환을 한다.copylogdb/applylogdb는 HA 복제 데몬이고,flashback은 실행 중인 서버가 보관하는 온라 인 변경 이력을 읽으며,checksumdb는 HA 모드의 일관성 검사를 한다. 이들 중 어느 것도 조용히 멈춘 온-디스크 데이터베이스를 대상으로 하지 않는다. 모두 데몬의 인메모리 상태가 필요하다. -
SA_CS— 어느 모드에서든 실행할 수 있으며 사용자가 선택 한다. 두 가지 방식이 모두 의미 있는 유틸리티들이다.backupdb는 서버를 통한 온라인 백업이나 독점 접근 오프라인 백업을 선택 할 수 있다.loaddb는 애플리케이션이 여전히 질의하는 동안 서버로 적재하거나, 최대 처리량을 위해 멈춘 데이터베이스에 직접 적재할 수 있다.unloaddb,compactdb,checkdb,vacuumdb,addvoldb,spacedb,paramdump,tde도 마찬가지다.-S/-C명령줄 플래그로 모드를 선택하며, 기본값은 유틸리티에 따라 다르다.
런타임 라이브러리 선택
섹션 제목: “런타임 라이브러리 선택”util_admin.c가 사용자가 요청한 모드를 파악하면, 맞는 공유
라이브러리를 불러와야 한다. 선택자는 util_get_library_name이다.
// util_get_library_name — src/executables/util_admin.cstatic const char *util_get_library_name (int utility_index){ int utility_type = ua_Utility_Map[utility_index].utility_type; UTIL_ARG_MAP *arg_map = ua_Utility_Map[utility_index].arg_map;
switch (utility_type) { case SA_ONLY: return LIB_UTIL_SA_NAME; case CS_ONLY: return LIB_UTIL_CS_NAME; case SA_CS: { // SA_CS 유틸리티는 -S, -C, 또는 HIDDEN_CS_MODE_S 플래그를 받는다. for (int i = 0; arg_map[i].arg_ch; i++) { int key = arg_map[i].arg_ch; if (key == 'C' && arg_map[i].arg_value.p != NULL) return LIB_UTIL_CS_NAME; if (key == HIDDEN_CS_MODE_S && arg_map[i].arg_value.p != NULL) return LIB_UTIL_CS_NAME; if (key == 'S' && arg_map[i].arg_value.p != NULL) return LIB_UTIL_SA_NAME; } } } if (utility_index == VACUUMDB || utility_index == TDE) return LIB_UTIL_SA_NAME; return LIB_UTIL_CS_NAME; // SA_CS의 나머지 기본값: CS}라이브러리 이름은 utility.h가 컴파일 타임에 해석한다.
// LIB_UTIL_*_NAME — src/executables/utility.h#if defined(WINDOWS)#define LIB_UTIL_CS_NAME "cubridcs.dll"#define LIB_UTIL_SA_NAME "cubridsa.dll"#elif defined(__APPLE__)#define LIB_UTIL_CS_NAME "libcubridcs.dylib"#define LIB_UTIL_SA_NAME "libcubridsa.dylib"#else#define LIB_UTIL_CS_NAME "libcubridcs.so"#define LIB_UTIL_SA_NAME "libcubridsa.so"#endif라이브러리는 이후 dlopen으로 열리고, 유틸리티의 진입점 함수
포인터는 dlsym으로 이름을 찾아 가져온다.
// util_admin.c — util_get_library_name -> utility_load_library 경로library_name = util_get_library_name (utility_index);status = utility_load_library (&library_handle, library_name);// ... 오류 처리 ...utility_load_symbol (library_handle, &symbol, function_name);// ... (*func) (arg) 호출 ...SQL 셸에서는 csql_launcher.c에서 같은 패턴이 반복된다.
// csql_launcher.c — 런타임 라이브러리 선택if (csql_arg.sa_mode) utility_load_library (&util_library, LIB_UTIL_SA_NAME);else utility_load_library (&util_library, LIB_UTIL_CS_NAME);
utility_load_symbol (util_library, (DSO_HANDLE *) (&csql), "csql");error = (*csql) (argv[0], &csql_arg);아키텍처적으로 중요한 점은 디스크에 있는 바이너리들(cub_admin,
csql, cub_compactdb, …)이 매우 얇다는 것이다. 이들은 런처다.
명령줄 플래그를 파싱하고, 요청을 분류하고, 맞는 .so를 불러오고,
그 안으로 호출한다. 파싱을 포함한 모든 실제 데이터베이스 로직은
런처가 런타임에 선택한 공유 라이브러리 안에 있다.
계층별 선택 다이어그램
섹션 제목: “계층별 선택 다이어그램”flowchart TD
A[사용자가 cub_admin compactdb -S mydb 실행] --> B[util_admin.c main]
B --> C{ua_Utility_Map에서\ncompactdb 조회}
C -->|SA_CS, -S 지정| D[util_get_library_name -> LIB_UTIL_SA_NAME]
C -->|SA_CS, -C 지정| E[util_get_library_name -> LIB_UTIL_CS_NAME]
C -->|SA_ONLY| D
C -->|CS_ONLY| E
D --> F[dlopen libcubridsa.so]
E --> G[dlopen libcubridcs.so]
F --> H[dlsym compactdb -> SA 모드 진입점]
G --> I[dlsym compactdb -> CS 모드 진입점]
H --> J[프로세스 내: DB 열기,\n복구 실행, 압축, 종료]
I --> K[CSS로 cub_server에 연결,\nRPC 전송, 서버가 압축]
호출별 디스패치 분기
섹션 제목: “호출별 디스패치 분기”CUBRID 소스 트리에서 가장 빈번하게 편집되는 패턴은, 클라이언트
측 디스패치 함수의 본문이 #if defined(CS_MODE) … #else …
#endif로 이루어진 형태다. CS 분기는 인자를 와이어 버퍼에 패킹해
CSS로 서버로 보내고, SA 분기는 서버 측 구현인 xfoo_*를
일반 C 호출로 직접 부른다. 위 계층에서 보이는 공개 심볼 —
locator_fetch, boot_register_client, heap_create, … — 은
두 모드에서 동일하다. 호출하는 쪽은 자신이 어느 분기에 있는지
알 수 없다.
대표적인 예가 src/communication/network_interface_cl.c의
locator_fetch다.
// locator_fetch — src/communication/network_interface_cl.cintlocator_fetch (OID * oidp, int chn, LOCK lock, LC_FETCH_VERSION_TYPE fetch_version_type, OID * class_oid, int class_chn, int prefetch, LC_COPYAREA ** fetch_copyarea){#if defined(CS_MODE) // ... OID, chn, lock, fetch_version_type, class_oid를 요청에 패킹 ... req_error = net_client_request_recv_copyarea (NET_SERVER_LC_FETCH, request, OR_ALIGNED_BUF_SIZE (a_request), reply, OR_ALIGNED_BUF_SIZE (a_reply), fetch_copyarea); // ... 응답 언패킹, 성공 코드 반환 ... return success;#else /* CS_MODE */ int success = ER_FAILED;
THREAD_ENTRY *thread_p = enter_server ();
success = xlocator_fetch (thread_p, oidp, chn, lock, fetch_version_type, fetch_version_type, class_oid, class_chn, prefetch, fetch_copyarea);
exit_server (*thread_p);
return success;#endif /* !CS_MODE */}CS 모드에서 이 함수는 인자를 패킹하고, net_client_request_recv_copyarea
(CSS — CUBRID 소켓 서비스 — 위에 놓여 결국 TCP 소켓에 쓰는 함수)
를 호출하고, 응답을 언패킹한다. SA 모드에서는 내장 스레드
매니저에서 스레드 엔트리를 얻고, 서버 측 구현인 xlocator_fetch를
일반 C 호출로 직접 부르고, 스레드 엔트리를 반납한다. 두 경로는
같은 반환 타입과 같은 오류 보고에서 만난다. 이 계층 위의 호출자는
어느 분기를 타는지 알 수 없다.
SA 모드의 스레드 엔트리 처리 방식은 흥미롭다. SA 모드에는 실제
스레드 풀이 없다. 유틸리티는 단일 스레드로 실행된다. 그러나
서버 측 코드 경로는 THREAD_ENTRY *를 보유하는 워커 스레드가
자신을 호출한다고 가정한다. SA 모드는 enter_server/exit_server
로 이를 흉내 낸다.
// enter_server, exit_server — src/communication/network_interface_cl.cunsigned int db_on_server = 0;
#if defined (SA_MODE)static voidenter_server_no_thread_entry (void){ db_on_server++; er_stack_push_if_exists ();
if (private_heap_id == 0) { assert (db_on_server == 1); private_heap_id = db_create_private_heap (); }}
static THREAD_ENTRY *enter_server (){ enter_server_no_thread_entry (); return thread_get_thread_entry_info ();}
static voidexit_server_no_thread_entry (void){ if ((db_on_server - 1) == 0 && private_heap_id != 0) { db_clear_private_heap (NULL, private_heap_id); } er_restore_last_error (); db_on_server--;}#endif // SA_MODE카운터 db_on_server는 재귀 깊이를 추적한다. SA 모드의 호출
시퀀스는 서버 공간에 재진입할 수 있다. 저장 프로시저 안의
질의가 다시 다른 질의를 유발하는 경우가 그렇다. 전용 힙은 가장
바깥의 반환에서만 정리되어야 한다. thread_get_thread_entry_info는
SA 모드 스레드 매니저가 부팅 시 할당하는 싱글턴 스레드 엔트리를
반환한다. 서버 측 코드의 관점에서는 cub_server의 워커 풀에서
호출된 것과 구분할 수 없다.
부팅 경로 분기
섹션 제목: “부팅 경로 분기”SA/CS 차이가 가장 눈에 띄는 곳이 부팅이다. CS 모드에서
boot_restart_client는 이미 실행 중인 cub_server에 TCP 연결을
열고 새 클라이언트 등록을 요청한다. SA 모드에서는 같은 함수가
같은 프로세스 안의 서버 측 boot_restart_server를 호출하고, 필요
하면 전체 충돌 복구를 수행한다.
boot_initialize_client(createdb 경로)의 핵심 부분은 다음과 같다.
// boot_initialize_client — src/transaction/boot_cl.cintboot_initialize_client (BOOT_CLIENT_CREDENTIAL * client_credential, ...){ // ... lang_init, msgcat_init, sysprm 로드, area_init ...
#if defined(CS_MODE) /* 통신 서브시스템 초기화 */ error_code = boot_client_initialize_css (db, client_credential->client_type, false, BOOT_NO_OPT_CAP, false, DB_CONNECT_ORDER_SEQ, false); if (error_code != NO_ERROR) goto error_exit;#endif /* CS_MODE */
// ... tp_init, perfmon_initialize ...
/* 디스크와 서버 파트 초기화 */ tran_index = boot_initialize_server (client_credential, db_path_info, db_overwrite, file_addmore_vols, npages, db_desired_pagesize, log_npages, db_desired_log_page_size, &rootclass_oid, &rootclass_hfid, tran_lock_wait_msecs, tran_isolation); // ...}boot_initialize_server는 항상 존재하는 공개 API다. 그러나
디스패치 계층(network_interface_cl.c)에서의 구현은 모드에 따라
달라진다.
// boot_initialize_server (CS 디스패치 / SA 통과)// — src/communication/network_interface_cl.cintboot_initialize_server (const BOOT_CLIENT_CREDENTIAL * client_credential, BOOT_DB_PATH_INFO * db_path_info, ...){#if defined(CS_MODE) /* CS_MODE에서는 호출되지 않아야 한다 */ assert (0); return NULL_TRAN_INDEX;#else /* CS_MODE */ int tran_index = NULL_TRAN_INDEX;
enter_server_no_thread_entry ();
tran_index = xboot_initialize_server (client_credential, db_path_info, db_overwrite, file_addmore_vols, db_npages, db_desired_pagesize, log_npages, db_desired_log_page_size, rootclass_oid, rootclass_hfid, client_lock_wait, client_isolation);
exit_server_no_thread_entry ();
return (tran_index);#endif /* !CS_MODE */}CS 모드 본문이 assert(0)인 몇 안 되는 곳 중 하나다. 이유는
CS 모드에서 새 데이터베이스를 만들려면 cub_server가 존재하지
않는 데이터베이스를 대상으로 시작해야 하는, 모순적인 상황이 필요
하기 때문이다. 따라서 createdb는 ua_Utility_Map에서 SA_ONLY
다. assert는 개발자가 실수로 CS 모드 호출자를 createdb 경로에
연결했을 때만 발동한다.
재시작 경로는 더 흥미롭게 분기한다. boot_cl.c의
boot_restart_client는 모든 클라이언트가 호출한다. csql 세션,
loaddb, JDBC 연결 모두 해당한다(일부 유틸리티는 등록 단계를
건너뛰므로 예외다). SA 모드에서는 내장 빌드의 경우 전체 디스크
측 초기화를 수행하는 체인으로 결국 boot_restart_server를
직접 호출하게 된다.
sequenceDiagram
participant U as 유틸리티
participant CL as boot_cl.c (boot_restart_client)
participant DISP as network_interface_cl.c
participant SR as boot_sr.c (xboot_register_client / boot_restart_server)
participant DAEMON as cub_server (CS 전용)
rect rgb(230, 240, 255)
Note over U,SR: SA 모드
U->>CL: boot_restart_client(creds)
CL->>DISP: boot_register_client(creds)
DISP->>DISP: enter_server_no_thread_entry()
DISP->>SR: xboot_register_client(...)
SR->>SR: 필요 시 boot_restart_server(...)
SR->>SR: log_recovery, locator_initialize, ...
SR-->>DISP: tran_index
DISP->>DISP: exit_server_no_thread_entry()
DISP-->>CL: tran_index
CL-->>U: NO_ERROR
end
rect rgb(255, 240, 230)
Note over U,DAEMON: CS 모드
U->>CL: boot_restart_client(creds)
CL->>CL: boot_client_initialize_css(db, ...)
CL->>DAEMON: NET_SERVER_BO_REGISTER_CLIENT (CSS 경유)
DAEMON->>SR: xboot_register_client(...)
Note right of SR: cub_server는 이미 부팅 완료;<br/>복구는 서버 시작 시 이미 완료
SR-->>DAEMON: BOOT_SERVER_CREDENTIAL
DAEMON-->>CL: 응답 패킷
CL-->>U: NO_ERROR
end
다이어그램이 드러내는 구조적 요점은 이렇다. SA 모드에서는
boot_sr.c가 유틸리티 자체 프로세스에서 실행되고, 충돌 복구
(boot_restart_server ⇒ log_recovery)가 유틸리티가 처음 데이터
베이스를 열 때 트리거된다. CS 모드에서는 boot_sr.c가 데몬에서
실행되고, 복구는 데몬이 시작될 때 이미 완료되었다.
SA 모드의 복구와 독점 접근
섹션 제목: “SA 모드의 복구와 독점 접근”SA 모드 유틸리티는 실행되는 내내 데이터베이스 파일을 독점적으로
보유한다. 버퍼 풀, 락 테이블, WAL 라이터를 모두 자신이 소유한다
고 믿는 두 프로세스가 같은 파일을 안전하게 열 수 없기 때문이다.
CUBRID는 이를 협력적 메커니즘으로 강제한다. SA 모드 유틸리티는
같은 데이터베이스에 cub_server가 이미 실행 중이면 시작을 거부
하고, cub_server는 SA 모드 유틸리티가 현재 데이터베이스를 사용
중이면 시작을 거부한다. 메커니즘은 데이터베이스 볼륨 락이다.
boot_restart_server가 볼륨 정보 파일에 배타적 flock 스타일 락
을 획득하려 시도하고, 실패하면 ER_BO_CWD_FAIL/
ER_BO_CANNOT_FINE_VOLINFO 또는 ER_LOG_DOESNT_CORRESPOND_TO_DATABASE
를 보고한다. 실제 운영에서의 규칙은 단순하다. SA 전용 유틸리티를
실행하기 전에 서버를 먼저 중지하라.
SA 모드의 복구는 cub_server의 복구와 동일한 코드다. 다른 바이너
리로 컴파일되었을 뿐 같은 코드이기 때문이다. compactdb -S가
직전에 깨끗하게 종료되지 않은 데이터베이스를 열면, 단독 프로세스
안의 boot_restart_server 경로가 로그 컨트롤 레코드로
비정상 종료를 감지하고, 유틸리티의 첫 읽기 전에 log_recovery
(분석, 리두, 언두 패스)를 실행한다. 그리고 나서야 압축 작업을
수행한다. 유틸리티는 복구가 일어났다는 것을 모른다. 평범한 데이터
베이스 열기를 요청했을 뿐이고, 그것을 받았을 뿐이다. 비용은
손상된 데이터베이스를 상속한 SA 모드 유틸리티의 시작이 오래 걸릴
수 있다는 것이다. 장점은 운영자가 “먼저 서버를 시작해 복구하고,
멈추고, 유틸리티를 실행한다”는 순서를 기억할 필요가 없다는 점
이다.
오류 심볼 ER_NOT_IN_STANDALONE과 ER_ONLY_IN_STANDALONE
(src/base/error_code.h:734에 정의)은 런타임의 안전 장치다. CS
모드가 반드시 필요한 코드는 SA 모드에서 호출되면
ER_NOT_IN_STANDALONE을 발생시킨다. 예컨대 boot_restart_client
847행의 db@host 문법이 그렇다. 서버가 자기 프로세스 안에 있을
때 원격 호스트로 연결하는 것은 의미가 없다. SA 모드가 반드시
필요한 코드는 ER_ONLY_IN_STANDALONE을 발생시킨다. 예컨대
log_manager.c 1018행처럼, 로깅 없는 모드는 단일 프로세스 빌드
에서만 허용된다.
유틸리티별 분류 표
섹션 제목: “유틸리티별 분류 표”유틸리티와 모드 사이의 매핑은 이 분석에서 가장 중요한 표다. 표의 형태 — SA_ONLY가 많고, CS_ONLY가 몇 개, SA_CS가 그보다 적은 중간 — 는 명시적으로 서술할 만한 설계 원칙을 반영한다.
유틸리티가
SA_ONLY인 것은 조용히 멈춘 데이터베이스의 온-디스크 상태를 대상으로 할 때다.CS_ONLY인 것은 실행 중인 서버의 런타임 상태를 대상으로 할 때다.SA_CS인 것은 같은 논리적 연산이 두 맥락 모두에서 의미 있고, 운영자가 처리량 대 동시성의 절충을 선택할 때다.
구체적인 유틸리티로 풀면 다음과 같다.
| 유틸리티 | 분류 | 이유 |
|---|---|---|
createdb | SA_ONLY | 데이터베이스가 아직 없어 데몬이 연결할 대상이 없다. |
deletedb | SA_ONLY | 파일이 곧 사라지는 상황이므로 데몬이 열어 놓으면 안 된다. |
restoredb | SA_ONLY | 백업 이미지로 바이트를 덮어 쓰는 중이므로 독점 접근이 필요하다. |
installdb | SA_ONLY | 처음 생성된 데이터베이스에 시스템 카탈로그를 설치한다. |
optimizedb | SA_ONLY | 힙을 직접 스캔해 통계를 재구성한다. |
diagdb | SA_ONLY | 진단 덤프를 위해 온-디스크 페이지를 직접 읽는다. |
patchdb | SA_ONLY | 컨트롤 구조를 강제로 변경하므로 실시간 쓰기 주체와 공유할 수 없다. |
alterdbhost | SA_ONLY | 데이터베이스 위치 메타데이터를 다시 쓴다. |
genlocale | SA_ONLY | 로케일 데이터 파일을 빌드하는 단독 도구다. |
dumplocale | SA_ONLY | 로케일 데이터 파일을 덤프하는 단독 도구다. |
synccolldb | SA_ONLY | 정렬 규칙 테이블을 다시 쓴다. |
gen_tz | SA_ONLY | 타임존 라이브러리를 빌드하는 단독 도구다. |
dump_tz | SA_ONLY | 타임존 라이브러리를 덤프하는 단독 도구다. |
restoreslave | SA_ONLY | 백업으로 슬레이브를 구성하며 restoredb와 같은 신뢰 모델을 쓴다. |
lockdb | CS_ONLY | 데몬의 인메모리 락 테이블을 덤프한다. |
killtran | CS_ONLY | 데몬 안에서 실행 중인 트랜잭션을 종료한다. |
plandump | CS_ONLY | 데몬의 플랜 캐시를 덤프한다. |
statdump | CS_ONLY | 데몬의 성능 카운터를 덤프한다. |
changemode | CS_ONLY | HA 상태 전환이므로 실행 중인 데몬이 있어야 의미가 있다. |
copylogdb | CS_ONLY | 마스터에서 로그 레코드를 가져오는 HA 복제 데몬이다. |
applylogdb | CS_ONLY | 가져온 로그를 슬레이브에 적용하는 HA 복제 데몬이다. |
applyinfo | CS_ONLY | HA 복제 상태를 읽는다. |
acldb | CS_ONLY | 데몬의 IP ACL을 다시 불러온다. |
tranlist | CS_ONLY | 데몬에서 실행 중인 트랜잭션 목록을 보여 준다. |
checksumdb | CS_ONLY | HA 모드에서 마스터와 슬레이브 사이 페이지별 일관성 검사를 한다. |
flashback | CS_ONLY | 실행 중인 서버가 보관하는 온라인 변경 이력을 읽는다. |
memmon | CS_ONLY | 데몬의 메모리 모니터를 읽는다. |
backupdb | SA_CS | 온라인(CS) 또는 오프라인(SA) 백업이다. 동시성 대 단순성의 절충이다. |
addvoldb | SA_CS | 온라인(CS) 또는 오프라인(SA)으로 볼륨을 추가한다. |
spacedb | SA_CS | 공간 사용량을 보고한다. CS는 실행 중인 서버에서, SA는 온-디스크 메타데이터를 스캔한다. |
cleanfiledb | SA_CS | 댕글링 파일을 정리한다. 온라인 또는 오프라인 중 선택한다. |
checkdb | SA_CS | 일관성 검사다. SA 모드가 더 철저하지만 데이터베이스를 막는다. |
loaddb | SA_CS | SA는 클래스 배타 락으로 힙에 직접 삽입. CS는 일반 삽입이다. |
unloaddb | SA_CS | SA는 힙 직접 스캔. CS는 일반 SELECT다. |
compactdb | SA_CS | SA는 배타 압축. CS는 온라인 압축이다. |
paramdump | SA_CS | 디스크에서(SA) 또는 실행 중인 서버에서(CS) 파라미터를 덤프한다. |
vacuumdb | SA_CS | 기본은 SA. 오프라인 전체 청소. CS는 실행 중인 서버에 청소를 트리거한다. |
tde | SA_CS | 투명 데이터 암호화 키 관리다. |
이 표가 관리 작업에 관한 사용자와 엔진 사이의 전체 계약이다.
표에 없는 것은 SQL 명령이고, csql으로 실행된다(csql 자체는
SA_CS다).
Loaddb — SA_CS 패턴의 실제 예
섹션 제목: “Loaddb — SA_CS 패턴의 실제 예”load_db.c는 cub_loaddb의 런처이자 SA_CS 패턴을 실제 코드로
보여 주는 곳이다. 같은 소스가 libcubridsa.so와 libcubridcs.so
모두에 컴파일되며, 런처가 호출하는 loaddb 함수는 컴파일 타임
모드 플래그로 디스패치한다.
// load_db.c — 경로 분기#if defined (SA_MODE)#include "load_sa_loader.hpp"#endif // SA_MODE
// ...
#if defined (SA_MODE) /* 컴파일러 경고 방지 (longjump에 의한 clobber) */ volatile bool interrupted = false;#else bool interrupted = false;#endif
// ...
#if defined (SA_MODE) // load_sa: 클래스 배타 BU 락으로 클래스를 열고, // locator_*를 통해 직접 삽입, 행별 WAL 없음#else // !SA_MODE = CS_MODE // load_cs: CSS를 통해 서버로 배치 전송, // 서버가 워커 풀 실행#endif // !SA_MODE = CS_MODE사용자 인터페이스는 같은 플래그를 운영자에게 노출한다. -S는
SA 모드(args->sa_mode = ...)를 선택하고, -C는 CS 모드
(args->cs_mode = utility_get_option_bool_value (arg_map, LOAD_CS_MODE_S);,
1281행)를 선택한다. util_admin.c의 런처가 그 플래그를 확인해
맞는 .so를 불러오고, 선택된 라이브러리에서 찾아낸 loaddb 심볼
이 해당 코드를 실행한다.
CSQL — SQL 셸의 동일한 패턴
섹션 제목: “CSQL — SQL 셸의 동일한 패턴”csql_launcher.c는 cub_csql을 구현하며 같은 템플릿을 따른다.
- 런처는 얇은 바이너리다.
libcubridsa.so나libcubridcs.so에 직접 링크하지 않는다. --SA-mode/-S와--CS-mode/-C플래그를csql_arg.sa_mode와csql_arg.cs_mode로 파싱한다.- 조합의 유효성을 검사한다(
-S는-C나--write-on-standby와 공존할 수 없다). LIB_UTIL_SA_NAME또는LIB_UTIL_CS_NAME으로utility_load_library를 호출하고,dlsym으로csql진입점을 찾아 호출한다.- 라이브러리 안에서
csql.c가 SQL을 파싱하고, XASL을 생성하고, CSS로 전송하거나(CS_MODE), 서버 측 질의 실행기를 직접 호출해 프로세스 안에서 실행한다(SA_MODE).
이 의미는 csql -S mydb가 디스크에서 같은 실행 파일을 공유하더
라도 사실상 csql mydb와 다른 바이너리라는 것이다. 전자는 csql
프로세스 안에서 mydb의 파일을 열고, 후자는 이미 mydb를 서비스
하는 cub_server에 연결한다.
세 바이너리 아키텍처 다이어그램
섹션 제목: “세 바이너리 아키텍처 다이어그램”flowchart LR
subgraph SRC[소스 트리 - 같은 .c/.cpp 파일을 세 번 컴파일]
BC[boot_cl.c]
BS[boot_sr.c]
NC[network_cl.c]
NIC[network_interface_cl.c]
NS[network_sr.c]
BTREE[btree.c, heap_file.c,<br/>page_buffer.c, log_*.c, ...]
PARSER[parser/, optimizer/,<br/>query/, compat/, object/]
end
subgraph SERVER[cub_server 바이너리 - SERVER_MODE]
BS2[boot_sr.c]
NS2[network_sr.c]
BTREE2[스토리지 + 트랜잭션]
end
subgraph SA[libcubridsa.so - SA_MODE]
BC1[boot_cl.c]
BS1[boot_sr.c]
NIC1[network_interface_cl.c<br/>SA 분기: enter_server,<br/>xfoo 직접 호출]
BTREE1[스토리지 + 트랜잭션]
PARSER1[파서 + 옵티마이저 + 질의]
end
subgraph CS[libcubridcs.so - CS_MODE]
BC2[boot_cl.c]
NC2[network_cl.c]
NIC2[network_interface_cl.c<br/>CS 분기: 인자 패킹,<br/>net_client_request]
PARSER2[파서 + 옵티마이저]
end
BC --> BC1
BC --> BC2
BS --> BS1
BS --> BS2
NC --> NC2
NIC --> NIC1
NIC --> NIC2
NS --> NS2
BTREE --> BTREE1
BTREE --> BTREE2
PARSER --> PARSER1
PARSER --> PARSER2
LAUNCHER[cub_admin / csql / cub_loaddb / cub_compactdb] -->|dlopen| SA
LAUNCHER -->|dlopen| CS
CS -. CSS / TCP .-> SERVER
부팅 경로 순서
섹션 제목: “부팅 경로 순서”flowchart TD
A[유틸리티 시작] --> B{런처가 불러온\n라이브러리는?}
B -->|libcubridsa.so| SA1[boot_initialize_client / boot_restart_client<br/>-- SA_MODE 컴파일]
B -->|libcubridcs.so| CS1[boot_initialize_client / boot_restart_client<br/>-- CS_MODE 컴파일]
SA1 --> SA2[boot_client_initialize_css 건너뜀<br/>#if defined CS_MODE 가드로 제외]
SA2 --> SA3[network_interface_cl.c의 boot_initialize_server<br/>SA 분기 -> enter_server_no_thread_entry]
SA3 --> SA4[boot_sr.c의 xboot_initialize_server<br/>볼륨 열기, log_recovery 실행,<br/>locator_initialize]
SA4 --> SA5[유틸리티 코드가 같은 프로세스에서 실행,<br/>xfoo_*를 직접 호출]
SA5 --> SA6[boot_shutdown_client -> 볼륨 닫기,<br/>log_final, 종료]
CS1 --> CS2[boot_client_initialize_css로 cub_server에 TCP 열기,<br/>NET_SERVER_PING 핸드셰이크]
CS2 --> CS3[network_interface_cl.c의 boot_initialize_server<br/>CS 분기 -> assert 0]
CS3 --> CS4{createdb 인가?}
CS4 -->|예, SA_ONLY| CS5[도달하지 않음 -- 런처가 SA를 선택]
CS4 -->|아니오| CS6[boot_register_client -> NET_SERVER_BO_REGISTER_CLIENT<br/>CSS 경유]
CS6 --> CS7[유틸리티 코드가 로컬에서 실행,<br/>각 xfoo_* 호출이 패킹된 RPC가 됨]
CS7 --> CS8[boot_shutdown_client -> NET_SERVER_BO_UNREGISTER_CLIENT,<br/>소켓 닫기, 종료]
소스 코드 가이드
섹션 제목: “소스 코드 가이드”함수와 구조체를 서브시스템과 호출 흐름 기준으로 묶어 정리한다.
빌드 연결
cubrid/CMakeLists.txt—add_executable(cubrid …),target_compile_definitions(cubrid PRIVATE SERVER_MODE …)sa/CMakeLists.txt—add_library(cubridsa SHARED …),target_compile_definitions(cubridsa PRIVATE SA_MODE CUBRID_EXPORTING …)cs/CMakeLists.txt—add_library(cubridcs SHARED …),target_compile_definitions(cubridcs PRIVATE CS_MODE CUBRID_EXPORTING …)
라이브러리 / 런처 선택
LIB_UTIL_SA_NAME(매크로) —src/executables/utility.hLIB_UTIL_CS_NAME(매크로) —src/executables/utility.hua_Utility_Map(테이블) —src/executables/util_admin.cutil_get_library_name—src/executables/util_admin.cutility_load_library/utility_load_symbol—src/executables/utility.h(선언) 및 라이브러리 로더main(cub_admin 진입점) —src/executables/util_admin.cmain(csql 진입점) —src/executables/csql_launcher.c
부팅 — 클라이언트 측
BOOT_IS_CLIENT_RESTARTED(매크로) —src/transaction/boot_cl.hboot_initialize_client—src/transaction/boot_cl.cboot_restart_client—src/transaction/boot_cl.cboot_client_initialize_css(CS 전용) —src/transaction/boot_cl.cboot_check_locales(CS 전용) —src/transaction/boot_cl.cboot_check_timezone_checksum(CS 전용) —src/transaction/boot_cl.cboot_shutdown_client—src/transaction/boot_cl.cboot_Host_connected(CS 전용 정적 변수) —src/transaction/boot_cl.c
부팅 — 서버 측
BO_IS_SERVER_RESTARTED(매크로) —src/transaction/boot_sr.hboot_Server_process_id—src/transaction/boot_sr.cxboot_initialize_server—src/transaction/boot_sr.cboot_restart_server—src/transaction/boot_sr.cxboot_register_client—src/transaction/boot_sr.clog_initialize(복구 드라이버) —src/transaction/log_manager.clocator_initialize—src/transaction/locator_sr.c
호출별 디스패치 (모든 서버 바운드 호출의 SA/CS 분기)
db_on_server(카운터) —src/communication/network_interface_cl.center_server—src/communication/network_interface_cl.c(SA 전용)enter_server_no_thread_entry—src/communication/network_interface_cl.c(SA 전용)exit_server—src/communication/network_interface_cl.c(SA 전용)exit_server_no_thread_entry—src/communication/network_interface_cl.c(SA 전용)boot_initialize_server(디스패치) —src/communication/network_interface_cl.cboot_register_client(디스패치) —src/communication/network_interface_cl.clocator_fetch(대표 예시) —src/communication/network_interface_cl.c
CS 전용 전송
set_server_error—src/communication/network_cl.cnet_client_request—src/communication/network_cl.cnet_client_request_internal—src/communication/network_cl.cnet_client_request_no_reply—src/communication/network_cl.cnet_client_request2—src/communication/network_cl.cnet_client_request_send_large_data—src/communication/network_cl.cnet_client_request_recv_large_data—src/communication/network_cl.cnet_client_request_recv_copyarea—src/communication/network_cl.cnet_Server_host,net_Server_name(정적 변수) —src/communication/network_cl.c- 컴파일 타임 가드
#if !defined (CS_MODE)/#error Does not belong to cs module—src/communication/network_cl.c
Loaddb 분기
loaddb(진입점, sa/cs 라이브러리에서 내보냄) —src/loaddb/load_db.cload_args(구조체) —src/loaddb/load_common.hppLOAD_CS_MODE_S(옵션 문자) —src/executables/utility.hload_sa_loader.cpp(sa/CMakeLists.txt에만 있는 SA 전용 파일) —src/loaddb/ldr_validate_object_file—src/loaddb/load_db.c
CSQL 런처
main—src/executables/csql_launcher.cCSQL_SA_MODE_S,CSQL_CS_MODE_S(옵션 문자) —src/executables/csql_launcher.c
오류 가드
ER_NOT_IN_STANDALONE(매크로) —src/base/error_code.hER_ONLY_IN_STANDALONE(매크로) —src/base/error_code.hER_NOT_IN_STANDALONE발생 지점 —src/transaction/boot_cl.cER_ONLY_IN_STANDALONE발생 지점 —src/transaction/log_manager.c
이 문서 작성 시점의 위치 힌트
섹션 제목: “이 문서 작성 시점의 위치 힌트”| 심볼 | 파일 | 행 |
|---|---|---|
target_compile_definitions(cubrid PRIVATE SERVER_MODE …) | cubrid/CMakeLists.txt | 675 |
target_compile_definitions(cubridsa PRIVATE SA_MODE …) | sa/CMakeLists.txt | 718 |
target_compile_definitions(cubridcs PRIVATE CS_MODE …) | cs/CMakeLists.txt | 577 |
add_library(cubridsa SHARED …) | sa/CMakeLists.txt | 671 |
add_library(cubridcs SHARED …) | cs/CMakeLists.txt | 536 |
LIB_UTIL_SA_NAME (리눅스) | src/executables/utility.h | 1809 |
LIB_UTIL_CS_NAME (리눅스) | src/executables/utility.h | 1808 |
ua_Utility_Map[] | src/executables/util_admin.c | 966 |
util_get_library_name | src/executables/util_admin.c | 1168 |
csql_launcher main, util 라이브러리의 dlopen | src/executables/csql_launcher.c | 466 |
boot_initialize_client | src/transaction/boot_cl.c | 275 |
boot_initialize_client CS 모드 CSS 초기화 | src/transaction/boot_cl.c | 509 |
boot_restart_client | src/transaction/boot_cl.c | 690 |
boot_restart_client CS 모드 db@host 파싱 | src/transaction/boot_cl.c | 824 |
ER_NOT_IN_STANDALONE 발생 지점 | src/transaction/boot_cl.c | 847 |
boot_Host_connected (CS 전용) | src/transaction/boot_cl.c | 150 |
xboot_initialize_server | src/transaction/boot_sr.c | 1385 |
boot_restart_server | src/transaction/boot_sr.c | 1969 |
db_on_server | src/communication/network_interface_cl.c | 103 |
enter_server_no_thread_entry | src/communication/network_interface_cl.c | 124 |
enter_server | src/communication/network_interface_cl.c | 142 |
exit_server_no_thread_entry | src/communication/network_interface_cl.c | 152 |
exit_server | src/communication/network_interface_cl.c | 168 |
locator_fetch (CS/SA 분기) | src/communication/network_interface_cl.c | 270 |
boot_initialize_server (디스패치) | src/communication/network_interface_cl.c | 3919 |
#error Does not belong to cs module | src/communication/network_cl.c | 65 |
set_server_error | src/communication/network_cl.c | 148 |
net_client_request | src/communication/network_cl.c | 587 |
loaddb SA의 load_sa_loader.hpp 포함 | src/loaddb/load_db.c | 28 |
loaddb SA volatile bool interrupted | src/loaddb/load_db.c | 543 |
loaddb SA/CS 분기 | src/loaddb/load_db.c | 823 |
LOAD_CS_MODE_S cs_mode 옵션 | src/loaddb/load_db.c | 1281 |
ER_ONLY_IN_STANDALONE 발생 지점 | src/transaction/log_manager.c | 1018 |
ER_ONLY_IN_STANDALONE (#define) | src/base/error_code.h | 734 |
소스 검증 노트
섹션 제목: “소스 검증 노트”SA/CS 구분은 CUBRID의 첫 오픈소스 릴리스 이후 안정적인 아키텍처 기능으로 유지되어 왔다. 브로커/CAS의 엔진 통합보다 앞서 있으며, 여러 차례의 HA, MVCC, TDE 리팩터를 거쳐도 살아남았다. 계층화가 겉보기보다 덜 깔끔한 몇 가지 지점이 있다.
-
network_interface_cl.c는 두 코드 경로를 모두 담는다. 이름에도 불구하고 이 파일은libcubridsa.so와libcubridcs.so모두에 컴파일된다. 내부의 모든 서버 바운드 호출은#if defined (CS_MODE)…#else…#endif형태를 갖는다. 이 파일의 크기(이 글을 쓰는 시점에 약 278 KB)는 그 중복된 본문에 직접 기인한다. 디스패치를*_cs.c와*_sa.c파일로 분리하려는 이전 시도가 있었다. 그러나 함수 시그니처, 인자 패킹 로직, 공개 API 상용구를 중복하면서도 전체 소스 크기를 줄이지 못한다는 점이 문제였다. 함수별 분기가 더 작은 악이다. -
이름이 클라이언트인 파일 중에도
SA_MODE전용 서버 측 코드를 담는 것이 있다. 예컨대locator_cl.c에는#if defined (SA_MODE) && !defined (CUBRID_DEBUG)로 보호된 블록이 있다(1011, 1713, 1872행). 이는 SA 모드 전용 디버그 빌드에서 경계를 넘는 경로다. “*_cl.c는 클라이언트,*_sr.c는 서버”라는 구조적 규칙은 절대적이지 않다. 정확히는 “*_cl.c파일이 와이어 또는 서버 중 하나로 디스패치하는 진입점이다”는 의미다. -
SERVER_MODE플래그는SA_MODE와 같지 않다. 두 모드 모두 서버 측 번역 단위를 끌어들이지만 말이다. 구분되는 축은 이렇다. (a)SERVER_MODE는 실제 스레드 풀, 연결 수락기, 마스터 프로세스 프로토콜을 활성화한다. (b)SA_MODE는 이를 단일 스레드 스텁 (enter_server_no_thread_entry, 싱글턴을 반환하는thread_get_thread_entry_info)으로 단락시킨다.#if defined (SERVER_MODE)로 보호된 코드는cub_server에서만 실행된다.#if !defined (CS_MODE)로 보호된 코드는cub_server와libcubridsa.so모두에서 실행된다. -
분류 테이블은 단일 진실의 원천이지만 완전하지는 않다. 일부 유틸리티는
util_get_library_name안에 하드코딩된 기본값 (if (utility_index == VACUUMDB || utility_index == TDE) return LIB_UTIL_SA_NAME;)을 가지며 이것이 SA_CS의 기본값을 덮어쓴다. 테이블에 새 유틸리티를 추가할 때 이 뒷부분 블록을 생각하지 않으면 조용히 잘못된 라이브러리를 선택하게 될 수 있다. -
SA 모드의 복구는 깨끗한 종료 후에도 완전한 무동작이 아니다. SA 모드의
boot_restart_server경로는 로그 컨트롤 레코드에서 인메모리 상태를 복원하기 위해 복구의 분석 패스를 항상 실행 한다. 로그가 깨끗하게 종료되었다고 말하면 리두와 언두 패스는 건너뛰지만 분석 패스는 항상 실행된다. 운영자가 대용량 데이터 베이스에서compactdb -S시작이 느린 것을 복구 때문이라고 말할 때가 있다. 실제로 느린 부분은 볼륨 열기와locator_initialize가 클래스 캐시를 구성하는 것이지, 로그 재생이 아닌 경우가 많다는 점이다.
미해결 질문
섹션 제목: “미해결 질문”-
SA/CS 분리를 장기적으로 유지할 가치가 있는가. 아니면 — MySQL이
libmysqld로 했던 것처럼 — CUBRID도 결국cub_server와 PostgreSQL의postgres --single과 유사한 “단일 사용자 데몬 모드”로 통합될 것인가. SA 모드를 유지하는 논거는 일부 연산 (createdb,restoredb,installdb)이 데몬으로는 진정 수행될 수 없다는 점이다. 반대 논거는 번역 단위 수의 두 배, 바이 너리 크기의 두 배,#if defined (CS_MODE)블록의 지속적인 유지 비용이다. -
cub_admin의dlopen-of-cubrid{sa,cs}.so 모델을 정적 링크로 대체할 수 있는가. 현재 어떤 유틸리티도 같은 프로세스에서 두 라이브러리를 모두 불러오지 않으므로. 이 모델은 원래 단일 바이 너리가 두 경우를 모두 처리할 수 있도록 (cub_admin compactdb -S대-C) 선택되었지만, 실제로는 모든 유틸리티가 시작 시 결정 하고 마음을 바꾸지 않는다. -
SA 모드 버퍼 풀 크기 결정이 데몬이 꺼져 있다는 운영자의 기대와 어떻게 충돌하는가. SA 모드 유틸리티는
cub_server가 할당했을 것과 같은 버퍼 풀을 할당한다 (prm_get_integer_value (PRM_ID_PB_NBUFFERS) * page_size). 운영 데이터베이스에서는 수십 기가바이트에 달할 수 있다. 할당이 실패했을 때의 오류 보고가cub_server가 출력하는 “버퍼 풀을 줄이고 다시 시작하라”는 메시지만큼 명확하지 않다는 점이다. -
ER_NOT_IN_STANDALONE과ER_ONLY_IN_STANDALONE을 모드 파라 미터를 가진 단일 오류로 통합해야 하는가. 둘은 본질적으로 같은 논리적 오류(SA 모드에서 CS 전용 기능을 사용했다 / 그 반대)이며, 오류 코드가 중복되면 메시지 카탈로그 관리가 더 번잡해진다는 점이다.
src/transaction/boot_cl.c— 클라이언트 측 부팅, 모드 인식.src/transaction/boot_sr.c— 서버 측 부팅 및 복구.src/communication/network_cl.c— CS 전용 소켓 전송.src/communication/network_interface_cl.c—#if defined (CS_MODE)…#else…#endif로 된 호출별 디스패치 계층.src/executables/util_admin.c— 유틸리티 분류 테이블 및 런타임 라이브러리 로더.src/executables/utility.h—LIB_UTIL_*_NAME매크로.src/executables/csql_launcher.c—csql라이브러리 선택.src/loaddb/load_db.c— 로더 진입점의 SA_CS 분기.src/base/error_code.h—ER_NOT_IN_STANDALONE,ER_ONLY_IN_STANDALONE.sa/CMakeLists.txt—cubridsa공유 라이브러리 구성;target_compile_definitions(cubridsa PRIVATE SA_MODE …).cs/CMakeLists.txt—cubridcs공유 라이브러리 구성;target_compile_definitions(cubridcs PRIVATE CS_MODE …).cubrid/CMakeLists.txt—cub_server실행 파일;target_compile_definitions(cubrid PRIVATE SERVER_MODE …).