Skip to content

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_big0누군가 인덱스를 만들었다. 그 상태로 잰 베이스라인은 무효다

전체 점검은 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_관통코드_생성.sql
powershell
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_베이스라인_측정.sql
powershell
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문항에 답한다.

  1. 조건 반전: "대상이 전체의 절반이면 이 판단은 어떻게 뒤집히나?"
  2. 반례 요구: "이 처방이 오히려 해가 되는 경우를 하나 든다면?"
  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결과행전체 대비우선순위
Q21,026,849398.2%성능 1순위
Q4+Q4c11,652101.1%2
Q15,82710.6%3
Q384150.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을 들고 간다. 인덱스를 만들어 보고, 그래도 안 타는 걸 보게 된다.