콘텐츠로 이동

(KO) CUBRID 쿼리 처리 — 섹션 개요

목차

쿼리 처리(query-processing)는 CUBRID code-analysis 트리에서 가장 두툼한 묶음이다. SQL 한 줄이 행 한 무더기로 풀려 나오기까지의 모든 길이 여기 들어 있다 — 텍스트를 파싱하고 검증하는 프론트엔드, 플랜을 골라 직렬화하는 미들엔드, 그 플랜을 스토리지 위에서 굴리는 백엔드, 그리고 백엔드가 행마다·그룹마다·조인마다 부르는 런타임 도구들까지. 이 표지 아래 세부 문서가 19편 들어 있고, 각자 한 단계 또는 한 알고리즘 가족을 맡는다. 이 문서는 그 19편의 라우터다. 단계를 호명하고, 파이프라인을 한 번 그려 두고, 세부 문서를 가리키되 그 내용을 다시 적지는 않는다.

CUBRID의 쿼리 처리 코드를 처음 들여다보는 독자라면 여기서 시작하는 게 좋다. 한 단계는 이미 알고 그다음이 궁금하다면 읽는 순서가 의존 관계 순으로 문서를 줄 세워 둔다. 옆에 짝이 있는 reading-path 문서 cubrid-rpath-select.mdSELECT * FROM t WHERE x > 10 한 문장이 같은 파이프라인을 어떻게 통과하는지를 실제 예제로 보여 준다. 이 개요가 지형도라면, 그쪽은 여행 일지다.

이 섹션의 책임은 한 줄로 잡힌다 — 텍스트 → 튜플. 경계는 두 군데다. 위쪽 경계는 broker — SQL을 서버까지 실어 오는 길이고, BC&I 묶음의 cubrid-broker.md가 다룬다. 아래쪽 경계는 스토리지 스택이다. 백엔드가 그쪽으로 내려가서 부른다 — heap, B+Tree, 페이지 버퍼, MVCC가 모두 스토리지 엔진 묶음에 있다. 그 사이 영토를 이 섹션이 들고 있다 — 파서, 의미 검사기, 쿼리 재작성기, 비용 기반 옵티마이저, XASL 플랜 IR, 플랜 캐시, Volcano 식 실행기, 스캔 매니저 디스패치, list file 실체화 기반, 행 단위 술어 평가기, 스칼라 함수 라이브러리, external sort, GROUP BY · 윈도우 후처리기, hash join 가족, 런타임 메모이제이션 캐시, 파티션 가지치기, 커서 핸들, SERIAL · auto-increment 도구, 병렬 쿼리 워커 풀까지. 각자 자기 cubrid-<topic>.md를 들고 있다. 이 문서의 나머지는 그 각각이 파이프라인 어디에 박혀 있는지, 어떤 순서로 보면 자연스럽게 들어오는지, 그리고 셋을 가로질러 이어지는 줄기가 무엇인지를 정리한다.

파이프라인은 세 갈래의 하위 파이프라인 + 백엔드가 옆구리에서 부르는 런타임 도구 한 묶음으로 짜인다. 문서를 이 세 갈래에 맞춰 나누는 게 섹션을 길 잃지 않고 도는 비결이다. 세부 문서는 모두 정확히 한 칸에 들어간다. 런타임 도구는 한 단계에만 매달지 못하기에 따로 묶어 둔다.

프론트엔드. SQL 텍스트를 의미가 검증된, 정규화된 중간 트리로 옮기는 컴파일 파이프라인이다. 문서 셋이 이 칸에 들어간다.

  • cubrid-parser.md — Flex 어휘 분석기 + GLR Bison 문법이 PARSER_CONTEXT 단위 블록 할당기 위에서 PT_NODE 파스 트리를 빚는다.
  • cubrid-semantic-check.mdpt_check_with_info가 이름 해석, WHERE절 집계 검사, 호스트 변수 치환, 그리고 타입 검사·상수 폴딩을 도는 semantic_check_local/pt_semantic_type을 차례로 굴리고, 마지막에 pt_cnf로 술어를 CNF로 정리한다.
  • cubrid-query-rewrite.mdmq_rewrite와 그 가족(query_rewrite_select.c, query_rewrite_subquery.c, query_rewrite_set.c, query_rewrite_term.c, query_rewrite_auto_parameterize.c)이 술어 푸시다운, 뷰 인라이닝, 서브쿼리 평탄화, outer join 줄이기, 중복 join 제거, auto-parameterize, 그리고 LIMIT 절 낮추기를 굴린다.

미들엔드. 플랜을 짜는 단계다. 후보들의 비용을 매기고, 하나를 고르고, 실행기의 IR로 낮추고, 다음번을 위해 기억해 둘지를 결정한다.

  • cubrid-query-optimizer.mdqo_optimize_queryQO_ENV 쿼리 그래프를 짜고, 2^N 크기 join_info 벡터 위에서 부분-전체 동적 계획법으로 join을 열거한다. System R 식의 고정/가변 cpu·io 비용 모델로 채점해 QO_PLAN 트리를 내놓는다.
  • cubrid-xasl-generator.mdxasl_generation.c가 재귀적인 gen_outer/gen_inner로 그 플랜을 따라가며 XASL_NODE 트리를 만든다. aptr/dptr/scan_ptr 슬롯이 한 노드 안에 서브쿼리와 join을 숨기고, REGU_VARIABLE·ACCESS_SPEC·OUTPTR_LIST 하위 IR이 함께 따라간다. xts_* 오프셋 표 도구가 그 트리를 와이어로 보낼 수 있는 바이트 버퍼로 직렬화한다.
  • cubrid-xasl-cache.md — 재작성된 SQL의 SHA-1(hash text)을 키로 쓰는 서버 전역 lock-free 해시맵이다. cache_flag 참조 카운트, recompile-threshold(RT)로 짚는 카디널리티 드리프트, DDL에 매단 xcache_remove_by_oid 클래스별 무효화가 함께 들어간다.

백엔드. 캐시된 — 또는 막 컴파일된 — XASL 트리를 행으로 풀어 내는 런타임이다.

  • cubrid-query-executor.mdqexec_execute_mainblock_internalxasl->type으로 갈리고, SCAN_ID 연산자 위에 균일한 open/next/close 루프를 굴리며, 결과를 XASL 단위 list file에 적어 둔다. nested-loop join과 merge join이 이 자리에서 굴러간다.
  • cubrid-scan-manager.md — 다형 SCAN_ID 핸들 한 개와, switch 기반의 open/start/next/end/close 프로토콜, 그리고 SCAN_TYPE별로 heap, B+Tree, list file, set, value, JSON-table, dblink, show, 병렬 heap, method scan으로 갈리는 디스패치가 들어간다.
  • cubrid-list-file.md — 실체화가 필요한 모든 튜플 스트림(서브쿼리 결과, 정렬 출력, hash build 측, GROUP BY 누산, 최종 쿼리 결과)을 단 하나의 QFILE_LIST_ID 연결 페이지 추상으로 풀어 낸다. 안쪽에는 쿼리 단위 QMGR_TEMP_FILE이 메모리 버퍼로 시작해 FILE_TEMP로 옮겨 가는 두 단계 기반이 깔린다.

