Skip to content

[CBRD-26524] Improving analytic function execution by reading only required rows#6901

Open
Hamkua wants to merge 4 commits intoCUBRID:developfrom
Hamkua:CBRD-26524
Open

[CBRD-26524] Improving analytic function execution by reading only required rows#6901
Hamkua wants to merge 4 commits intoCUBRID:developfrom
Hamkua:CBRD-26524

Conversation

@Hamkua
Copy link
Contributor

@Hamkua Hamkua commented Mar 12, 2026

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

@greptile-apps
Copy link

greptile-apps bot commented Mar 12, 2026

Last reviewed commit: 226bc5c

Comment on lines +28434 to +28435
case QO_PLANTYPE_SORT:
return pt_count_analytic_covered_sort_list (parser, qo_plan->plan_un.sort.subplan, eval, info);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SORT 서브플랜 NULL 역참조 가능성

QO_PLANTYPE_SORT 케이스에서 qo_plan->plan_un.sort.subplanNULL일 경우, 재귀 호출 시 qo_plan->plan_type 접근에서 NULL 포인터 역참조가 발생할 수 있습니다. 이론적으로 SORT 플랜은 항상 서브플랜을 가지지만, 방어적 코드로 NULL 검사를 추가하는 것이 안전합니다.

Suggested change
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);

@greptile-apps
Copy link

greptile-apps bot commented Mar 12, 2026

Last reviewed commit: dc48ba5

@Hamkua
Copy link
Contributor Author

Hamkua commented Mar 16, 2026

/run sql medium

@greptile-apps
Copy link

greptile-apps bot commented Mar 17, 2026

Last reviewed commit: c8bf449

}
break;

case QO_PLANTYPE_SORT:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6746 (comment) 이전 PR의 코드 리뷰 반영입니다.

@greptile-apps
Copy link

greptile-apps bot commented Mar 18, 2026

Last reviewed commit: "코드 리뷰 반영"

Comment on lines +28444 to 28447
case QO_PLANTYPE_FOLLOW:
case QO_PLANTYPE_WORST:
return 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 switch 문 default 케이스 누락 — 미처리 플랜 타입 시 크래시 위험

switch 이후의 assert (qo_plan->plan_un.scan.index->head) 코드는 오직 QO_PLANTYPE_SCAN 케이스만을 전제로 합니다. 그런데 switch 문에 default: return 0; 케이스가 없어서, 향후 새로운 QO_PLANTYPE_* (예: QO_PLANTYPE_TEMP 등)이 추가되거나 현재 열거된 케이스 외의 플랜이 진입하면 switch 탈출 후 해당 assertqo_plan->plan_un.scan.index->head 역참조가 그대로 수행되어 크래시 또는 UB(Undefined Behavior)가 발생합니다.

DBMS 커널의 코드 안정성 원칙상, 핸들되지 않은 케이스는 명시적으로 return 0으로 처리하는 방어 코드가 반드시 필요합니다.

Suggested change
case QO_PLANTYPE_FOLLOW:
case QO_PLANTYPE_WORST:
return 0;
}
case QO_PLANTYPE_FOLLOW:
case QO_PLANTYPE_WORST:
return 0;
default:
return 0;
}

Comment on lines +20998 to +21002
if (XASL_IS_FLAGED (xasl, XASL_ANALYTIC_USES_LIMIT_OPT))
{
analytic_state.state = NO_ERROR;
goto wrapup;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 XASL_ANALYTIC_USES_LIMIT_OPT 조기 종료 위치 — 불필요한 리소스 초기화 비용

XASL_ANALYTIC_USES_LIMIT_OPT 플래그 체크가 qexec_initialize_analytic_state 호출 이후에 위치하고 있습니다. 이 위치로 인해 아래 두 가지 불필요한 작업이 발생합니다:

  1. is_skip_sort = false 경로 (sort_list_size = 0인 케이스): 각 함수에 대해 qfile_open_list가 두 번씩(group_list_id, value_list_id) 호출되어 리스트 파일이 할당되고, 곧바로 qexec_clear_analytic_function_state에서 해제됩니다.

  2. 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_idfunc_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)만 초기화한 뒤 조기 종료하는 별도 경로를 고려하세요.

Comment on lines +21182 to +21188
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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"));
Copy link
Contributor

@youngjinj youngjinj Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래 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 해주셔도 괜찮습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

집계함수에서는 false로 출력되므로 false로 통일하겠습니다.

Comment on lines +21182 to +21188
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래처럼 변경하면 좋을 것 같습니다.

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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants