콘텐츠로 이동

(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하느냐로 해소된다.

성숙한 엔진은 저마다 같은 질문에 답해야 했다 — “데몬이 없을 때 사용자는 무엇을 실행하는가?” 그 답들은 제각각이며, 그 차이가 흥미롭다.

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의 SA/CS 구분은 층이 나뉘어 있다. 전처리기 플래그, 라이브 러리 구성, 유틸리티별 분류, 런타임 라이브러리 선택, 호출별 디스 패치 분기 — 이렇게 다섯 층의 메커니즘이 있으며, 각 층은 그 아래 층 위에 세워진다.

같은 .c.cpp 파일들이 sa/CMakeLists.txtcs/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_MODESERVER_MODE는 함께 서버 측이 이 바이너리 안에 있다는 것을 뜻한다. CS_MODESERVER_MODE는 상호 배타적이다 — 전자는 항상 클라이언트 측이고, 후자는 항상 데몬이다.

세 라이브러리의 소스 파일 구성은 서로 달리 아키텍처를 드러낸다.

  • cs/CMakeLists.txtlibcubridcs.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.txtlibcubridsa.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.txtcub_server 데몬 바이너리를 빌드한다. 모든 서버 측 파일에 network_sr.cnetwork_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.c
static 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_ONLYcub_server가 붙지 않은 상태에서 실행해야 한다. 온-디스크 상태에 독점적 접근이 필요한 연산들이다. createdb는 데이터베이스가 아직 없어 데몬이 연결할 대상이 없고, deletedb 는 데이터베이스가 곧 사라지는 상황이며, restoredb는 백업 이미지로 온-디스크 상태를 덮어쓰는 중이다. optimizedb는 힙 페이지를 직접 스캔해 통계를 재구성하고, installdb는 처음 만들 어진 데이터베이스에 시스템 카탈로그를 설치하며, genlocale/ dumplocale은 로케일 데이터 파일을 다시 쓰고, synccolldb는 정렬 규칙 테이블을 다시 쓴다. 이 연산들에 CS 모드를 허용하는 것은 잘못된 것(같은 파일에 두 쓰기 주체)이거나 불가능한 것(데이 터베이스 자체가 없으니 데몬도 아직 없다)이다.

  • CS_ONLYcub_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.c
static 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.clocator_fetch다.

// locator_fetch — src/communication/network_interface_cl.c
int
locator_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.c
unsigned int db_on_server = 0;
#if defined (SA_MODE)
static void
enter_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 void
exit_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.c
int
boot_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.c
int
boot_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가 존재하지 않는 데이터베이스를 대상으로 시작해야 하는, 모순적인 상황이 필요 하기 때문이다. 따라서 createdbua_Utility_Map에서 SA_ONLY 다. assert는 개발자가 실수로 CS 모드 호출자를 createdb 경로에 연결했을 때만 발동한다.

재시작 경로는 더 흥미롭게 분기한다. boot_cl.cboot_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_serverlog_recovery)가 유틸리티가 처음 데이터 베이스를 열 때 트리거된다. CS 모드에서는 boot_sr.c가 데몬에서 실행되고, 복구는 데몬이 시작될 때 이미 완료되었다.

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_STANDALONEER_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인 것은 같은 논리적 연산이 두 맥락 모두에서 의미 있고, 운영자가 처리량 대 동시성의 절충을 선택할 때다.

구체적인 유틸리티로 풀면 다음과 같다.