런타임 도구. 백엔드가 옆구리에서 부르는 연산자, 캐시, 원시 함수 계층이다. 셋의 하위 파이프라인을 가로지른다 — 실행기는 행마다 술어 평가기와 스칼라 함수 라이브러리를 부르고, 스캔 매니저와 후처리기가 external sort를 같이 쓰고, 옵티마이저 비용 모델은 hash join을 본다. 한 단계에만 욱여넣을 수 없어서 따로 둔다.

  • Join 알고리즘. cubrid-hash-join.mdquery_hash_join.c의 Build/Probe를 다룬다. query_hash_scan.cHASH_LIST_SCAN을 그대로 가져다 쓰고, 표 레이아웃이 셋(인메모리 mht_hls, 메모리 인덱스 + 파일 튜플의 하이브리드, 확장 가능한 FHS 해시 파일)이며, spill 시에는 grace 식 동등 해시 파티셔닝으로 빠진다. nested-loop join과 merge join은 새 해시 기반을 들여오지 않고 Volcano 발판 위에 그대로 얹히기 때문에 cubrid-query-executor.md에서 함께 다룬다.
  • 정렬과 후처리. cubrid-external-sort.md는 두 단계로 나뉜 replacement-selection 식 run 생성기(sort_inphase_sort)와 균형 잡힌 k-way merge(sort_exphase_merge)를 FILE_TEMP run 위에서 굴려 sort_listfile로 내놓는 흐름을 정리한다. cubrid-post-processing.mdqexec_groupbyqexec_execute_analytic을 다룬다. 정렬 vs 해시 GROUP BY 결정, 표가 max_agg_hash_size를 넘기면 external sort로 빠지는 hash spill 폴백, 그리고 윈도우·analytic 프레임 실행이 들어 있다.
  • 술어와 연산자. cubrid-query-evaluator.md는 행마다의 keep/skip 판정을 다룬다. eval_predT_PRED 불리언 노드와 T_EVAL_TERM 잎으로 짜인 PRED_EXPR 트리를 3치 논리 아래에서 따라가고, fetch_peek_dbvalREGU_VARIABLE::type에 따라 갈린다. cubrid-scalar-functions.md는 그 아래 깔린 연산자 원시 함수 계층이다 — arithmetic.c, numeric_opfunc.c, string_opfunc.c, query_opfunc.c, crypt_opfunc.c, 그리고 string_regex_* 가족까지.
  • 캐시. cubrid-runtime-memoization.md는 같은 약속(DB_VALUE 배열을 키로 쓰는 해시, 가득 차면 실패하는 budget, 적중률 가드)을 세 가지 수명 범위에 끼워 둔 세 캐시를 다룬다 — XASL 단위의 sq_cache(상관 없는 스칼라 서브쿼리 결과), 서버 전역에 BTID별로 두는 fpcache(역직렬화한 함수 인덱스 술어), XASL 단위의 memoize::storage(nested-loop join 안쪽 튜플). XASL 플랜 캐시는 미들엔드 쪽에서 따로 다룬다.
  • 특수 기능. cubrid-partition.md(range/hash/list 파티션, 옵티마이즈 시점의 PRUNING_CONTEXT 가지치기, 실행 시점의 파티션별 라우팅), cubrid-cursor.md(서버 측 QFILE_LIST_ID 위에 얹은 클라이언트 측 CURSOR_ID, COMMIT을 견디는 holdable 커서), cubrid-serial.md(_db_serial에 사는 sequence·auto-increment 상태, 배타 객체 락, 클라이언트 측 캐싱), cubrid-parallel-query.md(전역 병렬 쿼리 워커 풀과, heap scan·hash join·query execute 각각을 위한 세 개의 오케스트레이터).

아래 Mermaid는 모든 세부 문서를 자기 단계에 짝지어 둔다. 세로 축이 데이터 흐름이다 — 위가 텍스트, 아래가 튜플. 런타임 도구는 통과되는 게 아니라 호출되기 때문에 옆쪽으로 빼서 그렸다.

