[CBRD-26524] Improving analytic function execution by reading only required rows#6901
[CBRD-26524] Improving analytic function execution by reading only required rows#6901Hamkua wants to merge 4 commits intoCUBRID:developfrom
Conversation
|
Last reviewed commit: 226bc5c |
| case QO_PLANTYPE_SORT: | ||
| return pt_count_analytic_covered_sort_list (parser, qo_plan->plan_un.sort.subplan, eval, info); |
There was a problem hiding this comment.
SORT 서브플랜 NULL 역참조 가능성
QO_PLANTYPE_SORT 케이스에서 qo_plan->plan_un.sort.subplan이 NULL일 경우, 재귀 호출 시 qo_plan->plan_type 접근에서 NULL 포인터 역참조가 발생할 수 있습니다. 이론적으로 SORT 플랜은 항상 서브플랜을 가지지만, 방어적 코드로 NULL 검사를 추가하는 것이 안전합니다.
| case QO_PLANTYPE_SORT: | |
| return pt_count_analytic_covered_sort_list (parser, qo_plan->plan_un.sort.subplan, eval, info); | |
| case QO_PLANTYPE_SORT: | |
| if (qo_plan->plan_un.sort.subplan == NULL) | |
| { | |
| return 0; | |
| } | |
| return pt_count_analytic_covered_sort_list (parser, qo_plan->plan_un.sort.subplan, eval, info); |
|
Last reviewed commit: dc48ba5 |
|
/run sql medium |
|
Last reviewed commit: c8bf449 |
| } | ||
| break; | ||
|
|
||
| case QO_PLANTYPE_SORT: |
|
Last reviewed commit: "코드 리뷰 반영" |
| case QO_PLANTYPE_FOLLOW: | ||
| case QO_PLANTYPE_WORST: | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
switch 문
default 케이스 누락 — 미처리 플랜 타입 시 크래시 위험
switch 이후의 assert (qo_plan->plan_un.scan.index->head) 코드는 오직 QO_PLANTYPE_SCAN 케이스만을 전제로 합니다. 그런데 switch 문에 default: return 0; 케이스가 없어서, 향후 새로운 QO_PLANTYPE_* (예: QO_PLANTYPE_TEMP 등)이 추가되거나 현재 열거된 케이스 외의 플랜이 진입하면 switch 탈출 후 해당 assert와 qo_plan->plan_un.scan.index->head 역참조가 그대로 수행되어 크래시 또는 UB(Undefined Behavior)가 발생합니다.
DBMS 커널의 코드 안정성 원칙상, 핸들되지 않은 케이스는 명시적으로 return 0으로 처리하는 방어 코드가 반드시 필요합니다.
| case QO_PLANTYPE_FOLLOW: | |
| case QO_PLANTYPE_WORST: | |
| return 0; | |
| } | |
| case QO_PLANTYPE_FOLLOW: | |
| case QO_PLANTYPE_WORST: | |
| return 0; | |
| default: | |
| return 0; | |
| } |
| if (XASL_IS_FLAGED (xasl, XASL_ANALYTIC_USES_LIMIT_OPT)) | ||
| { | ||
| analytic_state.state = NO_ERROR; | ||
| goto wrapup; | ||
| } |
There was a problem hiding this comment.
XASL_ANALYTIC_USES_LIMIT_OPT 조기 종료 위치 — 불필요한 리소스 초기화 비용
XASL_ANALYTIC_USES_LIMIT_OPT 플래그 체크가 qexec_initialize_analytic_state 호출 이후에 위치하고 있습니다. 이 위치로 인해 아래 두 가지 불필요한 작업이 발생합니다:
-
is_skip_sort = false경로 (sort_list_size = 0인 케이스): 각 함수에 대해qfile_open_list가 두 번씩(group_list_id,value_list_id) 호출되어 리스트 파일이 할당되고, 곧바로qexec_clear_analytic_function_state에서 해제됩니다. -
is_skip_sort = true경로 (covered_size == sort_list_size > 0인 케이스):qexec_initialize_analytic_function_state내부에서qdata_finalize_analytic_func가 호출되어 부분적으로만 누적된 데이터(LIMIT으로 인해 스캔이 조기 종료된 상태)에 대한 분석 함수 결과가 계산되고,func_p->group_list_id/func_p->order_list_id가func_state로 이전됩니다. 이 결과값은wrapup에서 전혀 사용되지 않고 즉시 파괴됩니다.
평가 함수 수(func_count)가 많거나 리스트 파일 오픈 비용이 높은 환경에서는 Disk I/O 및 메모리 할당 오버헤드가 누적될 수 있습니다.
qexec_initialize_analytic_state 호출 이전에 플래그를 체크하거나, qexec_initialize_analytic_state에서도 해당 플래그를 인식해 경량 초기화 경로를 제공하는 것을 권장합니다. 만약 wrapup 섹션에서 analytic_state 필드의 정리가 필요하다면, 최소한의 필드(input_scan = NULL, output_file = NULL, curr_sort_page.page_p = NULL, func_state_list = NULL)만 초기화한 뒤 조기 종료하는 별도 경로를 고려하세요.
| new_stat->analytic_stopkey = false; | ||
| new_stat->analytic_sort = !is_skip_sort; | ||
| if (XASL_IS_FLAGED (xasl, XASL_ANALYTIC_USES_LIMIT_OPT)) | ||
| { | ||
| new_stat->analytic_stopkey = true; | ||
| new_stat->analytic_sort = false; | ||
| } |
There was a problem hiding this comment.
analytic_stopkey 기반 통계에서 is_skip_sort = true 케이스의 정보 소실
이전 코드에서는 run_analytic = true가 무조건 세팅되어 "분석 함수가 실제 실행되었다"는 표식 역할을 했습니다. 새 코드에서는 아래의 경우:
is_skip_sort = true(인덱스로 정렬 생략)XASL_ANALYTIC_USES_LIMIT_OPT = false
analytic_sort = false이고 analytic_stopkey = false로 memset 초기값과 동일해집니다. 이 케이스가 덤프 출력 시 "sort" 항목이 아예 출력되지 않아 (qdump_print_stats_text 기준), 인덱스 스킵 소트가 적용된 경우와 단순히 통계가 없는 경우를 구별하기 어렵습니다.
운영 환경에서 쿼리 플랜 진단 시 혼란을 줄 수 있으므로, is_skip_sort = true인 경우를 위한 별도 필드(예: analytic_skip_sort) 추가 또는 기존 analytic_sort 필드의 삼값 처리를 검토하시기 바랍니다.
| if (curr->analytic_sort) | ||
| if (curr->analytic_stopkey) | ||
| { | ||
| json_object_set_new (analytic, "sort", json_string ("nosort stopkey")); |
There was a problem hiding this comment.
아래 3가지 상태가 존재하게 됩니다.
sort: true
sort: skip
sort: nosort stopkey
skip과 nosort에 동작 차이가 없다면 skip으로 통일하는게 좋을 것 같습니다.
저는 , sort: nosort stopkey, ... 출력 형태가 항목: 값 형태가 아니라서 부자연스럽다고 느껴집니다. 다른 출력 형태에 대해서도 의견을 모으면 좋겠습니다. 의견으로만 들어주세요.
예)
ANALYTIC #1 (time: 0, sort: nosort, stopkey: true, page: 0, ioread: 0, rows: 10)
ANALYTIC #1 (time: 0, sort: nosort, page: 0, ioread: 0, rows: 10) (stopkey)
확인만 하시고 Resolve 해주셔도 괜찮습니다.
There was a problem hiding this comment.
집계함수에서는 false로 출력되므로 false로 통일하겠습니다.
| new_stat->analytic_stopkey = false; | ||
| new_stat->analytic_sort = !is_skip_sort; | ||
| if (XASL_IS_FLAGED (xasl, XASL_ANALYTIC_USES_LIMIT_OPT)) | ||
| { | ||
| new_stat->analytic_stopkey = true; | ||
| new_stat->analytic_sort = false; | ||
| } |
There was a problem hiding this comment.
아래처럼 변경하면 좋을 것 같습니다.
if (XASL_IS_FLAGED (xasl, XASL_ANALYTIC_USES_LIMIT_OPT))
new_stat->analytic_stopkey = true;
new_stat->analytic_sort = false;
else
new_stat->analytic_stopkey = false;
new_stat->analytic_sort = !is_skip_sort;
http://jira.cubrid.org/browse/CBRD-26524
Purpose
큐브리드는 정렬이 필요한 분석함수가 있다면 테이블의 전체 행을 읽는 비효율이 있습니다.
인덱스를 활용하여 정렬을 생략할 수 있는 분석함수로만 구성된 쿼리는 전체 튜플을 스캔하지 않고 필요한 만큼만 스캔하도록 개선합니다.
LIMIT절 또는ROWNUM조건을 포함해야 합니다. (where rownum < 10,limit 10)PARTITION BY,ORDER BY) 인덱스의 선두 컬럼과 일치하여 정렬을 생략할 수 있어야 합니다.a_eval_list의 길이가 1 이어야 합니다.a_eval_list: 하나의 쿼리에 여러 분석 함수가 포함된 경우, 각 함수마다 매번 정렬한다면 효율이 낮아질 것입니다. CUBRID는 이러한 비효율을 줄이기 위해 동일한 정렬 기준(SORT_LIST)으로 처리 가능한 분석 함수들을 하나의 묶음으로 관리하는데, 이 묶음의 단위가 바로a_eval_list입니다.a_eval_list의 길이가 1이라는 것은 해당 쿼리 내 모든 분석 함수가 동일한 정렬 기준을 공유하며, 단 한 번의 정렬만으로 처리가 가능한 상태임을 의미합니다.대상 분석 함수 (
pt_check_analytic_limit_optimization()) :ROW_NUMBER()RANK()DENSE_RANK()FIRST_VALUE()(IGNORE NULLS제외)Implementation
정렬을 생략할 수 있고, limit 절 최적화가 가능하다면 processing 단계에서 instnum 을 평가하도록 수정
a_outptr_list_ex->valptrp에 instnum 함수를 삽입할 때, 순서가 잘못되는 문제를 함께 수정Remarks
N/A