콘텐츠로 이동

(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 XSELECT * FROM (가상-소스-X) 로 재작성하고, 가상 소스를 액세스 메서드 카탈로그의 또 한 항목(S_HEAP_SCAN·S_LIST_SCAN 옆에 S_SHOWSTMT_SCAN)으로 인코딩하며, 종류별 start/next/end 함수가 합성을 책임지게 한다. show_scan.c 가 이 셋을 한데 묶는 디스패치 테이블이고, show_meta.cpt_make_query_showstmt 가 재작성을 수행하는 파서 측 facade다.

네 개의 주요 엔진이 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 을 함께 수정한다는 뜻이다.

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.cshow_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.cmeta->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 THREADSSELECT * 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 = truescan_init_scan_id 를 돌리고, 모든 인자 DB_VALUEregu_variable_list 로부터 fetch_peek_dbval 로 들여다 보고, out_values[]val_list 의 슬롯에 가리키게 하고, show_type 을 struct에 복사하고, cursor = 0ctx = 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_HEADERdisk_volume_header_{start,next,end}_scan 을 등록하고, SHOWSTMT_GLOBAL_CRITICAL_SECTIONScsect_start_scan 과 범용 showstmt_array_next_scan/showstmt_array_end_scan 을 함께 등록하며, SHOWSTMT_THREADSthread_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_funcSHOWSTMT_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_mapfuncTS_DEAD 가 아닌 스레드마다 한 튜플을 만들어 THREAD_SCAN_COLUMN_COUNT = 26 개의 컬럼을 채운다. 락에 블록된 스레드의 경우 lockwait 컬럼은 thrd->lockwait 에서 채워지고, 그 외에는 DB_NULL이 된다. 이 행별 분기가 SHOW THREADS 를 단순히 부풀린 top 이상으로 만든다 — 락 매니저, connection 테이블, 워커 풀의 상태가 단일한 관계형 view로 통합된다.

showstmt_end_scan 은 종류별 end_func 를 호출한다. 배열 스타일 종류는 showstmt_array_end_scan (스냅샷을 showstmt_free_array_context 로 해제하고 포인터를 NULL로)을 사용하고, 스트리밍 스타일 종류는 자기 컨텍스트 타입을 직접 해제한다. _end_scan 이후 scan_close_scan 이 스캔별 arg_valuesout_values 배열을 해제한다. 이 두 단계 teardown은 힙·list 스캔이 iterator 상태 해제와 request 상태 해제를 분리하는 방식과 정확히 평행하다 — 매니저는 잎이 무엇이든 균일하게 머문다.

scan_next_showstmt_scan (in scan_manager.c)는 종류별 next_func 를, 다른 스캔 종류가 쓰는 것과 같은 술어 평가 루프로 감싼다 — showstmt_next_scanS_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_whereLIKE/WHERE 를 구문적 단축형으로 받는 이름을 열거한다. 재작성은 그 단축형을 SELECT의 WHERE에 접고, 둘러싼 SELECT … WHERE 자체는 어느 이름에도 물론 동작한다.

새 SHOW(예: SHOW REPLICATION STATUS)를 추가하려면 정확히 다섯 곳을 만진다 — (1) storage_common.h 에서 SHOWSTMT_END 앞에 SHOWSTMT_REPLICATION_STATUS 를 append하고, (2) metadata_of_replication_statusSHOWSTMT_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_scanscan_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.hSHOWSTMT_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_scanheap_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.ccsect_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_likeshow_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]).

심볼파일라인
SHOWSTMT_TYPE enum / SHOWSTMT_ENDsrc/storage/storage_common.h933 / 961
SHOWSTMT_ARRAY_CONTEXTsrc/query/show_scan.h32
START_SCAN_FUNC/NEXT_SCAN_FUNC typedefsrc/query/show_scan.c66
SHOW_REQUEST / show_Requests[]src/query/show_scan.c72 / 94
THREAD_SCAN_COLUMN_COUNTsrc/query/show_scan.c81
showstmt_scan_initsrc/query/show_scan.c102
showstmt_next_scan/_start_scan/_end_scansrc/query/show_scan.c252 / 285 / 311
showstmt_alloc_array_context/_free_/_alloc_tuple_src/query/show_scan.c338 / 375 / 404
showstmt_array_next_scan/_end_scansrc/query/show_scan.c449 / 479
thread_scan_mapfunc / thread_start_scansrc/query/show_scan.c500 / 820
SHOWSTMT_SCAN_ID / S_SHOWSTMT_SCANsrc/query/scan_manager.h302 / 86
scan_open_showstmt_scansrc/query/scan_manager.c3849
S_SHOWSTMT_SCAN start/end/close/next 디스패치src/query/scan_manager.c4380 / 4845 / 5056 / 5261
scan_next_showstmt_scansrc/query/scan_manager.c6820
qexec_open_scanTARGET_SHOWSTMT 분기src/query/query_executor.c7553
SHOWSTMT_METADATAsrc/parser/show_meta.h65
showstmt_metadata_init / showstmt_get_metadatasrc/parser/show_meta.c946 / 743
pt_make_query_showstmtsrc/parser/parser_support.c6828
SHOW 프로덕션 패밀리src/parser/csql_grammar.y7222
show_type 비단말src/parser/csql_grammar.y7335
show_type_arg1 / _arg1_optsrc/parser/csql_grammar.y7416
show_type_id / _id_dot_idsrc/parser/csql_grammar.y7445
csect_start_scansrc/thread/critical_section.c1625
log_active_log_header_start_scansrc/transaction/log_manager.c9208

라인 번호는 다음 reformat 또는 삽입에서 흘러갈(drift) 것이다. 정식 앵커는 심볼 이름이다.

  • SHOWSTMT_TYPEstorage_common.h 에 산다. show_scan.c 로컬 헤더나 show_meta.h 가 아니다. 의도적이다 — 파서, executor, 서브시스템 생성기들이 순환 include 없이 enum을 공유한다. 다만 엄밀히 스토리지 라고 보기 어려운 표면 일부가 storage_common.h 로 흘러 들어간 것은 사실이다. 새 SHOW 종류는 거기 추가한다.
  • SHOWSTMT_NULL = SHOWSTMT_START = 0 은 자리표시자다. 여기에는 메타데이터도 디스패치 항목도 등록되지 않는다. 이를 넘기는 호출자는 showstmt_next_scannext_func == NULL 방어 분기에 부딪혀 즉시 S_END 를 받는다.
  • thread_start_scan 의 선언만 show_scan.h 에 있다. 다른 모든 *_start_scan 은 자기 서브시스템 헤더에 선언되어 있는데, 유독 이것만 다르다. 역사적 이유는 thread_scan_mapfuncshow_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_scanctx 를 해제하고, scan_close_scanarg_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.csrc/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의 공개 문서를 참조한다.