flowchart TB
    SQL["SQL 텍스트<br/>(broker / CAS에서 도착)"]

    subgraph FE["프론트엔드 (컴파일)"]
        direction TB
        Parser["cubrid-parser.md<br/>Flex + GLR Bison &rarr; PT_NODE"]
        SC["cubrid-semantic-check.md<br/>이름 해석, 타입 검사, CNF"]
        Rewrite["cubrid-query-rewrite.md<br/>술어 푸시다운, 뷰 인라이닝,<br/>서브쿼리 평탄화, LIMIT 낮추기"]
        Parser --> SC --> Rewrite
    end

    subgraph ME["미들엔드 (플랜)"]
        direction TB
        Opt["cubrid-query-optimizer.md<br/>QO_ENV 그래프, DP join 열거,<br/>System-R 비용 모델 &rarr; QO_PLAN"]
        XGen["cubrid-xasl-generator.md<br/>QO_PLAN &rarr; XASL_NODE 트리<br/>· xts_∗ 직렬화"]
        XCache["cubrid-xasl-cache.md<br/>SHA-1 플랜 캐시, RT 재컴파일,<br/>클래스별 OID 무효화"]
        Opt --> XGen --> XCache
    end

    subgraph BE["백엔드 (실행)"]
        direction TB
        Exec["cubrid-query-executor.md<br/>qexec_execute_mainblock_internal<br/>Volcano open/next/close"]
        Scan["cubrid-scan-manager.md<br/>SCAN_ID 디스패치 &rarr;<br/>heap / btree / list / set / json / dblink / px"]
        LF["cubrid-list-file.md<br/>QFILE_LIST_ID 연결 페이지<br/>(실체화 후 다시 스캔)"]
        Exec --> Scan
        Exec <--> LF
    end

    subgraph RT["런타임 도구"]
        direction TB
        Eval["cubrid-query-evaluator.md<br/>PRED_EXPR 순회 + fetch_peek_dbval"]
        Scalar["cubrid-scalar-functions.md<br/>산술 / 문자열 / 숫자 /<br/>JSON / 정규식 / 암호"]
        Sort["cubrid-external-sort.md<br/>run 생성 + k-way merge"]
        Post["cubrid-post-processing.md<br/>GROUP BY (sort vs hash) /<br/>analytic / window"]
        HJ["cubrid-hash-join.md<br/>Build/Probe + HASH_LIST_SCAN<br/>· grace 식 spill"]
        Mem["cubrid-runtime-memoization.md<br/>sq_cache / fpcache / memoize"]
    end

    subgraph SPEC["특수 기능"]
        direction TB
        Part["cubrid-partition.md<br/>range/hash/list, PRUNING_CONTEXT"]
        Cur["cubrid-cursor.md<br/>QFILE_LIST_ID 위 CURSOR_ID,<br/>holdable / scrollable"]
        Ser["cubrid-serial.md<br/>_db_serial, AUTO_INCREMENT,<br/>캐시된 값"]
        Par["cubrid-parallel-query.md<br/>병렬 쿼리 워커 풀,<br/>parallel-heap / hash-join / execute"]
    end

    Tuples["결과 튜플<br/>(커서 / broker / 클라이언트로 전달)"]

    SQL --> FE --> ME --> BE --> Tuples

    Exec -.부른다.-> Eval
    Eval -.부른다.-> Scalar
    Exec -.쓴다.-> Sort
    Exec -.쓴다.-> Post
    Exec -.쓴다.-> HJ
    Exec -.쓴다.-> Mem
    Scan -.쓴다.-> Mem

    ME -.영향 준다.-> Part
    BE -.디스패치.-> Part
    BE --> Cur
    Exec -.쓴다.-> Ser
    BE -.병렬화.-> Par

    classDef fe fill:#eef,stroke:#557
    classDef me fill:#efe,stroke:#575
    classDef be fill:#fee,stroke:#755
    classDef rt fill:#fef,stroke:#757
    classDef spec fill:#ffe,stroke:#775
    class Parser,SC,Rewrite fe
    class Opt,XGen,XCache me
    class Exec,Scan,LF be
    class Eval,Scalar,Sort,Post,HJ,Mem rt
    class Part,Cur,Ser,Par spec

다이어그램이 한 가지를 살짝 가린다 — XASL 캐시는 미들엔드의 이 아니라 백엔드 파이프라인의 맨 앞에서 먼저 들춰진다. prepared statement를 다시 실행할 때 실행기는 캐시에 박힌 플랜을 곧장 읽어 가고, 프론트엔드 전체와 미들엔드의 나머지는 건너뛴다. 그림에서는 캐시를 미들엔드 끝에 두었는데, 그건 컴파일 작업의 출력 경계가 그곳이기 때문이다. 다음 실행 때는 그 위 모든 것이 캐시 한 줄에서 끊긴다. 이 빠른 길은 cubrid-xasl-cache.md가 따로 분명히 적어 둔다.

세부 문서 사이의 의존 그래프는 하위 파이프라인 안에서는 거의 한 줄로 흐르고, 백엔드에서 런타임 도구 쪽으로는 부채꼴로 갈라진다. 아래 순서는 가지치기에 들어가기 전에 척추부터 잡고 싶은 독자를 위한 권장 순서다.

프론트엔드, 줄 따라. 각 단계가 바로 앞 단계의 출력을 받아 쓰므로, 자료 구조는 앞 단계가 굴러간 뒤에야 존재한다.

  1. cubrid-parser.md — 여기서 시작한다. 이 문서가 만들어 내는 PT_NODE 트리가 뒤따르는 두 패스가 만져 가는 작업 표현이다. 그 모양 — 다형 태그 union, 함수 포인터 배열 셋, PARSER_CONTEXT 단위 블록 할당기 — 을 잡아 두는 게 프론트엔드 나머지 모두의 전제다.
  2. cubrid-semantic-check.mdPT_NODE 트리를 읽을 줄 알게 됐을 때 들어간다. 네 패스(이름 해석, WHERE절 집계 검사, 호스트 변수 치환, semantic_check_local)가 트리를 자리에서 주석 달아 가며 채워 넣는다. 어떤 패스가 어떤 필드를 채우는지 알아야, 나중에 그 필드를 읽는 코드가 풀린다.
  3. cubrid-query-rewrite.md — 프론트엔드 마지막. mq_rewrite 변환 가족은 이미 이름과 타입이 다 풀린 트리 위에서 굴러가므로, 앞 두 문서가 깔려 있어야 한다. LIMIT 절 사례 연구가 한 가지 SQL 기능이 의미 검사 → 재작성 → 플랜 생성 → 런타임에 걸쳐 어떻게 흩어지는지를 가장 또렷이 보여 주는 본보기다.

