콘텐츠로 이동

[KO] CUBRID PL/JavaSP — Java 저장 프로시저, JDBC 백채널, 그리고 PL/CSQL과 형제인 외부 PL 엔진

목차

이 문서는 JavaSP — Java 저장 프로시저 서브시스템 — 을 다룬다. 형제 문서인 cubrid-pl-plcsql.md 는 CUBRID 의 Oracle 방언 절차적 언어인 PL/CSQL 을 다룬다. 두 문서는 PL 패밀리 (pl-family 태그) 를 이루는데, 사용자 코드로 들어가는 마지막 디스패치 단계만 빼면 거의 모든 것을 공유하기 때문이다.

계층공유 산출물
카탈로그_db_stored_procedure, _db_stored_procedure_args, _db_stored_procedure_code 라는 동일한 세 시스템 클래스의 행들 (속성 이름은 sp_constants.hpp, 그 행을 쓰는 C++ 구조체는 sp_catalog.hpp 참조)
전송 (transport)pl_connection.cpp / pl_comm.ccub_server 에서 cub_pl 로 가는 Unix domain socket (UDS) 또는 TCP. 언어가 무엇이든 동일한 PL_CONNECTION_POOLconnection_view 타입이 쓰인다
실행기 (executor)pl_executor.cpp / pl_session.cpp — C++ executor 클래스가 모든 호출을 공유 connection pool 위로 cub_pl 에게 보낸다. session 클래스가 두 언어 모두를 cursor, execution stack, per-session 파라미터를 추적한다
JVM 호스팅Server.java, ListenerThread.java, ExecuteThread.java — 단일 cub_pl 프로세스가 JVM 하나를 띄우고, 그 JVM 이 JavaSP JAR 디스패치와 PL/CSQL 컴파일러+런타임을 함께 호스팅한다
관심사산출물
사용자 JAR 위의 reflective 디스패치TargetMethod.javaClassName.methodName(argTypes) 를 reflection 으로 풀고, StoredProcedure.invoke() 가 로드된 클래스 위에서 Method.invoke() 를 호출
클래스로더 위계classloader/ 패키지 — ClassLoaderManager, ContextClassLoader, SessionClassLoader, ServerClassLoader. 사용자 JAR 은 $CUBRID_DATABASES/<db>/java/ (동적) 또는 java_static/ (정적) 경로에서 로드된다
보안 샌드박스SpSecurityManager.java — 거의 모든 것을 허용하지만 System.exit() (서버 자체가 셧다운 중일 때 한정 예외) 와 사용자 클래스로더로부터의 native 라이브러리 로드 (System.loadLibrary) 를 차단하는 커스텀 SecurityManager
레거시 in-process JVM 경로pl_sr_jvm.cpppl_start_jvm_server() / pl_server_port(). 외부 cub_pl 프로세스 모델 이전에 쓰이던 옛 in-process JNI 임베딩. 컴파일은 되지만 대체된 상태

두 PL 문서를 함께 읽을 때, 카탈로그와 wire 프로토콜은 여기서 한 번 설명되고 PL/CSQL 쪽에서는 교차 참조된다는 점을 염두에 두면 된다.

