Skip to content

18화 · 튜너가 되는 날 ​

7월 8일 월요일, 3월 4일에 받은 원장 한 장을 다시 꺼낸다. 그날 적은 마지막 줄은 "18화 재측정 예정"이었다. 오늘은 처음으로 남이 쓴 리포트를 반려하고, 그동안 내가 쓴 리포트로 표준을 만든다. 그리고 저녁에는 아무도 옆에 앉지 않는다.


오전 9시 05분 ​

매주 하던 대로 리비전 폴더부터 열었다. 열여덟 번째였다. 이번에는 폴더가 아니라 맨 처음에 만든 파일을 열었다. 40_보고서와_표준/제출물/W01_베이스라인_원장.md였다. 마지막 줄이 그대로 남아 있었다.

이 표의 6조각을 동일 조건에서 재측정해 (01화값 → 18화값) 2열을 추가한다.

메신저에 세 줄이 와 있었다.

박팀장  08:41
오늘부터 정하은 씨가 튜닝팀에 합류합니다. 개발팀에서 왔고 SQL 잘 씁니다.
첫 주는 선배님(나) 옆자리. 오후에 하은 씨가 쓴 리포트 하나 리뷰 부탁해요.
그리고 반기 보고에 "튜닝 표준" 한 장 넣기로 했습니다. 오늘 초안 가능할까요?
오과장  08:55
월마감 집계가 지난주 금요일 밤에 중간에 죽었습니다(디스크 경보 오탐으로 강제 종료).
오늘 오전에 다시 돌려야 하는데, 낮에 돌리면 화면 느려지죠? 리소스 매니저로 막을 수 있다고 들었는데요.

윤세라 선배는 9시 10분에 출근했다. 커피를 내 자리가 아니라 자기 자리에 내려놓았다. 18주 동안 한 번도 없던 일이었다.

"오늘은 내가 옆에 안 앉아." 선배가 말했다. "물으면 답은 할게. 대신 내가 먼저 묻지는 않을 거야. 저녁에 새 고객사 하나를 넘겨줄 건데, 그건 혼자 해."

"하은 씨 리포트는요?"

"그건 네가 리뷰해. 내가 3월에 너한테 한 것처럼." 선배가 자리에 앉으며 덧붙였다. "기억나? 내가 처음에 뭘 물었는지."

기억났다. 30초라는 건 누가 잰 거야?


오전 9시 30분 — 같은 규약으로 다시 ​

원장의 전제부터 읽었다. 3월 4일의 나는 표 위에 이렇게 적어 두었다.

인덱스 상태  T_ORD_BIG 인덱스 0개   ← 이 원장의 전제
부하 조건    단일 세션 · 워크로드 프로파일 없음
통계         GATHER_SCHEMA_STATS 직후

'동일 조건'은 이미 맞출 수 없었다. T_ORD_BIG에는 인덱스가 셋 있다. 02화에 만든 IX_ORD_BIG_DT와 IX_ORD_BIG_DT_AMT, 09화에 만든 IX_ORD_BIG_DT_SEQ다. ORD_ST에는 10화부터 히스토그램이 있다. 되돌릴 수는 있었다. 인덱스 셋을 지우고 통계를 3월로 돌리면 된다. 그러면 3월의 숫자가 다시 나오겠지만, 그 숫자는 이미 원장에 있다.

"동일 조건이 뭘 뜻하는지부터 정해야겠어요." 내가 말했다. "3월로 되돌려서 재면 3월 숫자가 다시 나올 뿐이에요."

선배는 고개만 끄덕였다.

그래서 이렇게 정했다. 같은 규약(T0)으로, 지금 상태에서 잰다. 단일 세션에서 스크립트를 두 번 돌려 두 번째 판을 쓰고(3월의 '두 번째 실행'과 같은 조건), STATISTICS_LEVEL = ALL을 켜고, Buffers는 V$SQL_PLAN_STATISTICS_ALL에서 가장 큰 줄을 읽는다. 재는 대상은 두 가지다. 3월의 SQL을 한 글자도 고치지 않은 판(rev00)과 지금 운영에 나가 있는 판(최신 rev)이다. P1과 N1은 원본 정산 테이블에 쓰면 안 되니까 복사본에서 잰다.

실습/02_W01_재측정.sql은 10초쯤 걸렸다. 두 번 돌렸다.

TAG          PLAN_HASH   BUFFERS   ROWS
-----------  ----------  --------  -----
W18_R0_Q1     914281838      5826      1
W18_R0_Q2    2289018667   1026849      3
W18_R0_Q3     119122671       840      5
W18_R0_Q4    3040227993      1007     10
W18_R0_Q4C     97409517      3365      1

Q1, Q2, Q3는 3월과 같았다. 계획 해시까지 같았다. Q2는 1,026,849로, 3월과 한 블록도 다르지 않았다.

Q4에서 손이 멈췄다. 5,826이 1,007이 되어 있었다. 총건수를 세는 Q4c도 5,826에서 3,365가 되어 있었다.

"강도현 씨 코드가 빨라졌네요." 나는 반쯤 웃으며 말했다. "rev00은 한 글자도 안 고쳤는데. 3월 원장이 틀렸던 건 아니죠?"

선배가 의자를 돌리지 않은 채 물었다. "SQL이 바뀌었어?"

"아뇨. rev00 그대로예요."

"그럼 뭐가 바뀌었어?"

계획 해시를 봤다. 3월의 Q4는 513693594였는데 지금은 3040227993이었다. 계획을 펼치자 INDEX FULL SCAN DESCENDING으로 IX_ORD_BIG_DT_SEQ를 읽고 있었다. 09화에 내가 만든 인덱스였다. rev01을 위해 만든 인덱스지만, 정렬 키가 같으니 rev00도 그 인덱스를 쓴다. Q4c도 마찬가지였다. 총건수를 한 번 셀 때 테이블 전체(5,826)가 아니라 인덱스 전체(3,365)를 읽는다. 09화 리비전 머리말에 이미 적혀 있던 일이었다. ★ 손대지 않았는데 계획이 바뀌었다.

"…코드가 빨라진 게 아니라, 코드 밑의 바닥이 바뀌었어요."

"3월 원장은?"

"맞아요. 인덱스가 0개일 때 숫자로는요. 전제를 적어 놨으니까 지금 나온 1,007이 원장을 반박하지는 않아요." 원장의 그 줄을 다시 봤다. ← 이 원장의 전제. 3월의 나는 그 화살표를 왜 그리는지도 몰랐다. 선배가 그리라고 해서 그렸을 뿐이다.

"그럼 1,007은 무슨 뜻인데?"

"rev00을 지금 다시 돌리면 1,007이 나온다는 거요. 그게 rev01 덕인지 rev00 덕인지는 이 숫자만 봐서는 몰라요. 09화의 rev01은 SQL은 그대로 두고 인덱스만 더한 판이었으니까, 사실상 지금의 rev00이 rev01이에요."

선배가 처음으로 의자를 돌렸다. "그걸 원장에 어떻게 적을 건데?"


오전 9시 50분 — 2열 ​

