Skip to content
튜닝 연습DML과 배치 튜닝
DML-064

12월 카드 승인 재대사를 작업 8개로 나눠 돌렸는데 한 작업만 12분 넘게 돌고 일곱은 1초 만에 끝났다

중급PLSQL버거489 / 700

상황 ​

버거원 본사는 카드사의 요청으로 2013년 12월 주문 전부의 카드 승인 내역을 다시 대사한다. 주문 한 건마다 카드사 중계 서버에 승인 내역을 묻고 결과를 대사 테이블에 적는데, 한 건에 평균 18ms가 걸린다(대부분 중계 서버의 응답 대기). 중계 서버는 동시 연결을 8개까지 받는다. 개발자는 DBMS_PARALLEL_EXECUTE로 주문번호를 8조각으로 나눠 작업 8개로 돌렸다. 그런데 작업 7개는 1초 안에 끝났고 한 작업만 12분 48초를 돌았다. 오과장: "한 줄로 돌린 것과 똑같잖아요. 작업을 16개로 늘리면 될까요?"

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록(블록당 약 45행) · ORD_NO NUMBER 1 ~ 1,000,000(빈 번호 없음, 주문 일시 순으로 매겼다) · 테이블도 주문번호 순서로 쌓였다
  • 인덱스 ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · 2013년 12월 주문 42,408건은 ORD_NO 957,593 ~ 1,000,000이다(ORD_X01 리프 약 140블록, 테이블 끝의 951블록).
  • PKG_CARD_IF.RECON(p_ord_no): 주문 한 건의 승인 내역을 중계 서버에 묻고 대사 테이블 CARD_RECON(이 문제용, PK ORD_NO)에 한 행을 넣는다. 주문끼리 같은 행을 고치지 않는다.
  • 배치 계정에는 CREATE JOB 권한이 있다.

원본 SQL ​

sql
-- 청크 하나가 하는 일 (RUN_TASK 가 청크마다 부르고, 끝나면 커밋한다)
PROCEDURE P_CARD_RECON(p_start NUMBER, p_end NUMBER) IS
BEGIN
  FOR r IN (SELECT ORD_NO
              FROM ORD
             WHERE ORD_NO BETWEEN p_start AND p_end
               AND ORD_DT BETWEEN '20131201' AND '20131231') LOOP
    PKG_CARD_IF.RECON(r.ORD_NO);                             -- 중계 서버 왕복 · 평균 18ms
  END LOOP;
END;

-- 작업 등록 (주문번호를 125,000씩 8조각 · 동시 작업 8개)
BEGIN
  DBMS_PARALLEL_EXECUTE.CREATE_TASK('RECON_201312');
  DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_NUMBER_COL('RECON_201312', 'DEV', 'ORD', 'ORD_NO', chunk_size => 125000);
  DBMS_PARALLEL_EXECUTE.RUN_TASK('RECON_201312', 'BEGIN P_CARD_RECON(:start_id, :end_id); END;',
                                 DBMS_SQL.NATIVE, parallel_level => 8);
END;

실행계획 학습용 예시 ​

xplan
[USER_PARALLEL_EXECUTE_CHUNKS — 작업 RECON_201312]
  CHUNK_ID    START_ID      END_ID    대상 주문    경과
      1             1      125,000          0      0.3초
      2       125,001      250,000          0      0.3초
      …
      7       750,001      875,000          0      0.3초
      8       875,001    1,000,000     42,408      12분 48초
  작업 전체 경과 12분 49초 · 일한 작업 수: 처음 1초 동안 8개, 그 뒤로 1개

[청크 1의 커서 — DISPLAY_CURSOR (ALLSTATS LAST)]
-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |         |      1 |        |      0 |00:00:00.02 |    1091 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| ORD     |      1 |   5302 |      0 |00:00:00.02 |    1091 |
|*  2 |   INDEX RANGE SCAN                  | ORD_X01 |      1 |  42419 |  42408 |00:00:00.01 |     140 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(("ORD_NO">=:B2 AND "ORD_NO"<=:B1))
   2 - access("ORD_DT">='20131201' AND "ORD_DT"<='20131231')

문제 ​

  1. 작업 8개로 나눴는데도 한 작업만 12분 넘게 돈 이유를 청크 표로 설명하시오. 작업을 16개로 늘리면 어떻게 될지도 쓰시오.
  2. 작업마다 비슷한 양을 맡도록 청크를 다시 만들어 작성하시오. 청크 수를 작업 수와 같게 할지 더 많게 할지도 쓰시오.
  3. CREATE_CHUNKS_BY_ROWID로 바꾸면 해결되는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자