저장 프로시저 는 데이터베이스 엔진 안에 보관되어 SQL 또는 클라이언트 호출로 불려 가는, 이름이 붙고 매개변수화된 계산 단위다. 데이터베이스 시스템 관점에서는 구현 공간을 좌우하는 설계 선택이 셋 있다.

  1. 언어 런타임의 거주지. 프로시저 본체는 데이터베이스 서버 프로세스 안에서 (in-process) 돌 수도 있고, 서버가 포크해 띄운 위성 프로세스 (외부 프로세스) 에서 돌 수도 있고, 별도 애플리케이션 서버에서 돌 수도 있다. In-process 는 호출 오버헤드가 가장 낮고 내부 자료구조에 직접 접근할 수 있지만, 결함 있는 저장 프로시저 하나가 서버 전체를 죽일 수 있다. 외부 프로세스는 IPC 지연이라는 비용을 치르고 격리를 얻는다. CUBRID 은 in-process JNI 접근에서 시작했다가 격리를 위해 포크된 외부 프로세스 (cub_pl) 로 옮겨 왔다.

  2. 언어. SQL/PSM (SQL 표준의 절차적 확장), PL/SQL 계열 언어 (Oracle 방언, Sybase 방언), Java (IBM DB2 의 Java 루틴, CUBRID JavaSP), 그리고 JavaScript / Python (현대 NewSQL 엔진). Java 가 역사적으로 매력적이었던 이유는 강타입이고, JDBC 기반 애플리케이션 로직에서 이미 쓰이고 있었으며, 이식성 있는 바이트코드를 만들기 때문이다. 그 대가는 JVM 시작 비용과 GC pause 다. CUBRID 은 JVM 을 장기 사이드카로 살려 두는 방식으로 이를 완화한다.

  3. JDBC 백채널. Java 저장 프로시저가 SQL 쿼리를 발행하면 데이터베이스 연결이 필요해진다. 그 쿼리를 평범한 클라이언트-서버 경로로 흘려 보내면 네트워크 round-trip 이 추가로 발생하고 사용자가 자격 증명을 따로 넘겨야 한다. 통상적인 패턴은 (IBM DB2 가 그렇고 CUBRID 도 같은 설계를 따른다) 네트워크를 우회하는 서버 사이드 JDBC 드라이버 를 두는 것이다. 이 드라이버는 호출을 전달했던 그 IPC 채널 위로 콜백 요청을 원래의 서버 워커 스레드로 되쏘아 보낸다.

cub_server (C/C++ process)
│ PRM_ID_STORED_PROCEDURE = true
├── pl_server_init() ← boot_sr.c calls this during server boot
│ └── server_manager::start()
│ └── create_child_process("cub_pl", db_name) ← fork+exec
├── server_monitor_task (daemon thread, 1-sec loop)
│ ├── is_terminated_process(pid) ← reap and restart if cub_pl crashes
│ ├── do_check_connection() → do_ping_connection() ← SP_CODE_UTIL_PING
│ └── do_bootstrap_request() ← SP_CODE_UTIL_BOOTSTRAP (sends sysprms)
└── connection_pool (10 connections, UDS or TCP)
└── connection_view (RAII handle: claim/retire)
cub_pl (Java process — pl_engine/ Gradle artifact)
├── Server.main()
│ ├── SpSecurityManager installed
│ ├── ClassLoaderManager dirs created
│ └── initializeSocket() → ServerSocket (UDS via junixsocket or TCP)
├── ListenerThread (accept loop)
│ └── for each accepted socket → new ExecuteThread(socket).start()
└── ExecuteThread (one per active connection)
├── RequestCode.UTIL_PING → respond with server name
├── RequestCode.UTIL_BOOTSTRAP → apply sysprm settings
├── RequestCode.INVOKE_SP → processStoredProcedure()
│ ├── PrepareArgs.readArgs()
│ ├── makeStoredProcedure() → StoredProcedure
│ └── StoredProcedure.invoke()
│ ├── [JavaSP] TargetMethod.getMethod() → Method.invoke()
│ └── [PL/CSQL] PlcsqlCompilerMain / compiled class dispatch
├── RequestCode.COMPILE → processCompile()
└── RequestCode.DESTROY → ContextManager.destroyContext()

이 그림이 보여 주는 것은 두 프로세스 사이의 비대칭이다. cub_server 측의 무게 중심은 connection pool 과 monitor task 두 가지에 실린다. pool 은 SP 호출이 실제로 흐르는 파이프이고, monitor task 는 자식 프로세스의 생명주기를 책임진다. cub_pl 측의 무게 중심은 ExecuteThread 의 dispatch 분기 에 실린다. RequestCode 한 줄짜리 분기 안에 ping, bootstrap, invoke, compile, destroy 다섯 개의 시스템 명령이 모두 들어가고, 그 중 INVOKE_SP 가 다시 JavaSP / PL/CSQL 두 갈래로 갈라진다.