01화의 표는 고치지 않았다. 그 아래에 18화 절을 새로 열고 같은 6조각을 세 열로 적었다. 3월의 값, 같은 SQL을 오늘 돌린 값, 오늘 운영에 나가 있는 판의 값이다. 그리고 맨 오른쪽에 열을 하나 더 만들었다. 그때는 왜 옳았고, 지금은 왜 다른가.

조각01화 (3월 4일)rev00 · 오늘최신판 · 오늘그때는 왜 옳았고 지금은 왜 다른가
Q15,8275,82691 (rev03)그때도 지금도 가공된 컬럼은 인덱스를 못 쓴다. 바뀐 것은 SQL이다(02 · 03화)
Q21,026,8491,026,8495,868 (rev01)회의록의 3초 → 0.4초는 그 서버에서는 사실이었다. 2만 건에서도 해시가 138배 이겼지만 손해가 10ms라 안 보였다 · 100만 건에서는 102만 블록(06화)
Q3841840839 (rev01)블록은 그대로지만 답이 50배 → 1배(08화). 원장의 Buffers로는 보이지 않는 줄이다
Q45,8261,00714 (rev03 · 100페이지)rev00이 빨라진 것은 09화 인덱스 덕이다. SQL이 아니라 바닥이 바뀌었다
Q4c5,8263,365없음총건수 요구를 화면 주인과 다시 정해서 사라졌다(09화 재협상서)
합계 Q1 ~ Q4c1,045,1691,037,8876,812같은 SQL로는 0.7%, 판을 바꿔서 99.3%

P1과 N1은 블록이 아니라 일의 양으로 적었다. 3월의 나는 밀리초만 적었지만, 오늘은 그 옆에 논리 읽기와 redo, 커밋 수를 붙였다.

           01화 (3월)      rev00 · 오늘 (복사본)                          최신판 · 오늘 (복사본)
P1 (10만)  2,480 ms    850 ~ 1,010 ms · 논리 읽기 110,1xx ~ 110,4xx · redo 28.6 MB · 커밋 101   rev02  100 ~ 110 ms · 2,869 ~ 2,961 · 0.08 ~ 0.10 MB · 커밋 2
N1 (100회)   440 ms    80 ~ 90 ms     · 논리 읽기  56,800                             rev01    0 ms      ·     0 ~ 23

"P1 rev00도 빨라졌네." 선배가 말했다. 2,480이 1,000 안팎이 되어 있었다.

이번에는 웃지 않았다. "그건 모르죠. 3월의 2,480은 '설치 후 두 번째 실행'이라고만 적혀 있어요. 초는 그날 캐시가 어땠는지, 장비 상태가 어땠는지에 따라 달라져요. 그래서 오늘은 일의 양을 같이 적었어요. 다음에 이 원장을 여는 사람이 '빨라졌다'를 초로만 읽지 않게요."

"좋아." 선배는 그 말만 하고 의자를 다시 돌렸다.

걸리는 게 하나 더 있었다. Q1 rev00을 두 번째로 돌리자 자식 커서가 하나 더 생겼다. statistics feedback used for this statement. 첫 실행의 E-Rows 2,500이 틀렸다는 것을 옵티마이저가 기억해 두었다가, 두 번째 실행에서 27,390으로 고쳐 잡은 것이다. 가공된 컬럼은 인덱스를 못 쓰니 계획은 같았다. 3월 원장에는 이 줄이 없었다. 3월에는 '두 번째 실행' 하나만 쟀지만, 오늘은 두 번째와 세 번째 실행이 서로 달랐다. 원장의 측정 조건 칸에 한 줄을 더 적었다. rev00의 Q1은 두 번째 실행부터 statistics feedback 자식이다. 계획과 Buffers는 같다.

6조각 재측정 · 측정 조건 · 표 전체 → 2_파고들기.md §1 · 원장 → W01_베이스라인_원장.md


⛳ 오늘은 여기까지(중단 지점 1) 3월의 원장에 2열이 붙었다. 3월의 숫자는 한 칸도 지우지 않았다. 전제가 적혀 있었으니 지울 이유가 없었다. 오늘 할 일의 3분의 1이 여기서 끝난다. 이어지는 오전에는 '지금 계획을 내일도 지키는 법'을 다룬다. 도구가 처음으로 권고를 내놓고, 나는 그 권고를 받아들인다.


오전 10시 40분 — 내일도 같은 계획으로 ​

박팀장에게 원장의 2열을 보내자 답이 바로 왔다.

박팀장  10:31
1,045,169 → 6,812. 반기 보고에 이 숫자 넣겠습니다.
그런데 하나만요. 5월에 일요일 밤 통계 한 번에 Q2 가 다시 100만 블록으로 돌아간 적 있잖아요(10화).
이 6,812 가 내일 아침에도 6,812 라는 보장이 있나요?

보장은 없었다. 10화 그 일요일 밤에 바뀐 것은 SQL이 아니라 통계였다. 빈 테이블일 때 모은 통계 한 번 때문에 옵티마이저가 Q2에 다시 NESTED LOOPS를 골랐고, 1,026,852블록이 나왔다. 그 뒤로 통계 수집 정책서를 만들었지만, 정책서는 결국 사람이 지켜야 하는 문서였다.

"Q2에 힌트를 박아 두면 어때요?" 내가 선배 쪽을 보며 말했다. "USE_HASH 하나요. 통계가 무슨 일을 해도 해시로만 가게요."

선배가 의자를 돌리지 않고 물었다. "06화에서 네가 뗀 게 뭐였지?"

"…USE_NL이요."

"그건 누가 박았지?"

"강도현 씨요. 그때 주문 2만 건에서 3초를 0.4초로 줄였고요." 말하다가 멈췄다. 강도현 씨도 '통계가 무슨 일을 해도 이 계획으로'를 바랐을 것이다. 그리고 100만 건이 되어 그 힌트가 1,026,849블록을 읽을 때까지 아무도 힌트를 의심하지 않았다. 힌트는 코드 안에서 아무 소리도 내지 않았다. "같은 걸 하자는 거네요. 방향만 반대로요."

"계획을 지키고 싶은 건 맞아." 선배가 말했다. "근데 지키는 방법이 두 가지야. 코드에 박는 방법이 있고, 검사를 통과한 계획만 쓰게 하는 방법이 있어. 점심 먹으러 내려갈 때 1층 엘리베이터 앞 벽을 봐."

액자가 하나 걸려 있었다. 18주 동안 매일 지나다녔는데 한 번도 읽어 본 적이 없었다.

승강기 검사합격증명서
  승강기 번호 3호기          용도 승객용
  정격하중 1,000 kg          정원 15 명          정격속도 105 m/min
  검사 종류 정기검사          검사 결과 합격
  유효기간  ~ 내년 2월 28일

올라와서 선배에게 물었다. "새 엘리베이터를 달면 바로 태우나요?"

"네가 말해 봐."

"…검사부터 받겠죠. 지금 것이 잘 돌아가도, 더 빠른 새 것이 들어와도요. 새 것은 검사를 받아야 운행하고, 그동안은 원래 것이 계속 다니고요."

"그게 SQL Plan Management야." 선배가 말했다. "합격증이 붙은 계획만 운행해. 옵티마이저가 새 계획을 만들어도 바로 태우지 않고 대기 줄에 세워. 누가 시험 운행을 해서, 그러니까 둘 다 실제로 돌려 보고 더 낫다는 게 확인되면 그때 합격증을 붙여."