미들엔드, 줄 따라. 옵티마이저의 QO_PLAN이 XASL 생성기의 gen_outer/gen_inner 입력이다. XASL 캐시는 재작성된 SQL의 해시를 키로 쓰니 엄밀히는 생성기와 평행한 자리지만, 무엇이 캐시되는지를 안 다음에 들어가는 게 손에 잘 잡힌다.

  1. cubrid-query-optimizer.mdQO_ENV 쿼리 그래프, System R 식 비용 모델, 동적 계획법 join 열거. 문서 3에서 받은 재작성된 PT_NODE 트리를 들고 들어간다.
  2. cubrid-xasl-generator.mdgen_outer/gen_innerQO_PLAN을 따라가며 XASL_NODE를 빚는 길, aptr/dptr/scan_ptr 슬롯 의미, REGU_VARIABLE·ACCESS_SPEC·OUTPTR_LIST 하위 IR이 들어 있다. 옵티마이저의 다음 소비자라 옵티마이저 뒤에 둔다.
  3. cubrid-xasl-cache.md — SHA-1 키 lock-free 해시맵, RT 드리프트 감지, 클래스별 OID 무효화. 생성기가 내놓는 직렬화 XASL 바이트 버퍼를 전제로 하므로 미들엔드의 마지막 자리다.

백엔드, 줄 따라. 실행기가 척추다. 스캔 매니저는 그 한 층 아래에 자리 잡는다. list file은 두 문서가 모두 의지하는 실체화 기반이다. 위에서 아래로 따라간다.

  1. cubrid-query-executor.mdqexec_execute_mainblock_internal, proc-type 디스패치, Volcano open/next/close 루프, 그리고 nested-loop join · merge join.
  2. cubrid-scan-manager.md — 다형 SCAN_ID 핸들과 SCAN_TYPE별 디스패치. 실행기가 매 반복에서 부르는 자리니, 실행기 바로 다음에 본다.
  3. cubrid-list-file.mdQFILE_LIST_ID 연결 페이지 추상. 백엔드 셋 가운데 마지막에 둔다 — 실행기는 여기 적고, 스캔 매니저는 list scan으로 여기서 다시 읽어 가니, 기반을 잡아 두면 앞 두 문서에서 list file이라 부르는 자리가 단단해진다.

행 단위 풀이. 백엔드가 머리에 들어왔을 때 본다.

  1. cubrid-query-evaluator.md — 행 단위 keep/skip 판정. eval_predPRED_EXPR를 따라가고, fetch_peek_dbvalREGU_VARIABLE을 풀어 준다. 백엔드 모든 연산자가 후보 행마다 이 자리를 친다.

연산자 깊이. 첫 통독 때 잠시 미뤄도 되지만, 해당 쿼리를 따라가야 할 때는 빠질 수 없는 세 편이다.

  1. cubrid-hash-join.md — Build/Probe, 표 레이아웃 셋, grace 식 spill. HASHJOIN_PROC을 따라갈 때 실행기 문서와 같이 본다.
  2. cubrid-post-processing.mdqexec_groupbyqexec_execute_analytic, 정렬 vs 해시 GROUP BY, external sort로 빠지는 hash spill 폴백.
  3. cubrid-external-sort.mdsort_listfile과 두 단계 run 생성 + k-way merge. 후처리, ORDER BY, DISTINCT, B+Tree 대량 적재, 병렬 인덱스 빌드가 모두 이 한 자리를 같이 쓴다.