발견과 랑데부 (Discovery and rendezvous)

섹션 제목: “발견과 랑데부 (Discovery and rendezvous)”

cub_pl 은 자기 소켓을 바인드한 뒤 자신의 PID 와 포트 (UDS 모드라면 -1) 를 $CUBRID/var/pl_<db_name>.info 에 쓴다. C++ 측은 이 파일을 pl_read_info() (pl_file.c) 로 읽어 포트 번호를 알아낸다. monitor task 가 재접속을 시도할 때마다 이 파일을 폴링한다. UDS 모드에서는 소켓 경로 자체가 합의된 파일 경로라 info 파일에 포트 필드가 따로 필요 없다.

Wire 프로토콜 (SP_CODE / METHOD_REQUEST)

섹션 제목: “Wire 프로토콜 (SP_CODE / METHOD_REQUEST)”

소켓 위의 모든 메시지는 길이 prefix 가 붙은 바이트 버퍼다. 첫 필드는 세션 ID 와 요청 코드를 담은 Header 다. C++ 측은 시스템 수준 명령 (PING, BOOTSTRAP, DESTROY) 에 대해서는 SP_CODE 값 (pl_comm.h 정의) 을, SP 호출에 대해서는 METHOD_REQUEST 값 (sp_constants.hpp 정의) 을 쓴다. Java 측의 RequestCode 상수는 C++ enum 을 그대로 미러링한다.

// pl_comm.h — SP_CODE
SP_CODE_INVOKE = 0x01 // invoke a SP
SP_CODE_RESULT = 0x02 // result from SP to server (callback)
SP_CODE_ERROR = 0x04 // error from SP to server
SP_CODE_INTERNAL_JDBC = 0x08 // back-channel JDBC request
SP_CODE_DESTROY = 0x10 // session teardown
SP_CODE_COMPILE = 0x80 // PL/CSQL compile request
SP_CODE_UTIL_BOOTSTRAP = 0xDD // bootstrap sysprm
SP_CODE_UTIL_PING = 0xDE // liveness probe
SP_CODE_UTIL_STATUS = 0xEE // status query

pl_executor.cppinvoke_java packable struct 가 호출 페이로드를 직렬화한다. 트랜잭션 ID, signature 문자열 (ClassName.methodName), 인증 컨텍스트, 언어 태그 (SP_LANG_JAVA 또는 SP_LANG_PLCSQL), 인자 개수, 인자별 모드와 DB 타입, 반환 타입, 트랜잭션 제어 플래그.

// invoke_java::invoke_java — pl_executor.cpp
signature.assign (sig->ext.sp.target_class_name)
.append (".").append (sig->ext.sp.target_method_name);
lang = sig->type; // PL_TYPE_JAVA_SP or PL_TYPE_PLCSQL
transaction_control = (lang == SP_LANG_PLCSQL) ? true : tc;

PL/CSQL 언어는 항상 transaction_control = true 를 받는다. JavaSP 는 SP 정의 자체에서 그 값을 물려받는다.

저장 프로시저 메타데이터를 담는 시스템 클래스가 셋이다.

_db_stored_procedure (SP_CLASS_NAME)
unique_name, sp_name, sp_type, return_type, arg_count, args,
lang, pkg_name, is_system_generated, directive,
target_class, target_method, ← JavaSP-only meaningful fields
owner, sql_data_access, comment, created_time, updated_time
_db_stored_procedure_args (SP_ARG_CLASS_NAME)
sp_of, index_of, arg_name, data_type, mode,
default_value, is_optional, comment
_db_stored_procedure_code (SP_CODE_CLASS_NAME)
name, created_time, owner, is_static, is_system_generated,
stype (source type: PLCSQL=0 / JAVA=1),
scode (source code text),
otype (object code type: JAVA_CLASS / JAVA_JAR),
ocode (compiled object code, base64)