실습/03_SPM_계획을_지킨다.sql을 썼다. 원본 대신 T_ORD_BIG의 복사본(T_ORD_BIG_C18)을 대상으로 했다. 장면은 둘이었다.

첫 장면: 합격증은 좋은 계획도 막는다. 인덱스가 없는 복사본에서 Q1 꼴의 문장을 돌리고, 그 TABLE ACCESS FULL 계획을 베이스라인으로 올렸다(LOAD_PLANS_FROM_CURSOR_CACHE). 그다음 02화의 커버링 인덱스를 복사본에 만들었다. 인덱스만 있으면 91블록으로 끝나는 문장이다.

S18_Q1   TABLE ACCESS FULL  T_ORD_BIG_C18     Buffers 5,821
Note
-----
   - SQL plan baseline SQL_PLAN_0ywwuqhzqmtjf863f0086 used for this statement

여전히 FULL이었다. 옵티마이저는 인덱스 계획을 만들고도 태우지 않았다. DBA_SQL_PLAN_BASELINES에 줄이 하나 늘어 있었다. ORIGIN AUTO-CAPTURE · ACCEPTED NO. 대기 줄이었다.

진화 과제(CREATE_EVOLVE_TASK → EXECUTE_EVOLVE_TASK)를 돌렸다. 시험 운행이다.

 1. The plan was verified in 0.25000 seconds. It passed the benefit criterion
    because its verified performance was 63.70970 times better than that of the
    baseline plan.

합격증을 붙였다(IMPLEMENT_EVOLVE_TASK). 다음 실행은 INDEX RANGE SCAN, 91블록이었다.

둘째 장면: 합격증은 나쁜 계획을 막는다. 이번에는 10화의 그 일요일 밤을 복사본에서 그대로 재현했다. Q2 꼴 문장의 HASH JOIN 계획을 베이스라인으로 올리고, 복사본을 TRUNCATE한 뒤 빈 테이블의 통계를 모으고(NUM_ROWS 0), 100만 행을 다시 넣었다. 통계는 0행인 채로 두었다.

베이스라인 켬 (기본)        HASH JOIN       E-Rows 1 · A-Rows 1000K   Buffers 5,868
                            Note: SQL plan baseline SQL_PLAN_2pxha3x45hucyed7345b9 used
내 세션만 베이스라인 끔     NESTED LOOPS    PK_M_CUS Starts 1000K      Buffers 1026K

통계도 문장도 같았다. 옵티마이저는 이 테이블이 한 행이라고 믿고 있었고(E-Rows 1), 베이스라인이 없었다면 그 일요일 밤처럼 100만 번 왕복했을 것이다. 그 NL 계획은 대기 줄에 ACCEPTED NO로 올라와 있었다. 진화 과제는 그 계획을 시험 운행한 뒤 떨어뜨렸다. 0.33281 times worse.

"그럼 이걸로 6,812를 보장할 수 있겠네요." 내가 말했다.

"그 합격증의 유효기간은 언제까지야?" 선배가 물었다.

액자에 적힌 줄을 떠올렸다. 유효기간 ~ 내년 2월 28일. 그 위에는 정격하중 1,000kg · 정원 15명이 있었다. 합격은 그 조건에서 받은 합격이었다. 열여섯 명이 타면 합격증이 붙어 있어도 그 엘리베이터는 검사에 합격한 상태가 아니다.

"…베이스라인도 그때 데이터에서 통과한 계획이에요. M_CUS가 2만 명이 됐을 때도 해시가 옳은지는 베이스라인만 봐서는 알 수 없어요. 06화 대장에 적은 재검토 조건은 그대로 있어야 해요."

"합격증은 누가 떼?"

속성 두 개를 바꿔 봤다. 매뉴얼에 따르면 FIXED = YES인 계획이 있으면 옵티마이저는 새 계획을 대기 줄에 올리지 않는다. 더 좋은 계획이 나와도 시험 운행할 후보가 생기지 않는 것이다(이건 직접 재 보지 않았다. 공책에는 '매뉴얼 내용'이라고 적어 두었다). ENABLED = NO로 내리자 그 문장은 베이스라인이 없을 때처럼 계획을 골랐다. 통계는 아직 0행이었고, 그 일요일 밤의 NL이 그대로 돌아왔다. 1026K였다. 합격증을 떼는 것도, 새 엘리베이터를 못 들이게 막는 것도 결국 사람이 하는 일이었다.

마지막으로 자동 기능을 확인했다. 운영 EE에는 새 계획을 밤마다 알아서 시험 운행하는 자동 진화 과제가 있다. 실습/04_SPM_자동기능_SYSDBA.sql로 켜 보려 하자 루트에서 한 줄이 떴다.

AUTO_SPM_EVOLVE_TASK ON : ORA-38153: Software edition is incompatible with SQL plan management.

과제 SYS_AUTO_SPM_EVOLVE_TASK는 INITIAL 상태에 실행 0회였다. 자동 SQL 튜닝 과제도 실행 0회였고, 프로파일 자동 수락은 FALSE였다. Free에는 자동 기능이 없다. 대기 줄을 비우는 시험 운행은 사람이 돌려야 하고, 그러려면 정기 점검(T8)에 줄을 하나 더해야 한다.

SPM의 상태 셋 · 캡처 · 진화 리포트 · FIXED의 뜻 · Free의 자동 기능 → 2_파고들기.md §2 · §3


오전 11시 40분 — 99.42% ​

17화에 ADDM이 남긴 한 줄이 있었다. Top SQL Statements 항목의 Run SQL Tuning Advisor on the SQL statement with SQL_ID "8ac5jyu193w2j". 부하 발생기의 Q2였다. 17화에서는 적어 두기만 했고, 오늘 돌렸다(실습/05_튜닝_어드바이저.sql).

ADDM이 고른 세 문장(부하 발생기의 Q2, Q3, Q1)에 SQL Tuning Advisor가 내놓은 답은 짧았다.

TASK_NAME      TYPE              RECS EST_BENEFIT_PCT
T18_ADDM_Q1    (none)               0
T18_ADDM_Q2    (none)               0
T18_ADDM_Q3    (none)               0

There are no recommendations to improve the statement. 셋 다 이미 고친 판이었다. 그래서 강도현 씨의 rev00 셋을 넣어 봤다.

T18_R0_Q1      RESTRUCTURE SQL      2               0
T18_R0_Q2      SQL PROFILE          1           99.42
T18_R0_Q3      (none)               0

Q1 rev00에는 '가공된 컬럼이 인덱스를 막으니 같은 뜻의 다른 모양으로 고쳐 쓰라'는 권고가 붙었다. 02화에 내가 한 일이었다. 금액이 50배로 찍히는 그 문장, Q3 rev00에는 아무 권고도 없었다. 도구는 빠른지를 봤을 뿐 맞는지는 보지 않았다. 마지막은 LEADING(o c) USE_NL(c)가 박힌 그 문장, Q2 rev00이었다.

  Recommendation (estimated benefit: 99.42%)
  ------------------------------------------
  - Consider accepting the recommended SQL profile.

                           Original Plan  With SQL Profile  % Improved
                           -------------  ----------------  ----------
  Elapsed Time (s):             .741584           .238911      67.78 %
  Buffer Gets:                  1026849              5868      99.42 %