특수 기능. 필요할 때만. 흔한 쿼리의 척추에는 없지만, 해당 기능 갈래에서는 필수가 된다.

  1. cubrid-partition.md — 옵티마이즈 시점의 파티션 가지치기와 실행 시점의 파티션별 라우팅.
  2. cubrid-parallel-query.md — 병렬 쿼리 워커 풀과 세 연산자별 오케스트레이터(heap scan, hash join, query execute).
  3. cubrid-cursor.md — 서버 측 QFILE_LIST_ID 위에 얹은 클라이언트 측 CURSOR_ID, COMMIT을 견디는 holdable 커서.
  4. cubrid-serial.md_db_serial 행으로 굴러가는 sequence와 auto-increment.
  5. cubrid-runtime-memoization.md — 같은 약속을 공유하는 작은 캐시 셋(sq_cache, fpcache, memoize::storage).
  6. cubrid-scalar-functions.md — 연산자 원시 함수 라이브러리. T_EVAL_TERM 잎이 qdata_*db_string_*로 떨어진 다음 무슨 일이 벌어지는지 알고 싶을 때 본다.

reading-path 문서 cubrid-rpath-select.mdSELECT * FROM t WHERE x > 10 한 문장으로 1, 2, 3, 4, 5, 6, 7, 8, 9, 10번(과 이 섹션 바깥의 몇 편)을 한 줄로 엮어 보여 준다. 프론트엔드와 백엔드의 뼈대(1, 2, 4, 7번)를 익힌 다음에, 척추 전체가 한 호흡으로 보고 싶을 때 들어가면 된다.

세 줄기가 두 편 이상의 세부 문서를 가로지른다. 처음 만났을 때 헷갈리지 않게 먼저 호명해 둔다.

통계의 흐름. 옵티마이저의 비용 모델은 통계에 기대지만, 그 통계를 보관하는 카탈로그 표와 그 행을 채워 넣는 서버 측 xstats_update_statistics 워크는 DDL & 스키마 묶음의 cubrid-statistics.md에 산다. 통계의 받아 쓰는 쪽만 이 섹션에 있다. cubrid-query-optimizer.md가 받아 쓰는 인터페이스를 호명한다 — qo_get_attr_info, qo_iscan_cost, qo_sscan_cost, qo_equal_selectivity, qo_range_selectivity. cubrid-xasl-cache.md는 캐시된 플랜이 만들어진 뒤에도 통계가 충분히 변했는지를 보는 RT(recompile-threshold) 검사를 다룬다 — 변동이 크면 부드럽게 다시 컴파일하라는 신호를 띄운다. 만들어 내는 쪽은 cubrid-statistics.md에서 한 번만 읽어 두면 충분하다. 그 뒤로는 받아 쓰는 그림이 이 섹션 안에서 풀린다.

list file — 모든 실체화의 공용 파이프. CUBRID에서 실체화가 일어나야 하는 자리 — 정렬 출력, hash build 측, 서브쿼리 결과, GROUP BY 누산, 커서가 다시 읽는 최종 결과 — 가 모두 같은 QFILE_LIST_ID 기반을 쓴다. cubrid-list-file.md가 그 기준 문서지만, 그 기반은 어느 문서에서나 다시 등장한다 — cubrid-query-executor.md는 커서가 다시 읽을 XASL 단위 list file을 적어 두고, cubrid-external-sort.md는 정렬된 run을 list file에 쏟아 두고, cubrid-post-processing.md는 GROUP BY 행을 list file에 모아 두고, cubrid-hash-join.md는 build 측을 list file 기반의 FILE_TEMP run으로 spill하고, cubrid-cursor.md는 그 list file을 네트워크 페이지 단위로 다시 읽어 간다. 이 추상을 한 번 잡아 두면, 다른 모든 문서의 실체화 한 줄이 한꺼번에 풀린다.

술어의 일생 — PT_NODE → PRED_EXPR → eval_pred. 술어 한 줄이 살아가는 단계가 셋이고, 단계마다 자료 구조와 주인 문서가 다르다. 파서가 PT_NODE 트리를 적는다(예: PT_NAMEPT_VALUE> 연산을 단 PT_EXPR). 의미 검사가 이름과 타입을 풀어 준다. CNF 정규화가 불리언 구조를 다시 짠다. 쿼리 재작성이 가장 싼 평가 자리(인덱스 범위로 sargable, join 조건, 또는 잔여 필터)로 술어를 밀어 넣는다. XASL 생성기(문서 5)가 그것을 T_PRED 불리언 노드 + REGU_VARIABLE 피연산자를 단 T_EVAL_TERM 잎으로 짜인 PRED_EXPR 트리로 낮춘다. 실행기(문서 7)와 스캔 매니저(문서 8)가 PRED_EXPR를 알맞은 스캔에 매단다 — sargable 술어는 B+Tree 키 범위 항으로, 잔여 술어는 where_pred에 남는다. 평가기(문서 10)가 PRED_EXPR를 따라가며 fetch_peek_dbval로 피연산자를 풀고, 불리언 연산자에 3치 논리를 적용한다. 그러므로 같은 술어가 파서 → 의미 검사 → 재작성 → 옵티마이저 → XASL 생성기 → 실행기 → 스캔 매니저 → 평가기 → 스칼라 함수, 이 섹션 안 여덟 편 사이를 흘러 다닌다. 재작성 문서, XASL 생성기 문서, 평가기 문서가 자기 구간의 코드 조각을 가장 짙게 들고 있다.

