Skip to content

09화 · 실습과 기록 ​

여기서 하는 일: 페이지 곡선을 그리고, 책상을 좁혀 절벽을 찾고, 정렬을 없앤 다음 세는 일까지 없앤다.1_사건.md를 읽은 것은 "선배가 하는 걸 지켜본 것"이고, 이 문서를 끝내야 직접 해 본 것이 된다. 개념이 막히면 2_파고들기.md를 펼친다.

시간이 없는 주에는 STEP 0 → 1 → 2 → 4 → 5 → 8만 한다(약 50분).


STEP 0 — 직전 리비전을 연다 (3분) ★ 매주 처음 하는 일 ​

bash
docker start ora-hanorder
bash
cat 리비전/Q4_rev00.sql

오늘 고칠 SQL부터 눈으로 확인한다. 01화에서 잰 값은 Q4 5,826 + Q4c 5,826 = 11,652였다. 조건이 rn > 990이니 처음부터 100페이지째를 재고 있었던 것이다.

그리고 세 가지를 확인한다.

sql
-- ① T_ORD_BIG 의 인덱스 (기대: IX_ORD_BIG_DT, IX_ORD_BIG_DT_AMT — 02화 산출물)
SELECT index_name FROM user_indexes WHERE table_name = 'T_ORD_BIG' ORDER BY 1;

-- ② ORD_DT 와 ORD_SEQ 의 NULL 허용 여부 (기대: ORD_DT Y · ORD_SEQ N)  ← 오늘의 열쇠
SELECT column_name, nullable FROM user_tab_columns
 WHERE table_name = 'T_ORD_BIG' AND column_name IN ('ORD_DT','ORD_SEQ');

-- ③ work area 정책 (기대: AUTO)
SELECT value FROM v$parameter WHERE name = 'workarea_size_policy';

T_ORD_BIG에 위 두 개 말고 다른 인덱스가 있으면 그 인덱스부터 확인해야 한다. 그 인덱스가 Q4의 계획을 바꿀 수 있다. (IX_ORD_BIG_DT_SEQ가 이미 있다면 이 화를 한 번 돌린 것이다. 스크립트가 알아서 지우고 시작한다.)


STEP 1 — 예측을 봉인한다 (4분) ​

정산 목록 화면: "1페이지는 괜찮은데 100페이지쯤 넘어가면 멈춘다"
운영팀: "PGA 부터 올리자"

Q1. 1페이지와 100페이지의 Buffers 는 각각 얼마일 것 같은가?   1페이지 ______ / 100페이지 ______

Q2. PGA(정렬 책상)를 16배 늘리면 100페이지의 Buffers 는 어떻게 될 것 같은가?
    ______________

Q3. 02화에서 ORD_DT 에 인덱스를 만들었다. Q4 는 ORDER BY ORD_DT DESC, ORD_SEQ DESC 다.
    지금 Q4 는 그 인덱스를 쓰고 있을 것 같은가? 아니라면 이유는?
    ______________

Q4. 정렬을 인덱스로 대신하게 만들면, 1,000페이지의 Buffers 는 1페이지의 몇 배쯤일 것 같은가?
    ______________
▶ 적고 나서 펼치기

Q1: 대부분 "1페이지는 작고 100페이지는 크다"고 쓴다. 실측값은 둘 다 5,826이다. 정렬은 다 읽어야 첫 줄이 나온다.

Q2: "줄어든다"고 쓰기 쉽다. 실측값은 5,826 그대로다(64KB에서 TEMP 군살로 붙었던 1,336만 빠진다). 메모리는 정렬을 빨리 하게 할 뿐 안 하게 하지는 못한다.

Q3: 안 쓴다. 힌트를 줘도 무시된다. 이유는 두 가지다. ① ORD_DT가 NULL 허용이라 단일 컬럼 인덱스가 "모든 행"을 약속하지 못한다. ② 정렬 키 ORD_SEQ가 인덱스에 없다.

Q4: "비슷하다"고 쓰기 쉽다. 실측값은 1페이지 14 → 1,000페이지 10,037(인덱스 강제 시)로 약 700배다. 정렬은 없어졌지만 앞 페이지를 세는 비용이 드러난다. 게다가 옵티마이저는 935페이지부터 정렬 계획으로 돌아간다.


STEP 2 — 페이지 곡선을 그린다 (5분) ★ ​

