Skip to content

06화 · 100만 번의 왕복 — 힌트의 유효기간 ​

🗓️ 4월 8일 (월) · 05화에서 일주일 뒤 · 무대 ora-hanorder오늘 여는 것: Q2. 01화에서 전체 부하의 98%를 차지한 바로 그 조각이다. 오늘은 네 번 틀린다. 그리고 마지막에 지우는 것은 29글자다.


파일 세 개 ​

파일무엇을 하나시간
읽는다1_사건.md힌트를 찾아내고, 2018년 회의록을 뒤지고, 그 시절을 재현해 본 끝에 힌트를 떼어 내는 하루35분
한다3_실습과_기록.md조인 3종 대조 · 2018년 재현 · work area 3등급 · T1 4호 · 대장 1호60~90분
찾는다2_파고들기.mdStarts · NL 총일량 · build/probe · 손익분기점 재는 법 · PGA와 work area필요할 때

1을 읽고 3을 따라 한다. 2는 막힐 때 펼친다. 시간이 없는 주에는 1_사건을 중단 지점 1까지 읽고 3_실습은 STEP 0, 1, 2, 6만 한다(약 45분).


지난 화에서 들고 오는 것 ​

Q1 = 91블록02화에서 5,827 → 91. 커버링 인덱스로 64배
Q2 = 1,026,849블록01화에서 잰 뒤로 아직 손대지 않았다
T_ORD_BIG 인덱스2개 (IX_ORD_BIG_DT, IX_ORD_BIG_DT_AMT)
02화에서 배운 것"테이블을 읽으러 갈 이유를 없애면 된다" ← 오늘은 이게 안 통한다

그리고 박팀장에게서 메시지가 하나 와 있다.

"Q1 건은 잘 봤습니다. 그런데 조민우 씨가 말하는 '등급별 집계 화면'은 아직 그대로라고 합니다. 그게 제일 느린 거 아니었나요?"


오늘 여는 코드 ​

sql
-- 건드리지 말 것 (2018.11 강도현)
SELECT /*+ LEADING(o c) USE_NL(c) */
       c.CUS_GD, COUNT(*), SUM(o.ORD_AMT),
       MAX((SELECT r.RGN_NM FROM M_RGN r WHERE r.RGN_ID = c.RGN_ID))
  FROM T_ORD_BIG o, M_CUS c
 WHERE c.CUS_ID = o.CUS_ID
 GROUP BY c.CUS_GD;

【그때의 근거】 2018-11-06 성능개선 회의록"등급별 집계 3초 → USE_NL 적용 후 0.4초. 적용 승인."

회의록에 틀린 줄은 하나도 없다. 현상도, 조치도, 결과도, 승인도 사실이었다. 빠진 것은 두 줄이다. "그때 데이터는 2만 건이었다"와 "언제 다시 잴 것인가"다.


오늘의 전개 — 네 번 틀리고 29글자를 지운다 ​

내가 믿은 것측정
1차"M_CUS에 커버링 인덱스를 만들면 된다" (02화에서 생긴 관성)36,852. 28배. 그런데 Starts는 1,000,000 그대로
2차"2018년 2만 건에서는 NL이 옳았다"2만 건에서도 해시가 약 138배 이겼다
3차"그럼 NL은 그냥 나쁜 거네"20건까지는 NL이 이긴다(실측 손익분기점 21건)
4차"스칼라 서브쿼리도 100만 번 돌 거다"12블록. Starts는 8. 범인이 아니다
—힌트 29글자를 지웠다1,026,849 → 5,869. 175배

왜 네 번이나 틀리게 짰을까? 02화에서는 "인덱스를 만들면 빨라진다"는 믿음이 깨졌다. 오늘은 그 자리를 차지한 "커버링 인덱스를 만들면 된다"가 깨진다. "옛 코드도 그때는 옳았다"는 편한 이야기도 함께 깨진다.


오늘 배우는 한 문장 ​

조인 3종은 결국 "바깥 한 건에 맞는 안쪽 행을 어디서 찾느냐"만 다르다.

방식안쪽을 어디서비용
NL매번 원래 자리로 가서outer 건수 N에 비례
해시내 책상(PGA)에 복사해 둔 더미에서양쪽을 한 번씩 읽는 값
소트 머지양쪽을 줄 세워 나란히 놓고양쪽 정렬 비용 + 한 번씩

