(KO) CUBRID SHOW 명령 — 서버 런타임 상태를 가상 스캔으로 들여다보는 시스템 인트로스펙션
목차
학술적 배경
섹션 제목: “학술적 배경”카탈로그(catalog)는 시스템에 무엇이 존재하는가 를 알려 준다 —
클래스, 속성, 인덱스, 도메인. 그러나 카탈로그는 시스템이 지금
무엇을 하고 있는가 를 알려 주지는 않는다 — 어떤 스레드가 실행
중인지, 어떤 크리티컬 섹션이 잡혀 있는지, 페이지 버퍼가 얼마나
차 있는지, 로그 tail이 어디인지, 각 트랜잭션이 무엇을 하고 있는지.
이런 런타임 질문에 대한 답은 엔진 내부에 사적으로 존재한다 —
워커 스레드 struct 안에, latch 상태 안에, LRU 큐 안에,
log_Gl.hdr 안에. 어느 것도 힙 파일에 디스크 형태로 적혀 있지
않고, 어느 것도 보통의 액세스 메서드로 도달할 수 없다.
이 런타임 상태를 어떻게 SQL 표면으로 끌어올릴 것인가 — 이 질문에 세 가지 텍스트북적 아이디어가 프레임을 제공한다.
자기-기술(self-description)로서의 시스템 카탈로그. Selinger 1979
(Access Path Selection in a Relational DBMS, System R)와
Hellerstein·Stonebraker 의 Anatomy of a Database System (Red Book
4장)은 카탈로그를 엔진이 SELECT 로 직접 질의할 수 있는 균일한
일급 테이블 트리로 다룬다. 엄격한 해석은 그곳에 정적인 스키마만
산다는 것이고, 느슨한 해석 — 모든 현대 엔진이 받아들인 — 은 행이
요청 시점에 계산되는 테이블로 동적 상태도 노출할 수 있다는
것이다. CUBRID는 느슨한 해석을 택했다 — _db_class/_db_attribute
옆에 서버 메모리에서 매 질의마다 머터리얼라이즈되는 “show
의사(pseudo)-테이블”의 평행한 표면이 존재한다.
Dynamic Management View (DMV). DMV는 그 행이 어디에도 저장되어
있지 않은 관계다 — 그 행들은, 질의가 그 view를 건드릴 때 엔진이
호출하는 함수가 만들어 낸다. 운영자는 SQL로 질문을 적는다 (“어떤
스레드가 블록되어 있는가?”, 더티 페이지가 몇 개인가?). 옵티마이저는
그 위에 술어, 조인, 집계를 — 그 안에 무엇이 들었는지는 미리 알지
못하면서 — 적용한다. 트레이드오프는 운영적이다 — 값들은 다양한
일관성 수준(스냅샷, 더티 read, 래치 보유)으로 읽히고, 읽는 행위
자체가 측정 대상 시스템을 교란해서는 안 된다. Petrov의 Database
Internals 1장은 이것을 엔진이 이미 EXPLAIN ANALYZE 를 위해
하고 있는 일 위에 얹은 얇은 레이어라고 정리한다.
합성 관계(synthetic relation)를 위한 Volcano 잎. Graefe의
iterator 모델(Volcano, TKDE 1994; Query Evaluation Techniques,
ACM CS 1993)은 모든 연산자에게 open/next/close 를 부여한다.
어떤 잎이든 — 힙, B+Tree, list 파일, set, JSON-table, 가상
카탈로그 — 같은 세 메서드를 구현한다. SHOW 명령은 정확히 그런
잎이다 — 종류별 start 가 사적인 스냅샷을 만들고, next 가
한 번에 한 튜플을 합성하며, end 가 그것을 해제한다. 위쪽의
나머지 executor — 술어, 프로젝션, 정렬, 조인 — 는 그 합성 소스
위에서 힙과 똑같이 동작한다. 이 균일성이 있기에
SHOW THREADS WHERE Status = 'TS_WAIT' ORDER BY Conn_index 가
타입 검사를 통과하고 실행되는 동안, scan manager 바깥의 어떤
코드도 threads가 진짜 테이블이 아님을 알 필요가 없다.
세 아이디어는 하나의 디자인으로 합쳐진다 — SHOW X 를
SELECT * FROM (가상-소스-X) 로 재작성하고, 가상 소스를 액세스
메서드 카탈로그의 또 한 항목(S_HEAP_SCAN·S_LIST_SCAN 옆에
S_SHOWSTMT_SCAN)으로 인코딩하며, 종류별 start/next/end 함수가
합성을 책임지게 한다. show_scan.c 가 이 셋을 한데 묶는 디스패치
테이블이고, show_meta.c 와 pt_make_query_showstmt 가 재작성을
수행하는 파서 측 facade다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”네 개의 주요 엔진이 SHOW/DMV 기능을 약간씩 다른 계층에서 손에 쥔다.
PostgreSQL 은 표면을 둘로 가른다 — 통계 수집기(statistics
collector) 위의 pg_stat_* view (백그라운드 프로세스가 백엔드
별 카운터를 공유 메모리로 모으고, 보통의 힙 스타일 스캔이 그것을
읽는다)와, FunctionScan 으로 감싼 집합 반환 함수
(set-returning function)(pg_locks, pg_blocking_pids,
pg_get_indexdef). 전자는 누적된 영구 카운트, 후자는 순간 상태를
위한 것이다.
MySQL 은 같은 데이터에 세 개의 문을 둔다 — 구문 슈가로서의
SHOW <thing>, ANSI에 정렬된 view로서의 INFORMATION_SCHEMA,
링 버퍼 이벤트 테이블로서의 performance_schema. 내부적으로
SHOW PROCESSLIST 는 합성 information_schema 테이블 위의
SELECT 로 재작성된다. 액세스 메서드는 표준 handler API로
라우팅되는 커스텀 스토리지 엔진(ha_perfschema,
ha_information_schema) 이다.
Oracle 은 X$ 고정 테이블 위에 V$/GV$ 딕셔너리 view를 둔다
— 커널 내부의 C 배열을 하드코딩된 고정 테이블 액세스 드라이버
(kqfta)가 관계로 투영한다. 모든 V$ 는
SELECT … FROM x$… 이고, 나머지 SQL 스택은 X$ 를 보통의 소스로
취급한다. AWR/ASH는 보존을 위해 V$ 스냅샷을 실제 테이블로 영속화한다.
SQL Server 는 DMV(sys.dm_exec_*, sys.dm_os_*)를 사용한다 —
엔진이 내부 system-internal table 소스로 풀어 주는 카탈로그 view로,
기계적으로 Oracle의 X$ 와 유사하다.
CUBRID는 이들 중 가장 직접적인 디자인을 골랐다. 문법은
SHOW <name> 을 받고, 파서는 그것을 SELECT * FROM (PT_SHOWSTMT) 로
재작성한다 — 이때 derived table은 종류와 인자를 들고 있는
PT_SHOWSTMT 노드다. 옵티마이저는 TARGET_SHOWSTMT 인
access spec을 내고, executor는 S_SHOWSTMT_SCAN 을 연다.
별도의 INFORMATION_SCHEMA 엔진도, X$ 고정 테이블 드라이버도,
통계 수집기도 없다. 전체 이야기는 새로운 단일 SCAN_TYPE,
SHOW 종류별로 컬럼과 인자를 명명하는 파서 측 메타데이터 테이블,
그리고 서버 초기화 시점에 부착되는 종류별 start/next/end 함수
포인터로 끝난다. 트레이드오프는 — MySQL이나 Oracle보다 훨씬 가벼운
plumbing을 얻지만, 서드파티 플러그인 지점은 없다 — 새로운 SHOW로
CUBRID를 확장한다는 것은 SHOWSTMT_TYPE, show_meta.c, 문법,
showstmt_scan_init 을 함께 수정한다는 뜻이다.
CUBRID의 구현
섹션 제목: “CUBRID의 구현”csql 에서 입력된 SHOW THREADS 가 결과 집합이 되기까지의 전체
경로는 네 개의 계층을 거친다. 문법이 SHOW 구문을 SHOWSTMT_TYPE
enum 값으로 매핑한다. 파서가 SHOW를 합성 소스 위의 SELECT로
재작성한다. 옵티마이저/XASL 단계가 TARGET_SHOWSTMT 를 들고
있는 access spec을 낸다. executor가 가상 스캔을 여는데, 그
next 가 함수 포인터 디스패치 테이블에서 종류별 튜플 생성기를
호출한다.
flowchart LR GR["csql_grammar.y\nSHOW THREADS"] PT["parser_support.c\npt_make_query_showstmt\n→ SELECT ∗ FROM PT_SHOWSTMT"] MD["show_meta.c\nshowstmt_get_metadata\n(컬럼, 인자, orderby)"] XA["xasl_generation.c\nTARGET_SHOWSTMT spec\nshow_type, arg_list"] EX["query_executor.c\nqexec_open_scan\n→ scan_open_showstmt_scan"] SM["scan_manager.c\nS_SHOWSTMT_SCAN\nstart / next / end"] SS["show_scan.c\nshow_Requests[show_type]\n.start_func/.next_func/.end_func"] TG["서브시스템별 생성기\ncsect_start_scan\nthread_start_scan\nlog_active_log_header_start_scan\n…"] GR --> PT --> MD PT --> XA --> EX --> SM --> SS --> TG
SHOWSTMT_TYPE — 디스패치 디스크리미네이터
섹션 제목: “SHOWSTMT_TYPE — 디스패치 디스크리미네이터”전체 시스템은 storage_common.h 에 선언된 평탄한 enum 하나를
중심으로 회전한다. 멤버에는 SHOWSTMT_ACCESS_STATUS,
SHOWSTMT_VOLUME_HEADER, SHOWSTMT_ACTIVE_LOG_HEADER,
SHOWSTMT_ARCHIVE_LOG_HEADER, SHOWSTMT_SLOTTED_PAGE_HEADER/
_SLOTS, SHOWSTMT_HEAP_HEADER/SHOWSTMT_ALL_HEAP_HEADER,
SHOWSTMT_HEAP_CAPACITY/SHOWSTMT_ALL_HEAP_CAPACITY,
SHOWSTMT_INDEX_HEADER/SHOWSTMT_INDEX_CAPACITY 와
_ALL_INDEXES_* 변형들, SHOWSTMT_GLOBAL_CRITICAL_SECTIONS,
SHOWSTMT_JOB_QUEUES, SHOWSTMT_TIMEZONES/_FULL_TIMEZONES,
SHOWSTMT_TRAN_TABLES, SHOWSTMT_THREADS,
SHOWSTMT_PAGE_BUFFER_STATUS 가 포함되며, 끝에는 SHOWSTMT_END
가 닫는다.
SHOWSTMT_END 가 디스패치 테이블의 크기를 결정한다 — 새 SHOW를
추가한다는 것은 SHOWSTMT_END 앞에 enum 값을 하나 더하는 일이며,
그러면 show_meta.c 와 show_scan.c 의 디스패치 배열이 자동으로
한 칸 더 받는다. 이 enum이 show_scan.c 로컬 헤더가 아니라
storage_common.h 에 사는 이유는 — 파서, executor, 서브시스템별
코드가 순환 include 없이 같은 식별자를 공유하도록 하기 위함이다.
storage_common.h 는 CUBRID include DAG의 가장 아래쪽에 위치한다.
문법은 SQL 단어를 enum 값에 매핑하는 얇은 지도일 뿐이다. 어떤
SHOW 종류는 인자를 받고 어떤 것은 받지 않으며, LIKE/WHERE를
허용하는 부분 집합이 따로 있기 때문에, 문법은 이름들을
비단말(nonterminal) 패밀리로 갈라 둔다 — show_type,
show_type_of_like, show_type_of_where, show_type_arg1,
show_type_arg1_opt, show_type_arg_named, show_type_id,
show_type_id_dot_id. SHOW 프로덕션은 매칭되는 액션들로 부채살처럼
펼쳐지지만, 모든 액션은 단일 호출 — pt_make_query_showstmt (this_parser, type, args, like_where_syntax, like_or_where_expr) —
로 수렴한다. 이 함수가 곧 재작성 지점이다.
파서 측: pt_make_query_showstmt 가 SHOW를 SELECT로 재작성
섹션 제목: “파서 측: pt_make_query_showstmt 가 SHOW를 SELECT로 재작성”이 재작성이야말로 엔진 나머지가 SHOW 명령을 보통의 질의로 다룰 수
있게 만들어 주는 구조적 지렛대다. SHOW를 옵티마이저까지 특별한
구문 종류로 들고 가는 대신, 파서는 FROM 절이 PT_SHOWSTMT
노드를 백엔드로 갖는 derived table인 PT_SELECT 를 조작해 만든다.
pt_make_query_showstmt (in parser_support.c)는 정해진 모양을
따른다 — meta = showstmt_get_metadata (type) 로 메타데이터를
조회하고, meta->only_for_dba 를 검사하여 필요하면
ER_AU_DBA_ONLY 로 거절하고, PT_TYPE_STAR 프로젝션을 가진
PT_SELECT 를 할당하고, show_type 과 (pt_resolve_showstmt_args_named/
_unnamed 로) 해석된 show_args 를 들고 있는 PT_SHOWSTMT
노드를 할당하고, derived_table_type = PT_IS_SHOWSTMT 로 derived
table에 부착하고, LIKE/WHERE 단축형이 있다면 SELECT의 WHERE에
접고, meta->orderby 로부터 ORDER BY 를 합성한다.
세 가지 구조적 선택이 여기서 드러난다. 권한은 파싱 시점에 검사된다
— only_for_dba 플래그가 켜진 SHOW 종류는 어떤 plan이 만들어지기도
전에 ER_AU_DBA_ONLY 로 실패한다. 이는 “문법 수준의 오류는 일찍
실패시킨다”는 CUBRID의 일반적 입장과 일치한다. 인자 문법은
데이터 주도(data-driven)이다 — show_meta.c 의 meta->args
테이블이 종류가 위치(positional) 인자를 기대하는지 명명(named)
인자를 기대하는지를 선언하고, resolver가 그에 맞는 헬퍼를 고른다.
기본 정렬은 메타데이터에 부착되어 있다 — 각 메타데이터 블록은
orderby 배열을 들고 다닐 수 있고, 재작성은 그것으로부터
ORDER BY 절을 합성한다. 그래서 예컨대 SHOW HEAP HEADER OF c
는 — 아래쪽 생성기의 열거 순서가 구현 의존이라 하더라도 — 항상
같은 논리적 순서로 행을 돌려준다.
이 재작성을 구동하는 메타데이터는 show_meta.c 에 종류별 함수로
산다. 그 함수는 정적 SHOWSTMT_METADATA 를 반환하는데, 이
struct는 show 종류, only_for_dba, alias_print,
컬럼 이름/타입 테이블 cols[], 선택적 orderby[], 선택적
args[](positional 또는 named), 선택적 semantic_check_func,
그리고 합성된 showstmt_attrs (DB_ATTRIBUTE *)를 들고 있다.
부팅 시 한 번 호출되는 showstmt_metadata_init 가 모든 SHOW
종류를 순회하면서 cols[] 선언을 진짜 DB_ATTRIBUTE * 체인으로
바꿔 둔다. 그래야 이름 해석(name resolution)이 derived table을
실제 컬럼을 가진 것처럼 다룰 수 있다. 초기화 이후의 파서 관점에서
보면, SHOW THREADS 는
SELECT * FROM SOME_RELATION_WITH_26_COLUMNS 와 완전히 같다 —
컬럼 이름 lookup, 타입 검사, 술어 푸시다운이 모두 그냥 동작한다.
Executor 측: S_SHOWSTMT_SCAN 과 액세스 메서드 디스패치
섹션 제목: “Executor 측: S_SHOWSTMT_SCAN 과 액세스 메서드 디스패치”XASL 생성은 PT_SHOWSTMT 를 백엔드로 갖는 FROM 절을 access spec —
access = TARGET_SHOWSTMT 이고, show_type 과 해석된 arg_list
를 들고 있는 — 으로 번역한다. qexec_open_scan 은 target type 위에서
switch하여 scan_open_showstmt_scan (thread_p, s_id, grouped, curr_spec->single_fetch, curr_spec->s_dbval, val_list, vd, curr_spec->where_pred, curr_spec->s.showstmt_node.show_type, curr_spec->s.showstmt_node.arg_list) 를 호출한다.
scan_open_showstmt_scan 은 union 분기 scan_id->s.stsid
(SHOWSTMT_SCAN_ID)를 채운다 — scan_id->type = S_SHOWSTMT_SCAN 으로 설정하고, readonly_scan = true,
fixed = true 로 scan_init_scan_id 를 돌리고, 모든 인자
DB_VALUE 를 regu_variable_list 로부터 fetch_peek_dbval
로 들여다 보고, out_values[] 를 val_list 의 슬롯에 가리키게
하고, show_type 을 struct에 복사하고, cursor = 0 과
ctx = NULL 로 초기화하고(이후 start_func 가 ctx 를 채운다),
WHERE 술어로 scan_init_scan_pred 를 돈다.
SHOWSTMT_SCAN_ID 자체는 의도적으로 최소한이다 — show_type
(디스패치 디스크리미네이터), arg_values/arg_cnt (open 시점에
peek된), out_values/out_cnt (val_list 슬롯을 가리킴),
cursor (0에서 시작), void *ctx (SHOW 종류별 불투명 스냅샷),
그리고 scan_pred (WHERE 필터). 단일 void *ctx 가 핵심
다형성 지점이다 — 모든 SHOW 종류가 자기 사적 스냅샷을 그
포인터 뒤에 저장하고, 자기 자신의 next/end 함수만이 그 모양을
안다. scan manager는 그것을 결코 역참조하지 않는다.
scan_manager.c 의 네 개의 switch (scan_id->type) 분기 —
scan_start_scan, scan_next_scan_local, scan_end_scan,
scan_close_scan 안의 case S_SHOWSTMT_SCAN: — 가 SHOW 스캔을
show_scan.c 로 라우팅한다. end (스냅샷을 떨군다)와 close
(포인터 배열들을 떨군다)의 분리는 힙·list 스캔의 teardown과 평행하다
— CUBRID의 모든 스캔 종류는 iterator 상태 해제와 request 상태
해제를 분리한다.
show_scan.c: 함수 포인터 디스패치 테이블
섹션 제목: “show_scan.c: 함수 포인터 디스패치 테이블”show_scan.c 안에서, 세 개의 함수 포인터 typedef와 SHOWSTMT_TYPE
으로 인덱싱되는 평행한 배열이 디스패치의 전부다.
START_SCAN_FUNC 는 (THREAD_ENTRY*, int show_type, DB_VALUE arg_values, int arg_cnt, void ctx) 를 받고 에러 코드를
반환한다. NEXT_SCAN_FUNC 는 (THREAD_ENTRY*, int cursor, DB_VALUE **out_values, int out_cnt, void *ctx) 를 받고
SCAN_CODE 를 반환한다. END_SCAN_FUNC 는 (THREAD_ENTRY*, void **ctx) 를 받고 에러 코드를 반환한다. SHOW_REQUEST struct는
(show_type, start_func, next_func, end_func) 를 한 묶음으로
들고, static SHOW_REQUEST show_Requests[SHOWSTMT_END] 가
디스패치 배열이다.
showstmt_scan_init 가 서버 부팅 시점에 이 테이블을 채운다.
모양은 균일하다 — 모든 항목이 네 필드를 모두 설정한다. 예컨대
SHOWSTMT_VOLUME_HEADER 는
disk_volume_header_{start,next,end}_scan 을 등록하고,
SHOWSTMT_GLOBAL_CRITICAL_SECTIONS 는 csect_start_scan 과
범용 showstmt_array_next_scan/showstmt_array_end_scan 을 함께
등록하며, SHOWSTMT_THREADS 는 thread_start_scan 과 같은
범용 배열 워커/해제기를 등록한다.
한눈에 보이는 패턴이 있다. 어떤 SHOW 종류는 튜플을 스트리밍한다
— disk_volume_header_*, heap_header_capacity_*,
btree_index_*, log_active_log_header_* — 즉 그 start_func
는 작은 사적 컨텍스트를 만들고, next_func 는 아래쪽 객체를
순회하며 요청 시점에 튜플을 합성한다. 다른 종류는 앞에서
배열을 머터리얼라이즈한다 — csect_start_scan,
thread_start_scan, tz_timezones_start_scan, pgbuf_start_scan
— 즉 start_func 가 SHOWSTMT_ARRAY_CONTEXT 를 할당해 한 번에
행 하나씩 다 채워 두고, 범용 showstmt_array_next_scan 이 각
next 호출에 행 하나씩 다시 내보낸다. 이 선택은 종류별이며,
본질적으로 일관성 정책(consistency policy) 이다 — 배열
머터리얼라이제이션은 스캔 open 시점의 스냅샷 을 캡처하고,
스트리밍은 스캔이 진행되는 동안 변하는 상태를 그대로 비춘다.
공유 배열 워킹(array-walking) 인프라
섹션 제목: “공유 배열 워킹(array-walking) 인프라”배열 경로는 show_scan.c 에서 가장 많이 재사용되는 기계다.
컨텍스트는 작다 — DB_VALUE **tuples, num_cols, num_used,
num_total. showstmt_alloc_tuple_in_context 는 가득 찼을 때
tuples 를 1.5× 기하 재할당(amortised O(1) 삽입)하고,
num_cols 만큼의 DB_VALUE 행을 할당한 뒤, 모든 셀을
db_make_null 로 초기화한다. 그래야 부분 채움 후 에러로 중단되는
경로에서도 pr_clear_value 로 모두 안전하게 비울 수 있다.
tuples 와 행별 vals 는 모두 db_private_* 할당이라 connection의
사적 힙 위에 살고, 에러 시 그 힙과 함께 사라진다.
범용 워커 showstmt_array_next_scan 은 6줄 짜리 루프다 — 커서를
num_used 와 비교(범위 밖이면 S_END 반환)하고, 모든 컬럼을
ctx->tuples[cursor][i] 에서 out_values[i] 로 pr_clone_value
한 뒤, S_SUCCESS 를 반환한다. 빌리지(borrow) 않고 깊은
복제(deep clone) 하는 이유는 — showstmt_next_scan 이 행 사이에
pr_clear_value (stsidp->out_values[i]) 를 안전하게 호출할 수
있게 하기 위함이다. 모든 행은 executor의 val_list 슬롯으로의
fresh deep copy다.
스트리밍 생성기 — 로그 헤더 예시
섹션 제목: “스트리밍 생성기 — 로그 헤더 예시”소스가 단일 레코드인 SHOW 종류(예: log_Gl.hdr 위의
SHOWSTMT_ACTIVE_LOG_HEADER)의 경우, start 함수가 적절한 latch
아래에서 스냅샷 한 장을 캡처해 사적 컨텍스트에 저장하고, next
함수가 그것을 요청 시점에 디코딩한다. log_active_log_header_start_scan
은 반복적으로 등장하는 두 모드(two-mode) 인자 패턴을 보여 준다 —
arg_values[0] 가 DB_TYPE_NULL 이면 LOG_CS_ENTER_READ_MODE/
LOG_CS_EXIT 아래에서 log_Gl.hdr 를 읽고, 경로(path)이면
fileio_open 으로 파일을 열어 첫 페이지를 LOG_PAGE 로 읽고,
거기 박혀 있는 LOG_HEADER 를 복사한 뒤 magic과 db_creation 을
실행 중인 DB와 비교 검증한다(아니면 ER_IO_MOUNT_FAIL).
대응하는 next_func 는 단일 튜플 emitter다 — cursor 0이면
ctx->header 에서 한 행을 디코드하고, cursor ≥ 1이면 S_END.
커서 한정(bounded) emission이 디스패처가 의지하는 계약이다.
이것이 SHOWSTMT_TYPE 을 행이 몇 개냐 와 직교하게(orthogonal)
만들어 준다.
배열 스타일 두 가지 예시 — 크리티컬 섹션과 스레드
섹션 제목: “배열 스타일 두 가지 예시 — 크리티컬 섹션과 스레드”csect_start_scan (in critical_section.c)은 고정 카디널리티의
정석적 케이스다 — 12개 컬럼, CRITICAL_SECTION_COUNT 행, 모든
csectgl_Critical_sections[i] 필드를 한 DB_VALUE 행으로 복사한다.
튜플별 latching은 없다 — SHOW CRITICAL SECTIONS 는 진단 이고,
정렬된(aligned) 단일 int의 torn read 정도는 받아들인다. 모양은
이렇다 — db_make_int (&vals[idx++], csect->cs_index),
db_make_string (&vals[idx++], csect_name (csect)), 그리고
csect->rwlock 위의 짧은 switch가 %d readers / 1 writer /
none 을 렌더링하고, 이어서 대기 중인 reader/writer 수와
소유 스레드 정보를 채운다.
thread_start_scan (show_scan.c 자체에 있고, SERVER_MODE 로
gating됨)은 서브시스템 고유의 iterator와의 합성을 보여 준다.
고정 루프 대신, thread_num_total_threads () × THREAD_SCAN_COLUMN_COUNT
크기로 SHOWSTMT_ARRAY_CONTEXT 를 할당하고,
thread_get_manager ()->map_entries (thread_scan_mapfunc, thread_p, ctx, error) 로 디스패치한다. 매퍼 thread_scan_mapfunc 는
TS_DEAD 가 아닌 스레드마다 한 튜플을 만들어
THREAD_SCAN_COLUMN_COUNT = 26 개의 컬럼을 채운다. 락에 블록된
스레드의 경우 lockwait 컬럼은 thrd->lockwait 에서 채워지고,
그 외에는 DB_NULL이 된다. 이 행별 분기가 SHOW THREADS 를 단순히
부풀린 top 이상으로 만든다 — 락 매니저, connection 테이블,
워커 풀의 상태가 단일한 관계형 view로 통합된다.
종료(End of life)
섹션 제목: “종료(End of life)”showstmt_end_scan 은 종류별 end_func 를 호출한다. 배열 스타일
종류는 showstmt_array_end_scan (스냅샷을
showstmt_free_array_context 로 해제하고 포인터를 NULL로)을
사용하고, 스트리밍 스타일 종류는 자기 컨텍스트 타입을 직접
해제한다. _end_scan 이후 scan_close_scan 이 스캔별
arg_values 와 out_values 배열을 해제한다. 이 두 단계 teardown은
힙·list 스캔이 iterator 상태 해제와 request 상태 해제를 분리하는
방식과 정확히 평행하다 — 매니저는 잎이 무엇이든 균일하게 머문다.
합성 소스 주변의 술어 평가
섹션 제목: “합성 소스 주변의 술어 평가”scan_next_showstmt_scan (in scan_manager.c)는 종류별 next_func
를, 다른 스캔 종류가 쓰는 것과 같은 술어 평가 루프로 감싼다 —
showstmt_next_scan 을 S_SUCCESS 가 돌아올 때까지 돌리고,
stsidp->scan_pred.pr_eval_fnc (pred_expr, vd) 를 평가하고,
qualification == QPROC_QUALIFIED 일 때 V_FALSE/V_UNKNOWN 이면
continue, 성공이면 반환한다. 이 술어는 파서가 푸시다운 한 그
술어다. SHOW 명령은 힙 스캔과 정확히 같은 방식으로 컴포지션된다 —
WHERE 가 행을 거르고, ORDER BY 는 일반 정렬 연산자를 통과해
정렬되며, _db_class 와의 JOIN 은 사용자 클래스 메타데이터에
그것을 붙여 주고, 집계는 요약 행을 만든다. 합성 소스는 오직
잎 자리의 대체물일 뿐이다.
왜 void *ctx 인가, 타입 union이 아닌가
섹션 제목: “왜 void *ctx 인가, 타입 union이 아닌가”ctx 를 (SCAN_ID::s 같은) 또 하나의 태그된 union이 아닌
void * 로 두는 것은 의도적이다. 상태를 노출하는 모든 서브시스템이
자기 컨텍스트 모양을 소유한다(ACTIVE_LOG_HEADER_SCAN_CTX,
disk-manager의 volume-header 컨텍스트, SHOWSTMT_ARRAY_CONTEXT,
heap-capacity 컨텍스트 …). 그리고 show_scan.c 는 그중 어느
것도 알 필요가 없어야 한다. 타입 union을 도입하면 show_scan.c
가 모든 서브시스템 헤더에 의존하게 되어 — 프로젝트의 의존성 방향이
뒤집힌다(서브시스템이 생성기를 show_Requests 로 밀어 넣는다.
show_scan.c 가 그들의 내부를 끌어오지 않는다). void * 는
타입 정보를 잃기에 구조적으로 올바른 자리다.
운영자가 물을 수 있는 것
섹션 제목: “운영자가 물을 수 있는 것”재작성이 일반 SELECT 를 만들어 내기 때문에, 운영자의 어휘는
SQL이다 — SHOW THREADS WHERE Status = 'TS_WAIT',
SHOW CRITICAL SECTIONS, SHOW VOLUME HEADER OF 0,
SHOW LOG HEADER [OF '/path/to/active_log'],
SHOW HEAP HEADER OF c, SHOW ALL HEAP CAPACITY,
SHOW INDEX HEADER OF c.idx, SHOW PAGE BUFFER STATUS,
SHOW TRAN TABLES, SHOW TIMEZONES. 어떤 이름이 어느 비단말에
속하느냐가 그 이름이 SHOW 직후에 받을 수 있는 필터를 결정한다 —
show_type_of_like/show_type_of_where 는 LIKE/WHERE 를
구문적 단축형으로 받는 이름을 열거한다. 재작성은 그 단축형을
SELECT의 WHERE에 접고, 둘러싼 SELECT … WHERE 자체는 어느 이름에도
물론 동작한다.
새 SHOW를 추가하는 전체 시퀀스
섹션 제목: “새 SHOW를 추가하는 전체 시퀀스”새 SHOW(예: SHOW REPLICATION STATUS)를 추가하려면 정확히 다섯
곳을 만진다 — (1) storage_common.h 에서 SHOWSTMT_END 앞에
SHOWSTMT_REPLICATION_STATUS 를 append하고, (2)
metadata_of_replication_status 가 SHOWSTMT_METADATA 를 반환하게
작성하고 showstmt_metadata_init 에 등록하고, (3) 인자 모양에
맞는 show_type* 비단말에 REPLICATION STATUS → SHOWSTMT_REPLICATION_STATUS 분기를 추가하고, (4) 소유 서브시스템에
repl_status_{start,next,end}_scan 을 작성하고, (5)
showstmt_scan_init 에서 show_Requests[SHOWSTMT_REPLICATION_STATUS]
를 채운다. XASL 변경 없음, 옵티마이저 변경 없음, only_for_dba 외에
새 권한 경로 없음. 전체 파이프라인이 SELECT-로-재작성 트릭으로
컴포지션된다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”아래의 심볼들은 안정된 앵커다. 라인 번호는 섹션 끝의 위치 힌트 표로 들어간다.
공개 디스패치 (executor → show_scan). showstmt_scan_init 이
서버 부팅 시 한 번 show_Requests[] 를 채운다(show_scan_Inited
로 멱등). showstmt_start_scan/_next_scan/_end_scan 이
종류별 함수 포인터를 lookup해 호출한다. _next_scan 은 매 호출
앞에서 이전 out_values 를 비운다. scan_open_showstmt_scan 과
scan_next_showstmt_scan (둘 다 scan_manager.c)이 executor와
show_scan.c 사이에 앉아 SHOWSTMT_SCAN_ID 와 WHERE 술어를 들고 있다.
함수 포인터 타입과 디스패치 테이블. START_SCAN_FUNC,
NEXT_SCAN_FUNC, END_SCAN_FUNC (typedef), SHOW_REQUEST (한
행), show_Requests[SHOWSTMT_END] (파일 스코프 디스패치 배열),
디스크리미네이터로서 storage_common.h 의 SHOWSTMT_TYPE.
공유 배열 워킹 인프라. SHOWSTMT_ARRAY_CONTEXT,
showstmt_alloc_array_context, showstmt_alloc_tuple_in_context
(기하 재할당), showstmt_free_array_context,
showstmt_array_next_scan (범용 워커), showstmt_array_end_scan
(범용 teardown).
서브시스템별 종류별 생성기. disk_volume_header_*
(VOL_HEADER), log_active_log_header_* 와
log_archive_log_header_* (LOG_HEADER, 오프라인 파일에서 읽을
수도 있음), spage_header_* 와 spage_slots_* (slotted page),
heap_header_capacity_start_scan 가 heap_header_next_scan/
heap_capacity_next_scan 에 의해 공유, btree_index_* (B+Tree
헤더/용량, 단일 또는 모든 인덱스), css_user_access_status_start_scan,
css_job_queues_start_scan, tz_timezones_start_scan/
tz_full_timezones_start_scan, logtb_descriptors_start_scan
(트랜잭션 테이블), thread_start_scan 과 매퍼
thread_scan_mapfunc (26 컬럼), critical_section.c 의
csect_start_scan (12 컬럼), pgbuf_start_scan (페이지 버퍼
LRU/dirty 카운터).
파서 측 facade. pt_make_query_showstmt (재작성),
showstmt_metadata_init/_final (정적 cols[] 로부터
DB_ATTRIBUTE * 합성), showstmt_get_metadata/_attributes,
metadata_of_<name> 패밀리(SHOW 종류마다 함수 하나),
pt_check_show_index/pt_check_show_heap 의미 검사 hook.
문법 진입점 (csql_grammar.y). show_type (인자 없음),
show_type_of_like 와 show_type_of_where (LIKE/WHERE 단축형
허용), show_type_arg1/_arg1_opt (위치 인자),
show_type_arg_named (명명 인자), show_type_id/show_type_id_dot_id
(OF class_name [DOT identifier]).
위치 힌트 (2026-05-01 기준)
섹션 제목: “위치 힌트 (2026-05-01 기준)”| 심볼 | 파일 | 라인 |
|---|---|---|
SHOWSTMT_TYPE enum / SHOWSTMT_END | src/storage/storage_common.h | 933 / 961 |
SHOWSTMT_ARRAY_CONTEXT | src/query/show_scan.h | 32 |
START_SCAN_FUNC/NEXT_SCAN_FUNC typedef | src/query/show_scan.c | 66 |
SHOW_REQUEST / show_Requests[] | src/query/show_scan.c | 72 / 94 |
THREAD_SCAN_COLUMN_COUNT | src/query/show_scan.c | 81 |
showstmt_scan_init | src/query/show_scan.c | 102 |
showstmt_next_scan/_start_scan/_end_scan | src/query/show_scan.c | 252 / 285 / 311 |
showstmt_alloc_array_context/_free_/_alloc_tuple_ | src/query/show_scan.c | 338 / 375 / 404 |
showstmt_array_next_scan/_end_scan | src/query/show_scan.c | 449 / 479 |
thread_scan_mapfunc / thread_start_scan | src/query/show_scan.c | 500 / 820 |
SHOWSTMT_SCAN_ID / S_SHOWSTMT_SCAN | src/query/scan_manager.h | 302 / 86 |
scan_open_showstmt_scan | src/query/scan_manager.c | 3849 |
S_SHOWSTMT_SCAN start/end/close/next 디스패치 | src/query/scan_manager.c | 4380 / 4845 / 5056 / 5261 |
scan_next_showstmt_scan | src/query/scan_manager.c | 6820 |
qexec_open_scan 의 TARGET_SHOWSTMT 분기 | src/query/query_executor.c | 7553 |
SHOWSTMT_METADATA | src/parser/show_meta.h | 65 |
showstmt_metadata_init / showstmt_get_metadata | src/parser/show_meta.c | 946 / 743 |
pt_make_query_showstmt | src/parser/parser_support.c | 6828 |
| SHOW 프로덕션 패밀리 | src/parser/csql_grammar.y | 7222 |
show_type 비단말 | src/parser/csql_grammar.y | 7335 |
show_type_arg1 / _arg1_opt | src/parser/csql_grammar.y | 7416 |
show_type_id / _id_dot_id | src/parser/csql_grammar.y | 7445 |
csect_start_scan | src/thread/critical_section.c | 1625 |
log_active_log_header_start_scan | src/transaction/log_manager.c | 9208 |
라인 번호는 다음 reformat 또는 삽입에서 흘러갈(drift) 것이다. 정식 앵커는 심볼 이름이다.
교차 검증 노트
섹션 제목: “교차 검증 노트”SHOWSTMT_TYPE은storage_common.h에 산다.show_scan.c로컬 헤더나show_meta.h가 아니다. 의도적이다 — 파서, executor, 서브시스템 생성기들이 순환 include 없이 enum을 공유한다. 다만 엄밀히 스토리지 라고 보기 어려운 표면 일부가storage_common.h로 흘러 들어간 것은 사실이다. 새 SHOW 종류는 거기 추가한다.SHOWSTMT_NULL = SHOWSTMT_START = 0은 자리표시자다. 여기에는 메타데이터도 디스패치 항목도 등록되지 않는다. 이를 넘기는 호출자는showstmt_next_scan의next_func == NULL방어 분기에 부딪혀 즉시S_END를 받는다.thread_start_scan의 선언만show_scan.h에 있다. 다른 모든*_start_scan은 자기 서브시스템 헤더에 선언되어 있는데, 유독 이것만 다르다. 역사적 이유는thread_scan_mapfunc가show_scan.c안에 살기 때문이다(SERVER_MODE로 gating). 옮기려면thread_get_manager ()->map_entries를 더 넓게 export하거나 매퍼를 복제해야 한다.THREAD_SCAN_COLUMN_COUNT = 26이 두 곳에 중복되어 있다.show_scan.c와 파서 측metadata_of_threads. 매퍼 끝의assert (idx == THREAD_SCAN_COLUMN_COUNT)가 디버그 빌드에서 둘이 어긋날 때 발화한다 — 컬럼을 추가할 때마다 둘 다 함께 고친다.csect_start_scan은 각 CS를 읽으면서 latch를 잡지 않는다. 진단 목적이라 정렬된 단일 int의 torn read가 받아들여진다. 복합(composite) 필드 — 그 갱신이 비원자적인 — 로 행을 확장한다면 이 결정을 다시 검토해야 한다.only_for_dba검사는 파싱 시점에만 일어난다. 일단 SELECT가 만들어지고 디스패치되면, 두 번째 권한 게이트는 발화하지 않는다.pt_make_query_showstmt를 우회하는 적대적 클라이언트가 원리적으로는TARGET_SHOWSTMT인 XASL을 직접 보내 데이터에 도달할 수도 있다. 파서 측 검사를 유일한 게이트로 간주하라.- WHERE 술어 평가는 생성기가 행을 emit한 이후 에 돌아간다.
SHOW THREADS WHERE Status = 'TS_WAIT'도 여전히 모든 스레드를 순회하고 행마다 26개의DB_VALUE를 할당한다. 오늘은 생성기로의 술어 푸시다운이 없다. - 인자 peek은
scan_open_showstmt_scan시점에 한 번 일어난다. 문법이 인자를 리터럴/식별자로 제한하기 때문에 관찰 가능한 부수 효과는 없다. 문법을 넓히는 사람은fetch_peek_dbval사용을 다시 검토해야 한다. _end_scan은ctx를 해제하고,scan_close_scan은arg_values/out_values를 해제한다._end_scan을 건너뛰면ctx가 누수된다.qexec_clear_*의 새 abort 경로는 이 짝짓기 를 보존해야 한다.
미해결 질문
섹션 제목: “미해결 질문”- 생성기로의 술어 푸시다운. 많은 객체를 도는 스트리밍 생성기
(
heap_capacity_next_scan, B+Tree 변형들)가 WHERE를 본다면 값싼 술어에서 short-circuit할 수 있을 것이다. 운영자 경험 향상이 엔지니어링 비용을 정당화할까, 아니면 “항상 풀 스캔하고 매니저 계층에서 거른다”는 현재의 단순함이 더 나을까? - 플러그어블한 SHOW 종류. 지금은 모든 SHOW 종류가
SHOWSTMT_TYPE+showstmt_metadata_init+showstmt_scan_init에 하드코드된다. 옵셔널 서브시스템(CDC, 복제, 브로커 통계)이 동적으로 자기 자신을 등록할 수 있어야 할까? - SHOW 종류별 스냅샷 격리. 배열 스타일은 스냅샷을 본다.
스트리밍은 행을 가로질러 부분 상태를 본다. 이 차이는 어떤
next_func가 등록되었느냐에 따라 암묵적으로 결정된다 — 종류별로 명시적으로 문서화되어야 할까? - show 스캔의 통계 출력.
scan_print_stats_*은 generator 시간과 predicate 시간 사이의 분해 없이 일반show통계 한 줄만 낸다. 더 미세한 timing 이 큰 카탈로그에서SHOW ALL HEAP CAPACITY를 진단하는 데 도움이 될 것이다. - 컬럼 버저닝. 기존 SHOW 종류에 컬럼을 추가하면 ordinal 위치가 조용히 밀린다. SHOW 종류가 버전을 들고 다녀야 하나, 아니면 운영자가 명명된 컬럼 프로젝션에 의존하는 것을 전제로 두어야 하나?
코드만 바탕으로 한 문서 — raw/ 분석은 소비되지 않았다. 직접
출처는 다음과 같다 — src/query/show_scan.{c,h} (디스패치 테이블,
배열 워킹, thread 매퍼), src/query/scan_manager.{c,h}
(S_SHOWSTMT_SCAN 디스패치, SHOWSTMT_SCAN_ID, 술어 루프),
src/query/query_executor.c (TARGET_SHOWSTMT 분기),
src/parser/show_meta.{c,h} (메타데이터, 의미 검사),
src/parser/parser_support.c (pt_make_query_showstmt),
src/parser/csql_grammar.y (SHOW 프로덕션),
src/storage/storage_common.h (SHOWSTMT_TYPE enum),
src/transaction/log_manager.c 와 src/thread/critical_section.c
(생성기 예시).
텍스트북 프레이밍은 Selinger et al. (System R, 1979), Graefe
(Volcano, TKDE 1994; Query Evaluation Techniques, ACM CS 1993),
Hellerstein·Stonebraker (Anatomy of a Database System, Red Book
4장), Petrov (Database Internals 1, 7장)에 의지한다. 엔진 간
비교는 PostgreSQL pg_stat_* 와 SRF, MySQL INFORMATION_SCHEMA /
performance_schema, Oracle V$/X$, SQL Server sys.dm_*
DMV의 공개 문서를 참조한다.