bash
cd 10_본편_튜너의_1년/09화_100페이지째부터_화면이_멈춘다/실습
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 00_페이지_곡선.sql
powershell
Set-Location 10_본편_튜너의_1년\09화_100페이지째부터_화면이_멈춘다\실습
Get-Content 00_페이지_곡선.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1

스크립트는 맨 앞에서 09화 인덱스(IX_ORD_BIG_DT_SEQ)가 있으면 지우고 통계를 다시 수집한다. 그래서 몇 번을 돌려도 결과가 같다. 약 3초 걸린다.

단계무엇기대☐
[A]Q4 rev00(100페이지)SORT ORDER BY STOPKEY + FULL · 5,826 · 206K (0)☐
[B]Q4cFULL · 5,826☐
[C]페이지 1 · 10 · 100 · 1,000 · 10,000 · 100,000BUFFERS 전부 5,826 · USED_MEM_KB 4 → 74,638☐
[D1]02화 인덱스 + INDEX_DESC 힌트힌트 무시 · FULL☐
[D2]ORD_DT NULL 허용?Y · 실제 NULL 0건☐
[D3]IS NOT NULL + ORDER BY ORD_DT DESCINDEX FULL SCAN DESCENDING · 정렬 없음 · 1,007☐
[D4]+ ORD_SEQ DESC + 힌트인덱스 100만 + 테이블 100만 + 정렬 · 1,002K☐
[D5]하루 건수913~914☐
[E]ROWNUM을 바깥에만SORT ORDER BY(STOPKEY 없음) · 48M☐
[E2]OFFSET … FETCHWINDOW SORT PUSHED RANK · 206K☐

[C]의 표를 옮겨 적는다 ​

페이지BUFFERSUSED_MEM_KBLAST_EXECMS
1
10
100
1,000
10,000
100,000

📌 이 표에서 볼 것은 두 열이다 ​

BUFFERS는 평평하다. 1페이지도 테이블 전체를 읽는다. "1페이지는 괜찮다"는 말은 빨리 끝나서 아무도 몰랐다는 뜻일 뿐이다. USED_MEM_KB는 커진다. 페이지가 깊을수록 줄 세울 건수(= 페이지 × 10)가 늘어난다. 오늘 화면이 "멈추는" 원인의 씨앗이 이 열에 있다.


STEP 3 — 책상을 좁혀 절벽을 찾는다 (10분 · 실행에 20초~1분) ​

bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 01_책상_좁히기_정렬.sql
powershell
Get-Content 01_책상_좁히기_정렬.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1

⚠️ 느려지는 것이 정상이다 ​

[W1]의 64페이지와 100페이지는 각각 몇 초씩(디스크가 바쁘면 20초 넘게) 걸린다. 멈춘 게 아니다. 전부 ALTER SESSION이라 다른 세션에는 영향이 없고, 끝에서 AUTO로 되돌린다. 왜 MANUAL로 하나? 운영에서는 동시 사용자가 많을수록 한 사람 몫의 책상이 좁아진다. Free 컨테이너 한 대로는 그런 동시 부하를 만들 수 없어서, 06화처럼 내 세션의 책상을 직접 정해 재현한다. 서버 메모리를 바꿔 가며 비교하는 대규모 EE 환경 대조는 이 교재의 범위 밖이다(별권).

[요약] 표에서 확인할 것 ​

TAG · 책상 · 페이지기대 등급기대 BUFFERS (CU_GETS)☐
W0 · AUTO · 100OPTIMAL5,826 (0)☐
W1 · 64K · 1 / 10OPTIMAL5,826 (0)☐
W1 · 64K · 501 PASS5,832 (6)☐
W1 · 64K · 635 PASSES5,859 (33)☐
W1 · 64K · 64184 PASSES7,161 (1,335) · TEMP 26MB☐
W1 · 64K · 100184 PASSES7,162 (1,336) · TEMP 29MB☐
W2 · 1M · 100OPTIMAL5,826☐
W2 · 1M · 1,000 / 10,0001 PASS / 2 PASSES5,831 / 5,901☐
W3 · 16M · 1,000 / 10,000OPTIMAL5,826☐
W4 · AUTO · 100,000OPTIMAL · USED_KB 74,6385,826☐