1,026,849 → 5,868. 06화에 내가 힌트 두 줄을 지워서 얻은 숫자와 같았다. 도구는 SQL을 한 글자도 고치지 않고 같은 답에 이르렀다.

나는 권고를 받아들였다(ACCEPT_SQL_PROFILE). 그리고 T0로 다시 쟀다. 5,869블록, HASH JOIN, 계획 해시 2292855265로 rev01과 같은 계획이었다. Note: SQL profile PROF_T18_Q2 used for this statement. 메신저 창을 열어 박팀장에게 쓰기 시작했다. 개발팀 배포 없이도 계획을 고칠 수 있는 방법이 있습니다. 튜닝 어드바이저가 99.42% 개선을…

"보내기 전에." 선배가 말했다. 아침에 먼저 묻지 않겠다던 사람이 오늘 처음으로 먼저 입을 열었다. "그 프로파일 안에 뭐가 들었는지 봤어?"

못 봤다. 열어 봤다(DBA_SQL_PROFILES의 서명으로 SYS.SQLOBJ$DATA의 XML을 찾았다).

OPTIMIZER_FEATURES_ENABLE(default)
IGNORE_OPTIM_EMBEDDED_HINTS

두 줄이었다. 두 번째 줄을 소리 내어 읽었다. "…문장 안에 박힌 힌트를 무시하라." 튜닝 과제 리포트의 힌트 보고서에도 같은 말이 있었다. LEADING(o c) / rejected by IGNORE_OPTIM_EMBEDDED_HINTS.

"그러니까 이 프로파일이 한 일이 뭐야?"

"06화에 제가 한 일이요. 힌트를 뗀 거요." 화면을 봤다. "다만 코드에서 떼지 않고, 코드 바깥에 '그 힌트를 무시해'라고 적어 둔 거예요."

"반년 뒤에 누가 Q2 소스를 열면 뭐가 보여?"

"USE_NL이요. 박혀 있는 그대로요. 그 사람은 이 문장이 NL로 돈다고 생각할 텐데, 실행계획을 떠 보면 해시예요. 이유는 소스 어디에도 없고 딕셔너리 안에 있어요." 강도현 씨의 힌트는 적어도 코드 안에 있었다. 이 프로파일은 코드 밖에 있었다.

하나를 더 쟀다. 띄어쓰기와 대소문자만 바꾼 같은 문장에도 그 프로파일이 붙었다. 프로파일은 문장의 글자가 아니라 정규화한 서명에 붙기 때문에, 소스를 고쳐 써도 떨어지지 않는다. 프로파일을 지우고(DROP_SQL_PROFILE) 다시 쟀더니 1026K가 나왔다. 프로파일 하나가 그 숫자를 막고 있었던 것이다.

SQL Tuning Advisor의 과제 · 결과 읽기 · 프로파일의 정체 · 서명 → 2_파고들기.md §4


오후 12시 30분 — 팩을 끄면 ​

"하나만 더 재 봐." 선배가 말했다. "17화에서 팩 없는 서버 얘기 했지. 그 프로파일, 튜닝 팩이 없는 서버에서는 어떻게 돼?"

실습/06_팩_확인란_SYSDBA.sql로 CONTROL_MANAGEMENT_PACK_ACCESS를 메모리에서만 세 번 바꿨다. DIAGNOSTIC+TUNING(지금) → DIAGNOSTIC(진단 팩만 산 EE) → NONE(팩 없음) → 다시 DIAGNOSTIC+TUNING 순서다. 판마다 그 커서를 공유 풀에서 지우고 다시 쟀다.

판튜닝 과제 만들기베이스라인 올리기받아 둔 프로파일 (Q2 rev00)베이스라인 (S18_Q1)
DIAGNOSTIC+TUNING된다된다쓰인다 · 5,869 · 2292855265쓰인다
DIAGNOSTICORA-13717 Tuning Package License is needed된다에러 없이 무시된다 · 1026K · 2289018667쓰인다
NONEORA-13717된다에러 없이 무시된다 · 1026K쓰인다
DIAGNOSTIC+TUNING (되돌림)된다된다쓰인다 · 5,869쓰인다

에러는 과제를 만들 때만 났다. 받아 둔 프로파일은 에러 없이 사라졌다. 계획이 NL로 돌아갔고, 실행계획의 Note에서 프로파일 줄이 빠졌을 뿐 아무 경고도 없었다. 베이스라인은 NONE에서도 쓰였다. SPM은 팩에 딸린 기능이 아니었다.

"이게 왜 중요해?" 선배가 물었다.

"계약이 바뀌면 이 해결책이 사라지니까요. 서버를 SE2로 옮기거나, 계약을 갱신하면서 튜닝 팩을 빼거나, 누가 파라미터 하나만 바꿔도 Q2가 다음 날 아침 1,026,849로 돌아가요. 에러도 없이요. 그 원인은 코드에도 실행계획에도 없고요." 반쯤 쓴 메시지를 지웠다. "프로파일은 기각할게요. 코드로 고칠 수 있는 걸 계약으로 고치면 안 돼요. 그리고 Q2는 이미 코드로 고쳐져 있어요. rev01이요."

T1 13호를 썼다. SQL 프로파일 PROF_T18_Q2: 기각. 근거 칸에는 세 가지를 적었다. 프로파일의 정체(힌트를 무시하라는 두 줄), 팩 설정 하나로 에러 없이 사라진다는 점, 같은 계획을 코드가 이미 가지고 있다는 점이다. 「채택하지 않은 대안」 칸이 아니라 결론 칸에 '기각'을 적은 첫 리포트였다.

팩 설정 판마다의 실측 · 되돌림 확인 → 2_파고들기.md §5


오후 1시 20분 — 3배라는 건 누가 잰 거야 ​

정하은 씨가 노트북을 들고 왔다. 개발팀에서 Q2 화면을 맡았던 사람이었다.

"리뷰 부탁드려요. 팀장님이 첫 리포트로 써 보라고 하셔서요."

[튜닝 결과 보고]  등급별 매출 집계 화면
 - 개선 내용 : 주문을 고객별로 먼저 집계한 뒤 고객 등급과 조인하도록 변경
 - 결과      : 약 3배 빨라졌습니다 (개발 DB 에서 테스트 완료)
 - 첨부      : 변경 SQL 1건

첨부를 열었다. 08화에 내가 보류해 둔 모양과 같았다. 인라인 뷰에서 GROUP BY CUS_ID를 먼저 한 뒤 M_CUS에 붙이는 판이었다. 16화에 재검토 조건을 걸어 둔 바로 그 보류안이었다. 결론은 아마 맞을 것이다.

결론은 말하지 않았다. 3월 4일 9시 40분이 떠올랐다.

"3배라는 건 누가 잰 거예요?"

"제가요."

"몇 번요?"

"…한 번이요. 1.2초가 0.4초로 나왔어요."

"무엇으로요?"

"SQL Developer 아래쪽에 나오는 실행 시간이요."