유틸리티분류이유
createdbSA_ONLY데이터베이스가 아직 없어 데몬이 연결할 대상이 없다.
deletedbSA_ONLY파일이 곧 사라지는 상황이므로 데몬이 열어 놓으면 안 된다.
restoredbSA_ONLY백업 이미지로 바이트를 덮어 쓰는 중이므로 독점 접근이 필요하다.
installdbSA_ONLY처음 생성된 데이터베이스에 시스템 카탈로그를 설치한다.
optimizedbSA_ONLY힙을 직접 스캔해 통계를 재구성한다.
diagdbSA_ONLY진단 덤프를 위해 온-디스크 페이지를 직접 읽는다.
patchdbSA_ONLY컨트롤 구조를 강제로 변경하므로 실시간 쓰기 주체와 공유할 수 없다.
alterdbhostSA_ONLY데이터베이스 위치 메타데이터를 다시 쓴다.
genlocaleSA_ONLY로케일 데이터 파일을 빌드하는 단독 도구다.
dumplocaleSA_ONLY로케일 데이터 파일을 덤프하는 단독 도구다.
synccolldbSA_ONLY정렬 규칙 테이블을 다시 쓴다.
gen_tzSA_ONLY타임존 라이브러리를 빌드하는 단독 도구다.
dump_tzSA_ONLY타임존 라이브러리를 덤프하는 단독 도구다.
restoreslaveSA_ONLY백업으로 슬레이브를 구성하며 restoredb와 같은 신뢰 모델을 쓴다.
lockdbCS_ONLY데몬의 인메모리 락 테이블을 덤프한다.
killtranCS_ONLY데몬 안에서 실행 중인 트랜잭션을 종료한다.
plandumpCS_ONLY데몬의 플랜 캐시를 덤프한다.
statdumpCS_ONLY데몬의 성능 카운터를 덤프한다.
changemodeCS_ONLYHA 상태 전환이므로 실행 중인 데몬이 있어야 의미가 있다.
copylogdbCS_ONLY마스터에서 로그 레코드를 가져오는 HA 복제 데몬이다.
applylogdbCS_ONLY가져온 로그를 슬레이브에 적용하는 HA 복제 데몬이다.
applyinfoCS_ONLYHA 복제 상태를 읽는다.
acldbCS_ONLY데몬의 IP ACL을 다시 불러온다.
tranlistCS_ONLY데몬에서 실행 중인 트랜잭션 목록을 보여 준다.
checksumdbCS_ONLYHA 모드에서 마스터와 슬레이브 사이 페이지별 일관성 검사를 한다.
flashbackCS_ONLY실행 중인 서버가 보관하는 온라인 변경 이력을 읽는다.
memmonCS_ONLY데몬의 메모리 모니터를 읽는다.
backupdbSA_CS온라인(CS) 또는 오프라인(SA) 백업이다. 동시성 대 단순성의 절충이다.
addvoldbSA_CS온라인(CS) 또는 오프라인(SA)으로 볼륨을 추가한다.
spacedbSA_CS공간 사용량을 보고한다. CS는 실행 중인 서버에서, SA는 온-디스크 메타데이터를 스캔한다.
cleanfiledbSA_CS댕글링 파일을 정리한다. 온라인 또는 오프라인 중 선택한다.
checkdbSA_CS일관성 검사다. SA 모드가 더 철저하지만 데이터베이스를 막는다.
loaddbSA_CSSA는 클래스 배타 락으로 힙에 직접 삽입. CS는 일반 삽입이다.
unloaddbSA_CSSA는 힙 직접 스캔. CS는 일반 SELECT다.
compactdbSA_CSSA는 배타 압축. CS는 온라인 압축이다.
paramdumpSA_CS디스크에서(SA) 또는 실행 중인 서버에서(CS) 파라미터를 덤프한다.
vacuumdbSA_CS기본은 SA. 오프라인 전체 청소. CS는 실행 중인 서버에 청소를 트리거한다.
tdeSA_CS투명 데이터 암호화 키 관리다.

이 표가 관리 작업에 관한 사용자와 엔진 사이의 전체 계약이다. 표에 없는 것은 SQL 명령이고, csql으로 실행된다(csql 자체는 SA_CS다).

load_db.ccub_loaddb의 런처이자 SA_CS 패턴을 실제 코드로 보여 주는 곳이다. 같은 소스가 libcubridsa.solibcubridcs.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_launcher.ccub_csql을 구현하며 같은 템플릿을 따른다.

  • 런처는 얇은 바이너리다. libcubridsa.solibcubridcs.so에 직접 링크하지 않는다.
  • --SA-mode/-S--CS-mode/-C 플래그를 csql_arg.sa_modecsql_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.txtadd_executable(cubrid …), target_compile_definitions(cubrid PRIVATE SERVER_MODE …)
  • sa/CMakeLists.txtadd_library(cubridsa SHARED …), target_compile_definitions(cubridsa PRIVATE SA_MODE CUBRID_EXPORTING …)
  • cs/CMakeLists.txtadd_library(cubridcs SHARED …), target_compile_definitions(cubridcs PRIVATE CS_MODE CUBRID_EXPORTING …)

라이브러리 / 런처 선택

  • LIB_UTIL_SA_NAME (매크로) — src/executables/utility.h
  • LIB_UTIL_CS_NAME (매크로) — src/executables/utility.h
  • ua_Utility_Map (테이블) — src/executables/util_admin.c
  • util_get_library_namesrc/executables/util_admin.c
  • utility_load_library / utility_load_symbolsrc/executables/utility.h (선언) 및 라이브러리 로더
  • main (cub_admin 진입점) — src/executables/util_admin.c
  • main (csql 진입점) — src/executables/csql_launcher.c