💡 컨테이너를 새로 만든 뒤 처음 돌리면 TEMP를 쓰는 줄(64K의 64/100페이지, 1M의 10,000페이지)에서 괄호 안 숫자가 수십 블록 더 나온다(실측: 1,335 → 1,391 · 1,336 → 1,342 · 75 → 94). TEMP 공간을 처음 잡느라 생기는 몫이다. 한 번 더 돌리면 위 표와 끝자리까지 같아진다. 패스 수는 처음부터 같다.

그리고 100페이지를 한 번 볼 때의 세션 델타를 확인한다.

☐ sorts (disk) 가 1 올랐는가
☐ temp_writes 가 3,674 블록인가
☐ direct path write temp 가 3,674 번인가 (기다린 시간은 흔들린다 — 기록만 한다: ______ ms)
☐ 마지막에 workarea_size_policy 가 AUTO 로 돌아갔는가

☠️ 이 표가 STEP 3의 전부다 ​

64KB1MB16MB
100페이지184 PASSESoptimaloptimal
10,000페이지290 PASSES2 PASSESoptimal
Buffers5,826 + TEMP 군살5,826 + 약간5,826

책상을 넓히면 "멈추는 페이지"가 뒤로 밀릴 뿐, 없어지지 않는다. 그리고 Buffers는 5,826 아래로 한 블록도 내려가지 않는다. 마지막 페이지를 optimal로 끝내려면 사용자 한 명이 한 번 누를 때마다 74,638K가 필요하다.

(선택) 그 몇 초 동안 무슨 일이 있었나 — 10032 트레이스 ​

bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 90_정렬_트레이스_10032.sql
docker exec ora-hanorder bash -c "grep -E 'max intermediate merge width|Initial runs|Number of merges|Run directory block reads' /opt/oracle/diag/rdbms/free/FREE/trace/*CH09_S*.trc"
64KB · 100페이지1MB · 1,000페이지
합치기 폭 (max intermediate merge width)329
묶음 (Initial runs)9204
합치기 횟수 (Number of merges)1841
Run directory block reads1,336 ← 위 표의 CU_GETS5

트레이스 파일은 실행할 때마다 새로 생긴다. 위 grep이 여러 파일을 보여 주면 가장 최근 것(파일명의 프로세스 번호가 큰 것)을 본다.


STEP 4 — 인덱스로 정렬을 대신한다 (10분) ★ Q4 rev01 ​

bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 02_인덱스로_정렬_대신.sql
powershell
Get-Content 02_인덱스로_정렬_대신.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1

인덱스 IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ)를 만든다. 09화의 산출물이므로 끝에서 지우지 않는다. 약 5초 걸린다.

단계무엇기대☐
[B]rev01: SQL은 rev00 그대로SORT 줄이 사라진다 · INDEX FULL SCAN DESCENDING · 1,007 · OMem/Used-Mem 열 없음☐
[C1]Q4c (안 고쳤다)INDEX FAST FULL SCAN · 3,365 ← 계획이 저절로 바뀌었다☐
[C2]Q1그대로 IX_ORD_BIG_DT_AMT · 91☐
[C3]Q2그대로 HASH JOIN · 5,868☐
[D](참고) INVISIBLE 검증Q4c는 인덱스를 쓰고 Q4는 안 쓴다 · 힌트로는 1,007☐
[E]rev01 페이지 곡선14 · 104 · 1,007 · 5,020 · 6,024 · (1,000부터) 5,826 정렬☐
[F]갈아타는 페이지934 인덱스 · 935 정렬☐
[G]인덱스 끝까지 강제10,037 · 100,338 · 1,003,347☐
[H]늦은 테이블 방문16 · 46 · 347 · 3,356☐

각 단계에서 눈으로 짚을 것 ​

[B] — 계획에서 사라진 것 두 가지
    ☐ SORT ORDER BY STOPKEY 줄이 없는가
    ☐ OMem / 1Mem / Used-Mem 열이 통째로 없는가      ← 책상이 필요 없다 = PGA 와 무관하다
    ☐ INDEX FULL SCAN DESCENDING 의 Buffers 가 7 이고, 나머지 1,000 이 테이블인가

[C1] — 손대지 않은 SQL 의 계획이 바뀌었다
    ☐ 왜 바뀌었는지 한 문장으로: _______________________________________
    => 02화 선배의 말: "운영 중인 시스템에 갑자기 인덱스가 생기면 수십 개 쿼리의 계획이 동시에 바뀐다"