"개발 DB의 주문은 몇 건이에요?"

정하은 씨가 입을 열었다가 도로 닫았다. "모르겠어요. 운영 복사본이라고만 들었어요."

"그럼 이 리포트에는 측정이 하나도 없어요. 증언이 하나 있을 뿐이죠." 3월에 선배가 한 말이 내 입에서 그대로 나왔다. 내 목소리로 들으니 낯설었다. "결론이 틀렸다는 게 아니에요. 이 종이만 봐서는 맞는지 틀렸는지 알 수가 없다는 거예요. 같이 재 봐요. 대신 어떻게 잴지는 하은 씨가 정해요."

정하은 씨는 T0부터 펼쳤다. 같은 세션에서 한 번 데운 뒤 재고, Buffers N → M으로 적는다. 실습/09_Q2_보류안_T1.sql의 첫 판을 같이 돌렸다.

rev01    HASH JOIN 1000K → HASH GROUP BY            Buffers 5,869
rev02    VIEW (HASH GROUP BY 1000K → 2,000) → HASH JOIN 2,000   Buffers 5,868

"…한 블록이요." 정하은 씨가 화면을 봤다. "그럼 안 빨라진 거예요?"

"Buffers는 뭘 세는 거죠?"

"읽은 블록이요."

"이 SQL에서 줄어든 게 읽는 양이에요?"

정하은 씨가 계획을 다시 들여다봤다. 한참 걸렸다. "…아니요. 둘 다 T_ORD_BIG을 한 번씩 다 읽어요. 줄어든 건 해시 조인에 들어가는 행이에요. 100만 건이 2천 건이 됐어요. 블록이 아니라 CPU가 할 일이 준 거예요."

"그럼 그걸 어떻게 재요?"

"CPU를요. …그런데 한 번만 재면 또 증언이죠?"

같은 세션에서 10번을 한 묶음으로 세 번 재고, 스크립트를 두 번 돌렸다.

rev01    Buffers/run 5,868   CPU 10회 115 · 117 · 116 · 115 · 115 · 115 cs
rev02    Buffers/run 5,868   CPU 10회  33 ·  34 ·  33 ·  33 ·  33 ·  34 cs

3.5배였다. 정하은 씨가 말한 '약 3배'는 맞았다. 다만 이제는 숫자가 있었다.

"답은 같아요?" 내가 물었다.

정하은 씨가 정합성 회귀 체크리스트를 펼쳤다. 처음 보는 문서였다. 건수 3 = 3, 합계 255,398,623,700 = 255,398,623,700, MINUS 양방향 0 / 0이었다. 다음은 E 항목, 경계였다. 복사본에 지역 코드를 한 번 더 적재하자 두 판 모두 ORA-01427로 멈췄다. 08화에서 '틀리면 멈춰야 한다'며 지켜 둔 성질이 그대로 남아 있었다. 지역 코드 하나를 코드 표에서 빼자 두 판이 같은 답을 냈다.

"이제 리비전이에요?" 정하은 씨가 물었다.

"T0로는 아니에요. 블록이 같으니까요." 내가 말했다. "그래서 하나가 더 있어야 해요. 이 CPU가 시스템 전체에서 얼마짜리인지요. 16화에 쟀는데, 부하 모델에서 Q2가 DB CPU의 47.5%였어요. 그 47.5%에서 3분의 2가 빠진다는 것까지 적어야 이 보류안이 리비전이 돼요."

정하은 씨가 리포트를 다시 썼다. T1 12호다. 12칸을 다 채우는 데 한 시간이 걸렸다. 2번 칸 측정 환경에는 ora-hanorder의 주문 1,000,000건과 고객 2,000명을, 3번 칸에는 '단일 세션 · 개발 DB의 1.2 → 0.4초는 근거에서 뺌 (건수 미상)'을, 7번 칸에는 Buffers 5,868 → 5,868 · CPU 10회 115 → 33 cs (6묶음)를 적었다. 마지막 12번 칸 앞에서 정하은 씨가 물었다.

"재측정은 언제 해요?"

"하은 씨가 정해요. 이 리비전의 이득은 어디서 나와요?"

"해시 조인에서 맞춰 보는 행이 100만에서 2천으로 줄어서요. …고객 수만큼으로요." 정하은 씨가 천천히 말했다. "그럼 고객 한 명당 주문이 적어지면 이득도 줄어요. 지금은 한 명당 500건이에요."

정하은 씨는 12번 칸에 그대로 적었다. 고객 1인당 주문 10건 아래 · M_CUS 2만 명 초과 · PGA 변경 · 6개월. 리비전/Q2_rev02.sql이 18화의 유일한 리비전이 됐다.

보류안 → 리비전의 측정 · 정합성 · 판 번호 규칙 → 2_파고들기.md §6


오후 2시 30분 — 화물 운행 시간표 ​

오과장의 월마감 재실행이 남아 있었다. 낮에 돌리면 화면이 느려지는데, 리소스 매니저로 막을 수 있다고 들었다고 했다.

매뉴얼에는 그렇게 적혀 있었다. 소비자 그룹을 나누고 플랜에 MGMT_P1로 비율을 주면, CPU가 모자랄 때 그 비율대로 나눈다. 상한은 UTILIZATION_LIMIT로 따로 준다.

"온라인 90, 배치 10으로 주면 화면은 거의 원래대로 돌아오겠네요." 내가 말했다. "배치는 늦게 끝나고요."

"재 봤어?" 선배가 말했다.

실습/07a_리소스플랜_만들기_SYSDBA.sql로 그룹 둘(C18_ONLINE_GRP, C18_BATCH_GRP)과 플랜 둘(90 / 10인 C18_DAYTIME_PLAN, 여기에 배치 상한 10%를 더한 C18_CAP_PLAN)을 만들고, 부하 발생기의 MODULE로 세션을 나눴다. 월마감 집계는 07_배치_일꾼.sql 두 개로 흉내 냈다. 07_격리_세_판.sh로 5분씩 네 판을 돌렸다.

판   플랜                  온라인 (1분당 실행 · 평균 ms)   배치 (1분당 판)
A    (배치 없음)            41,741 · 2.87
B    DEFAULT_PLAN          20,497 · 5.83                      991
C    C18_DAYTIME_PLAN      23,754 · 5.03                      836
D    C18_CAP_PLAN          23,321 · 5.12                      861

배치가 들어오자 화면 처리량은 절반이 됐다. 90 / 10 플랜을 켜자 16%가 돌아왔다(한 번 더 돌렸을 때는 19%). 상한 10%를 걸어도 마찬가지였다. 판 D가 끝난 뒤 V$RSRC_CONSUMER_GROUP을 보니 더 분명했다. 배치 그룹이 쓴 CPU는 265초, 온라인 그룹은 342초였다. 10% 상한을 건 그룹이 CPU의 44%를 쓴 것이다.

세션은 제 그룹에 들어가 있었고 플랜도 켜져 있었다. resmgr:cpu quantum 대기도 찍혔다. 다만 비율이 지켜지지 않았다. 파라미터와 매핑, 그룹, CPU 범위를 한 시간 동안 뒤졌지만 원인을 찾지 못했다.