부팅 — 클라이언트 측

  • BOOT_IS_CLIENT_RESTARTED (매크로) — src/transaction/boot_cl.h
  • boot_initialize_clientsrc/transaction/boot_cl.c
  • boot_restart_clientsrc/transaction/boot_cl.c
  • boot_client_initialize_css (CS 전용) — src/transaction/boot_cl.c
  • boot_check_locales (CS 전용) — src/transaction/boot_cl.c
  • boot_check_timezone_checksum (CS 전용) — src/transaction/boot_cl.c
  • boot_shutdown_clientsrc/transaction/boot_cl.c
  • boot_Host_connected (CS 전용 정적 변수) — src/transaction/boot_cl.c

부팅 — 서버 측

  • BO_IS_SERVER_RESTARTED (매크로) — src/transaction/boot_sr.h
  • boot_Server_process_idsrc/transaction/boot_sr.c
  • xboot_initialize_serversrc/transaction/boot_sr.c
  • boot_restart_serversrc/transaction/boot_sr.c
  • xboot_register_clientsrc/transaction/boot_sr.c
  • log_initialize (복구 드라이버) — src/transaction/log_manager.c
  • locator_initializesrc/transaction/locator_sr.c

호출별 디스패치 (모든 서버 바운드 호출의 SA/CS 분기)

  • db_on_server (카운터) — src/communication/network_interface_cl.c
  • enter_serversrc/communication/network_interface_cl.c (SA 전용)
  • enter_server_no_thread_entrysrc/communication/network_interface_cl.c (SA 전용)
  • exit_serversrc/communication/network_interface_cl.c (SA 전용)
  • exit_server_no_thread_entrysrc/communication/network_interface_cl.c (SA 전용)
  • boot_initialize_server (디스패치) — src/communication/network_interface_cl.c
  • boot_register_client (디스패치) — src/communication/network_interface_cl.c
  • locator_fetch (대표 예시) — src/communication/network_interface_cl.c

CS 전용 전송

  • set_server_errorsrc/communication/network_cl.c
  • net_client_requestsrc/communication/network_cl.c
  • net_client_request_internalsrc/communication/network_cl.c
  • net_client_request_no_replysrc/communication/network_cl.c
  • net_client_request2src/communication/network_cl.c
  • net_client_request_send_large_datasrc/communication/network_cl.c
  • net_client_request_recv_large_datasrc/communication/network_cl.c
  • net_client_request_recv_copyareasrc/communication/network_cl.c
  • net_Server_host, net_Server_name (정적 변수) — src/communication/network_cl.c
  • 컴파일 타임 가드 #if !defined (CS_MODE) / #error Does not belong to cs modulesrc/communication/network_cl.c

Loaddb 분기

  • loaddb (진입점, sa/cs 라이브러리에서 내보냄) — src/loaddb/load_db.c
  • load_args (구조체) — src/loaddb/load_common.hpp
  • LOAD_CS_MODE_S (옵션 문자) — src/executables/utility.h
  • load_sa_loader.cpp (sa/CMakeLists.txt에만 있는 SA 전용 파일) — src/loaddb/
  • ldr_validate_object_filesrc/loaddb/load_db.c

CSQL 런처

  • mainsrc/executables/csql_launcher.c
  • CSQL_SA_MODE_S, CSQL_CS_MODE_S (옵션 문자) — src/executables/csql_launcher.c

오류 가드

  • ER_NOT_IN_STANDALONE (매크로) — src/base/error_code.h
  • ER_ONLY_IN_STANDALONE (매크로) — src/base/error_code.h
  • ER_NOT_IN_STANDALONE 발생 지점 — src/transaction/boot_cl.c
  • ER_ONLY_IN_STANDALONE 발생 지점 — src/transaction/log_manager.c