[E][G] — 정렬은 없어졌는데 페이지가 깊을수록 커진다
    ☐ 한 페이지에 몇 블록씩 느는가? ______   ← 왜 그 숫자인가 (CF 1,000,000)
    ☐ 600페이지에서 이미 FULL(5,826)보다 많은가

[H] — 버릴 행을 인덱스에서만 세면
    ☐ 기울기가 0 이 되었는가? (아니다 — 그래서 STEP 5 가 있다)

⚠️ [D] — 이 버전에서 확인한 INVISIBLE의 함정 ​

INVISIBLE + optimizer_use_invisible_indexes = TRUE 상태에서 Q4c는 새 인덱스를 썼지만 정작 Q4는 정렬 계획 그대로였다. 원인은 확인하지 못했다. INVISIBLE 검증은 '안 바뀌어야 할 SQL'을 잡는 데 쓰고, 정렬 대체 계획은 힌트로 따로 확인한다. 자세한 내용은 2_파고들기.md §7에 있다.


STEP 5 — 포스트잇을 붙인다 (10분) ★ Q4 rev02 ​

bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 03_포스트잇.sql
powershell
Get-Content 03_포스트잇.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1

IX_ORD_BIG_DT_SEQ가 없으면 맨 앞에서 만든다. 약 5초 걸린다([D3] 전체 순회가 3~4초).

단계무엇기대☐
[A]1페이지: 11건을 가져온다COUNT STOPKEY · 정렬 없음 · 16 · 11번째 행 = 포스트잇☐
[B]2 · 3페이지: 포스트잇 자리부터INDEX RANGE SCAN DESCENDING · access("ORD_DT"<=…) · 15☐
[C]100 · 1,000 · 10,000 · 마지막 페이지15 · 18 · 17 · 17 · 마지막 페이지만 10건☐
[D1]정합성 B: 100페이지 건수 · 합계 · MINUS10 = 10 · 2,627,200 = 2,627,200 · 0 / 0☐
[D2]정합성 E: 날짜 경계 92페이지10 = 10 · 2,848,500 · 0 / 0 · 날짜 2개☐
[D3]정합성 C: 10만 페이지 전체 순회pages=100000 rows=1000000 sum=255398623700 mismatch=0☐
[D4] · [D5]정렬 키 중복 · Q4c의 답0 · 1,000,000☐
[E]날짜로 가기(12월 20일)15☐
[F]총건수 대안COUNT(*) 3,365 · 상한 1,001 13 · 통계 행수 14 · 다음 있음 0☐

손으로 해 보기 — 포스트잇이 어디에 붙는가 ​

[A]의 결과 11줄 중 11번째 줄의 K_DT와 K_SEQ를 적는다. 그 값이 [B]의 WHERE에 들어간 값인지 확인한다.

11번째 줄  K_DT = __________   K_SEQ = __________
[B] 의 SQL :  WHERE ORD_DT <= DATE '__________' AND (ORD_DT < DATE '__________' OR ORD_SEQ <= ______)

📌 곡선 세 개를 한 장에 ​

페이지rev00rev01 (인덱스 강제)rev02
15,8261416
1005,8261,00715
1,0005,82610,03718
마지막5,8261,003,34717

rev00은 평평하고 높다(정렬). rev01은 기울었다(세기). rev02는 평평하고 낮다(세지 않는다). rev01에서 rev02로 가는 것은 SQL만 바뀌는 일이 아니다. "100페이지로 바로 가기"와 "총건수"를 잃는다. 그래서 STEP 8이 있다.


STEP 6 — 엑셀 버튼: ARRAYSIZE와 Fetch Call (5분) ​

bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 04_ARRAYSIZE_와_Fetch.sql
powershell
Get-Content 04_ARRAYSIZE_와_Fetch.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1

채점표: 돌리기 전에 빈칸을 예상값으로 채워 보자.

ARRAYSIZE내 예상 consistent gets실측실측 roundtrips
정렬 있음(6월 27,390건)155,8271,827
〃1005,826275
〃10005,82629
정렬 없음157,6411,827
〃10005,85429

정렬이 끼면 ARRAYSIZE는 왕복 횟수만 바꾸고 일량은 바꾸지 못한다. 정렬은 첫 줄을 내주기 전에 전부 읽기 때문이다. 정렬이 없으면 일량까지 바뀐다. Fetch Call마다 읽던 블록을 다시 잡던 1,800번 가까운 읽기가 사라진다. 이것이 부분범위 처리다.