"매뉴얼이 틀린 거예요?" 정하은 씨가 물었다.

"매뉴얼은 EE 얘기예요. 이건 CPU 두 개짜리 Free의 PDB고요. 이 장비에서 이 플랜이 약속대로 나누지 않았다는 데까지가 우리가 잰 거예요. 왜 그런지는 몰라요." T3 3호의 「동시 실행 영향」 칸에 그대로 적었다. 재 본 것: 낮에 겹치면 온라인 처리량 −51%. 90 / 10 플랜 · 상한 10%로 되찾은 것: 처리량의 16 ~ 19%뿐. 원인 미확인. 운영 EE에서 같은 시험을 하기 전에는 리소스 매니저를 근거로 낮 실행을 허락하지 않는다.

그러다 1층 엘리베이터 홀의 액자 옆에 붙은 작은 안내문이 떠올랐다. 17화에서 8층 이삿짐 업체가 문을 잡고 엘리베이터를 5분 동안 세워 둔 뒤에 붙은 것이었다.

화물 운행 안내
  화물 · 이삿짐은 3호기만 · 평일 19:00 이후 · 주말
  관리실에 미리 신청

건물은 엘리베이터를 승객용과 화물용으로 나눠 동시에 돌리지 않고, 시간을 나눴다. 오과장에게 답을 보냈다. 월마감 재실행은 오늘 19시 이후에 부탁드립니다. 낮에 겹치면 화면 처리량이 절반이 되고, 이 서버에서는 리소스 매니저로 그걸 막지 못했습니다 (측정값 첨부). 19시에 시작하면 예상 종료는…

예상 종료 시각은 오과장이 알고 있었다. 그래서 그것도 물었다.

플랜 · 그룹 · 매핑 · 네 판의 숫자 · 빈 값으로 끄면 생기는 일 → 2_파고들기.md §7


오후 3시 30분 — 옛 판과 지금 판 ​

박팀장은 반기 보고에 한 장을 더 넣고 싶어 했다. 18주 전과 지금, 서버 전체로는 뭐가 달라졌나.

16화에 AWR 리포트를 처음 읽었고, 17화에는 그 리포트의 평균을 의심하는 법을 배웠다. 오늘은 두 구간을 나란히 놓고 비교하는 리포트 awrddrpt를 뽑았다. 다만 18주 전의 구간은 없다. 3월의 AWR은 보존 기간(8일)이 지나 오래전에 지워졌다. 그래서 같은 날 두 구간을 연달아 만들었다. 하나는 부하 발생기의 나쁜 판 셋을 10분 내내 켠 구간이다. Q2의 USE_NL(rev00), Q4의 문자열 이어 붙이기, P1의 커서 루프(rev00)가 그 셋이다. 다른 하나는 지금 판으로만 10분 돌린 구간이다. 두 구간 모두 같은 3세션에 생각 시간은 0이다. 실습/08_옛판_지금판.sh로 20분을 돌렸다.

리포트의 첫 화면은 이랬다.

Snapshot Set  Begin Snap  End Snap   Avg Active Users  Elapsed Time (min)  DB time (min)
1st (옛 판)          50        51                3.0                 8.0           24.0
2nd (지금 판)        52        53                3.0                 8.0           24.1
                                          %Diff:  0.7%                0.0%           0.5%

"18주 했는데 DB time이 그대로네요?" 정하은 씨가 말했다. 반쯤 농담이었지만, 박팀장도 그 줄만 읽으면 똑같은 말을 할 터였다.

"DB time은 세션이 일하거나 기다린 시간을 더한 값이에요. 세 세션이 스냅샷 사이 8분 동안 쉬지 않았으니까 두 구간 모두 3 × 8분 = 24분이에요. 그 칸으로는 '얼마나 바빴나'를 알 수 있어요. '무엇을 했나'는 다른 칸에 있고요." 화면을 아래로 내렸다.

                         옛 판 / 초       지금 판 / 초     %Diff
Logical read (blocks)    5,042,205.5        275,051.0      −94.5
Redo size (bytes)       30,228,711.0        651,251.1      −97.8
Executes (SQL)             100,721.5          1,515.3      −98.5

부하 발생기가 센 일의 양도 봤다. 같은 10분짜리 런에서 옛 판은 조각을 58,640번, 지금 판은 363,367번 돌렸다. 6.2배였다. Q2 한 번에 294.56ms이던 것이 40.43ms가 됐고, P1 한 번에 1,370ms이던 것이 142ms가 됐다. 옛 판의 대기 1위는 log file switch (checkpoint incomplete)(22.9%)였다. 커서 루프가 초당 30MB의 redo를 썼기 때문이다. 지금 판의 대기 1위는 resmgr:cpu quantum(23.0%)이었다. 일이 빨라지자 이제는 CPU 두 개가 한계였다.

한 줄이 더 있었다. 시간 모델 표에서 %Diff가 가장 큰 줄이었다.

sequence load elapsed time      0.0 %  →  48.3 %  of DB time      0.1 s → 698.2 s

"이건요?" 정하은 씨가 물었다. "시퀀스가 DB time의 절반이에요?"

17화의 Sequence Usage가 떠올랐다. 지금 판에서 시퀀스를 부르는 조각은 N1 하나뿐이다. N1은 그 10분 동안 50,682번, 한 번에 0.14ms씩 돌았으니 다 합쳐도 7초다. 698초가 나올 수 없었다. ASH를 조각(ACTION)별로 셌다. 시퀀스 적재 깃발(IN_SEQUENCE_LOAD)이 켜진 표본이 Q2에 363장, P1에 231장, Q3에 205장 있었다. 모두 시퀀스를 한 번도 부르지 않는 조각이었다. 옛 판에서는 0장이었다.

"이 칸으로는 결론을 내지 않아요." 내가 말했다. "%Diff가 제일 크다고 제일 중요한 건 아니에요. 다른 칸에서도 같은 얘기가 나올 때까지는요. 이 깃발이 언제 켜지는지는 17화에서도 오늘도 밝히지 못했어요. 그것도 그렇게 적어 둬요."

T5-003을 썼다. 1번 칸에는 측정 조건을 적었다. 같은 날 · 같은 장비 · 같은 3세션 · 연달아 10분씩 (워크로드 프로파일 WP-18O / WP-18N). 3월과 지금을 비교한 것이 아니라 '3월의 나쁜 판 셋'과 지금을 비교했다는 점도 첫 줄에 밝혔다. 부하 발생기가 바꿀 수 있는 조각은 셋뿐이었고, Q1과 Q3은 두 구간 모두 지금 판이었다.

awrddrpt의 칸 · %Diff는 두 구간의 조건이 같을 때만 뜻이 있다 · 시퀀스 깃발 → 2_파고들기.md §8


오후 4시 30분 — 마지막 조항 ​

책상 위에 18주 동안 쓴 리포트를 펼쳤다. T1 1호부터 13호까지, T2 둘, T3 셋, T4 둘, T5 셋, T7 셋, T8 하나, 정책서 둘, 재협상서 하나, 대장 하나, 그리고 03화의 정합성 이상 보고였다. 박팀장이 원한 「튜닝 표준」은 이 종이들에서 나와야 했다.

