(KO) CUBRID 서버 아키텍처 — 섹션 개요
목차
이 섹션의 범위
섹션 제목: “이 섹션의 범위”이 섹션은 cub_server를 서버답게 만들어 주는 모든 부분을 다룬다. 스토리지·쿼리·복제 엔진 그 자체가 아니라, 그것들을 둘러싼 프로세스 차원의 비계가 주제다. 서버 프로세스가 어떻게 깨어나는지, 클라이언트 드라이버에서 출발한 요청이 워커 스레드까지 어떤 길을 따라 오는지, 요청과 요청 사이에서 클라이언트별 상태가 어디 보관되는지, 어떤 스레드 도구가 실제로 일을 굴리는지, 그 일이 스토리지 계층으로 어떻게 넘어가는지, 그리고 다른 모든 모듈이 의지하는 횡단 인프라(설정, 에러, 모니터링)가 무엇인지 — 이 모두가 여기 들어 있다.
세부 문서 열세 개가 네 가지 결로 갈라진다.
- 라이프사이클 — 부팅 순서, SA vs CS 컴파일 시 결정.
- 클라이언트 인터페이스 — C 클라이언트 API, 모든 TCP 클라이언트 앞에 서는 broker 풀, 요청 하나하나를 실어 나르는 CSS 프레이밍과 NRP 디스패치.
- 클라이언트별 상태와 동시성 —
SESSION_STATE컨테이너, 그리고 스레드·워커 풀(CBRD-26177 NG 재설계 포함). - 데이터로의 다리 — 로케이터 OID 워크스페이스.
거의 모든 서브시스템이 읽거나 쓰는 횡단 모듈 셋(시스템 파라미터, 에러 관리, 모니터링)도 같이 들어 있다. 전문 유틸리티 문서(loaddb)가 한 자리 차지하는 이유는, SA 모드 임베딩과 서버 모드 워커 풀 경로 둘 다를 가장 잘 보여 주는 사례이기 때문이다.
이 섹션은 스토리지 엔진, 쿼리 프로세서, DDL 실행기, 복제 스택의 내부 동작을 설명하지 않는다. 그쪽은 각자 자기 섹션이 따로 있다. 이 섹션은 그 모든 것의 아래에 깔린 바닥이다.
프로세스와 요청 흐름
섹션 제목: “프로세스와 요청 흐름”세부 문서들은 살아 있는 cub_server의 요청 경로 위에서 맡는 역할로 묶인다.
라이프사이클. cubrid-boot.md는 cub_server가 모든 서브시스템을 차례로 깨우는 엄격한 순서를 정리한다(에러 리포터 → sysparam → 메모리 → 로케일 → 페이지 버퍼 → 로그 → 락 → MVCC → 카탈로그 → 스키마 → 네트워킹). 클라이언트 쪽의 짝인 boot_restart_client 흐름, 최초 한 번 도는 createdb 길, 재시작 시 log_recovery가 돌리는 세 단계 재실행도 모두 한 문서에 들어가 있다. cubrid-sa-cs-runtime.md는 같은 소스 트리가 왜 세 가지 산출물로 빌드되는지를 풀어 둔다. cub_server(SERVER_MODE 데몬), libcubridsa(SA_MODE 인프로세스 엔진), libcubridcs(CS_MODE 와이어 드라이버). 유틸리티마다 SA_ONLY/CS_ONLY/SA_CS로 분류해 두고, 런타임에 적절한 .so를 dlopen하는 트릭 덕분에 CLI 바이너리 한 개가 엔진을 직접 안고 돌거나 CSS로 통신하거나를 골라 잡을 수 있다.
클라이언트 인터페이스. cubrid-dbi-cci.md는 클라이언트 쪽 C API다. JDBC, CCI, ODBC, Python, PHP가 결국 모두 이 계층을 통과한다(db_open_buffer, db_compile_statement_local, db_execute_statement, db_query_first_tuple, db_close_session). 4단계 구문 FSM(Initial → Compiled → Prepared → Executed)이 boot_cl과 network_cl 위에서 어떻게 굴러가는지를 함께 설명한다. cubrid-broker.md는 그 앞에 서는 현관이다. cub_broker 부모가 TCP 리스너 하나를 열고, 정해진 수의 cub_cas 워커 프로세스를 미리 fork해 두고, 들어온 클라이언트마다 SCM_RIGHTS로 파일 디스크립터를 넘기는 유닉스 도메인 랑데부 채널로 한가한 CAS에 떠넘긴다. 이후 그 CAS가 클라이언트의 CSS 프레임 트래픽을 cub_server 쪽으로 프록시한다. 이 모든 협업은 작업 큐, ACL, 모니터링 카운터를 다 같이 담아 둔 SysV 공유 메모리 한 덩어리에서 조율된다. cubrid-network-protocol.md는 양쪽의 와이어를 다룬다. CSS의 길이-접두 패킷 프레이밍, NRP 오피코드 공간(NET_SERVER_*), 오피코드를 서버 핸들러에 묶어 두는 정적 디스패치 표((action_attribute, handler) 행), 그리고 클라이언트와 서버가 함께 쓰는 대칭형 마샬링(or_pack_*/or_unpack_*).
클라이언트별 상태. cubrid-server-session.md는 cub_server 안쪽 클라이언트별 컨테이너다. SESSION_ID로 키를 잡는 락 프리 해시맵 위에 prepared 캐시, autocommit 모드, last-insert-id, SET 변수 바인딩이 함께 얹혀 있다. 한 번 풀어 둔 SESSION_STATE *는 연결 엔트리에 캐시되어 다음 요청부터 O(1)로 잡히고, 스레드별 TDES에 묶여 있어 어떤 핸들러로 빠지든 자기 트랜잭션 디스크립터를 그대로 받아 간다.
동시성 도구. cubrid-thread-worker-pool.md는 기준선 — 옛 스레드 계층을 다룬다. 스레드별 cubthread::entry 컨텍스트, 쿼리/vacuum/loaddb/병렬 redo를 굴리는 cubthread::worker_pool 템플릿(코어 → 워커 → 태스크 큐), 주기적인 플러시·감지 뒤에 자리한 daemon + looper 패턴, 락 매니저와 페이지 버퍼가 함께 쓰는 락 프리 해시맵, 스레드별 트래커를 단 무거운 csect RW 프리미티브가 들어간다. cubrid-thread-manager-ng.md는 Guava 시점의 살아 있는 그림이다. CBRD-26177 재설계로, 경계가 매겨진 epoll 기반 connection_worker들, 리밸런싱과 자동 스케일링을 중재하는 coordinator, 송수신 budget, 워커당 컨텍스트 freelist, 원자 연산을 빼낸 통계가 핵심이다. NG 재설계는 옛 스레드-퍼-커넥션 + max_clients 태스크 워커 배치를 대체한다. 두 문서 모두 함께 읽어야 굴러가는 엔진이 보인다. NG는 옛 도구를 갈아 끼운 것이 아니라 그 위에 새로 얹은 층이다.
데이터로의 다리. cubrid-locator.md가 이 섹션과 스토리지 엔진 섹션의 경계 위에 서 있다. 클라이언트 쪽에서는 더티 객체를 LC_COPYAREA에 모으는 OID 워크스페이스가 있고, 서버 쪽에는 같은 진입점 하나에서 출발해 heap·btree·lock·log·FK·복제 길로 갈라지는 locator_*_force 가족이 있다. 이 한 쌍이 한 문서에 들어가 있다. 로케이터를 이 섹션에 둔 까닭은, 그 자체가 서버 쪽 모임 지점 — 모든 DML이 스토리지에 닿기 전에 들르는 표준 입구 — 이기 때문이다. 실제 일은 스토리지에서 벌어지지만, 입장은 여기서 받는다.
횡단 인프라. 사실상 모든 서브시스템이 의지하는 모듈 셋이 한자리에 모인다. cubrid-system-parameters.md는 prm_Def[] 레지스트리, cubrid.conf 파서, 환경 변수 오버라이드, db_set_system_parameters SQL 길, 세션별 SESSION_PARAM 배열을 다룬다. 모두 prm_get_*_value를 거쳐 읽힌다. cubrid-error-management.md는 스레드별 cuberr::context, printf 스타일 포맷의 er_set 가족, 로컬라이즈된 cubrid.msg / csql.msg / utils.msg nl_catd 카탈로그, 도는 cubrid_*.err 로그, 그리고 프로세스 사이를 건너는 에러를 OR_INT 트리플로 평탄화하는 er_get_area_error / er_set_area_error 와이어 포맷을 다룬다. cubrid-monitoring.md는 카운터 시스템을 정리한다. 트랜잭션 단위 시트가 있는 C++ 템플릿 cubmonitor, SHOW STATS와 statdump가 쓰는 옛 C perf_monitor / pstat_Metadata 배열, 그리고 vacuum 오버플로 페이지 임계값 추적기 같은 서브시스템 전용 모니터까지.
대량 적재. cubrid-loaddb.md는 벌크 로더 유틸이다. 보통 SA 모드 바이너리로 도는데, CUBRID 형식 객체 파일을 토큰화하고, 배치로 끊고, Bulk-Update 락을 잡고, locator_multi_insert_force로 heap 페이지에 직접 쓰고, 끝에서 클래스마다 통계를 다시 쌓는다. SA 모드 임베딩(인프로세스 엔진, CSS 한 단계 생략)을 가장 깔끔히 보여 주는 사례이고, 병렬 배치를 위해 서버 모드 워커 풀 길도 한 번 거친다.
아래 다이어그램이 정상 경로 전체 그림이다. 애플리케이션 안의 JDBC 드라이버가 TCP로 broker에 닿고, broker가 한가한 CAS와 짝지어 주고, 그 CAS가 cub_server로 CSS 연결을 연다. 서버 안에서는 NRP 디스패치가 요청을 워커 스레드로 보내고, 워커는 자기 세션 상태를 본 뒤 로케이터를 거쳐 스토리지로 진입한다.
flowchart LR
subgraph App["애플리케이션 프로세스"]
JDBC["JDBC / CCI / ODBC<br/>(드라이버)"]
end
subgraph Broker["cub_broker 호스트"]
BR["cub_broker<br/>(TCP 리스너<br/>· shm)"]
CAS["cub_cas 워커<br/>(fork됨, DB_SESSION·<br/>SQL 로그·ACL 보유)"]
end
subgraph CubServer["cub_server (DB 1개)"]
NET["network_sr<br/>(CSS 프레이머 + NRP<br/>디스패치 표)"]
WP["워커 풀<br/>(레거시 worker_pool<br/>또는 NG connection_worker<br/>· coordinator/epoll)"]
SESS["SESSION_STATE<br/>(prepared stmt·<br/>SET 변수·TDES 바인딩)"]
LOC["로케이터<br/>(OID 워크스페이스,<br/>locator_force 가족)"]
XCUT["횡단 인프라<br/>sysparam · error_manager<br/>· monitor / perfmon"]
end
Storage[("스토리지 엔진<br/>heap · btree · log · lock · MVCC")]
JDBC -->|TCP, CCI/JDBC 와이어| BR
BR -->|"SCM_RIGHTS<br/>fd 핸드오프"| CAS
CAS -->|CSS 프레임,<br/>NRP 오피코드| NET
NET --> WP
WP --> SESS
SESS --> LOC
LOC --> Storage
XCUT -.읽힌다.-> NET
XCUT -.읽힌다.-> WP
XCUT -.읽힌다.-> SESS
XCUT -.읽힌다.-> LOC
점선은 횡단 모듈을 가리킨다. cub_server 안 모든 박스가 sysparam을 읽고, er_set으로 에러를 올리고, perf 카운터를 올린다.
읽는 순서
섹션 제목: “읽는 순서”CUBRID 서버를 처음 열어 보는 사람이라면 알파벳순보다 다음 순서가 훨씬 빠르다.
- cubrid-boot.md — 여기서 시작한다. 부팅 순서가 그대로 의존성 그래프이기 때문이다. 어떤 서브시스템이 누구보다 먼저 올라오는지 손에 잡히면, 엔진의 나머지가 줄줄이 풀린다.
createdb와 복구 디스패치까지 한 문서에 들어 있어, 처음 띄우기와 크래시 뒤 다시 띄우기 두 결을 같이 본다. - cubrid-network-protocol.md와 cubrid-broker.md — 묶어서 읽는다. broker 문서는 프로세스 모양(broker, CAS,
cub_server)과, TCP 소켓이 CAS가 들고 있는 엔진 핸들로 어떻게 둔갑하는지를 설명한다. 네트워크 문서는 그 핸들 안에서 흐르는 프로토콜(CSS 프레이밍, NRP 오피코드, 디스패치 표)을 다룬다. 어느 한쪽만으로는 그림이 반쪽이다. - cubrid-server-session.md — 요청이 어떻게 들어오는지 잡았다면, 이번엔 어디에 떨어지는지를 본다. 세션은 클라이언트별 상태 컨테이너이자, 연결 엔트리와 트랜잭션 디스크립터를 잇는 다리다.
- cubrid-thread-worker-pool.md — 누가 일을 굴리는가. NG 재설계로 넘어가기 전에 옛 도구(워커, 데몬, 락 프리 해시, csect)부터 잡는다. 옛 도구는 여전히 바이너리 안에 그대로 있다.
- cubrid-locator.md — 스토리지로 가는 다리. 모든 DML, 모든 fetch, 모든 워크스페이스 flush가 이 길을 지난다. 요청 흐름 묶음 가운데 마지막에 두는 게 좋다. 핸들러가 결국 호출하게 되는 자리이기 때문이다.
- cubrid-thread-manager-ng.md — 그 위에 새로 얹은 현대적 재설계. 옛 워커 풀이 머리에 들어와 있어야 NG가 풀린다. 이 문서는 CBRD-26177의 connection-worker / coordinator / epoll, 송수신 budget, 자동 스케일링, 워커당 freelist를 차례로 다룬다. 지금 Guava에서 도는 그림이 여기 들어 있다.
- 횡단 모듈은 그때그때.
- cubrid-system-parameters.md —
prm_get_*_value를 부르는 코드를 읽거나, 튜닝이 의미를 가지는 서브시스템(버퍼 크기, 타임아웃, 임계치)을 들여다볼 때. - cubrid-error-management.md —
er_set,ER_*, 또는 CUBRID 에러 코드를 돌려주는 코드를 읽을 때 — 사실상 서버의 거의 모든 파일에 해당된다. - cubrid-monitoring.md —
perfmon_*,cubmonitor::*,pstat_*을 쓰는 코드, 또는 SHOW STATS / statdump 출력을 살필 때.
- cubrid-system-parameters.md —
- cubrid-dbi-cci.md — 클라이언트 쪽 코드(CCI, JDBC 네이티브 다리, csql, 실행 중인 서버에 말 거는 유틸 바이너리)를 읽기 시작할 때 꺼낸다. broker의 CAS가
ux_*로 부르는 인터페이스라, CAS 바깥으로 쿼리를 따라가기 시작하는 순간부터 유용하다. - cubrid-sa-cs-runtime.md — 유틸 바이너리(loaddb, unloaddb,
--SA-modecsql, backupdb, restoredb, compactdb)를 다뤄야 할 때 읽는다. 두 모드 컴파일이 어째서 같은db_*API가 엔진을 안고 돌 수도 있고 원격으로 통신할 수도 있는지를 풀어 준다. - cubrid-loaddb.md — 전문이지만 가치가 크다. SA 모드 길을 끝에서 끝까지 가장 또렷이 보여 주는 사례(파서, 배치, 로케이터를 우회한 쓰기, 통계 재구성)이고, 섹션을 다 읽은 뒤 정리용으로도 좋다.
머릿속 모델 한 장으로 시작하고 싶다면 cubrid-boot.md + cubrid-network-protocol.md + 위 다이어그램만 챙겨도 된다. 실제 요청을 소스에서 따라가기 시작하기에는 그것만으로 충분하다.
횡단 관심사
섹션 제목: “횡단 관심사”세부 문서를 따로따로 읽으면 놓치기 쉬운 사실 몇 가지를 짚어 둔다.
CBRD-26177 NG 재설계는 옛 워커 풀을 갈아 끼우지 않는다, 그 위에 얹는다. 굴러가는 엔진을 보려면 cubrid-thread-worker-pool.md와 cubrid-thread-manager-ng.md가 둘 다 필요하다. 템플릿 cubthread::worker_pool은 여전히 서버 안의 태스크 풀(vacuum 워커, loaddb 워커, 병렬 redo)을 떠받친다. NG가 갈아 낀 자리는 연결 처리 계층뿐이다. 옛 스레드-퍼-커넥션 + max_clients 태스크 워커 배치가 경계 매긴 epoll 기반 connection_worker + 리밸런싱과 자동 스케일링을 중재하는 coordinator로 바뀌었다. NG 문서 없이 네트워크 코드를 읽거나, 옛 문서 없이 vacuum/loaddb 코드를 읽으면 그림이 절반만 보인다.
로케이터가 이 섹션과 스토리지 엔진의 경계다. 서버 아키텍처 섹션에 자리 잡은 까닭은 단순하다. 서버 쪽 모임 지점이고, 모든 DML이 스토리지에 닿기 전에 들르는 표준 입구다. 게다가 워크스페이스 절반은 CS 모드에서는 클라이언트 쪽에 산다. 그러나 실제 일은 스토리지 계층에서 벌어진다(heap 페이지, btree 리프, 락 테이블, 로그 레코드, FK 강제, 복제로 흘려보내기). 쓰기를 따라갈 때 로케이터 문서가 경첩이 된다. 그다음 정거장은 스토리지 엔진 섹션이다.
Sysparam, 에러, 모니터링은 다른 모든 서브시스템에 닿아 있다. cubrid-system-parameters.md는 모든 요청에서 모든 모듈이 한 번씩은 두드린다(타임아웃, 버퍼 크기, 기능 토글). cubrid-error-management.md는 모든 모듈이 실패를 알리는 길이다(er_set 호출이 코드베이스 전체를 가로지르는 단면이라고 봐도 된다). cubrid-monitoring.md는 모든 모듈이 카운터를 내거는 자리다(perfmon_inc_stat, cubmonitor::*). 개념이 다르니 문서는 따로지만, 다른 어느 섹션의 어느 세부 문서를 읽더라도 이 셋이 거기 있다는 전제 위에서 읽힌다.
부트 문서는 비슷해 보이는 두 흐름을 같이 다룬다. 최초의 createdb는 볼륨을 포맷하고 root-class 카탈로그를 부트스트랩한다. 기존 데이터베이스 재시작은 클라이언트를 받기 전에 log_recovery의 세 단계 재실행을 돌린다. 둘 다 cubrid-boot.md 안에 들어 있지만, 코드 안에서 가는 길은 매우 다르다. 시작 단계에서 무언가 어그러지면, 어느 흐름에 있는가가 첫 진단 질문이 된다.
SA 모드와 CS 모드는 같은 db_* API가 다른 바닥 위에 얹힌 모양이다. cubrid-sa-cs-runtime.md는 유틸리티별 분류와 dlopen 선택을 풀어 둔다. 이걸 머리에 한 번 넣어 두면, 어떤 유틸 바이너리든 런처가 어떤 .so를 무는지만 보고도, 같은 db_* 호출을 인프로세스 엔진(SA) 또는 CSS 와이어(CS) 어느 쪽으로든 따라갈 수 있다. cubrid-dbi-cci.md에 적힌 클라이언트 쪽 db_* API는 두 경우 모두 동일하다. 그게 이 설계의 핵심이다.
broker의 CAS도 외부 클라이언트와 똑같은 db_* API를 쓴다. broker는 특권 길이 아니다. CAS가 db_compile_statement_local, db_execute_statement, db_query_*를 부르는 모습이 CCI나 JDBC 네이티브와 다르지 않다. CAS라는 추상은 오직 프로세스 차원의 풀링만을 위한 것이다(fork된 워커, fd 패싱, ACL, SQL 로그). 전체 그림을 보려면 cubrid-broker.md와 cubrid-dbi-cci.md를 같이 보면 된다.
개별 문서 요약
섹션 제목: “개별 문서 요약”| # | 문서 | 모듈 | 한 줄 역할 |
|---|---|---|---|
| 1 | cubrid-boot.md | boot | 의존성 순서대로 서브시스템 기동, createdb, 재시작 복구 디스패치, 클라이언트 연결 핸드셰이크 |
| 2 | cubrid-sa-cs-runtime.md | sa-cs-runtime | 세 갈래 컴파일(cub_server, libcubridsa, libcubridcs)과 유틸리티별 dlopen 선택 |
| 3 | cubrid-dbi-cci.md | dbi-cci | JDBC/CCI/ODBC/CSQL 아래의 db_* C 클라이언트 API와 4단계 구문 FSM |
| 4 | cubrid-broker.md | broker | cub_broker + fork된 cub_cas 풀, fd 패싱 랑데부, SQL 로그, ACL, SysV shm |
| 5 | cubrid-network-protocol.md | network-protocol | CSS 길이-접두 프레이밍과 NRP NET_SERVER_* 디스패치 표 |
| 6 | cubrid-server-session.md | server-session | 클라이언트별 SESSION_STATE 락 프리 해시 + TDES 바인딩 |
| 7 | cubrid-thread-worker-pool.md | thread-worker-pool | 옛 스레드 계층 — cubthread::entry, 워커 풀, 데몬, csect, 락 프리 해시맵 |
| 8 | cubrid-thread-manager-ng.md | thread-manager-ng | CBRD-26177 NG 재설계 — epoll connection worker, coordinator, budget, 자동 스케일링 |
| 9 | cubrid-locator.md | locator | OID 워크스페이스 + heap/btree/lock/log/FK/복제로 갈라지는 locator_*_force 서버 모임 지점 |
| 10 | cubrid-system-parameters.md | system-parameters | prm_Def[] 레지스트리, conf/env/URL 파싱, 세션 단위 스코프, prm_get_*_value |
| 11 | cubrid-error-management.md | error-management | 스레드별 cuberr::context, er_set 가족, 메시지 카탈로그, 로그 회전, 와이어 포맷 |
| 12 | cubrid-monitoring.md | monitoring | cubmonitor C++ 라이브러리와 옛 perf_monitor/pstat_Metadata C 배열 |
| 13 | cubrid-loaddb.md | loaddb | SA 모드 벌크 로더 — 파서, 배치, 로케이터 우회 삽입, 통계 재구성 |
인접 섹션
섹션 제목: “인접 섹션”CUBRID code-analysis 트리의 다른 섹션은 모두 이 섹션 위에 서 있다. 서버 아키텍처가 나머지 엔진의 바닥이라는 뜻이다. 의존이 갈래는 다음 정도다.
- 스토리지 엔진(heap, btree, 페이지 버퍼, 로그, 락, MVCC, 복구, 더블 라이트 버퍼, 디스크 매니저, extendible hash, external sort, list file, overflow file, prior list, checkpoint, backup-restore)은 여기 적힌 로케이터를 통해 닿는다. 모든
locator_*_force호출이 결국 heap 또는 btree 코드로 떨어진다. cubrid-locator.md를 핸드오프 지점으로 본다. - 쿼리 처리(파서, 옵티마이저, 실행기, 평가기, 해시 조인, 병렬 쿼리, list file, 후처리, 파티션, JSON 테이블, 커서)는 cubrid-thread-worker-pool.md가 추적하는 서버 워커 스레드 안에서 굴러가고, 상태는 cubrid-server-session.md의 prepared 캐시에 들어가며, cubrid-network-protocol.md에 적힌
NET_SERVER_QM_*오피코드 가운데 하나로 들어온다. - DDL과 스키마(카탈로그 매니저, 클래스 객체, ddl-execution, 인증, charset/collation)는 같은 부팅 순서 cubrid-boot.md의 카탈로그 부트스트랩을 받아 쓰고, 카탈로그 클래스 쓰기마다 cubrid-locator.md를 거쳐 스토리지에 닿는다.
- 복제와 HA(ha-replication, heartbeat, CDC, 2PC, flashback)는 cubrid-network-protocol.md에 적힌 CSS 프로토콜의 옆 갈래로 자기 트래픽을 흘리고,
cub_master와 broker 클러스터가 그 위에서 감시한다. 부팅 순서도 그대로 따라가고, 로그 시핑·복제 적용 흐름은 cubrid-thread-worker-pool.md의 워커 풀과 로케이터의locator_force가족 위에 한 층 더 얹힌다. - PL 패밀리(pl-javasp, pl-plcsql)는
cub_server에서 시작한 연결로 JVM이 들고 있는pl_server와 메시지를 주고받는다. 요청은 여기 적힌 broker / CAS / CSS 길로 그대로 들어오고, 그다음 옆 프로세스로 위임된다.
이 가운데 어느 섹션을 읽다가 이 코드까지 요청이 어떻게 닿았지?라는 물음이 떠오르면, 답은 이 섹션 안에 있다.