심볼파일
target_compile_definitions(cubrid PRIVATE SERVER_MODE …)cubrid/CMakeLists.txt675
target_compile_definitions(cubridsa PRIVATE SA_MODE …)sa/CMakeLists.txt718
target_compile_definitions(cubridcs PRIVATE CS_MODE …)cs/CMakeLists.txt577
add_library(cubridsa SHARED …)sa/CMakeLists.txt671
add_library(cubridcs SHARED …)cs/CMakeLists.txt536
LIB_UTIL_SA_NAME (리눅스)src/executables/utility.h1809
LIB_UTIL_CS_NAME (리눅스)src/executables/utility.h1808
ua_Utility_Map[]src/executables/util_admin.c966
util_get_library_namesrc/executables/util_admin.c1168
csql_launcher main, util 라이브러리의 dlopensrc/executables/csql_launcher.c466
boot_initialize_clientsrc/transaction/boot_cl.c275
boot_initialize_client CS 모드 CSS 초기화src/transaction/boot_cl.c509
boot_restart_clientsrc/transaction/boot_cl.c690
boot_restart_client CS 모드 db@host 파싱src/transaction/boot_cl.c824
ER_NOT_IN_STANDALONE 발생 지점src/transaction/boot_cl.c847
boot_Host_connected (CS 전용)src/transaction/boot_cl.c150
xboot_initialize_serversrc/transaction/boot_sr.c1385
boot_restart_serversrc/transaction/boot_sr.c1969
db_on_serversrc/communication/network_interface_cl.c103
enter_server_no_thread_entrysrc/communication/network_interface_cl.c124
enter_serversrc/communication/network_interface_cl.c142
exit_server_no_thread_entrysrc/communication/network_interface_cl.c152
exit_serversrc/communication/network_interface_cl.c168
locator_fetch (CS/SA 분기)src/communication/network_interface_cl.c270
boot_initialize_server (디스패치)src/communication/network_interface_cl.c3919
#error Does not belong to cs modulesrc/communication/network_cl.c65
set_server_errorsrc/communication/network_cl.c148
net_client_requestsrc/communication/network_cl.c587
loaddb SA의 load_sa_loader.hpp 포함src/loaddb/load_db.c28
loaddb SA volatile bool interruptedsrc/loaddb/load_db.c543
loaddb SA/CS 분기src/loaddb/load_db.c823
LOAD_CS_MODE_S cs_mode 옵션src/loaddb/load_db.c1281
ER_ONLY_IN_STANDALONE 발생 지점src/transaction/log_manager.c1018
ER_ONLY_IN_STANDALONE (#define)src/base/error_code.h734

SA/CS 구분은 CUBRID의 첫 오픈소스 릴리스 이후 안정적인 아키텍처 기능으로 유지되어 왔다. 브로커/CAS의 엔진 통합보다 앞서 있으며, 여러 차례의 HA, MVCC, TDE 리팩터를 거쳐도 살아남았다. 계층화가 겉보기보다 덜 깔끔한 몇 가지 지점이 있다.

  1. network_interface_cl.c는 두 코드 경로를 모두 담는다. 이름에도 불구하고 이 파일은 libcubridsa.solibcubridcs.so 모두에 컴파일된다. 내부의 모든 서버 바운드 호출은 #if defined (CS_MODE)#else#endif 형태를 갖는다. 이 파일의 크기(이 글을 쓰는 시점에 약 278 KB)는 그 중복된 본문에 직접 기인한다. 디스패치를 *_cs.c*_sa.c 파일로 분리하려는 이전 시도가 있었다. 그러나 함수 시그니처, 인자 패킹 로직, 공개 API 상용구를 중복하면서도 전체 소스 크기를 줄이지 못한다는 점이 문제였다. 함수별 분기가 더 작은 악이다.

  2. 이름이 클라이언트인 파일 중에도 SA_MODE 전용 서버 측 코드를 담는 것이 있다. 예컨대 locator_cl.c에는 #if defined (SA_MODE) && !defined (CUBRID_DEBUG)로 보호된 블록이 있다(1011, 1713, 1872행). 이는 SA 모드 전용 디버그 빌드에서 경계를 넘는 경로다. “*_cl.c는 클라이언트, *_sr.c는 서버”라는 구조적 규칙은 절대적이지 않다. 정확히는 “*_cl.c 파일이 와이어 또는 서버 중 하나로 디스패치하는 진입점이다”는 의미다.

  3. 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_serverlibcubridsa.so 모두에서 실행된다.

  4. 분류 테이블은 단일 진실의 원천이지만 완전하지는 않다. 일부 유틸리티는 util_get_library_name 안에 하드코딩된 기본값 (if (utility_index == VACUUMDB || utility_index == TDE) return LIB_UTIL_SA_NAME;)을 가지며 이것이 SA_CS의 기본값을 덮어쓴다. 테이블에 새 유틸리티를 추가할 때 이 뒷부분 블록을 생각하지 않으면 조용히 잘못된 라이브러리를 선택하게 될 수 있다.

  5. 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_admindlopen-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_STANDALONEER_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.hLIB_UTIL_*_NAME 매크로.
  • src/executables/csql_launcher.ccsql 라이브러리 선택.
  • src/loaddb/load_db.c — 로더 진입점의 SA_CS 분기.
  • src/base/error_code.hER_NOT_IN_STANDALONE, ER_ONLY_IN_STANDALONE.
  • sa/CMakeLists.txtcubridsa 공유 라이브러리 구성; target_compile_definitions(cubridsa PRIVATE SA_MODE …).
  • cs/CMakeLists.txtcubridcs 공유 라이브러리 구성; target_compile_definitions(cubridcs PRIVATE CS_MODE …).
  • cubrid/CMakeLists.txtcub_server 실행 파일; target_compile_definitions(cubrid PRIVATE SERVER_MODE …).