처음에는 쓰고 싶은 조문부터 썼다. 첫 줄은 '날짜 범위에 BETWEEN을 쓰지 않는다'였다. 02화에서 얻은 첫 교훈이었다.

선배가 지나가다 멈췄다. "그거 몇 번 나왔어?"

"…02화요."

"또?"

"03화에서는 BETWEEN이 맞았어요. VARCHAR2(8)이라 잘릴 시분초가 없어서요." 그날 선배는 '규칙을 외우기 전에 컬럼을 본다'고 했다. 조문을 지웠다. "한 번이에요."

"한 번 나온 건 사건이야. 두 번 나오면 구조고." 선배가 말했다. "표준에는 구조만 적어. 사건을 조문으로 만들면 표준이 그날그날 네 기분 따라 불어나."

규칙을 정했다. 원인 칸의 말을 그대로 뽑아 같은 원인끼리 묶고, 리포트 둘 이상에서 나온 원인만 조문으로 올린다. 한 번 나온 것은 버리지 않고 대기 줄에 세운다. 조문마다 붙는 각주 셋(근거 리포트, 성능고도화 좌표, 공식문서 좌표)은 그 리포트를 쓴 화에 적어 둔 것만 옮긴다. 없으면 좌표 미확인이라고 적는다.

정하은 씨가 옆에서 같이 셌다. '한 칸으로 결론을 냈다'는 T1 5호, T5-001, T8 1호, T5-002로 넷이었다. 'Buffers로 안 보이는 것이 회귀했다'는 여섯, '이 데이터에서 같다를 맞다로 읽었다'는 다섯, '행마다 건너간다'는 넷이었다. 오후 내내 센 끝에 열여섯 개가 남았다. 11화의 바인드 조문 여섯은 대기 줄로 내려갔다. 숫자는 컸지만 리포트는 T1 9호 하나뿐이었다.

가장 많이 나온 원인은 마지막까지 남았다. 아홉 번이었다. T2-001의 "RANGE SCAN으로 변경 완료" 한 줄, 06화의 회의록, 08화의 '1쪽'이라는 전제, 09화의 총건수 요구, 10화의 "적재 직후 통계 수집", 12화의 "매기는 곳이 하나뿐", 15b화의 "DETERMINISTIC", 그리고 오늘 정하은 씨의 "3배"였다. 모양은 다 달랐지만 하는 말은 하나였다. 결정은 남았는데, 그 결정이 기대던 것은 남지 않았다.

마지막 조항을 썼다.

모든 성능 결정에 그 당시의 데이터 규모와 근거 수치를 함께 적는다.

그 아래 해설에 한 줄을 붙였다. 강도현은 숫자를 남기지 않았다.

선배가 그 줄을 읽었다. 이번에는 묻지 않고 파일 하나를 열어 내 쪽으로 돌렸다. 06화에서 처음 본 회의록이었다.

2018-11-06 성능개선 회의록
  3. 등급별 매출 집계 화면 지연
     - 현상 : 조회 시 약 3초
     - 조치 : 조인 방식을 USE_NL 로 고정
     - 결과 : 3초 -> 0.4초
     - 결론 : 적용 승인

3초 → 0.4초. 숫자는 있었다. 커밋 로그에는 이유도 있었다. 해설을 고쳤다. 강도현은 숫자를 남겼다. 그 숫자가 어떤 규모에서 나왔는지는 남기지 않았다.

선배가 스크롤을 끝까지 내렸다. 첨부 자료의 마지막 줄이 나왔다.

   ※ 측정 조건 : 운영 DB 복제본, 주문 20,000 건

06화에 내가 찾아낸 줄이었다. 그날 그 줄을 보고 2만 건에 굵게 밑줄을 쳤다. 그런데 오늘은 그 줄을 잊고 있었다.

"…남겼네요. 규모도." 해설을 다시 지웠다. 이유는 커밋 로그에, 숫자는 회의록 본문에, 규모는 첨부의 맨 끝 줄에 있었다. 셋 다 있었다. 다만 셋이 모두 다른 곳에 있었다. 그리고 다음 사람이 실제로 열어 보는 곳, 곧 소스에는 한 줄뿐이었다. -- 건드리지 말 것 (2018.11 강도현). 언제 다시 잴지는 어디에도 없었다.

1층의 액자가 떠올랐다. 합격증에서 가장 큰 글씨는 합격이었다. 그런데 그 한 단어만으로는 아무것도 알 수 없다. 합격이 무슨 뜻인지는 그 아래 작은 글씨, 정격하중 1,000kg · 정원 15명 · 유효기간에 있었다. 그리고 그 셋은 검사 기관의 서류철이 아니라 한 장에 적혀, 사람이 타는 문 옆에 걸려 있었다. 3초 → 0.4초는 합격이었다. 정격하중은 서류철 맨 뒤에 있었다.

해설을 다시 썼다. 강도현이 하지 않은 단 한 가지는 숫자와 규모를 함께, 다음 사람이 보는 자리에 적는 것이다. 마지막 조항의 '함께'를 굵게 표시했다.

"3월 4일에 네 원장에 화살표 하나 그리라고 했지." 선배가 말했다. "← 이 원장의 전제. 그때 왜 그리는지 알았어?"

"몰랐어요."

"오늘 아침에 1,007을 보고도 원장을 안 지운 건 그 화살표 덕분이야." 선배가 의자를 제자리로 돌렸다. "그게 그 조항이야."

사내 튜닝 표준 T6 v1.0이 나왔다. 조문 열여섯, 대기 줄 아홉, 코드 리뷰 체크리스트, 팩 라이선스 확인란, 그리고 마지막 조항으로 이루어져 있다. 같은 규칙으로 SQL 코딩 가이드도 v1.0이 됐다. 11화의 조문 여섯은 대기 조문으로 내려갔고, 15b화의 조문 11은 03화의 NLS와 묶여 '숨은 입력'이라는 더 넓은 조문으로 올라갔다.

승격 판정표 · 각주 · 실패한 후보 · 체크리스트 → 3_실습과_기록.md STEP 10 · 2_파고들기.md §9


⛳ 오늘은 여기까지(중단 지점 2) 표준이 나왔다. 그 마지막 줄은 3월 4일에 그린 화살표 하나였다. 오늘 할 일은 여기까지만 해도 다 한 셈이다. 이어지는 저녁은 시험이다. 선배가 새 고객사 하나를 넘겨주고 옆자리를 비운다.


오후 5시 30분 — 봉인 ​

선배가 봉투 하나를 건넸다. 안에는 접속 정보 한 장과 문서 두 개의 경로를 적은 메모가 들어 있었다.

ora-newshop  ·  ecommerce / ecommerce  ·  FREEPDB1
신규 이커머스 플랫폼 — 정규화 30 테이블
설명서 : 이커머스_ERD_조감도.md · 이커머스_데이터셋_구축가이드.md
현업 SQL 넷 (E1 ~ E4) — 셋은 느리다는 민원 · 하나는 숫자가 이상하다는 민원

"오늘 저녁부터는 혼자야." 선배가 말했다. "설명서는 먼저 읽어. 그리고 믿지 마."

"선배가 쓴 거예요?"

