(KO) CUBRID 서버 세션 — 클라이언트별 상태, prepared statement 레지스트리, 그리고 TDES 바인딩
목차
학술적 배경
섹션 제목: “학술적 배경”관계형 데이터베이스 엔진은 클라이언트의 요청을 받아 처리해야 하고, 클라이언트 가 접속하는 순간 서로 직교하는 세 가닥의 상태가 동시에 태어난다. connection (TCP/UDS 소켓과 그에 매달린 버퍼와 큐), session (prepared statement, autocommit 모드, last insert id, locale 설정, 기본 격리 수준 — 즉 statement 사이를 가로질러 클라이언트가 유지 되리라 기대하는 모든 것), 그리고 transaction (엔진이 atomic 하게 commit하거나 abort하는 단위) 이다. Database Internals (Petrov) 5장 §Transactions 는 이 세 층을 분명히 분리해서 다룬다. connection은 네트워크 층이 소유하고 소켓과 함께 죽는다. transaction은 복구 매니저와 lock 매니저가 소유하고 commit / abort 시점에 죽는다. session은 그 사이에 자리한다. 단일 transaction을 넘어서 살고 (한 session이 여러 transaction을 실행한다), 단일 connection까지도 넘어설 수 있다 (TCP 소켓을 잃어버린 클라이언트가 이전 session id를 들고 다시 접속하면 prepared statement 캐시와 SET 변수 바인딩을 다시 컴파일하거나 다시 바인드하지 않고도 재개할 수 있다).
구체적인 서버 세션 모듈의 모양을 결정하는 두 가지 구현 선택이 있다.
-
세션 상태가 어디에 살고 어떻게 명명되는가. 교과서적인 답은 “접속 셋업 시점에 서버가 클라이언트에게 건네 준 불투명한 정수 를 키로 하는 해시 테이블” 이다. 변형은 그 테이블이 정적 슬롯 테이블인지 탄력적 해시인지, 요청 한 건당 lookup 지연 예산이 얼마인지, connection이 끊어졌을 때 무엇이 살아남는지 정도다. CUBRID는
SESSION_ID(32비트 unsigned 정수) 를 키로 하는 탄력적 lock-free 해시를 채택했고, 같은 소켓 위 후속 요청들은 해시 lookup 비용을 내지 않도록 세션의 실제SESSION_STATE *를 connection entry에 캐시한다. -
세션이 트랜잭션 descriptor와 어떻게 묶이는가. 모든 쿼리는
SESSION_STATE(prepared statement, row count, last insert id, autocommit 을 찾기 위해) 와LOG_TDES(트랜잭션의 lock 집합, 로그 범위, MVCC 스냅샷을 찾기 위해) 둘 다 필요하다. CUBRID는 connection 층으로 요청을 라우팅하는 방식으로 이를 해결한다. connection은 이미 자기당 트랜잭션 인덱스 하나를 소유하고 있고, worker 스레드가 그 인덱스를 자기THREAD_ENTRY에 복사해 둠으로써LOG_FIND_THREAD_TRAN_INDEX(thread_p)가 올바른LOG_TDES를 반환한다. 세션은 부기 컨테이너이고 TDES는 트랜잭션 컨테이너이며, 이 둘을 묶어 주는 것이 connection entry다.
이 두 선택이 분명해지면 session 모듈의 다른 모든 부분 — timeout reaper, 스레드가 conn entry를 두고 떠날 때의 ref-count 핸드오프, holdable cursor 리스트, prepared statement 캐시, lock-free 해시 내부 — 이 두 선택 중 하나를 위해 존재한다는 점이 보인다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”prepared statement, session 단위 설정 (autocommit, 기본 격리 수준, locale), 재접속을 지원하는 모든 관계형 엔진은 비슷한 한 줌의 패턴을 채택한다.
connection별 vs session별 상태. PostgreSQL은 connection과
session을 거의 한 덩어리로 합친다. 각 백엔드 프로세스가 단일
클라이언트에 대응하며, MyProc (그 프로세스의 PGPROC 슬롯),
MyBackendId, 프로세스별 pg_stat_session 행 이 session이고,
그것들은 백엔드가 종료될 때 함께 죽는다. MySQL도 비슷하게 모든
것을 connection별 단일 THD (thread descriptor) 위에 둔다. 열린 테이블, prepared statement, session 변수, 트랜잭션 컨텍스트
모두. THD는 connection과 함께 죽는다. Oracle은 정반대 극단으로
간다. session에 주소가 붙고 (v$session.SID,SERIAL#),
connection 사이를 마이그레이션할 수도 있으며 (shared-server,
multitenant), 공유 cursor 캐시는 SGA 수준에서 산다. CUBRID는
의도 면에서 Oracle 쪽에 가깝다 (session이 일급 식별 가능 객체
이고, 소켓을 잃은 클라이언트가 옛 SESSION_ID 를 제시해서
prepared statement와 session 변수를 복구할 수 있다) 는 점에서.
하지만 구현 면에서는 PostgreSQL / MySQL 쪽에 가깝다 (shared
server가 없고, worker pool이 하나이며, session이 특정 프로세스
나 스레드에 묶이는 대신 서버 전역 lock-free 해시에 산다).
SESSION_ID 가 주소이고, SESSION_STATE 가 컨테이너이며,
CSS_CONN_ENTRY 가 그 컨테이너를 가리키는 물리 connection별
캐시다.
요청마다의 session lookup. 토폴로지가 어떻든 모든 엔진은 SQL
파이프라인으로 디스패치하기 전에 “이 패킷이 어느 session에 속하
는가” 라는 질문에 답해야 한다. PostgreSQL은 그 질문을 건너뛴다.
백엔드가 바로 session이기 때문이다. MySQL은 THD 포인터를
스레드 로컬 저장소에 캐시한다. Oracle은 SID를 네트워크 패킷에
넣어 보낸다. CUBRID는 MySQL이 하는 일을 connection-entry
granularity로 한다. CSS_CONN_ENTRY 마다 session_id 와
session_p 필드를 가지며, 첫 xsession_check_session 이 성공한
이후로는 worker가 해시 lookup 비용 없이 session_p 를 직접
읽는다.
session 단위 캐시로서의 prepared statement 레지스트리.
prepared statement는 트랜잭션보다는 길게 살아남되 session보다는
짧게 사는 어딘가에 보관해야 한다. PostgreSQL은 백엔드 위에 둔다.
MySQL은 THD 위에 둔다. Oracle은 두 단계로 나눈다. statement는
session이 소유하지만 parsed / optimised plan은 SGA에서 공유된다.
CUBRID는 다시 하이브리드다. 이름 붙은 statement entry
(PREPARED_STATEMENT — name, alias_print, sha1, 직렬화된 info
blob) 는 session 위에 살고, XASL plan 은 plan source의 SHA-1을
키로 하는 서버 전역 캐시 (xasl_cache_ent) 에 산다. session은
xcache_find_sha1 으로 그 캐시에서 자기 plan을 찾아 쓸
권리를 보유한다.
ref count + timeout reaping. 한 session은 여러 worker 스레드
에 의해 동시 참조될 수 있으므로, 어떤 엔진도 소켓이 닫혔다는
이유만으로 session을 파괴하지 않는다. CUBRID는 SESSION_STATE
위에 명시적인 ref_count 를 두고, 스레드가 conn entry에 session
을 바인딩할 때 session_state_increase_ref_count 로 증가시키며,
바인딩을 해제할 때 감소시킨다. 주기적인 reaper
(session_remove_expired_sessions, session_control_daemon 이
60초마다 실행) 가 테이블을 걸어가며 active_time 이
PRM_ID_SESSION_STATE_TIMEOUT 보다 오래되었고 동시에
ref_count 가 0이며 동시에 is_keep_session 플래그가 꺼진
session들을 파괴한다.
session vs 트랜잭션 lifetime. session은 한 시점에 최대 한 개
의 활성 트랜잭션만 가질 수 있고, 트랜잭션은 session 안에서 시작
되어 session 안에서 끝난다. CUBRID는 이를 암묵적으로 강제한다. LOG_TDES 는 worker가 처음 필요로 할 때 (commit / abort 후 첫
트랜잭셔널 요청에서 lazy하게) logtb_assign_tran_index 가
할당하며, 라이프사이클에서 session의 유일한 역할은 각 statement가
트랜잭션을 암묵적으로 닫을지를 결정하는 autocommit 플래그
(SESSION_STATE::auto_commit) 를 들고 있는 것뿐이다.
이론 ↔ CUBRID 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 | CUBRID 명칭 |
|---|---|
| Session identifier | SESSION_ID (compat/dbtype_def.h 에서 unsigned int 로 typedef) |
| Session state container | SESSION_STATE (session.c) |
| 서버 전역 session 테이블 | ACTIVE_SESSIONS::states_hashmap (session.c) |
| 빈 / 미바인딩 session | DB_EMPTY_SESSION = 0 (compat/dbtype_def.h) |
| Connection entry session 캐시 | CSS_CONN_ENTRY::session_p + ::session_id (connection_defs.h) |
| Per-session prepared statement | PREPARED_STATEMENT (session.c) |
| Holdable cursor / 쿼리 결과 | SESSION_QUERY_ENTRY (session.c) |
| 서버 전역 XASL plan 캐시 | XASL_CACHE_ENTRY (xasl_cache.h) — SHA-1 키 |
| Per-session locale 영역 | SESSION_STATE::session_tz_region |
| Per-session sysparam 오버라이드 | SESSION_STATE::session_parameters (SESSION_PARAM 배열) |
| Session ↔ 트랜잭션 바인딩 | CSS_CONN_ENTRY::transaction_id (set_tran_index 가 설정) |
| Session ↔ 스레드 바인딩 | THREAD_ENTRY::conn_entry->session_p |
| Reaper daemon | session_remove_expired_sessions 를 실행하는 session_Control_daemon |
| 서버 진입점 — find or create | xsession_create_new, xsession_check_session (session_sr.c) |
| 서버 진입점 — end | xsession_end_session (session_sr.c) |
| 네트워크 핸들러 — connect | ssession_find_or_create_session (network_interface_sr.cpp) |
| 네트워크 핸들러 — disconnect | ssession_end_session (network_interface_sr.cpp) |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”session 모듈의 움직이는 부분은 네 가지다. 살아 있는
SESSION_STATE 들을 모두 들고 있는 active sessions 테이블,
prepared statement / query / 변수 리스트가 박혀 있는
SESSION_STATE 본체, 테이블 안의 entry가 거치는 lifecycle
상태 머신, 그리고 요청이 막 도착했다 를 “올바른 session, 올바른
트랜잭션 위에서 실행하라” 로 바꿔 주는 thread / connection / TDES
바인딩. 이 순서대로 본다.
전체 구조
섹션 제목: “전체 구조”flowchart LR
subgraph CL["클라이언트 측"]
DBSES["db_Session_id\n(프로세스별, CS/SA 모드)"]
end
subgraph NET["네트워크 진입 (network_interface_sr.cpp)"]
SFC["ssession_find_or_create_session\n→ xsession_check_session\n→ xsession_create_new"]
SES["ssession_end_session"]
OTHER["ssession_set_row_count\nssession_create_prepared_statement\nssession_get_prepared_statement\n..."]
end
subgraph SR["서버 코어 (session.c / session_sr.c)"]
XCREATE["xsession_create_new"]
XCHECK["xsession_check_session"]
XEND["xsession_end_session"]
SCREATE["session_state_create"]
SCHECK["session_check_session"]
SDESTROY["session_state_destroy"]
SGET["session_get_session_state"]
end
subgraph TBL["sessions (ACTIVE_SESSIONS)"]
HASH["states_hashmap\n(lockfree_hashmap<SESSION_ID, session_state>)"]
LSI["last_session_id (atomic 카운터)"]
NHC["num_holdable_cursors"]
end
subgraph CONN["Connection 층 (connection_sr.c)"]
CONNENT["CSS_CONN_ENTRY\n.session_id, .session_p, .transaction_id"]
end
subgraph THR["Thread / TDES"]
TE["THREAD_ENTRY\n.conn_entry, .tran_index"]
TDES["LOG_TDES\n(log_Gl.trantable.all_tdes[tran_index])"]
end
DBSES --> NET
SFC --> XCHECK
SFC --> XCREATE
SES --> XEND
OTHER --> SGET
XCREATE --> SCREATE
XCHECK --> SCHECK
XEND --> SDESTROY
SCREATE --> HASH
SCHECK --> HASH
SDESTROY --> HASH
SCREATE --> CONNENT
SCHECK --> CONNENT
SGET --> CONNENT
CONNENT --> TE
TE --> TDES
Active sessions 테이블
섹션 제목: “Active sessions 테이블”모듈 전체가 단 하나의 static ACTIVE_SESSIONS 값을 닻으로 삼는다.
session.c 파일 스코프에 선언되어 있다.
// active_sessions — session.ctypedef struct active_sessions{ session_hashmap_type states_hashmap; SESSION_ID last_session_id; int num_holdable_cursors; // ... ctor zero-initialises all three ...} ACTIVE_SESSIONS;
static ACTIVE_SESSIONS sessions;이 hashmap은 typedef alias가 붙은 lock-free 해시다.
// session_hashmap_type — session.cusing session_hashmap_type = cubthread::lockfree_hashmap<SESSION_ID, session_state>;서버 부팅 시에 session_states_init 에서 한 번 초기화되고,
종료 시 session_states_finalize 에서 정리된다.
// session_states_init — session.c (condensed)sessions.last_session_id = 0;sessions.num_holdable_cursors = 0;sessions.states_hashmap.init (sessions_Ts, THREAD_TS_SESSIONS, SESSIONS_HASH_SIZE /* 1000 */, 2, 50, session_state_Descriptor);#if defined (SERVER_MODE)session_control_daemon_init ();#endifhashmap descriptor에는 freelist link, chain link, lock-free delete-id, key, entry별 mutex의 offset이 박혀 있다. 짚고 갈 포인트가 셋이다.
- 해시 버킷 수는
1000, free-list 성장 파라미터는(2, 50)— 50% 차면 2배로 자동 grow한다는 뜻이다. - entry locking 모드로 hazard pointer가 아니라
LF_EM_USING_MUTEX를 골랐다 (각SESSION_STATE가 자기pthread_mutex_t mutex를 들고 있다). 이유는 session이 multi-step 조작 (insert + initialise, lookup + ref-count 갱신) 사이에 latch 된 상태로 잡혀 있어야 하기 때문이다. last_session_id는 create 시마다ATOMIC_INC_32로 증가한다. hashmap 내부에는key_increment콜백 (session_key_increment) 이 등록되어 있어 두 스레드가 동일한 후보 id에 부딪히면 hash가 투명하게 다음 빈 슬롯으로 bump하고, 호출자가 그 결과를ATOMIC_CAS_32로 글로벌 카운터에 다시 써 준다.
// session_state_create — session.c (condensed)next_session_id = ATOMIC_INC_32 (&sessions.last_session_id, 1);*id = next_session_id;(void) sessions.states_hashmap.insert (thread_p, *id, session_p);ATOMIC_CAS_32 (&sessions.last_session_id, next_session_id, *id);이 패턴 — 카운터로 후보를 제안하고, hashmap이 실제 id를 정하고,
실제 id를 다시 쓰는 — 덕분에 두 스레드가 두 별개 클라이언트를 두고
xsession_create_new 에서 동시에 경합해도 카운터가
monotonic 단조 비감소 상태를 유지한다.
SESSION_STATE 구조체
섹션 제목: “SESSION_STATE 구조체”// session_state — session.c (condensed)typedef struct session_state SESSION_STATE;struct session_state{ SESSION_ID id; /* session id (the hash key) */ SESSION_STATE *stack; /* used in freelist */ SESSION_STATE *next; /* used in hash table chain */ pthread_mutex_t mutex; /* state mutex */ UINT64 del_id; /* delete transaction id (lock-free) */
bool is_keep_session; /* survive timeout + disconnect */ bool is_trigger_involved; bool is_last_insert_id_generated; bool auto_commit; DB_VALUE cur_insert_id; DB_VALUE last_insert_id; int row_count;
SESSION_VARIABLE *session_variables; /* SET @x = .. list */ PREPARED_STATEMENT *statements; /* PREPARE name AS .. list */ SESSION_QUERY_ENTRY *queries; /* holdable cursor results */
time_t active_time; /* used by reaper */ SESSION_PARAM *session_parameters; /* per-session sysprm overrides */ char *trace_stats; char *plan_string; int trace_format;
int ref_count; /* # threads / conns referencing */ TZ_REGION session_tz_region; /* locale */ int private_lru_index; /* private buffer-pool LRU */
load_session *load_session_p; /* loaddb sub-session */ PL_SESSION *pl_session_p; /* PL/SP sub-session */};이 구조체에는 다섯 갈래의 서브시스템이 매달려 있다.
- Identity / hash 배관.
id,stack,next,mutex,del_id.lockfree_hashmap에 넘기는 descriptor가 이 필드들을offsetof로 읽는다. - statement별 편의 상태.
cur_insert_id,last_insert_id,row_count,is_last_insert_id_generated,is_trigger_involved. 한 트랜잭션 안의 여러 statement에 걸쳐 유지되며, SQL 함수LAST_INSERT_ID(),ROW_COUNT()가 이 값을 읽어 간다. - 이름 붙은 session 단위 객체의 카탈로그.
session_variables(SET @v = ...),statements(PREPARE name FROM ...),queries(commit을 넘어 살아남는 holdable cursor). session별 개수에 상한이 있기 때문에 (MAX_SESSION_VARIABLES_COUNT = 20,MAX_PREPARED_STATEMENTS_COUNT = 20) 단순한 단방향 연결 리스트로 충분하다. - session 단위 sysparam + locale.
session_parameters,session_tz_region. session이 자기 영역에서intl_*,tz_*, 격리 기본값 등을 로컬 오버라이드할 수 있고, 이 배열은 connect 패킷에서ssession_find_or_create_session안의sysprm_session_init_session_parameters가 풀어 준다. - 서브 session.
load_session_p(한 번의loaddb호출에 대응하는 bulk loader의 parser-and-interrupt 컨텍스트) 와pl_session_p(PL/JavaSP 실행 컨텍스트 —cubrid-pl-javasp.md참조). 둘 다 session이 소유하고session_state_uninit에서 파괴된다. statement 경계를 넘어 살 수 있기 때문에 트랜잭션이 아니라 session 위에 산다.
Lifecycle
섹션 제목: “Lifecycle”stateDiagram-v2 [*] --> NEW : xsession_create_new NEW --> ACTIVE : session_state_create \n hash insert + conn_entry 바인딩 ACTIVE --> ACTIVE : xsession_check_session \n active_time 갱신, conn 바인딩 갱신 ACTIVE --> SLEEPING : worker가 요청을 끝냄 \n session_state_decrease_ref_count SLEEPING --> ACTIVE : 같은 conn에 새 요청 \n ref_count++ SLEEPING --> EXPIRED : reaper 판정 \n now - active_time > PRM_ID_SESSION_STATE_TIMEOUT \n AND ref_count == 0 EXPIRED --> KEPT : is_keep_session == true KEPT --> ACTIVE : 같은 SESSION_ID 로 reconnect EXPIRED --> DEAD : session_state_uninit + hash에서 erase ACTIVE --> DEAD : xsession_end_session (is_keep_session==false) DEAD --> [*]
세 전이가 자세히 볼 만하다.
NEW → ACTIVE. 새 클라이언트의 유일한 진입로는
ssession_find_or_create_session 네트워크 핸들러다. 핸들러
내부 로직은 옛 session id를 살려 보고, 안 되면 새로 만든다
한 줄로 요약된다.
// ssession_find_or_create_session — network_interface_sr.cpp (condensed)ptr = or_unpack_int (request, (int *) &id);ptr = or_unpack_stream (ptr, server_session_key, SERVER_SESSION_KEY_SIZE);ptr = sysprm_unpack_session_parameters (ptr, &session_params);ptr = or_unpack_string_alloc (ptr, &db_user);ptr = or_unpack_string_alloc (ptr, &host);ptr = or_unpack_string_alloc (ptr, &program_name);
if (id == DB_EMPTY_SESSION || memcmp (server_session_key, xboot_get_server_session_key (), ...) != 0 || (error = xsession_check_session (thread_p, id)) != NO_ERROR) { er_clear (); error = xsession_create_new (thread_p, &id); /* fresh id */ }else if (error == NO_ERROR) { xsession_set_is_keep_session (thread_p, false); }server_session_key 는 서버 부팅마다 xboot_get_server_session_key()
가 발행하는 16바이트 cookie다. 클라이언트가 캐시해 둔 session id가
지금 이 서버의 마지막 부팅 이전 에 발급된 것이라면 이 검사가
통과되지 않아 새 session이 강제로 생성된다. 이 key 검사가 없다면
재부팅된 서버에 옛 session id를 들이밀어 “내가 만든 적 없는
session을 내가 들고 있다고 믿게” 만드는 트릭이 가능해진다.
session 생성이 성공하면 session_state_create 가 바인딩 작업을
처리한다.
// session_state_create — session.c (condensed)next_session_id = ATOMIC_INC_32 (&sessions.last_session_id, 1);*id = next_session_id;(void) sessions.states_hashmap.insert (thread_p, *id, session_p);ATOMIC_CAS_32 (&sessions.last_session_id, next_session_id, *id);
session_p->pl_session_p = new PL_SESSION (session_p->id);session_p->active_time = time (NULL);
#if defined (SERVER_MODE)session_state_increase_ref_count (thread_p, session_p);session_p->private_lru_index = pgbuf_assign_private_lru (thread_p);session_set_conn_entry_data (thread_p, session_p);logtb_set_current_user_active (thread_p, true);#endifhash insert 외에 의미 있는 부수 효과가 셋이다.
- session 단위 private LRU 가 buffer pool에 할당된다
(
pgbuf_assign_private_lru). session에 자기 LRU chain이 따로 생기므로 heap-scan을 많이 하는 클라이언트가 공유 LRU를 휩쓸어 가는 일을 방지한다. - connection entry가 session으로 역포인터 를 갖는다
(
session_set_conn_entry_data가conn_entry->session_p와conn_entry->session_id를 쓴다). 이 connection 위의 후속 요청은 모두 hash lookup을 우회한다. - TDES가 user-active 로 표시된다
(
logtb_set_current_user_active(thread_p, true)가tdes->is_user_active를 토글). connection 모니터와 shutdown 경로가 이 TDES가 살아 있는 사용자에게 소유되어 있음을 알도록 한다.
ACTIVE → SLEEPING → EXPIRED. Reaper는 부팅 시에 등록되는
cubthread::daemon 이다.
// session_control_daemon_execute — session.cif (!BO_IS_SERVER_RESTARTED ()) return;session_remove_expired_sessions (&thread_ref);60초마다 한 번씩 (cubthread::looper(std::chrono::seconds(60)))
hashmap을 걸어가고, entry당 판정 규칙은 session_check_timeout
안에 있다.
// session_check_timeout — session.c (condensed)if ((curr_time - session_p->active_time) >= prm_get_integer_value (PRM_ID_SESSION_STATE_TIMEOUT)) { /* extra safety: ask the connection layer for the active * session ids; if our id is among them, refresh and skip. */ if (active_sessions->count == -1) css_get_session_ids_for_active_connections ( &active_sessions->session_ids, &active_sessions->count); for (i = 0; i < active_sessions->count; i++) if (active_sessions->session_ids[i] == session_p->id) { session_p->active_time = time (NULL); /* refresh */ return err; } *remove = true; }두 가지가 눈에 띈다.
active_time이 stale인데css_Active_conn_anchor위에 살아 있는 connection이 여전히 있다면 — 이때 entry는 제거되지 않고 제자리에서 갱신된다. 긴 쿼리가 단지active_time을 re-touch하지 못해 timeout을 넘긴 케이스를 이 분기가 처리한다.- 실제 삭제는 lock-free 해시의 함정을 피하느라 두 단계로 나뉜다.
session_state_uninit은 iterator 안에서 호출하지만,states_hashmap.erase는 iterator 밖에서 호출한다 (session_remove_expired_sessions의 주석에 “lf_hash_delete may have to retry, which also resets the lock-free transaction. And resetting lock-free transaction can break our iterator.” 라고 쓰여 있다). 한 번의 pass에서 최대 1024개의 만료 entry를 버퍼링한 뒤 짧은 루프로 erase하고, 그러고 나서 iterator를 재시작한다.
ACTIVE → KEPT. 자신이 곧 disconnect할 것을 알지만 session은
warm 상태로 남기고 싶은 클라이언트는 xsession_end_session 호출
이전에 xsession_set_is_keep_session 으로 is_keep_session = true
를 켠다. session_state_destroy 가 이 플래그를 존중한다.
// session_state_destroy — session.c (condensed)if (is_keep_session == true) { session_p->is_keep_session = true; pthread_mutex_unlock (&session_p->mutex); return NO_ERROR; }session은 hash에 남고, conn entry는 분리되며
(thread_p->conn_entry->session_p = NULL), reaper도 이 entry를
무시한다 (timeout 분기가 is_keep_session 을 보고 cleanup을
건너뛴다). 같은 SESSION_ID 로 다시 접속해 오면
xsession_check_session 이 성공하면서 부활한다.
session 단위 prepared statement 레지스트리
섹션 제목: “session 단위 prepared statement 레지스트리”SESSION_STATE::statements 필드는 PREPARED_STATEMENT 의 단방향
연결 리스트다.
// PREPARED_STATEMENT — session.ctypedef struct prepared_statement PREPARED_STATEMENT;struct prepared_statement{ char *name; char *alias_print; /* decompiled SQL printed back */ SHA1Hash sha1; /* keys the XASL cache */ int info_length; char *info; /* serialised parameter info */ PREPARED_STATEMENT *next;};session_create_prepared_statement 는 같은 name (대소문자 무시)
의 기존 entry를 찾아 리스트를 walk하고, 있으면 떨어내고 새 entry를
앞에 prepend한다. 리스트의 길이는 MAX_PREPARED_STATEMENTS_COUNT = 20 으로 막혀 있고 그 이상은 ER_SES_TOO_MANY_STATEMENTS 를
반환한다.
lookup은 동일한 walk다.
// session_get_prepared_statement — session.c (condensed)for (stmt_p = state_p->statements; stmt_p != NULL; stmt_p = stmt_p->next) if (intl_identifier_casecmp (stmt_p->name, name) == 0) break;...err = xcache_find_sha1 (thread_p, &stmt_p->sha1, XASL_CACHE_SEARCH_GENERIC, xasl_entry, NULL);요컨대 캐시가 두 층이다. session은 바인딩 을 소유한다. 이름과
SHA-1 그리고 파라미터 info blob의 묶음. 서버 전역 XASL 캐시 가
SHA-1을 키로 하는 plan 을 소유한다. 다른 session이 동일한
SQL로 prepare하면 자기 리스트에 새 PREPARED_STATEMENT entry는
하나 생기지만 같은 XASL_CACHE_ENTRY 를 재사용한다. 이는 정확히
Oracle의 2-tier 모델이다.
session 단위 쿼리 결과로서의 holdable cursor
섹션 제목: “session 단위 쿼리 결과로서의 holdable cursor”JDBC의 HOLD_CURSORS_OVER_COMMIT 플래그가 켜진 쿼리는 commit 경계
를 넘어 클라이언트가 결과를 계속 읽을 수 있어야 한다. session
저장소가 없다면 file 매니저가 commit 시점에 결과 list file을 지워
버린다. session은 그 결과를 스냅샷으로 가져 둔다.
// SESSION_QUERY_ENTRY — session.ctypedef struct session_query_entry SESSION_QUERY_ENTRY;struct session_query_entry{ QUERY_ID query_id; QFILE_LIST_ID *list_id; QMGR_TEMP_FILE *temp_file; int num_tmp; int total_count; QUERY_FLAG query_flag; SESSION_QUERY_ENTRY *next;};query 매니저가 holdable 결과 하나마다 session_store_query_entry_info
를 호출한다. 함수는 query 매니저의 entry를 session 안으로 복사
하면서 list_id 와 temp_vfid 포인터를 훔쳐 오고 (원본 쪽에서
는 NULL로 비워서 query 매니저가 회수하지 못하게 한다), temp file은
file_temp_preserve 로 preserved 플래그가 박혀 commit 시에
file 매니저가 지우지 못하게 만든다.
// session_preserve_temporary_files — session.c (condensed)tfile_vfid_p = qentry_p->temp_file;tfile_vfid_p->prev->next = NULL;while (tfile_vfid_p) { if (!VFID_ISNULL (&tfile_vfid_p->temp_vfid)) if (!tfile_vfid_p->preserved) { file_temp_preserve (thread_p, &tfile_vfid_p->temp_vfid); tfile_vfid_p->preserved = true; } tfile_vfid_p = tfile_vfid_p->next; }대칭적인 session_load_query_entry_info, session_remove_query_entry_info
가 fetch와 최종 close를 처리한다. 모든 session에 걸쳐 살아 있는
holdable 결과의 총 개수는 글로벌 카운터 sessions.num_holdable_cursors
가 추적한다. boot 경로가 list file 공간 예약량을 결정할 때 이
값을 본다.
TDES 바인딩 — request 진입 경로
섹션 제목: “TDES 바인딩 — request 진입 경로”session 모듈에서 단연 가장 중요한 사실은 요청 진입 시점에 무슨
일이 일어나는가 다. CUBRID의 네트워크 dispatcher
(network_sr.c 안의 net_server_request) 는 connection worker가
클라이언트 패킷마다 한 번씩 호출한다. 이 함수는 *session lookup을
하지 않는다. session은 이미 바인딩되어 있다.
// net_server_request — network_sr.c (condensed)conn = thread_p->conn_entry;assert (conn != NULL);
if (IS_INVALID_SOCKET (conn->fd) || conn->status != CONN_OPEN) goto end;...if (net_Requests[request].action_attribute & IN_TRANSACTION) conn->in_transaction = true;...func = net_Requests[request].processing_function;if (conn->invalidate_snapshot != 0) logtb_invalidate_snapshot_data (thread_p);(*func) (thread_p, rid, buffer, size);request handler가 올바른 상태 에 도달하기까지의 포인터 사슬은 이렇다.
flowchart LR PKT["네트워크 패킷\n(rid, request_code, buffer)"] CONN["CSS_CONN_ENTRY\n.session_id, .session_p, .transaction_id"] TE["THREAD_ENTRY\n.conn_entry, .tran_index"] STATE["SESSION_STATE\n(prepared statements,\nrow count,\nsession 변수)"] TDES["LOG_TDES\n(trid, isolation,\nsavepoint, lock 집합,\nMVCC 스냅샷)"] PKT --> CONN CONN -->|conn->session_p| STATE CONN -->|conn->transaction_id| TE TE -->|LOG_FIND_TDES(tran_index)| TDES STATE -.PL_SESSION,\nload_session.-> TE
connection entry가 단일 hub다. 다음을 들고 있다.
session_id(정수) 와session_p(resolve된 포인터). create / check 시점에session_set_conn_entry_data가 채워 둔다.transaction_id(log_Gl.trantable.all_tdes[]에 대한 트랜잭션 단위 인덱스).css_conn_entry위의set_tran_index/get_tran_index로 조작한다. TDES는 그 인덱스를 받아LOG_FIND_TDES(tran_index)한 번에 resolve된다.
worker 스레드가 패킷을 집어들 때, dispatcher는 thread_p 가
이미 thread_p->conn_entry 에 바인딩된 상태로 호출된다. 그
결과 session lookup은 O(1)이다.
// session_get_session_state — session.c (condensed)if (thread_p == NULL) thread_p = thread_get_thread_entry_info ();if (thread_p != NULL && thread_p->conn_entry != NULL && thread_p->conn_entry->session_p != NULL) return thread_p->conn_entry->session_p;else { if (thread_p->type == TT_WORKER) er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_SES_SESSION_EXPIRED, 0); return NULL; }이 패턴 — *session은 connection entry에 캐시되어 있고, thread는
connection entry를 가지고 다니며, 모든 request handler는
thread_p 의 함수다. 가, xsession_set_row_count 같은
서버 측 핸들러가 THREAD_ENTRY * 만 받고 SESSION_ID 를 절대로
받지 않는 이유다. session은 호출 컨텍스트에 암시적으로 들어 있다.
Reference counting
섹션 제목: “Reference counting”성공적인 바인딩이 있을 때마다 session의 ref_count 가 올라간다.
// session_state_increase_ref_count — session.cATOMIC_INC_32 (&state_p->ref_count, 1);conn entry가 hold를 놓으면 내려간다.
// session_state_decrease_ref_count — session.cATOMIC_INC_32 (&state_p->ref_count, -1);관련 지점은 다음과 같다.
session_state_create: 새 conn entry를 위해 한 번 올린다.xsession_check_session: conn entry가 들고 있던 이전 session이 있다면 내리고, 새 session을 위해 올린다.xsession_end_session/session_state_destroy: 정상 종료 시 내린다. 만일 destroy 시점에ref_count > 0이라면 session은 손대지 않고 ("This session_state is busy, I can't remove") conn entry의 바인딩만 끊는다.css_shutdown_conn: 죽어 가는 connection이 아직 session을 들고 있었다면 내린다.
debug 빌드에는 session_state_verify_ref_count 라는 검증기가
있어 css_Active_conn_anchor 를 walk하며 이 session을 참조하는
conn 수를 직접 세고, 부기와 어긋나면 assertion으로 터진다.
connection-entry 단위 sub-session
섹션 제목: “connection-entry 단위 sub-session”SESSION_STATE 는 두 개의 포인터-타입 sub-session 컨테이너를
들고 있다.
load_session_p—loaddb클라이언트를xloaddb_init이 채운다. bulk loader는 long-running이고 부분 상태가 있으며 interrupt 도 노출해야 하는데, 그 cancel 핸들을 session이 소유한다.pl_session_p— session 탄생 시점에session_state_create안 에서new PL_SESSION (session_p->id)로 생성된다. PL session이 stored procedure 실행 stack과 JavaSP / PL-CSQL 호출자에 대한 cross-thread interrupt 플래그를 들고 있다.cubrid-pl-javasp.md에 자세히 다룬다.
session_stop_attached_threads (session_state_uninit 안에서
호출) 가 둘 모두를 걸어 정리한다.
// session_stop_attached_threads — session.c (condensed)if (session->load_session_p != NULL) { session->load_session_p->interrupt (); session->load_session_p->wait_for_completion (); delete session->load_session_p; session->load_session_p = NULL; }if (session->pl_session_p) if (thread_p && thread_p->type == TT_WORKER) { session->pl_session_p->set_interrupt (er_errid ()); session->pl_session_p->wait_until_pl_session_done (); }session 종료가 사소한 작업이 아닌 이유가 여기에 있다. state 컨테이너를 파괴하기 전에 그 안에 박혀 있는 sub-session worker 스레드들을 먼저 풀어 줘야 한다.
commit / rollback 시의 정리
섹션 제목: “commit / rollback 시의 정리”session은 commit의 단위가 아니다. 대부분의 session 상태는 commit / rollback 을 그대로 통과한다. prepared statement, session 변수, last insert id, autocommit flag, 격리 기본값, locale.
트랜잭션과 함께 움직이는 것은 셋이다.
- 사실은 holdable 가 아니었던 holdable cursor. query 매니저가
자기 non-holdable list를 commit 시에 닫고,
state_p->queries까지 살아 들어간 것만 살아남는다. - TDES 그 자체. commit / rollback이
tran_index를 trantable의 free list로 돌려보내지만 (cubrid-transaction.md참조) conn entry의transaction_id는 *0으로 비워지지 않는다. 다음 트랜잭셔널 요청이logtb_assign_tran_index시점에 동일한 인덱스를 다시 집어 오거나, conn entry의 캐시된 id가 여전히 할당되어 있다면 그대로 재사용한다. - TDES의
is_user_active플래그는 사용자 활동 주변에서 토글 되며, shutdown 경로가 이 플래그를 보고 기다린다.
반대 경계 — connection 끊김 — 은 더 적극적이다.
net_server_conn_down (worker 아래에서 TCP 소켓이 죽었을 때
connection 층이 호출) 은 session_remove_query_entry_all 을
호출해 session의 모든 holdable cursor를 선제적으로 닫는다.
session 본체는 disconnect 패킷에서 is_keep_session == false 인
xsession_end_session 이 들어왔을 때만 죽는다.
동시성 모델
섹션 제목: “동시성 모델”session은 다음의 주체로부터 참조될 수 있다.
- 현재 패킷을 처리 중인 conn-worker 스레드 (보통의 케이스).
- 정리를 위해 trantable을 walk하지만 session 상태는 건드리지 않는 vacuum worker.
- reaper pass 중인 session-control daemon.
- 진행 중인 stored procedure를 PL session으로 들어온 외부 worker 스레드.
SESSION_STATE 의 mutex는 hash 내부 연산 (find, insert, erase)
과 mutable list head를 짧게 읽는 동안에만 잡힌다. 리스트 walk
(session_get_prepared_statement 등) 는 캐시된
conn_entry->session_p 를 dereference한 뒤로는 mutex 없이
진행된다. 가정은 “이 conn entry의 session은 이 conn entry의
worker에서만 mutate된다” 이고, 이 가정은 다음과 같이 성립한다.
- 같은 conn에 대한 두 번째 worker가 들어오려면 먼저 session에 바인딩해야 하는데, 그 경로가 hashmap을 거치며 mutex를 잡는다.
- reaper는
ref_count > 0이면 삭제를 건너뛰고,== 0이면 그 정의상 다른 스레드가 리스트를 walk하고 있을 수 없다.
이 덕분에 prepared statement 리스트와 session 변수 리스트는 별도 list-lock 없는 단순 단방향 연결 리스트로 충분하다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”심볼들을 서브시스템별로 묶었다. 라인 번호는 이 문서의 updated:
시점에 관찰된 값이며, 시간이 지나면 어긋난다. 심볼 이름을 닻으로
삼아라.
Lifecycle / hash 배관
섹션 제목: “Lifecycle / hash 배관”| 심볼 | 역할 |
|---|---|
ACTIVE_SESSIONS | static sessions 값: hashmap, last id, holdable-cursor 카운터 |
SESSION_STATE | session별 컨테이너 구조체 |
session_state_Descriptor | lockfree-hashmap의 동작을 설정하는 LF_ENTRY_DESCRIPTOR |
session_state_alloc / session_state_free | hashmap의 entry malloc/free hook |
session_state_init / session_state_uninit | entry 재활용 hook (insert 시 init, erase 시 uninit) |
session_key_copy / session_key_compare / session_key_hash / session_key_increment | lockfree-hashmap의 key 콜백 |
session_states_init / session_states_finalize | 모듈 init/teardown (서버 boot/shutdown에서 호출) |
session_state_create | 실제 create-and-insert 루틴 |
session_state_destroy | 실제 erase 루틴 (is_keep_session 존중) |
session_check_session | active_time touch + conn entry 재바인딩 |
session_remove_expired_sessions | reaper 안쪽 루프 |
session_check_timeout | entry별 만료 판정 (css_get_session_ids_for_active_connections 참고) |
session_control_daemon_execute / _init / _destroy | 60초 reaper daemon 배관 |
서버 진입점 (session_sr.c → network_sr.c에서 등록)
섹션 제목: “서버 진입점 (session_sr.c → network_sr.c에서 등록)”| 심볼 | 역할 |
|---|---|
xsession_create_new | 새 session 발급 |
xsession_check_session | 기존 session 검증 / 부활 |
xsession_end_session | 종료 (또는 is_keep_session 마킹) |
xsession_set_is_keep_session | keep 플래그 토글 |
xsession_set_row_count / xsession_get_row_count | statement별 row count |
xsession_set_cur_insert_id / xsession_get_last_insert_id / xsession_reset_cur_insert_id | LAST_INSERT_ID() 백엔드 |
xsession_create_prepared_statement / xsession_get_prepared_statement / xsession_delete_prepared_statement | prepared statement 레지스트리 |
xlogin_user | TDES에 사용자 이름 push (tdes->client.set_user) |
xsession_set_session_variables / xsession_get_session_variable / xsession_drop_session_variables | SET @v = … |
xsession_store_query_entry_info / xsession_load_query_entry_info / xsession_remove_query_entry_info / xsession_clear_query_entry_info | holdable cursor lifecycle |
xsession_set_tran_auto_commit | session의 autocommit flag 토글 |
네트워크 핸들러 (network_interface_sr.cpp)
섹션 제목: “네트워크 핸들러 (network_interface_sr.cpp)”| 심볼 | 역할 |
|---|---|
ssession_find_or_create_session | connect 경로: check를 시도하고 실패하면 create. server_session_key, sysparam blob, db_user / host / program을 클라이언트로 회신 |
ssession_end_session | disconnect 경로 |
ssession_set_row_count / ssession_get_row_count | wire request 백엔드 |
ssession_get_last_insert_id / ssession_reset_cur_insert_id | 동상 |
ssession_create_prepared_statement / ssession_get_prepared_statement / ssession_delete_prepared_statement | 동상 |
ssession_set_session_variables / ssession_get_session_variable / ssession_drop_session_variables | 동상 |
네트워크 dispatch 테이블 (network_sr.c)
섹션 제목: “네트워크 dispatch 테이블 (network_sr.c)”| 심볼 | 역할 |
|---|---|
net_Requests[NET_SERVER_SES_CHECK_SESSION].processing_function = ssession_find_or_create_session | 핸드셰이크 request |
net_Requests[NET_SERVER_SES_END_SESSION] = ssession_end_session | disconnect request |
NET_SERVER_SES_* 계열 | wire code 211 이후 (network.h 참조) |
net_server_request | dispatcher; thread_p->conn_entry 를 읽고 핸들러 호출 |
net_server_conn_down | connection-loss 콜백; session_remove_query_entry_all 호출 |
connection 층 통합 (connection_sr.c)
섹션 제목: “connection 층 통합 (connection_sr.c)”| 심볼 | 역할 |
|---|---|
CSS_CONN_ENTRY::session_id | 캐시된 session id |
CSS_CONN_ENTRY::session_p | 캐시된 resolve된 SESSION_STATE * |
CSS_CONN_ENTRY::transaction_id (set_tran_index / get_tran_index 경유) | LOG_FIND_TDES 용 캐시된 tran_index |
css_initialize_conn | session_id = DB_EMPTY_SESSION, session_p = NULL 로 초기화 |
css_shutdown_conn | conn이 바인딩되어 있었다면 session의 ref_count 를 내림 |
css_find_conn_by_tran_index | 역방향 lookup (interrupt / kill에서 사용) |
css_get_session_ids_for_active_connections | reaper 헬퍼: 모든 connection의 session id 목록 |
css_Active_conn_anchor (+ rwlock) | reaper가 walk하는 connection 리스트 |
session.c 안의 session 단위 캐시 배관
섹션 제목: “session.c 안의 session 단위 캐시 배관”| 심볼 | 역할 |
|---|---|
session_get_session_state | 핫 패스 getter: thread_p->conn_entry->session_p 반환 |
session_set_conn_entry_data | conn_entry->session_p, conn_entry->session_id 를 쓰고, thread_p->private_lru_index 를 설정하고, pgbuf_thread_variables_init 호출 |
session_state_increase_ref_count / session_state_decrease_ref_count | atomic ref count |
session_state_verify_ref_count (NDEBUG에서 비활성) | css_Active_conn_anchor 를 walk하는 sanity check |
session 단위 카탈로그
섹션 제목: “session 단위 카탈로그”| 심볼 | 역할 |
|---|---|
PREPARED_STATEMENT + session_create_prepared_statement / session_get_prepared_statement / session_delete_prepared_statement / session_free_prepared_statement | prepared statement 리스트 (상한 20) |
SESSION_VARIABLE + session_add_variable / session_drop_variable / update_session_variable / free_session_variable | SET @v = … 리스트 (상한 20) |
SESSION_QUERY_ENTRY + qentry_to_sentry / sentry_to_qentry / session_preserve_temporary_files / session_free_sentry_data / session_remove_query_entry_all | holdable cursor 리스트 |
TDES 바인딩 (transaction/log_tran_table.c)
섹션 제목: “TDES 바인딩 (transaction/log_tran_table.c)”| 심볼 | 역할 |
|---|---|
LOG_FIND_TDES(tran_index) | 매크로: log_Gl.trantable.all_tdes[tran_index] |
LOG_FIND_THREAD_TRAN_INDEX(thread_p) | thread_p->tran_index |
logtb_assign_tran_index | session에 바인딩된 thread가 첫 트랜잭셔널 요청을 낼 때 TDES 슬롯을 할당 |
logtb_set_current_user_active | session_state_create / _destroy 가 TDES의 user-bound 마킹을 토글하도록 호출 |
logtb_set_current_user_name | ssession_find_or_create_session 이 db_user 를 TDES에 박아 넣을 때 호출 |
위치 힌트 (이 리비전 시점 관찰값)
섹션 제목: “위치 힌트 (이 리비전 시점 관찰값)”| 심볼 | 파일 | 라인 |
|---|---|---|
SESSION_STATE | src/session/session.c | 115 |
ACTIVE_SESSIONS / sessions | src/session/session.c | 188 / 205 |
session_state_Descriptor | src/session/session.c | 163 |
session_states_init | src/session/session.c | 609 |
session_states_finalize | src/session/session.c | 634 |
session_state_create | src/session/session.c | 664 |
session_state_destroy | src/session/session.c | 778 |
session_check_session | src/session/session.c | 855 |
session_remove_expired_sessions | src/session/session.c | 929 |
session_check_timeout | src/session/session.c | 1037 |
session_get_session_state | src/session/session.c | 2847 |
session_set_conn_entry_data | src/session/session.c | 2780 |
session_state_increase_ref_count | src/session/session.c | 3138 |
session_state_decrease_ref_count | src/session/session.c | 3163 |
session_control_daemon_execute | src/session/session.c | 561 |
session_create_prepared_statement | src/session/session.c | 1750 |
session_get_prepared_statement | src/session/session.c | 1862 |
session_store_query_entry_info | src/session/session.c | 2507 |
session_get_session_tz_region | src/session/session.c | 3052 |
session_stop_attached_threads | src/session/session.c | 3315 |
xsession_create_new | src/session/session_sr.c | 39 |
xsession_check_session | src/session/session_sr.c | 54 |
xsession_end_session | src/session/session_sr.c | 67 |
xsession_create_prepared_statement | src/session/session_sr.c | 185 |
xsession_get_prepared_statement | src/session/session_sr.c | 204 |
ssession_find_or_create_session | src/communication/network_interface_sr.cpp | 9181 |
ssession_end_session | src/communication/network_interface_sr.cpp | 9315 |
ssession_create_prepared_statement | src/communication/network_interface_sr.cpp | 9484 |
ssession_get_prepared_statement | src/communication/network_interface_sr.cpp | 9584 |
net_server_request | src/communication/network_sr.c | 790 |
net_Requests[NET_SERVER_SES_CHECK_SESSION] = | src/communication/network_sr.c | 616 |
net_server_conn_down | src/communication/network_sr.c | 1040 |
CSS_CONN_ENTRY::session_p | src/connection/connection_defs.h | 478 |
CSS_CONN_ENTRY::session_id | src/connection/connection_defs.h | 480 |
CSS_CONN_ENTRY::set_tran_index / get_tran_index | src/connection/connection_defs.h | 495 / 496 |
css_initialize_conn | src/connection/connection_sr.c | 254 |
css_shutdown_conn (session decref) | src/connection/connection_sr.c | 402 |
css_get_session_ids_for_active_connections | src/connection/connection_sr.c | 1267 |
logtb_set_current_user_active | src/transaction/log_tran_table.c | 2086 |
DB_EMPTY_SESSION | src/compat/dbtype_def.h | 504 |
소스 검증 노트
섹션 제목: “소스 검증 노트”-
vs. cubrid-transaction.md. 그 문서는
LOG_TDES,TRANTABLE, savepoint / topops 스택, 격리 수준 강제 등을 다룬다. 본 문서는 session을 다루며 TDES 내부를 다시 유도하지 않는다. 바인딩은 단방향이다.conn_entry->transaction_id는 정수이고LOG_FIND_TDES가 그 정수를 resolve한다. session에는LOG_TDES *타입의 필드가 없고, TDES에도SESSION_ID필드가 없다. 양쪽을 함께 들고 있는 객체는 connection entry뿐이다. session의 트랜잭션 이라는 표현은 사실 “지금 이 session의 현재 connection entry에 캐시되어 있는tran_index의 TDES” 의 단축어다. reconnect를 끼면 엄밀하게는 1:1 관계가 아니다. keep-alive로 살려둔 session이 재접속하면 새 conn entry가 새tran_index를 잡아 오고, 이전 TDES는 이전 disconnect의xtran_server_commit/_abort경계 에서 이미 풀려 있기 때문이다. -
vs. cubrid-pl-javasp.md. 그 문서는
PL_SESSION을 PL/SP 실행 컨텍스트로 본다. 본 문서는SESSION_STATE::pl_session_p포인터 필드의 시각으로 본다. PL session은session_state_create에서 만들어져session_stop_attached_threads에서 정리된다. PL session은 서버 session의 peer가 아니라 sub-state 이며, PL 호출 은 session의pl_session_p로 흘러 들어가고 session의 lifetime을 상속받는다. -
db_Session_id필드. SERVER_MODE가 아닌 CS-mode와 SA-mode 에서는session_get_session_id가thread_p->conn_entry->session_id대신db_Session_id를 반환한다. 단일 프로세스 모드에는 connection entry가 없기 때문이다. 그 경우에도 hashmap은 여전히 쓰이지만conn_entry->session_p캐시가 없으므로 SA-mode에서는session_get_session_state호출마다 hashmap lookup 비용을 낸다. SA-mode의 session이 정확히 한 개이므로 문제가 되지 않는다. -
캐시된
session_pvs. 만료된 session. reaper가 conn entry에 여전히session_p가 캐시된 session을 만료시키면, 그 conn entry의 다음 요청이 dangling pointer를 쓰게 된다. 보호 장치는 reaper의 규칙이다.ref_count > 0인 session은 제거되지 않고 refresh된다. conn entry의 바인딩은 ref-count 되므로, 여전히 캐시되어 있는 session을 reaper가 free할 수 있는 경로는 존재할 수 없다.session_state_verify_ref_count는 추가 belt-and-braces 가드다.
미해결 질문
섹션 제목: “미해결 질문”-
connection당 session 1개인가, 여러 개 가능한가? 자료구조 자체는
ref_count로 둘 이상의 connection entry가 동일한SESSION_STATE를 동시에 참조하는 것을 허용 한다. 그러나 실제 로ref_count를 올리는 경로는 하나의 conn entry의 바인딩 경로뿐이다. reconnect는 새 conn의 ref가 잡히기 전에 옛 conn의 ref를 내린다. connection 풀의 reconnect storm 시 일부 드라이버가 요청하는 session pinning 처럼, 동시에 살아 있는 두 connection 이 동일 session id를 공유할 수 있는지는 문서화되어 있지 않다.session_state_verify_ref_count는conn->session_id == session->id인 conn을 모두 세므로 이 경우에도 통과는 하지만, 중복을 금지하는 assertion이 없다. 의도된 시나리오인지 검증할 설계 테스트가 필요하다. -
broker 프로세스 재시작.
cub_cas브로커 프로세스가 재활용 될 때 (BROKER_RESTART_TIME등), CAS는 서버 측에 열린 session들 을 들고 있는 채 종료한다. cub_server의 reaper가 끊긴 TCP를 보고 즉시 session을 free하는지, 아니면 graceful shutdown 직전에 CAS 풀이 켜 둔is_keep_session플래그가 다음 CAS가 받아 갈 때까지 session을 붙들어 두는지는 broker shutdown이 graceful이냐 kill -9이냐에 따라 갈린다. 두 경로 모두 존재한다. -
statement 레지스트리 — thread별인가 session별인가. prepared-statement 리스트는 session별이며 lock이 없다. 이는 one-conn-↔-one-worker invariant에 의존한다. 향후 같은 conn entry 의 두 worker를 동시에 허용하는 재구성이 일어난다면 list walk 주변에 session 단위 lock을 두든지 lock-free 구조로 promote해야 한다.
-
왜
MAX_PREPARED_STATEMENTS_COUNT = 20인가? 큰 애플리케이션 은ER_SES_TOO_MANY_STATEMENTS에 부딪히고, JDBC 드라이버가 LRU eviction으로 가려 주긴 하지만 이 상한은 soft hint가 아니라 hard limit이다. 상한을 올리면 list walk의 비용도 선형으로 따라 오른다.
src/session/session.c— session state hash, lifecycle, prepared-statement / holdable-cursor 리스트, daemonsrc/session/session.h— 외부 공개 surfacesrc/session/session_sr.c—xsession_*서버 진입점src/connection/connection_defs.h—session_p와session_id필드를 가진CSS_CONN_ENTRY정의src/connection/connection_sr.c—css_initialize_conn,css_shutdown_conn,css_get_session_ids_for_active_connectionssrc/communication/network_sr.c—net_server_requestdispatcher,NET_SERVER_SES_*테이블 채움,net_server_conn_downsrc/communication/network_interface_sr.cpp—ssession_*네트워크 핸들러src/transaction/log_tran_table.c— TDES 테이블,LOG_FIND_TDES,logtb_set_current_user_activesrc/compat/dbtype_def.h—SESSION_IDtypedef,DB_EMPTY_SESSION상수- 자매 문서:
cubrid-transaction.md,cubrid-pl-javasp.md