sp_info::lang 이 JavaSP 에 대해서는 SP_LANG_JAVA (1), PL/CSQL 에 대해서는 SP_LANG_PLCSQL (0) 이다. 이 단일 필드 하나가 ExecuteThread 안에서 호출을 적절한 핸들러로 갈라 보낸다.

sp_catalog.cpp 의 C++ 함수 sp_add_stored_procedure(), sp_add_stored_procedure_argument(), sp_add_stored_procedure_code()CREATE PROCEDURE / CREATE FUNCTION 시점에 이 행들을 쓴다. jsp_cl.cpp 가 클라이언트 측 DDL 처리 (parse tree 에서 카탈로그 행으로) 를 담당한다. DDL 은 클라이언트 측에서 도는 일이라 #if !defined(SERVER_MODE) 가드가 걸려 있다.

pl_session.hppcubpl::session 은 CUBRID 세션별 상태 객체다.

  • m_stack_map / m_exec_stack — execution stack 추적 (재귀 SP 호출당 execution_stack 하나씩, 각자 cub_pl thread pool 안의 워커 스레드를 받음).
  • m_cursor_map — JDBC 백채널 쿼리가 연 서버 사이드 커서.
  • m_session_connections — 호출이 도는 동안 글로벌 풀에서 빌려 온 connection_view 의 deque.
  • m_session_params — 세션별 파라미터 그림자 (DBMS_OUTPUT 플래그 등). METHOD_CALLBACK_SET_PL_SESSION_PARAM 으로 cub_pl 과 동기화된다.

pl_executor.hppcubpl::executor 는 단일 호출을 굴린다.

  1. fetch_args_peek() — XASL value descriptor 또는 직접 CALL 문장의 인자 리스트에서 인자 DB_VALUE 들을 읽어 들임.
  2. request_invoke_command()invoke_java 를 패킹해 잡아 둔 connection 위로 전송.
  3. response_invoke_command() — 응답을 읽는 루프를 돌린다. 각 응답은 최종 결과 (SP_CODE_RESULT), 에러 (SP_CODE_ERROR), 또는 콜백 요청 (SP_CODE_INTERNAL_JDBC) 중 하나다. 콜백 요청은 callback_prepare(), callback_execute(), callback_fetch() 등으로 분기되어 서버의 쿼리 실행 기계로 들어가고, 결과를 같은 소켓으로 cub_pl 로 되돌려 쓴다.

JavaSP 고유 — reflective 디스패치와 클래스로더

섹션 제목: “JavaSP 고유 — reflective 디스패치와 클래스로더”

TargetMethod 는 호출 시점에 타겟을 풀어낸다. 클래스명, 메서드명, 콤마로 구분된 인자 타입 디스크립터 문자열을 담은 Signature 로부터 생성된다. 생성자가 classesFor() 를 호출해 각 타입 이름을 정적 argClassMap 으로 Class<?> 에 매핑한다. 이 맵은 모든 primitive, 그 박스 등가물, java.math.BigDecimal, java.sql.Date/Time/Timestamp, cubrid.sql.CUBRIDOID, 그리고 이 모두의 1차원 / 2차원 배열을 커버한다. Class<?>[] 가 풀리고 나면 getMethod() 가 reflection 으로 Class.getMethod(methodName, argsTypes) 를 호출한다.

클래스로더 위계는 다음과 같다.

JVM bootstrap classloader
└── ServerClassLoader (server JARs: cubrid-jdbc, pl_server.jar)
└── ContextClassLoader (per-database user JARs, dynamic path)
└── SessionClassLoader (per-session isolation, if needed)