STEP 7 — 본사 매출 목록: 정렬 키가 틀렸다 (10분) ​

★ 컨테이너가 다르다.

bash
docker start ora-dist
docker exec -i ora-dist sqlplus -s pup/pup@localhost/FREEPDB1 < 05_본사_매출목록.sql
powershell
Get-Content 05_본사_매출목록.sql -Encoding UTF8 | docker exec -i ora-dist sqlplus -s pup/pup@localhost/FREEPDB1

03화를 마친 상태에서 시작한다. CF10 원본에는 한 글자도 쓰지 않는다. 교정 실험은 복사본 CF10_PG에서 하고 끝에서 지운다. 약 3초 걸린다.

단계무엇기대☐
[A]현업 화면 최신순 1페이지FULL + Top-N · 4,511 · 맨 윗줄 95503 (오전 9:55)☐
[B]원인길이 5 = 33,241건 · 12월 31일의 진짜 마지막 = 225914☐
[C]ORDER BY SLE_NO DESCCF10_PK 거꾸로 · 정렬 없음 · 7 · 100페이지 28 · 1,000페이지 225☐
[D]전제앞 8자리 = 판매일자 500,000/500,000 · 순서 역전 0 · 같은 초 겹침 13,585☐
[E]복사본에서 교정 후두 정렬의 순번이 어긋나는 행 0☐
[F]반납CF10의 길이 5 = 여전히 33,241(원본 그대로)☐

📌 1,000페이지: 225 vs 10,037 ​

같은 OFFSET인데 ora-dist는 225, 한빛유통(인덱스 강제)은 10,037이다. CF10_PK의 CF는 9,340(테이블 4,583블록)이고, T_ORD_BIG의 인덱스 CF는 1,000,000이다. 버리는 행이 같은 블록에 모여 있으면 세는 비용도 싸다. 04화의 커피 캐리어 이야기다.


STEP 8 — 「화면 요구 재협상서」 1호 ★ 이 교재의 새 표준 ​

양식은 화면요구_재협상서.md를 쓴다.

먼저 — 화면 주인에게 물을 질문을 고른다 ​

정산 목록 화면 (Q4 + Q4c). 지금 화면이 묻는 것:
  ① 페이지 번호 (1, 2, 3, … 100,000)  ② 총 N건

이 둘 각각에 대해, 화면 주인에게 "없애도 되나요?" 대신 무엇을 물어야 하는가?
  ① _______________________________________________
  ② _______________________________________________
▶ 1_사건의 질문

① "100페이지까지는 왜 넘기세요?" → "어제 마감분 보려고요." (하루 913~914건 = 91페이지 남짓) ② "총건수는 어디에 쓰세요?" → "0건인지만 봐요. 정확한 건수는 월말 정산 리포트에서 맞춰요."

"없애도 되나요?"라고 물으면 대부분 "있는 게 좋죠"라고 답한다. "왜"를 물어야 진짜 요구가 나온다.

재협상서 1호를 채운다 ​

양식의 1~7번 칸을 전부 채운다. 숫자는 STEP 2~5에서 직접 잰 값을 쓴다.

★ 7번 「다음 재협상 조건」을 비우면 이 화는 미완성이다 ​

rev02가 이길 수 있었던 전제는 세 가지이고, 셋 다 변할 수 있다.

  • 정렬 기준이 최신순 하나다 → 금액순이 생기면 그 순서의 인덱스와 포스트잇이 따로 필요하다
  • 화면에 검색 조건이 없다 → 거래처 조건이 생기면 인덱스 설계가 달라진다
  • 화면 주인이 이 요구를 기억한다 → 사람이 바뀌면 이 문서를 넘긴다

"필요시 재검토"라고 쓰면 반려된다. 구체적인 사건으로 적는다.


STEP 9 — T1 리포트 7호 ★ 오늘의 산출물 ​

양식은 T1_단건SQL_튜닝리포트.md를 따른다.

