Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-033

엑셀 내려받기를 고친 다음 날부터 고객센터 주문 이력 화면이 굼뜨고 WAS 힙이 두 배가 됐다

실전SELECT한빛유통578 / 700

상황 ​

지난주 정산팀이 엑셀 내려받기가 느리다고 하자, 개발팀은 커넥션 풀 설정에 defaultRowPrefetch = 5000을 넣었다. 드라이버 기본값(10)을 모든 문장에 대해 바꾼 것이다. 내려받기는 1.9초 → 0.3초가 됐다. 다음 날부터 고객센터 「고객 주문 이력」 화면이 0.01초에서 0.3초 안팎으로 느려졌고, 화면 서버(WAS)의 힙 사용량이 늘어 GC가 잦아졌다. 상담원은 주문 이력의 첫 화면(20건)만 보고 닫는 일이 대부분이다. 박팀장이 문장마다 fetch size를 어떻게 둘지 정하고, 무엇을 잃는지도 적어 달라고 했다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객 2,000명이라 고객 한 명의 주문은 약 500건 · 같은 고객의 주문은 테이블 곳곳에 흩어져 있다(클러스터링 팩터가 나쁘다)
  • 인덱스 IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT)(이 문제에만 있다) · IX_ORD_BIG_DT (ORD_DT) · 통계 최신
  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ)
  • WAS와 DB는 같은 IDC에 있다(왕복 0.5ms). 고객센터가 여는 고객은 그때그때 달라서 그 고객의 주문 블록은 버퍼 캐시에 없을 때가 많다.

원본 SQL ​

sql
// DataSourceConfig.java (지난주 반영) : 커넥션 풀의 모든 연결에 건다
props.setProperty("defaultRowPrefetch", "5000");

-- [S1] 정산팀 엑셀 내려받기 : 한 달 치 전부 (6월 27,390건)
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM T_ORD_BIG
 WHERE ORD_DT >= :d_from AND ORD_DT < :d_to
 ORDER BY ORD_DT, ORD_SEQ;

-- [S2] 고객센터 고객 주문 이력 : 화면은 앞 20건을 보여 주고, 상담원이 닫으면 ResultSet을 닫는다
SELECT ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
  FROM T_ORD_BIG
 WHERE CUS_ID = :cus
 ORDER BY ORD_DT DESC;

-- [S3] 주문 한 건 상세 : PK 단건
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, PAY_TP, ORD_AMT, PAY_AMT
  FROM T_ORD
 WHERE ORD_SEQ = :seq;

실행계획 학습용 예시 · [S1]의 왕복 수는 교재 09화 실측과 같다 ​

xplan
[S2 · 같은 고객 (CUS_000731) · DISPLAY_CURSOR 'ALLSTATS LAST']

(지난주까지 · fetch size 10 · 화면이 20건을 읽고 닫음)
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                   |      1 |        |     20 |00:00:00.01 |      24 |     19 |
|   1 |  TABLE ACCESS BY INDEX ROWID  | T_ORD_BIG         |      1 |    500 |     20 |00:00:00.01 |      24 |     19 |
|*  2 |   INDEX RANGE SCAN DESCENDING | IX_ORD_BIG_CUS_DT |      1 |    500 |     20 |00:00:00.01 |       4 |      1 |
----------------------------------------------------------------------------------------------------------------------

(지금 · defaultRowPrefetch 5000 · 화면은 똑같이 20건만 쓰고 닫음)
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                   |      1 |        |    500 |00:00:00.29 |     506 |    471 |
|   1 |  TABLE ACCESS BY INDEX ROWID  | T_ORD_BIG         |      1 |    500 |    500 |00:00:00.29 |     506 |    471 |
|*  2 |   INDEX RANGE SCAN DESCENDING | IX_ORD_BIG_CUS_DT |      1 |    500 |    500 |00:00:00.01 |       6 |      2 |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("CUS_ID"=:CUS)

[S1 · 엑셀 내려받기 · FULL + SORT ORDER BY · Used-Mem 1.9M]
  fetch size     페치   SQL*Net roundtrips   Buffers    경과
      10        2,740         2,741           5,826     1.9 초
    5000            6             7           5,826     0.3 초
    (1000으로 재 보면)  28       29           5,826     0.3 초

[S3 · 주문 한 건]  Buffers 3 · 페치 1 · 설정과 상관없이 같다

[WAS · 지난주 대비]  힙 사용량 1.1GB → 2.3GB · GC 시간당 40번 → 310번

문제 ​

  1. 모든 문장의 fetch size를 5000으로 바꾼 뒤 [S1]은 빨라지고 [S2]는 느려진 이유를 위 계획과 숫자로 설명하시오.
  2. [S1] ~ [S3]마다 fetch size를 어떻게 둘지 정하고, [S2]는 설정과 상관없이 20건만 읽도록 SQL을 고치시오.
  3. 고른 안의 대가를 쓰고, 전역 설정을 5000으로 두는 안과 견주어 무엇을 잃는지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자