문서단계한 줄 역할
cubrid-parser.md프론트엔드Flex + GLR Bison 문법이 PARSER_CONTEXT 단위 블록 할당기 위에서 PT_NODE 파스 트리를 빚는다.
cubrid-semantic-check.md프론트엔드pt_check_with_info가 이름 해석, WHERE절 집계 검사, 호스트 변수 치환, semantic_check_local(pt_semantic_type로 타입 검사·상수 폴딩), 그리고 pt_cnf로 CNF 정규화를 차례로 굴린다.
cubrid-query-rewrite.md프론트엔드mq_rewrite가 술어 푸시다운, 뷰 인라이닝, 서브쿼리 평탄화, outer join 줄이기, 중복 join 제거, auto-parameterize, INST_NUM / ORDERBY_NUM / GROUPBY_NUM 술어를 만드는 LIMIT 절 낮추기를 굴린다.
cubrid-query-optimizer.md미들엔드qo_optimize_queryQO_ENV 쿼리 그래프를 짜고, System R 식 비용 모델로 부분-전체 동적 계획법 join 열거를 굴려 QO_PLAN 트리를 내놓는다.
cubrid-xasl-generator.md미들엔드xasl_generation.c의 재귀적인 gen_outer/gen_inner가 플랜을 따라가며 XASL_NODE 트리를 만든다(aptr/dptr/scan_ptr 슬롯과 REGU_VARIABLE·ACCESS_SPEC·OUTPTR_LIST 하위 IR). xts_*가 직렬화한다.
cubrid-xasl-cache.md미들엔드재작성된 SQL의 SHA-1을 키로 쓰는 lock-free 서버 전역 해시맵이다. cache_flag 참조 카운트, RT 드리프트 감지, DDL에 매단 xcache_remove_by_oid 클래스별 무효화가 함께 들어간다.
cubrid-query-executor.md백엔드qexec_execute_mainblock_internalxasl->type에 따라 갈리고, Volcano 식 open/next/close 루프를 굴린다. nested-loop join과 merge join이 그 위에 그대로 얹힌다.
cubrid-scan-manager.md백엔드다형 SCAN_ID 핸들 한 개와 switch 기반 open/start/next/end/close 프로토콜, 그리고 SCAN_TYPE별로 heap, B+Tree, list file, set, value, JSON-table, dblink, show, 병렬 heap, method scan으로 갈라지는 디스패치.
cubrid-list-file.md백엔드실체화가 필요한 모든 튜플 스트림(서브쿼리 결과, 정렬 출력, hash build, GROUP BY 누산, 최종 결과)을 쿼리 단위 QMGR_TEMP_FILE(메모리 버퍼 → FILE_TEMP) 위에 얹은 단일 QFILE_LIST_ID로 풀어 낸다.
cubrid-query-evaluator.md런타임 도구eval_predPRED_EXPR 불리언 트리를 3치 논리 아래에서 따라가고, fetch_peek_dbvalREGU_VARIABLE::type에 따라 갈려 행 단위 풀이로 들어간다.
cubrid-scalar-functions.md런타임 도구연산자 원시 함수 계층 — arithmetic.c, numeric_opfunc.c, string_opfunc.c, query_opfunc.c, crypt_opfunc.c, 그리고 평가기가 들르는 string_regex_* 가족.
cubrid-external-sort.md런타임 도구두 단계 replacement-selection 식 run 생성기(sort_inphase_sort)와 균형 잡힌 k-way merge(sort_exphase_merge)를 FILE_TEMP run 위에서 굴려 sort_listfile로 내놓는다.
cubrid-post-processing.md런타임 도구qexec_groupbyqexec_execute_analytic — 정렬 vs 해시 GROUP BY 결정, external sort로 빠지는 hash spill 폴백, 윈도우·analytic 프레임 실행.
cubrid-hash-join.md런타임 도구query_hash_join.c의 Build/Probe가 표 레이아웃 셋과 grace 식 동등 해시 파티셔닝으로 HASH_LIST_SCAN 원시를 그대로 받아 쓴다.
cubrid-runtime-memoization.md런타임 도구같은 약속(DB_VALUE 배열 해시 키, 가득 차면 실패, 적중률 가드)을 세 수명 범위에 끼워 둔 캐시 셋 — sq_cache, fpcache, memoize::storage.
cubrid-partition.md특수 기능range/hash/list 파티셔닝, 마스터 클래스에 박는 SM_PARTITION 규칙, 옵티마이즈 시점의 PRUNING_CONTEXT 가지치기, 실행 시점의 파티션별 라우팅.
cubrid-cursor.md특수 기능서버 측 QFILE_LIST_ID 위에 얹은 클라이언트 측 CURSOR_ID, qfile_get_list_file_page로 페이지 단위 fetch, 세션 범위 리스트로 COMMIT을 견디는 holdable 커서.
cubrid-serial.md특수 기능_db_serial에 사는 sequence·auto-increment 행. 배타 객체 락 아래에서 진행하고 클라이언트 측 캐싱이 가능하다. AUTO_INCREMENT 컬럼은 합성된 <class>_ai_<attr> serial로 굴러간다.
cubrid-parallel-query.md특수 기능전역 병렬 쿼리 워커 풀, compute_parallel_degree() 정책, cubthread::worker_pool 위에서 도는 세 연산자별 오케스트레이터(parallel_heap_scan::manager, parallel_query::hash_join, parallel_query_execute::query_executor).