NL만 N이 곱해진다. 그래서 데이터가 늘어나면 NL 비용만 함께 커진다.

하나 더 있다.

힌트는 그 시점의 데이터 크기를 보고 내린 판단이다. 판단을 기록할 때 전제를 같이 적지 않으면, 전제가 바뀌어도 아무도 알아채지 못한다.


🔑 오늘의 비유 — 4층 거래처 카드함 ​

선배가 전표 스무 장을 건네며 "4층 자료실에서 거래처 등급을 알아 와"라고 시킨다. 한 장마다 계단을 오르내리다가 네 번째에서 포기하고, 카드함을 통째로 복사해 책상에 펼친다.

사무실오라클
전표 한 장마다 4층 왕복NL 조인 · Starts = 왕복 횟수
카드함을 한 번에 복사해 책상에 펼치기해시 조인 · build
전표를 넘기며 책상에서 집기probe
내 책상(남이 안 보니 자물쇠가 없다)PGA(공유하지 않으니 래치가 없다)
책상 넓이work area (OMem · Used-Mem)
책상이 좁아 옆 회의실 테이블에 쌓아 두기TEMP(Used-Tmp) · onepass / multipass

(01화의 비유는 커피 캐리어(블록), 02화는 방문객 출입대장(인덱스 = 정렬된 목록)이었다.)


☠️ 오늘의 함정 — 블록은 그대로인데 느려진다 ​

rev01의 175배는 해시 조인이 메모리 안에서 끝날 때 얻은 값이다. 책상을 64KB로 좁혀 보면 이렇게 된다.

Buffers시간
optimal(0패스)5,8690.17초
4패스(Used-Tmp 24M)5,8685~8초

일량은 한 블록도 늘지 않았다. T0 측정규약(Buffers로 말한다)으로는 잡히지 않는 첫 번째 경우다. 그래서 리포트의 「환경 의존성」 칸에 적어 두고 09화로 넘긴다.


실습 준비 ​

bash
docker start ora-hanorder

02화를 마친 상태(T_ORD_BIG 인덱스 2개 · M_CUS 2,000건 · M_RGN 8건)에서 시작한다. 스크립트 세 개 모두 맨 앞에서 임시 객체를 치우고, 끝나면 원래 상태로 되돌린다. 그래서 몇 번을 돌려도 결과가 같다.

스크립트무엇걸리는 시간
실습/00_조인_3종.sql[A]~[H]. 오늘 실습의 본체약 4초
실습/01_2018년_재현.sql크기별 NL vs 해시. 손익분기점약 3초
실습/02_책상_좁히기.sqlwork area 3등급 · V$SQL_WORKAREA약 30초(일부러 느리게 돌린다)

리비전은 리비전/에 Q2_rev00 → Q2_rev01 순서로 남는다.

ℹ️ 이 교재의 실습은 Docker로 띄운 Oracle Database Free 한 대에서 모두 끝난다. work area 등급이 뒤집히는 현상도 같은 컨테이너 안에서 세션 파라미터(WORKAREA_SIZE_POLICY=MANUAL + HASH_AREA_SIZE)로 재현한다. 서버 메모리를 바꿔 가며 대규모 EE 환경과 비교하는 일은 이 교재에서 다루지 않는다.


이 화의 산출물 ​

무엇어디
Q2_rev01: 1,026,849 → 5,869(175배)리비전/Q2_rev01.sql
T1 리포트 4호: 「환경 의존성」을 처음 채운 리포트3_실습과_기록.md STEP 6
「힌트 유효기간 대장」 ★ 새 표준 문서40_보고서와_표준/템플릿/힌트_유효기간_대장.md

이 화가 다음으로 넘기는 것 ​

무엇어디로
M_RGN 쪽 Starts가 8인 이유(그리고 그게 안 될 때)07화: 스칼라 서브쿼리 캐싱
PGA에 래치가 없는 이유(= 버퍼 캐시에는 있는 이유)07화
Used-Tmp와 패스 수(4 PASSES · 100 PASSES)09화: 소트와 TEMP
100만 행을 읽어 3행을 만드는 게 맞는 요구인가08화
rev01의 유효기간(M_CUS가 커지면)대장에 적어 두고 18화에서 다시 잰다

← 05화 「블록이라는 그릇」 · 교재 전체 안내 · 다음 → 07화 「히트율 99퍼센트인데 서버가 탄다」