┌──────────────────────────────────────────────────────────────────┐
│ T1 단건 SQL 튜닝 리포트 — 7호                                     │
├──────────────────────────────────────────────────────────────────┤
│ 1. 대상      : PKG_ORD_STLM Q4 + Q4c · rev00 → rev02 · diff 첨부  │
│ 2. 측정 환경 : ora-hanorder · 26ai Free 23.26.2.0.0 · PGA 512M AUTO│
│                T_ORD_BIG 1,000,000 · ORD_DT NULL 허용 · 하루 913건 │
│ 3. 부하 조건 : 단일 세션 · 1회 실행  ★ 비면 리포트 무효            │
│                (work area 축소는 MANUAL 64KB 로 재현 — 명시할 것)  │
│ 4. 현상      : 페이지 1~100,000 의 Buffers / Used-Mem 곡선 ______ │
│ 5. 원인      : ______________________________________________     │
│                [성능고도화 II권 Ch5 01·05·06절] [tgdba PGA]        │
│ 6. 개선안    : 채택 rev01(인덱스) → rev02(포스트잇 + 재협상서 1호) │
│                기각 ______________________________________        │
│ 7. 효과      : 한 번 클릭 Buffers ________ → ________              │
│                페이지 깊이별: rev00 ____ / rev02 ____             │
│ 8. 정합성    : ★ 08화 정합성 회귀 체크리스트 A~H 표를 붙인다      │
│ 9. 환경 의존성: ______________________________________________     │
│ 10. 리스크   : ______________________________________________     │
│ 11. 롤백 절차: ______________________________________________     │
│ 12. 재측정   : ______________________________________________     │
└──────────────────────────────────────────────────────────────────┘

6번 「기각한 대안」 — 오늘은 셋이다 ​

① PGA(work area) 증설: 64KB → 1MB에서 100페이지는 optimal로 돌아오지만 10,000페이지는 2 PASSES다. 멈추는 페이지가 뒤로 밀릴 뿐이고 Buffers는 5,826 그대로다. 마지막 페이지는 한 번에 74,638K가 필요하다. ② rev01에서 멈추기: 100페이지 1,007, Q4c 3,365다. 그러나 페이지에 비례해 커진다(강제 시 1,000페이지 10,037). 935페이지부터는 정렬 계획으로 돌아간다. ③ 늦은 테이블 방문: 페이지 번호를 유지할 수 있다. 100페이지 16, 마지막 페이지 3,356이다. 재협상이 안 됐을 때 쓸 B안으로 재협상서에 남긴다.

8번 「정합성」 — 08화 체크리스트를 처음으로 붙인다 ★ ​

양식은 정합성_회귀_체크리스트.md에 있다. T1 7호는 이 표를 처음 붙이는 리포트다. STEP 5 [D1]~[D5]의 숫자로 A~H 여덟 칸을 채운다.

☐ B — 100페이지만 맞춰 보고 끝내지 않았는가 (건수 · 합계 · MINUS 양방향 · 중복 키 다섯 개 전부)
☐ C — 잘라 내기 전 전체로: 10만 페이지를 끝까지 넘겨 대조했는가
☐ E — 어떤 경계를 골랐는가 (날짜가 바뀌는 페이지 · 마지막 페이지)
☐ H — 새 인덱스가 바꾼 다른 SQL(Q4c)의 답을 확인했는가
☐ D · G — 「해당 없음」이면 이유를 같은 칸에 썼는가
▶ 채운 예 (쓰고 나서 대조)
#항목대상 / 방법결과판정
A기준rev00(같은 화면 · 같은 ORDER BY). 원장은 T_ORD_BIG 전체를 같은 순서로 정렬한 것—정함
B건수 · 합계 · MINUS 양방향 · 중복 키rev02 vs rev00, 100페이지10 = 10 · 2,627,200 = 2,627,200 · 0 / 0 · 정렬 키 (ORD_DT, ORD_SEQ) 중복 0통과
C잘라 내기 전 전체포스트잇으로 100,000페이지를 끝까지 넘기며 전체 정렬 결과와 한 행씩 대조(PL/SQL)페이지 100,000 · 행 1,000,000 · 합계 255,398,623,700 · 어긋난 행 0통과
D조인마다 1 쪽Q4는 조인이 없다(B안인 늦은 테이블 방문은 ROWID 1:1 조인)—해당 없음(조인 없음)
E경계 데이터날짜가 바뀌는 92페이지(12월 30일 3건 + 29일 7건) · 마지막 페이지(10건, 다음 없음)92페이지 10 = 10 · 2,848,500 = 2,848,500 · 0 / 0 · 마지막 페이지 10건통과
F계획 해시와 무관하게 대조rev00과 rev01은 SQL이 같고 계획이 다르다. 계획이 아니라 결과로 대조(B · C)통과
G변환을 근거로 쓰지 않음——해당 없음(변환 없음)
H영향 분석새 인덱스 IX_ORD_BIG_DT_SEQ → T_ORD_BIG을 읽는 조각 전부Q4c 계획 변경 · 답 1,000,000 = 1,000,000 · Q1 91 · 27,390건 그대로 · Q2 5,868 · 해시 2292855265 그대로통과

