테마
01화 · 실습과 기록
여기서 하는 일: 직접 재고, 직접 쓴다.
1_사건.md를 읽은 것은 "선배가 재는 걸 지켜본 것"이고, 이 문서를 끝내야 직접 재 본 것이 된다. 개념이 막히면2_파고들기.md를 펼친다.시간이 없는 주에는 STEP 0 → 1 → 4만 한다(약 40분). 그래도 리비전은 한 걸음 나아가고, 원장에는 한 줄이 남는다.
STEP 0 — 무대 확인 (3분)
bash
docker start ora-hanorder측정은 상태가 전제와 같을 때만 유효하다. 다음 두 가지를 통과해야 한다.
sql
SELECT 'T_ORD_BIG' t, COUNT(*) c FROM T_ORD_BIG -- 기대 1,000,000
UNION ALL SELECT 'T_ORD', COUNT(*) FROM T_ORD -- 기대 100,000
UNION ALL SELECT 'T_ORD_DET', COUNT(*) FROM T_ORD_DET -- 기대 200,000
UNION ALL SELECT 'M_CUS', COUNT(*) FROM M_CUS; -- 기대 2,000
SELECT COUNT(*) AS idx_on_ord_big FROM user_indexes WHERE table_name='T_ORD_BIG';| 확인 | 기대 | 아니면 |
|---|---|---|
| 네 테이블 행수 | 위 주석대로 | 00_자료준비/01_첫_고객사_ora-hanorder.md를 다시 실행 |
idx_on_ord_big | 0 | 누군가 인덱스를 만들었다. 그 상태로 잰 베이스라인은 무효다 |
전체 점검은
03_환경검증.sql하나로도 끝낼 수 있다.E-Rows가333K가 아니라20000으로 나오면04_상태복구.sql을 돌린다.
STEP 1 — 예측을 봉인한다 (5분) ★ 건너뛰면 오늘의 절반을 잃는다
실행계획을 보기 전에 적는다. 보고 나면 누구나 "나도 그럴 것 같았어"라고 생각하게 되고, 그 순간 무엇을 몰랐는지가 기록에서 사라진다.
날짜: ____________
Q1. 왜 느릴 것 같은가? 한 문장으로.
________________________________________________________________
Q2. 6조각 중 가장 많은 블록을 읽을 것 같은 것과 가장 적을 것 같은 것은?
가장 많음: ______ 가장 적음: ______
이유: ______________________________________________________
Q3. 가장 먼저 고쳐야 할 조각은? 그 판단의 근거는?
________________________________________________________________▶ 적고 나서 펼치기: 대부분 이렇게 답한다
"
T_ORD_BIG이 100만 건인데 인덱스가 0개니까요. 인덱스를 만들면 될 것 같습니다."
이 답에 선배는 고개를 끄덕이지도 젓지도 않았다.
"답일 수도 있어. 그런데 지금 그걸 어떻게 알아?""'100만 건이라 느리다'가 맞으려면, 100만 건을 다 읽고 있다는 걸 봤어야지."
📌 이 답은 지우지 않는다. STEP 4에서 직접 반박하게 된다. 그리고 18화에서 다시 꺼내 읽으며, 1년 전에 무엇을 몰랐는지 확인한다.
STEP 2 — 강도현의 코드를 설치한다 (1분)
bash
cd 10_본편_튜너의_1년/01화_인수인계_없는_인수인계/실습
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 00_관통코드_생성.sqlpowershell
Set-Location 10_본편_튜너의_1년\01화_인수인계_없는_인수인계\실습
Get-Content 00_관통코드_생성.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1패키지 PKG_ORD_STLM과 테이블 2개(T_ORD_STLM · T_STLM_NUM)가 생긴다. 끝에 나오는 네 줄이 모두 VALID여야 한다.
STEP 3 — 계기판을 켜고 한 장을 끝까지 읽는다 (15분)
6조각을 한꺼번에 재기 전에 SQL 하나를 자세히 본다. 이때 쓰는 도구가 ②번(GATHER_PLAN_STATISTICS + DISPLAY_CURSOR)이다.
sql
SET SERVEROUTPUT OFF
SELECT /*+ GATHER_PLAN_STATISTICS */ COUNT(*)
FROM T_ORD_BIG WHERE ORD_ST = 'CANCEL';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST'));나온 표에서 다섯 군데를 직접 짚어 본다. 읽는 법은 2_파고들기.md §3에 정리해 두었다.
☐ ① Operation 이 TABLE ACCESS FULL 인가? → 인덱스를 안 탄다는 뜻
☐ ② Buffers 가 5,826 인가? → 블록을 몇 번 만졌나
☐ ③ E-Rows 와 A-Rows 가 몇 배 차이인가? → 333K vs 20,000 = 16배
☐ ④ Predicate 가 filter 인가 access 인가? → filter = 다 읽고 버렸다
☐ ⑤ Reads 는 몇인가? → 0이면 캐시에 있던 것. Buffers 는 거의 안 변한다 (아래 표 참고)③이 왜 그런지 직접 확인한다.
sql
SELECT ORD_ST, COUNT(*),
ROUND(RATIO_TO_REPORT(COUNT(*)) OVER ()*100, 2) AS pct
FROM T_ORD_BIG GROUP BY ORD_ST ORDER BY 2 DESC;90 / 8 / 2. 옵티마이저는 종류가 3개라는 것만 알고 분포는 몰라서 1,000,000 ÷ 3으로 찍었다.
반드시 밟게 되는 함정 두 개
SET SERVEROUTPUT OFF를 빼면DISPLAY_CURSOR가 엉뚱한 커서를 보여 준다GATHER_PLAN_STATISTICS를 빼면A-Rows와Buffers칸이 비어 있다
STEP 4 — 6조각을 전부 잰다 (5분) ★ 오늘의 산출물
bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 01_베이스라인_측정.sqlpowershell
Get-Content 01_베이스라인_측정.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1스크립트가 하는 일은 세 가지다. ① STATISTICS_LEVEL = ALL로 실행 통계를 모으게 한다. ② Q1~Q4, P1, N1을 각각 한 번씩 돌린다(SQL마다 /* BASE_Qn */ 태그를 박아 둔다). ③ V$SQL에서 태그로 찾아 한 표로 뽑는다.
기대 출력
PIECE SQL_ID EXECS BUFFERS READS MS ROWS_OUT
------ -------------- ------ ---------- ---------- ---------- ----------
Q1 gcn22v46wnzy3 1 5827 0 112 1
Q2 5agpn6jmgm8rf 1 1026849 0 774 3
Q3 ff93fhdf13hwz 1 841 0 14 5
Q4 9dhfhrd7cqvsf 1 5826 0 42 10
Q4c 8zwr294pmr8zn 1 5826 0 9 1숫자가 다르게 나온다면
증상 원인 조치 모든 값이 정확히 2배 V$SQL은 누적이다. 두 번 돌린 것이다EXECS열을 본다. 1보다 크면 그 값으로 나눈다설치 직후 첫 실행에서 Q1 5,842 · Q2 1,026,867 · Q3 871 · Q4 5,850이 나온다 정상이다. 막 적재한 테이블을 처음 읽을 때만 붙는 비용이다 스크립트를 한 번 더 돌리면 교재 값(5,827 · 1,026,849 · 840 안팎 · 5,826)이 나온다 한동안 쉬었다 왔더니 전부 조금씩 크다 밤사이 자동 통계 수집이 통계를 바꿨다 04_상태복구.sql을 돌린 뒤 다시 측정Q1이 5,827보다 훨씬 작다 누군가 인덱스를 만들었다 STEP 0을 다시 확인한다. 0행이어야 한다 Reads가 0이 아니고Buffers가 교재보다 몇 블록 적다(예: 5,827 → 5,822)캐시가 비어 있다( docker start직후 첫 실행). 디스크에서 곧장 읽어 온 블록은 세는 방식이 조금 다르다정상이다. 한 번 더 돌리면 교재 값이 나온다. 0.1% 차이라 판단은 바뀌지 않는다 Q2가 오래 걸린다 정상이다 100만 번 왕복하는 중이다. 이게 오늘 관찰할 대상이다 Q3만은 교재 값과 10% 남짓 차이가 날 수 있다. 이유는
2_파고들기.md가 아니라 아래 §주의에 적어 두었다.
PL/SQL 조각(P1·N1)은 따로 잰다
V$SQL은 SQL 문장 단위라서 프로시저 전체의 일량이 잡히지 않는다. 그래서 세션 통계로 앞뒤를 감싸서 잰다.
sql
-- 실행 전
SELECT n.name, s.value FROM v$mystat s JOIN v$statname n ON n.statistic#=s.statistic#
WHERE n.name IN ('session logical reads','redo size','user commits','execute count');
EXEC PKG_ORD_STLM.P1_LOAD(100000);
-- 실행 후 → 차이가 P1 의 일량
user commits를 꼭 봐야 한다. P1은 1,000건마다 커밋하므로 100이 찍힌다. 이 숫자는 13화에서redo size와 함께 다시 나온다.
이제 STEP 1의 예측을 꺼낸다
| 질문 | |
|---|---|
| 예측이 맞았나? | |
| Q2가 전체의 98%를 차지한다는 걸 예측했나? | |
| 가장 작은 Q3가 가장 위험하다는 건? | |
| 1,026,849 ÷ 5,826 = 176배. 이 크기를 상상했나? |
예측과 실제가 다른 만큼이 오늘 배운 양이다.
STEP 5 — 서술 답안 400~600자 ★ 빈 종이에 쓴다
실무 보고서도, 면접도, 후배에게 하는 설명도 모두 문장으로 한다. 표를 보여 줄 수 없는 자리에서도 말로 설명할 수 있어야 한다.
4단계 프레임
| 단계 | 쓸 것 | 통과 조건 |
|---|---|---|
| 1. 현상/진단 | 무엇이 느린가: 측정값으로 | 숫자 1개 이상 |
| 2. 원인 | 왜 그런가: 구조로 설명 | "~때문에"가 있어야 한다 |
| 3. 개선안 | 무엇을 할 것인가 | 오늘은 "아직 안 고침"도 정답 |
| 4. 근거/효과 | 어떻게 증명할 것인가 | 숫자 1개 이상 |
🚫 통과 못 하는 문장 / ✅ 통과하는 문장
| 🚫 | ✅ |
|---|---|
| "쿼리가 느립니다" | "Q2가 1회 실행에 1,026,849블록을 읽습니다" |
| "인덱스를 만들면 빨라집니다" | "전체 스캔이 5,826블록이므로, 2% 선택도라면 인덱스가 유리할 수 있습니다" |
| "튜닝했더니 개선되었습니다" | "1,026,849 → 5,869블록, 약 175분의 1" |
제1원칙: "빨라진다"가 아니라 "블록이 N→M으로 준다"고 쓴다. 4단계 각각에 이 원칙을 지키는 문장이 최소 1개씩 있어야 통과다.
오늘의 과제
박팀장에게 보낼 회신을 쓴다. 상대는 개발자가 아니다. Buffers가 뭔지도 모른다.
수신: 박팀장님 (운영1팀) 건: #4417 야간정산 화면 지연
1. 현상/진단
____________________________________________________________________
2. 원인
____________________________________________________________________
3. 개선안
____________________________________________________________________
4. 근거/효과
____________________________________________________________________▶ 선배의 첨삭 포인트 (쓰고 나서)
① "아직 안 고쳤습니다"를 쓸 수 있는가 오늘 보고서의 결론은 "고쳤습니다"가 아니라 "재는 체계를 만들었습니다"다. 고치지 않았다고 당당하게 보고하는 것도 능력이다. 단, 다음에 뭘 할지를 함께 적어야 한다.
② 상대가 알아듣는 말로 옮겼는가 박팀장에게 "1,026,849블록"이라고만 쓰면 뜻이 전달되지 않는다. "한 번 조회할 때 전체 데이터를 176번 반복해서 읽고 있습니다"라고 옮겨야 한다.
③ 숫자에 단위와 조건이 붙었는가 "30초"는 증언이고, "1회 실행당 1,026,849블록"은 측정이다. 무엇을, 어떤 조건에서, 어떤 단위로 쟀는지가 붙어야 한다.
④ 지킬 수 없는 약속을 하지 않았는가 "다음 주에 해결하겠습니다"라고 쓰면 안 된다. 아직 원인도 모르기 때문이다. "가장 큰 Q2부터 순서대로 보겠습니다"가 정직한 문장이다.
STEP 6 — 파인만 자가설명 ★ 건너뛰기 불가
이 훈련의 핵심은 "설명해 보기"가 아니라 "막히는 지점을 데이터로 남기기"다.
1단 · 백지 3분 설명
계기판과 문서를 모두 닫고, 오늘 일을 입사 첫날의 자신에게 설명하듯 200~400자로 쓴다.
규칙은 세 가지다. ⓐ 실행계획을 다시 열지 않는다. ⓑ 전문 용어마다 괄호 안에 한 줄 풀이를 단다. ⓒ 숫자를 반드시 넣는다. 숫자가 없는 문장은 설명으로 인정하지 않는다.
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________2단 · 막힘 태그 — 최소 1개는 반드시 찾는다
설명이 끊긴 문장을 그대로 잘라 붙이고 세 칸을 채운다.
| 태그 | 의미 | → 돌아갈 곳 |
|---|---|---|
#용어 | 단어를 모른다 | 성능고도화 해당 절 + cncpt |
#인과 | A 다음에 B가 온다는 건 아는데 이유를 모른다 | 성능고도화 해당 장 + 2_파고들기.md |
#수치 | 크기 감각이 없다 | 실습 재측정 (수치를 직접 만든다) |
#절차 | 어떻게 확인하는지 모른다 | refrn의 V$ 정의 + tgdba |
[내가 쓴 어정쩡한 문장] ______________________________________
[무엇을 모르는지 한 문장] ____________________________________
[태그] #______⚠️ 태그가 0개라면 그 설명은 얕은 것이다. 스스로를 속이지 않기 위한 장치다. 오늘 가장 많이 나오는 태그는
#인과로, "논리적 읽기가 왜 공짜가 아닌가" 같은 문장이다.
3단 · 원천 복귀 — 좌표 없으면 무효
#인과 논리적 읽기가 왜 공짜가 아닌가
→ 성능고도화 I권 Ch1 「DB 버퍼 캐시」 (책 p.23 / PDF p.21)
→ cncpt 「Memory Architecture > Database Buffer Cache」
→ 알아낸 것: ____________________________________________"다시 읽었다"만으로는 인정하지 않는다. 좌표를 적는다. 좌표 찾는 법은 2_파고들기.md §7에 있다.
4단 · 재설명 + 되묻기 3문
같은 주제를 다시 3분 동안 설명하고, 1차 설명과 무엇이 달라졌는지 한 줄로 적는다. 그리고 다음 고정 3문항에 답한다.
- 조건 반전: "대상이 전체의 절반이면 이 판단은 어떻게 뒤집히나?"
- 반례 요구: "이 처방이 오히려 해가 되는 경우를 하나 든다면?"
- 측정 지목: "내 주장을 반증하려면 어떤 수치를 보여 줘야 하나?"
막힘 로그에 옮긴다
태그는 모두 _막힘로그.md에 한 줄씩 쌓는다. 같은 항목이 3회 이상 나오면 재학습 큐로 올라가고, 18화 졸업 전에 반드시 보강할 목록이 된다.
STEP 7 — 오늘의 산출물 세 장
T0 — 측정규약 (제정, 18화까지 불변)
이 교재의 헌법이다. 이 규약을 어긴 측정은 증거로 치지 않는다.
| 조 | 내용 |
|---|---|
| 제1조 | "빨라졌다"가 아니라 Buffers N → M 형태로 말한다. 초는 보조 지표일 뿐 판단 근거가 아니다 |
| 제2조 | 도구 3종: EXPLAIN PLAN(예상) · GATHER_PLAN_STATISTICS+DISPLAY_CURSOR(실제) · AUTOTRACE(간이) |
| 제3조 | 관찰 4수치: Buffers · A-Rows vs E-Rows · Starts · Reads |
| 제4조 | 측정 전에 SET SERVEROUTPUT OFF. 전제 상태(인덱스 유무, 행수)를 먼저 확인한다 |
| 제5조 | 통계나 파라미터를 일부러 바꿨으면 반드시 원복한다 |
| 제6조 | 운영 DB에서 실험하지 않는다. 인덱스는 INVISIBLE로 먼저 만든다 |
| 제7조 | 파인만 태그가 0개인 화는 완료로 치지 않는다 |
| 제8조 | 가용성은 선언이 아니라 실행으로 확인한다. V$OPTION이 TRUE라고 해서 되는 게 아니다 |
T9 — 튜닝 대상 선정·중단 기준 (제정)
선정: 3축으로 본다. 성능 하나로만 줄 세우지 않는다.
| 축 | 묻는 것 | 오늘의 적용 |
|---|---|---|
| ① 정합성 | 답이 맞는가? | Q3: 틀린다. 최우선 |
| ② 영향도 | 사람이 기다리는가? | Q2(98%) → Q4 → Q1 |
| ③ 증가율 | 데이터가 늘면 더 나빠지는가? | Q2는 주문 수에 따라 선형으로 늘어난다 |
오늘의 결론: 성능은 Q2부터 보되, Q3는 별도 트랙으로 바로 다룬다. 느린 것과 급한 것은 다르다.
중단: 언제 그만 고치는가. ⓐ 목표 수치를 달성했을 때, ⓑ 개선 폭이 측정 오차 수준일 때, ⓒ 리스크가 이득보다 클 때, ⓓ 설계를 바꿔야 하는 문제로 밝혀졌을 때 멈춘다. ⓓ는 튜닝이 아니라 재협상의 영역이다.
W01 — 베이스라인 원장 (제출)
| 조각 | Buffers | 결과행 | 전체 대비 | 우선순위 |
|---|---|---|---|---|
| Q2 | 1,026,849 | 3 | 98.2% | 성능 1순위 |
| Q4+Q4c | 11,652 | 10 | 1.1% | 2 |
| Q1 | 5,827 | 1 | 0.6% | 3 |
| Q3 | 841 | 5 | 0.08% | 정합성 · 별도 트랙 최우선 |
제출 위치는 40_보고서와_표준/제출물/W01_베이스라인_원장.md다.
📮 선배의 반려 사유 — 처음 낸 보고서는 대개 반려된다
① 리스크 칸이 비어 있다. "지금 비워 두면 13화에서 그 칸 때문에 장애가 나." (실제로 그렇게 된다) ② 측정 조건이 안 적혀 있다. 언제, 어떤 상태에서, 몇 회 실행했는지 적어야 한다. 한 달만 지나도 기억나지 않는다. ③ "느리다"가 남아 있다. 전부 블록으로 바꾼다.
⚠️ 주의 — Q3는 몇 블록씩 흔들린다
Q3는 실행할 때마다 839~841 사이로 찍힌다(08화에서 여러 번 잰 값). 비용의 대부분은 TABLE ACCESS FULL T_ORD_DET(808)이고, 나머지 몇 블록이 실행마다 조금씩 다르다. 판단을 바꿀 만한 크기는 아니다.
그래서 Q3는 숫자보다 성질로 기억한다. 6조각 중 가장 작은데 답이 틀린다.
완료 체크리스트
- [ ] STEP 0:
idx_on_ord_big이 0이다 - [ ] STEP 1: 보기 전에 예측을 적었다
- [ ] STEP 2: 패키지 4개 객체가
VALID다 - [ ] STEP 3: 실행계획 한 장에서 다섯 군데를 직접 짚었다
- [ ] STEP 4: 6조각 원장을 만들고 예측과 대조했다
- [ ] STEP 5: 박팀장 회신을 400자 이상 문장으로 풀어 썼다
- [ ] STEP 6: 막힘 태그를 최소 1개 찾아
_막힘로그.md에 옮겼다 - [ ] STEP 7: T0 · T9 · W01 세 장을 제출했다
완결성 게이트: STEP 6의 태그와 STEP 7의 W01이 비어 있으면 이 화는 미완성이다.
다음 → 02화 「인덱스는 있는데 안 탄다」: Q1 = 5,827을 들고 간다. 인덱스를 만들어 보고, 그래도 안 타는 걸 보게 된다.