다른 세 묶음이 쿼리 처리 안으로 들어오거나, 거꾸로 이쪽이 그쪽으로 내려간다. 경계가 어디 그어져 있는지 알면, 엉뚱한 섹션에서 문서를 찾아 헤매지 않는다.

DDL과 스키마. 카탈로그가 프론트엔드의 첫 입력이다 — 이름 해석은 카탈로그 메타데이터를 읽어 모든 PT_NAME을 묶어 두고, 옵티마이저는 카탈로그에서 통계를 받아 간다. cubrid-catalog-manager.md가 카탈로그 그 자체를, cubrid-statistics.md가 통계 파이프라인의 만드는 쪽(xstats_update_statistics가 카탈로그의 통계 행을 채우는 워크)을 다룬다. DDL 실행 길(cubrid-ddl-execution.md)은 xcache_remove_by_oid도 부른다 — 의존하는 XASL 캐시 항목을 무효로 만드는 호출이다. 이게 DDL에서 미들엔드의 플랜 캐시로 들어오는 인바운드 간선이다.

스토리지 엔진. 백엔드의 스캔 매니저는 next()마다 스토리지 스택으로 내려간다. cubrid-heap-manager.mdS_HEAP_SCAN의 도착지, cubrid-btree.mdS_INDEX_SCAN의 도착지, 그 둘 아래에는 cubrid-page-buffer-manager.md가 깔린다. cubrid-mvcc.md는 스캔 매니저가 후보 행을 평가기에 넘기기 직전에 부르는 가시성 검사를 들고 있다. cubrid-list-file.md 자체는 스토리지 계층 에 자리 잡는다 — cubrid-disk-manager.mdFILE_TEMP로 페이지를 받아 가긴 하지만, 그것을 받아 쓰는 쪽은 모두 이 섹션 안에 있다.

절차적 언어(PL). PL 쿼리도 SQL과 같은 xqmgr_execute_query 입구로 실행기에 들어온다. cubrid-pl-javasp.mdcubrid-pl-plcsql.md가 그 쿼리를 짜고 던지는 PL 쪽을 다룬다. PL 저장 프로시저가 SQL을 발행하면, 제어가 cub_pl에서 네트워크를 거쳐 다시 cub_server로 돌아오고, JDBC 클라이언트 쿼리와 같은 길로 cubrid-query-executor.md에 도착한다. PL 가족이 자기 묶음에 따로 들어 있는 이유는, 언어와 런타임이 쿼리 처리와는 독립이기 때문이다 — 그쪽이 발행하는 쿼리만 결국 이 섹션으로 떨어진다.