"그 고객사 IT에서 보내온 거야. 누가 썼든 마찬가지야. 3월에 너는 강도현 씨 주석 한 줄, '건드리지 말 것'을 받았지. 그 주석도 사실이었어. 그때는." 선배가 가방을 들었다. "설명서에 적힌 내용이 데이터 사전과 다르면 데이터 사전을 믿고 설명서를 고쳐. 고칠 때는 지우지 말고 옆에 적고."

설명서를 읽었다. 테이블 30개, 행수 표, 관계선, 인덱스 절이 있었다. '있는 것 / 없는 것' 표의 마지막 줄에는 굵은 글씨가 있었다.

그리고 첫 쿼리를 쳤다. 3월 4일에 처음 던진 것과 같은 질문, 무엇이 얼마나 있는가였다. 그다음은 제약 조건이 몇 개이고 어떤 상태인가였다.

sql
SELECT constraint_type, status, validated, COUNT(*)
  FROM user_constraints
 GROUP BY constraint_type, status, validated
 ORDER BY 1, 2;

첫 줄부터 설명서와 달랐다.

어느 줄이 어떻게 달랐는지는 여기에 적지 않는다. 그것이 독자의 첫 번째 과제다. 그다음 줄부터 나는 고아 행을 세고, 현업 SQL 넷을 같은 규약으로 재고, 원장을 썼고, 한 번 틀렸다. 그 저녁의 나머지는 3_실습과_기록.md의 졸업 과제에 있다. 모범 답안은 봉인되어 있다. 답안을 열기 전에 자기 원장을 먼저 써야 한다.


오후 6시 10분 ​

사무실이 비어 가기 시작했다. 정하은 씨가 가방을 싸다가 내 화면에 떠 있는 원장을 봤다. W01, 2열이 붙은 그 표였다.

"이 원장은 누가 잰 거예요?"

"내가요. 3월 4일에. 인덱스가 하나도 없을 때."

"그럼 오늘 숫자랑은 비교가 안 되는 거 아니에요?"

"그래서 전제를 적어 뒀어요. 맨 위에." 그 줄을 가리켰다. T_ORD_BIG 인덱스 0개 ← 이 원장의 전제. "비교가 된다기보다, 뭐가 달라져서 숫자가 달라졌는지를 읽을 수 있게 되는 거죠."

정하은 씨가 고개를 끄덕이고 나갔다. 원장의 18화 절 맨 아래에 한 줄을 더 적었다. 다음 재측정: 1월 첫 주 (6개월) · 또는 T_ORD_BIG 1,000만 건 · 또는 고객 2만 명 · 또는 인덱스 · 통계 정책이 바뀔 때. 그날 이 표 오른쪽에 열을 하나 더 붙인다.

선배가 코트를 입고 내 자리 옆을 지나갔다.

"오늘 내가 먼저 물은 게 몇 번이었는지 알아?"

"…두 번이요. 프로파일 보내기 전에, 그리고 팩."

"세 번. 화살표도." 선배가 웃었다. "그래도 세 번이면 됐어. 내일부터는 하은 씨한테 네가 그 세 번을 아껴. 답을 알아도 먼저 말하지 말고, 숫자를 물어."

선배가 나간 뒤 3월 4일에 받은 티켓을 다시 열었다. #4417 야간정산 화면 지연: 정산 조회 화면이 30초 넘게 걸립니다. 상태 칸을 해결로 바꾸고, 메모 칸에 이렇게 적었다.

30초는 측정이 아니라 증언이었다 (3월 4일). 같은 화면의 Q2는 3월 1,026,849블록 → 7월 5,868블록 · 같은 6조각 합 1,045,169 → 6,812 (같은 규약 · 각 날의 상태). 근거: W01 원장 2열. 규모: 주문 1,000,000 · 고객 2,000.


오늘 남은 것 ​

무엇값
W01 원장 2열rev00 그대로 오늘 Q1 5,826 · Q2 1,026,849 · Q3 840 · Q4 1,007 · Q4c 3,365(09화 인덱스 덕, SQL은 그대로) / 최신판 91 · 5,868 · 839 · 14 · 없음 / 합 1,045,169 → 6,812 · P1 rev02 100 ~ 110ms · N1 rev01 0ms
SPMQ1 꼴: 합격증(FULL 5,821)이 인덱스 계획을 막음 → 진화 63.7배 → 91 / Q2 꼴: 빈 테이블 통계에서도 HASH 5,868 유지 · 베이스라인을 끄면 NL 1026K · NL 계획은 진화에서 0.33배로 탈락 / Free의 자동 진화 ORA-38153
SQL Tuning Advisor고친 판 셋: 권고 없음 / Q2 rev00: 프로파일 99.42%(1,026,849 → 5,868) = IGNORE_OPTIM_EMBEDDED_HINTS / Q3 rev00(50배): 권고 없음 → 기각(T1 13호)
팩 확인란DIAGNOSTIC · NONE: 과제 ORA-13717 · 프로파일은 에러 없이 무시(1026K) · 베이스라인은 쓰인다 → 되돌림 DIAGNOSTIC+TUNING
Q2 rev02 (T1 12호)Buffers 5,868 → 5,868 · CPU 10회 115 → 33cs · 해시 조인 입력 1,000,000 → 2,000 · MINUS 0 / 0 · 경계 둘 같음
리소스 매니저 (T3 3호)온라인 41,741 → 배치와 겹치면 20,497 /분 · 90 / 10 플랜 23,754 · 상한 10% 23,321 · 배치 그룹 CPU 265초 vs 온라인 342초: 비율이 지켜지지 않음 · 원인 미확인 → 시간으로 나눈다
awrddrpt (T5-003)DB time 24.0 vs 24.1분(같은 3세션) · 일 6.2배 · Logical read −94.5% · Redo −97.8% · sequence load elapsed time 48.3%는 결론에 쓰지 않음
산출물T6 사내 튜닝 표준 v1.0 · SQL 코딩 가이드 v1.0 · W01 원장 2열 · 리비전 대장 · T1 12 · 13호 · T3 3호 · T5-003 · T7 3호 · T2-002 갱신 · 이커머스 설명서 정정 둘

오늘 배운 한 문장 ​

결정과 그 결정이 기대던 규모를 따로 적으면, 다음 사람에게는 결정만 도착한다. 합격증의 '합격'은 정격하중과 한 장에 적혀 있을 때만 뜻이 있다. 3월의 원장이 7월에 지워지지 않은 것은 숫자 바로 위에 전제가 적혀 있었기 때문이다.

다음으로 넘기는 것 ​

이 교재는 여기서 끝난다. 넘기는 것은 둘이다.

  • 졸업 과제: ora-newshop을 혼자 진단한다 → 3_실습과_기록.md (독자의 몫)
  • Free에서 잴 수 없었던 것: 병렬 · RAC · 운영 EE의 리소스 매니저 · 자동 SPM 진화 · 진짜 팩 없는 서버 → 별권

다음 → 3_실습과_기록.md에서 원장에 2열을 붙이고, 계획을 지키고, 권고를 기각하고, 후배의 리포트를 다시 쓰고, 표준을 만든다. 그리고 혼자서 새 고객사를 진단한다. 개념이 막히면 → 2_파고들기.md