Skip to content
튜닝 연습파티션
PART-050

연간 상품 판매 집계를 DOP 2에서 8로 올렸더니 TEMP를 쓰며 오히려 느려졌다

실전SELECT유통425 / 700

상황 ​

유통 본사 MD팀은 분기마다 「연간 상품별 매장별 판매」를 병렬로 뽑는다. 2021년에 만든 SQL에는 분배 방식을 고정한 힌트가 붙어 있고, 그때부터 DOP 2로 돌았다. 올해 분석 DB 서버를 늘리면서 DOP를 8로 올렸는데, 6.1초 걸리던 것이 8.4초로 늘었고 TEMP 사용량 경보가 울렸다. 판매상품은 지난해 데이터팀이 판매번호 해시 8칸 복사본으로 다시 만들어 두었다. 박팀장: "분배 방식 몇 가지를 비교하고, 판매 테이블 설계까지 바꿀 가치가 있는지 판단해서 가져와."

테이블과 인덱스 ​

  • CF10_P 판매(파티션 복사본, 문제 설정): CF10 500,000행을 PARTITION BY RANGE (SLE_DT) 월별 24칸(P202201 ~ P202312)과 빈 PMAX로 나눴다. 2023년은 13 ~ 24번째 칸이고 약 250,000행, 2,292블록이다. 보관 기간이 지난 달은 DROP PARTITION으로 정리한다.
  • CF11_H 판매상품(분석 복사본, 문제 설정): CF11 1,096,708행을 PARTITION BY HASH (SLE_NO) PARTITIONS 8로 CTAS했다. 판매일 컬럼이 없어 연도로 칸을 고를 수 없다. 8칸 전체는 약 8,208블록이다. 2023년 판매의 판매상품은 약 548,000행이다.
  • 조인 키는 SLE_NO다. 매장 5곳 × 상품 109개라서 결과는 545행이다. 인덱스는 이 조회에 쓰이지 않는다.
  • 분석 DB는 EE이고 자동 PGA 관리를 쓴다. 병렬 작업에서는 PX 서버 하나가 쓸 수 있는 해시 작업 영역이 DOP가 커질수록 작아진다(이 DB에서 DOP 2이면 약 25MB, DOP 8이면 약 6MB). 판매 25만 행으로 지은 해시 테이블은 약 11MB다. 통계는 최신이다.

원본 SQL ​

sql
-- 2021.05 · DOP 2 에서 분배 방식을 고정했다
SELECT /*+ LEADING(s h) USE_HASH(h) PQ_DISTRIBUTE(h BROADCAST NONE) PARALLEL(8) */
       s.SHP_NO, h.ITM_NO, SUM(h.SLE_QTY) AS qty, SUM(h.SLE_QTY * h.SLE_UPRC) AS amt
  FROM CF10_P s, CF11_H h
 WHERE s.SLE_DT BETWEEN '20230101' AND '20231231'
   AND h.SLE_NO = s.SLE_NO
 GROUP BY s.SHP_NO, h.ITM_NO

실행계획 학습용 예시 · 병렬 계획은 EE 모양(교재 14화 §9.3) · FORMAT => 'ALLSTATS ALL' ​

xplan
------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name     | Starts | A-Rows | Buffers | Used-Mem  | Used-Tmp | Pstart | Pstop |    TQ  |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |          |      1 |    545 |   10500 |           |          |        |       |        |      |            |
|   1 |  PX COORDINATOR                  |          |      1 |    545 |   10500 |           |          |        |       |        |      |            |
|   2 |   PX SEND QC (RANDOM)            | :TQ10002 |      8 |    545 |   10500 |           |          |        |       |  Q1,02 | P->S | QC (RAND)  |
|   3 |    HASH GROUP BY                 |          |      8 |    545 |   10500 |     389K  |          |        |       |  Q1,02 | PCWP |            |
|   4 |     PX RECEIVE                   |          |      8 |   4360 |   10500 |           |          |        |       |  Q1,02 | PCWP |            |
|   5 |      PX SEND HASH                | :TQ10001 |      8 |   4360 |   10500 |           |          |        |       |  Q1,01 | P->P | HASH       |
|   6 |       HASH GROUP BY              |          |      8 |   4360 |   10500 |     412K  |          |        |       |  Q1,01 | PCWP |            |
|*  7 |        HASH JOIN                 |          |      8 |   548K |   10500 | 5952K (1) |      11M |        |       |  Q1,01 | PCWP |            |
|   8 |         PX RECEIVE               |          |      8 |  2000K |    2292 |           |          |        |       |  Q1,01 | PCWP |            |
|   9 |          PX SEND BROADCAST       | :TQ10000 |      8 |  2000K |    2292 |           |          |        |       |  Q1,00 | P->P | BROADCAST  |
|  10 |           PX BLOCK ITERATOR      |          |      8 |   250K |    2292 |           |          |     13 |    24 |  Q1,00 | PCWC |            |
|* 11 |            TABLE ACCESS FULL     | CF10_P   |    104 |   250K |    2292 |           |          |     13 |    24 |  Q1,00 | PCWP |            |
|  12 |         PX BLOCK ITERATOR        |          |      8 |  1096K |    8208 |           |          |      1 |     8 |  Q1,01 | PCWC |            |
|  13 |          TABLE ACCESS FULL       | CF11_H   |    104 |  1096K |    8208 |           |          |      1 |     8 |  Q1,01 | PCWP |            |
------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   7 - access("H"."SLE_NO"="S"."SLE_NO")
  11 - filter(("S"."SLE_DT">='20230101' AND "S"."SLE_DT"<='20231231'))

  (Used-Mem · Used-Tmp 은 서버 하나의 값 · 서버 여덟이 같은 크기의 해시 테이블을 따로 짓는다)

[같은 SQL · DOP 2 (지난 분기) 와 DOP 8 (이번 분기)]
                                     DOP 2          DOP 8
  경과                                6.1초          8.4초
  TQ10000 이 보낸 판매 행             500,000        2,000,000   (25만 × 서버 수)
  서버 하나의 해시 작업 영역            약 25 MB       약 6 MB
  해시 조인                           메모리 안 (0)   원패스 (1) · 서버마다 TEMP 약 11 MB · 합계 약 88 MB

문제 ​

  1. DOP를 2에서 8로 올렸는데 오히려 느려지고 TEMP를 쓰게 된 까닭을 실행계획의 9번 줄과 7번 줄, 서버 하나가 쓸 수 있는 해시 작업 영역으로 설명하시오.
  2. ⓐ 힌트를 떼고 두 테이블을 해시로 다시 나눠 보내는 안, ⓑ 판매만 판매상품의 칸 기준에 맞춰 보내는 부분 파티션 와이즈 조인, ⓒ 판매를 판매일 Range와 판매번호 Hash로 다시 나눠 완전 파티션 와이즈 조인을 하는 안을 보내는 행 수, 서버마다의 해시 테이블 크기, 설계 변경의 대가로 비교하시오.
  3. 고른 안의 SQL을 작성하고, 그 힌트가 앞으로 언제 다시 틀릴 수 있는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자