9번 「환경 의존성」 — 06화 4호와 비교한다 ​

06화 4호에는 "PGA가 줄면 같은 계획이 TEMP를 쓰기 시작하고 수십 배 느려진다"고 썼다. 박팀장은 그 문장을 "PGA를 올리면 된다"로 읽었다.

오늘의 9번 칸에는 rev02가 PGA와 무관해졌다는 것과, 그 대신 무엇에 의존하는지를 쓴다.

  • 인덱스 IX_ORD_BIG_DT_SEQ가 있어야 한다(없으면 포스트잇이 있어도 정렬한다)
  • 정렬 키 (ORD_DT, ORD_SEQ)가 유일해야 한다

그리고 1_사건 끝에서 선배가 한 말대로, "무엇을 하라"가 아니라 "무엇을 재 보라"까지 쓴다.

10번 「리스크」 ​

  • T_ORD_BIG의 인덱스가 3개로 늘었다. 적재 비용은 14화에서 청구된다
  • 새 인덱스가 Q4c의 계획을 바꿨다(좋은 쪽으로). 다른 SQL도 바뀔 수 있으니 INVISIBLE로 먼저 만든다. 단 이 버전의 함정(STEP 4 [D])도 함께 적는다
  • 포스트잇 값을 문자열로 붙이면 페이지마다 다른 SQL이 된다(11화)

11번 「롤백 절차」 ​

sql
-- rev02 → rev01 : 화면을 페이지 번호 방식으로 되돌린다 (SQL 은 rev01 파일)
-- rev01 → rev00 : 인덱스를 지운다. Q4c 도 FULL 로 돌아간다
DROP INDEX IX_ORD_BIG_DT_SEQ;
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'T_ORD_BIG',method_opt=>'FOR ALL COLUMNS SIZE 1',cascade=>TRUE,no_invalidate=>FALSE);

STEP 10 — 서술 답안 (400~600자) ​

박팀장에게 보낼 회신을 쓴다. 아침 메시지는 "PGA부터 올리면 되는 것 아닙니까?"였다.

4단계 프레임(현상/진단 → 원인 → 개선안 → 근거/효과)은 01화 3_실습과_기록.md와 같다.

▶ 선배의 첨삭 포인트 (쓰고 나서)

① "PGA 증설은 효과가 없습니다"는 틀린 보고다. 효과는 있다. 64KB → 1MB에서 100페이지는 6초 → 0.04초가 된다. 정확하게 쓰면 "효과는 있지만 멈추는 페이지를 뒤로 미는 것이고, 읽는 양은 그대로입니다"가 된다. 상대의 제안을 틀렸다고 하지 말고 어디까지 맞는지를 말한다.

② 화면이 바뀐다는 것을 숨기지 않는다. "100페이지로 바로 가기"와 "총건수"가 없어진다. 그걸 정산팀과 합의했다는 사실이 이 회신의 핵심이다.

③ 박팀장이 알아듣는 말로 쓴다. "한 번 누를 때 읽는 양이 11,652블록에서 약 15블록, 700분의 1 남짓으로 줄었습니다"처럼 쓴다.

④ 오늘 쓴 문장도 처방으로 읽힌다는 것을 염두에 둔다. "인덱스를 만들면 된다"로 읽히지 않도록, 인덱스 + 화면 변경 + 정렬 키의 유일성이 한 묶음이라는 것을 쓴다.


STEP 11 — 파인만 자가설명 ​

절차는 01화와 같다. 1단 백지 설명 → 2단 막힘 태그(최소 1개) → 3단 원천 복귀 → 4단 재설명 순서로 진행한다.

오늘 자주 나오는 태그 세 가지

태그전형적인 문장
#인과"PGA를 늘렸는데 왜 Buffers가 안 주는지 설명하다가 막혔다" → 2_파고들기.md §1 · §6
#용어"onepass와 multipass의 차이를 '합치기 폭'으로 말하지 못하겠다" → §5
#수치"rev01이 왜 한 페이지에 10블록씩 느는지 계산이 안 된다" → §8의 식에 CF를 넣어 본다