ClassLoaderManager 는 동적 JAR 의 루트를 $CUBRID_DATABASES/<db>/java/ 로, 정적 JAR 의 루트를 $CUBRID_DATABASES/<db>/java_static/ 로 잡는다. 마지막 수정 타임스탬프를 추적하기 때문에 loadjava 를 다시 실행하면 cub_pl 을 재시작하지 않고도 클래스로더가 새 JAR 을 집어 올린다.

SpSecurityManagerServer 생성 시점에 System.setSecurityManager() 로 설치된다. 핵심 제약은 다음과 같다.

  • checkExit()Server 인스턴스가 이미 셧다운 상태가 아니면 SecurityException 을 던진다. 사용자 저장 프로시저가 System.exit() 을 불러 JVM 을 죽이는 것을 막기 위함이다.
  • checkLink() — 클래스로더 체인을 살펴, 어느 한 프레임이라도 ContextClassLoader 또는 SessionClassLoader 에 속한다면 native 라이브러리 로드를 차단하면서 loadjava -jni 를 안내하는 메시지를 띄운다.
  • 그 외 모든 check*() 메서드는 관대한 no-op (빈 오버라이드) 이다.

cub_pl 안에서는 CUBRIDServerSideDriver / CUBRIDServerSideConnection 이 JDBC 드라이버로 등록되어 있다. 사용자 Java 코드가 SQL 쿼리를 발행하면 JDBC 호출은 CUBRIDServerSidePreparedStatement.execute() 로 흘러가서 METHOD_CALLBACK_QUERY_PREPARE 또는 METHOD_CALLBACK_QUERY_EXECUTE 요청을 직렬화한 뒤, 원래의 SP_CODE_INVOKE 를 전달했던 그 동일한 소켓 으로 cub_server 에게 되돌려 보낸다. C++ executor::response_callback_command() 루프가 이 콜백 코드를 인지해 적절한 서버 서브시스템 (쿼리 prepare, execute, cursor fetch, LOB 연산 등) 으로 디스패치하고, 그 결과를 다시 cub_pl 로 쓴다.

이 구조가 단일 TCP/UDS 연결 위의 동기 콜백 루프 를 만든다.

cub_server cub_pl
──── SP_CODE_INVOKE ──────►
◄─── METHOD_CALLBACK_QUERY_PREPARE ───
──── (prepared stmt handle) ─────────►
◄─── METHOD_CALLBACK_QUERY_EXECUTE ───
──── (cursor, rows) ──────────────────►
◄─── METHOD_CALLBACK_FETCH ────────────
──── (row data) ──────────────────────►
◄─── SP_CODE_RESULT ───────────────────

재귀적 SP 호출 (Java SP 가 또 다른 SP 를 부르는 경우) 은 각자 새 execution_stack 엔트리를 잡고 별도의 ExecuteThread 로 처리되지만, 같은 세션을 재사용한다.

전체 오피코드 분류 체계, 서버 측 디스패처 (cubpl::executor::response_callback_command 와 12 개의 핸들러), 재귀 깊이 가드 (METHOD_MAX_RECURSION_DEPTH = 15), 그리고 공유 packed 와이어 구조체는 PL 패밀리의 세 번째 형제 문서인 cubrid-pl-server-bridge.md 에 정리되어 있다는 점이다. 그 문서는 같은 오피코드 집합을 사용하는 더 오래된 server→CAS 콜백 경로 (Path A) 도 함께 다룬다. 두 경로는 물리적으로는 분리되어 있지만 같은 METHOD_CALLBACK_* 열거형을 공유한다.

server_monitor_task 는 작은 상태 머신을 구현한다.

STOPPED ──fork cub_pl──► READY_TO_INITIALIZE
ping poll (up to 10×, 1s each)
bootstrap_request (send sysprms)
┌──────────────┴──────────────────┐
RUNNING FAILED_TO_INITIALIZE
│ │
(monitor daemon re-checks every 1s) (after >10 failures)
process exits? → STOPPED → re-fork

SERVER_MODE 에서는 monitor 데몬이 cubthread::daemon (1 초 looper) 으로 돈다. SA_MODE (standalone / csql) 에서는 do_monitor() 가 동기적으로 호출되며 최대 10 회 재시도한다.

심볼파일역할
pl_server_initpl_sr.cpp서버 부팅 시 boot_sr.c 가 호출하는 진입점. server_manager 를 만들고 cub_pl 을 fork 한다
pl_server_destroypl_sr.cpp서버 셧다운 시 호출. server_manager 를 삭제
pl_server_wait_for_readypl_sr.cppinit 직후 cub_pl 이 연결을 받기 시작할 때까지 블로킹
get_connection_poolpl_sr.cpp글로벌 PL_CONNECTION_POOL 반환. executor 가 connection 을 청구할 때 사용
pl_server_port_from_infopl_sr.cpppl_read_info()$CUBRID/var/pl_<db>.info 를 읽음
server_managerpl_sr.cpppool + monitor task 를 소유. start / stop 라이프사이클
server_monitor_taskpl_sr.cpp데몬 태스크 — cub_pl fork, ping, bootstrap, crash 감지
bootstrap_requestpl_sr.cppPackable. 시스템 파라미터 스냅샷을 보냄 (SP_CODE_UTIL_BOOTSTRAP)
connection_poolpl_connection.hpp고정 크기의 connection 객체 풀. 재시작 시 epoch 기반 무효화
connectionpl_connection.hppSOCKET 래퍼. send_buffer_args, receive_buffer, 자동 재접속
pl_connect_serverpl_comm.c저수준 소켓 connect (UDS 또는 TCP)
pl_writen / pl_readnpl_comm.c신뢰성 있는 write / read 헬퍼 (EINTR 처리)
SP_CODE enumpl_comm.hJava 측 RequestCode 와 공유되는 wire 프로토콜 코드
executorpl_executor.hpp단일 호출 드라이버 — 인자 fetch → invoke 송신 → 콜백 처리 → 결과 반환
invoke_javapl_executor.cppPackable 호출 페이로드 (signature, lang, args, result type)
sessionpl_session.hppCUBRID 세션별 — 스택, 커서, 연결, 파라미터, 인터럽트
sys_parampl_session.hppPackable 시스템 파라미터 (DB prm 또는 PL 전용 prm)
pl_signaturepl_signature.hpp풀린 SP 디스크립터 — 타입, 이름, 인증, 인자 모드 / 타입, ext (target class / method 또는 code OID)
sp_infosp_catalog.hpp_db_stored_procedure 행의 C++ 표현
sp_arg_infosp_catalog.hpp_db_stored_procedure_args 행의 C++ 표현
sp_code_infosp_catalog.hpp_db_stored_procedure_code 행의 C++ 표현
sp_add_stored_proceduresp_catalog.cppCREATE 시점에 새 SP 행을 씀
PL_SERVER_INFOpl_file.hpl_read_info / pl_write_info 로 읽고 쓰는 {pid, port} 구조체
jsp_create_stored_procedurejsp_cl.cpp클라이언트 측 CREATE PROCEDURE / FUNCTION 핸들러
jsp_make_pl_signaturejsp_cl.cppPT_NODE 로부터 pl_signature 를 빌드
심볼파일역할
ServerServer.java진입점. SpSecurityManager 설치, 소켓 셋업, ListenerThread 시작
ListenerThreadListenerThread.javaServerSocket.accept() 루프. 연결당 ExecuteThread 생성. 에러 시 exponential backoff
ExecuteThreadExecuteThread.java연결당 스레드. Header.code 로 디스패치. processStoredProcedure() / processCompile()
processStoredProcedureExecuteThread.java인자 read, StoredProcedure 빌드, invoke() 호출, 결과 송신
TargetMethodTargetMethod.javaSignature 로부터 Class.getMethod(name, argTypes) 를 reflection 으로 풀어냄
TargetMethod.argClassMapTargetMethod.java정적 맵 — 타입 이름 문자열 → Class<?> (primitive, boxed, SQL 타입, 배열)
TargetMethod.getMethodTargetMethod.java호출용 Method 반환. 메시지에 풀 시그니처를 담은 NoSuchMethodException 던짐
SpSecurityManagerSpSecurityManager.java커스텀 SecurityManager — 셧다운 중이 아니면 exit 차단, 사용자 클래스로더의 native lib load 차단
SpSecurityManager.checkLinkSpSecurityManager.javagetClassContext() 체인 점검. ContextClassLoader / SessionClassLoaderloadLibrary 거부
ClassLoaderManagerclassloader/ClassLoaderManager.javaroot / static / dynamic 경로 관리. 핫 JAR 리로드용 last-modified 추적
ClassLoaderManager.getDynamicPathclassloader/ClassLoaderManager.java$db_path/java/ 반환 (loadjava 로 올린 사용자 JAR)
ClassLoaderManager.getStaticPathclassloader/ClassLoaderManager.java$db_path/java_static/ 반환 (서버 전체 JAR)
심볼파일대략 라인
pl_server_initsrc/sp/pl_sr.cpp696
server_manager::startsrc/sp/pl_sr.cpp262
server_monitor_task::do_monitorsrc/sp/pl_sr.cpp356
server_monitor_task::do_bootstrap_requestsrc/sp/pl_sr.cpp625
pl_server_port_from_infosrc/sp/pl_sr.cpp763
SP_CODE enumsrc/sp/pl_comm.h44
connection_pool 클래스src/sp/pl_connection.hpp64
executor::executesrc/sp/pl_executor.cpp(executor 클래스 본문 안)
invoke_java::invoke_javasrc/sp/pl_executor.cpp45
session 클래스src/sp/pl_session.hpp106
pl_signature structsrc/sp/pl_signature.hpp86
SP_LANG_JAVA, SP_LANG_PLCSQLsrc/sp/sp_constants.hpp147
SP_CLASS_NAME 매크로src/sp/sp_constants.hpp22
sp_info structsrc/sp/sp_catalog.hpp115
PL_SERVER_INFO structsrc/sp/pl_file.h35
Server 생성자pl_engine/.../Server.java70
ListenerThread.runpl_engine/.../ListenerThread.java63
ExecuteThread.runpl_engine/.../ExecuteThread.java129
ExecuteThread.processStoredProcedurepl_engine/.../ExecuteThread.java319
TargetMethod.getMethodpl_engine/.../TargetMethod.java243
SpSecurityManager.checkLinkpl_engine/.../SpSecurityManager.java72
ClassLoaderManager.getDynamicPathpl_engine/.../classloader/ClassLoaderManager.java58
  • pl_sr_jvm.cpp 는 레거시 잔재다. pl_start_jvm_server()pl_server_port() 는 JVM 이 cub_server 안에 직접 임베드되어 있던 옛 in-process JNI 경로의 잔해다. 심볼 자체는 여전히 컴파일되지만, 실제로 살아 있는 코드 경로는 pl_sr.cppserver_manager fork / exec 모델이다. JNI 경로는 죽은 코드 거나, 특정 빌드 구성용으로 남아 있을 가능성이 있다.

  • SpSecurityManager 는 Java 17 부터는 deprecated 다. SecurityManager 는 Java 17 에서 제거를 위해 deprecated 로 표시되었다 (JEP 411). CUBRID 가 신버전 JVM 으로 옮겨 가면 이 샌드박스 메커니즘은 다른 것 (예: 제한된 클래스 로딩 정책, 또는 SP 실행마다 별도 프로세스를 두는 방식) 으로 대체되어야 한다. 현재 코드가 노리는 타깃은 JDK 1.8+ 다.

  • connection_pool::CONNECTION_POOL_SIZE = 10 은 하드코드 되어 있다. 풀 크기를 다스리는 시스템 파라미터가 없다. 동시 SP 호출이 많은 고동시성 시나리오에서는 풀에 큐잉이 걸린다. is_system_pool 플래그가 monitor 자신의 시스템 연결 풀과 메인 풀을 구분한다.

  • TargetMethod.argClassMap 은 고정된 타입 집합만 다룬다. 맵에 없는 클래스 타입 (예: 사용자 정의 DTO 클래스) 을 받는 사용자 JAR 메서드는 디스패치 시점에 ClassNotFoundException 을 낸다. classFor() 의 주석은 이 빈자리를 인정하면서 ClassAccess.getClass() 구현으로 가는 TODO 를 가리킨다.

  • info 파일의 race window. monitor task 에서 fork() 가 돌아온 시점과 cub_pl 이 자신의 포트를 info 파일에 쓰는 시점 사이에는 pl_server_port_from_info()PL_PORT_DISABLED 를 반환하는 구간이 있다. monitor 는 폴링 루프 (do_check_connection(), 최대 10×, 1 초씩) 로 대응하지만, JVM 시작이 매우 느리면 그 예산을 초과할 수 있다.

  1. SpSecurityManager 의 Java 17+ 우아한 업그레이드. 현재 소스에서 대체 메커니즘이 보이지 않는다. JDK 17+ 가 타깃 베이스라인이 될 때 계획은 무엇인가?

  2. 핫 JAR 리로드의 단위. ClassLoaderManager.isModified() 는 JAR 파일 단위로 last-modified 타임스탬프를 추적한다. 이미 클래스를 로드한 채 진행 중인 SP 호출은 다음 호출 전까지 옛 버전을 보게 되는가? 핸드오프 메커니즘이 있는가?

  3. pl_sr_jvm.cpp 의 운명. in-process JNI 경로는 현재 어떤 제품 구성에서든 컴파일되고 있는가, 아니면 완전히 죽은 코드라 제거 예정인가?

  4. 재귀 SP 깊이 제한. sp_constants.hppMETHOD_MAX_RECURSION_DEPTH15 로 정의되어 있다. 이 값을 강제하는 쪽은 C++ 측인가, Java 측인가, 아니면 둘 다 인가? Java ExecuteThread 는 이를 직접 점검하지 않는 것으로 보인다.

  5. SP 에러 시 트랜잭션 rollback. JavaSP 가 잡히지 않은 예외를 던지면 ExecuteThread 가 그것을 catch 하고 로깅한 뒤 sendError() 를 보낸다. C++ executor::response_invoke_command() 가 이를 어떻게 CUBRID 트랜잭션 rollback 으로 번역하는가? 콜백 루프가 thread 위에 rollback 을 트리거하는 에러 조건을 세팅해 줘야 하지만, 그 정확한 경로는 본 문서가 추적하지 않았다.

  • src/sp/ — C/C++ PL 서버 브리지 (위 references: 에 나열된 모든 파일)
  • pl_engine/pl_server/src/main/java/com/cubrid/jsp/ — Java PL 엔진
  • pl_engine/pl_server/src/main/java/com/cubrid/jsp/classloader/ JAR 클래스로더 위계
  • pl_engine/AGENTS.md — Gradle 빌드 구조 개요
  • references/cubrid/CLAUDE.md — CUBRID 엔진 구조와 빌드 노트
  • knowledge/code-analysis/cubrid/cubrid-pl-javasp.md (영문 원본) 이 문서의 영어판.
  • knowledge/ko/code-analysis/cubrid/cubrid-pl-plcsql.md — PL 패밀리의 형제 문서. PL/CSQL 컴파일러와 런타임이 이 문서에서 설명한 동일 카탈로그 / 전송 / 실행기 인프라를 어떻게 재사용하는지를 다룬다.