4단 되묻기 3문 (오늘 버전) ​

  1. 조건 반전: "ORD_DT에 NOT NULL 제약이 있었다면 02화의 IX_ORD_BIG_DT로 Q4의 정렬을 없앨 수 있었나?" (힌트: 이유가 두 가지였다)
  2. 반례 요구: "키 기반 페이징이 OFFSET보다 나쁜 화면을 하나 든다면?"
  3. 측정 지목: "'정산팀이 동시에 쓰면 멈춘다'를 증명하려면 무엇을 재야 하나? Free 컨테이너에서 못 재는 것은 무엇인가?"

태그는 _막힘로그.md로 옮긴다.


완료 체크리스트 ​

  • [ ] STEP 0: Q4_rev00.sql을 열었고, ORD_DT가 NULL 허용인 것을 확인했다
  • [ ] STEP 1: 보기 전에 예측 4개를 적었다
  • [ ] STEP 2: 페이지 곡선 표를 옮겨 적었고, BUFFERS가 평평한 것을 봤다
  • [ ] STEP 3: 64KB 책상에서 63 → 64페이지의 절벽을 봤고, AUTO로 복구했다
  • [ ] STEP 4: rev01에서 SORT 줄과 Used-Mem 열이 사라진 것, Q4c가 저절로 바뀐 것을 봤다
  • [ ] STEP 5: 포스트잇이 11번째 행에 붙는 것을 손으로 확인했고, 10만 페이지 전체 순회 mismatch 0을 봤다
  • [ ] STEP 6: 정렬이 있을 때와 없을 때 ARRAYSIZE가 바꾸는 것이 다르다는 것을 봤다
  • [ ] STEP 7: ora-dist 매출 목록의 맨 윗줄이 틀린 것을 확인했고, SLE_NO의 전제 세 가지를 쟀다
  • [ ] STEP 8: 재협상서 1호의 7번 칸(다음 재협상 조건)을 구체적인 사건으로 채웠다
  • [ ] STEP 9: T1 7호를 썼고, 08화 정합성 체크리스트 A~H, 「기각한 대안」 셋, 「환경 의존성」 칸을 채웠다
  • [ ] STEP 10: 박팀장 회신을 문장으로 풀어 썼다
  • [ ] STEP 11: 막힘 태그를 최소 1개 _막힘로그.md에 옮겼다

완결성 게이트: STEP 8의 7번 칸, STEP 9의 6번 칸(기각한 대안 셋)과 8번 칸(정합성 체크리스트)이 비어 있으면 이 화는 미완성이다. 이 화의 진짜 산출물은 15블록이 아니다. "화면의 질문을 바꾸는 것도 튜닝이고, 그건 혼자 정하지 않는다"를 문서로 남긴 것이 진짜 산출물이다.


다음 화로 넘기는 상태 ​

무엇값
T_ORD_BIG 인덱스3개(IX_ORD_BIG_DT, IX_ORD_BIG_DT_AMT, IX_ORD_BIG_DT_SEQ)
★ 확인00, 01 스크립트는 맨 앞에서 IX_ORD_BIG_DT_SEQ를 지운다. 마지막에는 02를 돌린 상태로 끝낸다(03도 인덱스가 없으면 만든다)
Q1 · Q2 · Q309화에서는 손대지 않았다(STEP 4 [C2][C3]에서 Q1 91, Q2 5,868이 그대로인 것만 확인)
Q4 현재 리비전rev02 · 어느 페이지든 15~18 · Q4c 삭제(재협상서 1호)
세션 파라미터workarea_size_policy = AUTO(복구 완료)
ora-dist변화 없음. CF10_PG는 지웠다. SLE_HMS 교정은 유통지원팀이 정기 점검 때 적용할 예정
남긴 빚인덱스 3개의 적재 비용 → 14화
열어 둔 질문ROWNUM 계획의 E-Rows → 10화 · 포스트잇 값을 바인드로 → 11화

다음 → 10화 「어제는 0.1초 오늘은 30초」: 오늘 실행계획에서 E-Rows는 999K였는데 실제로는 11건이었다. 옵티마이저는 무엇을 믿고 계획을 세우는 걸까.