테마
15b화 · 실습과 기록
여기서 하는 일: 얼어붙은 정산일을 컨테이너 시계를 건드리지 않고 재현하고, 캐싱 4계층을 같은 일에 나란히 재고, 무효화와 FORCE, 경합을 확인하고, 정산일 함수를 고쳐 경계에서 대조하고, T1 리포트 11호를 쓴다.
1_사건.md를 읽은 것은 "선배가 하는 걸 지켜본 것"이고, 이 문서를 끝내야 직접 해 본 것이 된다. 개념이 막히면2_파고들기.md를 펼친다.시간이 없는 주에는 STEP 0 → 1 → 2 → 3 → 8만 한다(약 40분). 무효화, FORCE, 경합은 다음 주에 돌아와서 해도 된다.
이 화의 실습 지도
| 스크립트 | 무엇 | 계정 | 소요 |
|---|---|---|---|
실습/91_결과캐시_권한_SYSDBA.sql | DBMS_RESULT_CACHE 실행 권한(한 번) | SYS | 1초 |
실습/00_준비와_점검.sql | 15화 종료 상태 확인 · 복사본(C15B) · 함수 판(版)들 · 수첩 · 옆 세션 연락판 | sqlp | 5초 |
실습/01_얼어붙은_정산일.sql → 01b_옆세션.sql | ★ 오늘의 사건: 의존 객체 · '초' 판 · 목록 · 시간대 · NLS · 세션 상태 · 거절/받아 줌 · 녹이기 · 네 판 · 경계 시험 | sqlp(세션 둘을 차례로) | 15초 + 5초 |
실습/02_캐싱_4계층.sql + 일꾼.sql | ★ 캐싱 4계층 · 칸 · DETERMINISTIC의 범위 · Bypass | sqlp × 2 | 10초 |
실습/03_무효화.sql + 일꾼.sql | 커밋 전 · 뒤 · ROLLBACK · 같은 값 · 한 명 → 전부 · RELIES_ON · 예외 · 컴파일 | sqlp × 2 | 5초 |
실습/04_FORCE_세션.sql | RESULT_CACHE_MODE = FORCE(내 세션에서만) | sqlp | 5초 |
실습/05_경합.sql + 일꾼.sql | 둘이 같은 답을 동시에 찾으면 · 래치 | sqlp × 2 | 5초 |
실습/93_결과캐시_무결성_SYSDBA.sql | (선택) ENFORCED면 무엇을 거절하나 · 끝에서 원복 | SYS | 5초 |
실습/99_정리.sql → 92_결과캐시_권한회수_SYSDBA.sql | C15B 삭제 · 결과 캐시 비우기 · 15화 종료 상태 확인 → 권한 회수 | sqlp → SYS | 수 초 |
PowerShell에서는 먼저
$OutputEncoding = New-Object System.Text.UTF8Encoding $false를 한 줄 실행한다. 모든 스크립트를 두 번씩 돌려 같은 판정이 나오는 것을 확인했다. 본문 실행 수, 찾기, 만들기, 논리 읽기, 건수는 같고 경과 시간과 래치 misses만 흔들린다. 판마다 달라지는 것이 하나 있는데, 칸이 밀려날 때 어느 결과가 밀려나는지다. 이것은 해당하는 자리에 적었다. 원본M_CUS,T_ORD_STLM,T_ORD_BIG에는 한 줄도 쓰지 않는다. 고치는 것은 전부_C15B복사본이다. ℹ️ 스크립트를 다시 돌리면 실행계획 자리에ORA-01403: no data found가 가끔 찍힌다(교재 실측:02[A]에서 아홉 판 중 한 번).00이 복사본을 다시 만들어 커서가 무효화된 직후라DISPLAY_CURSOR가 계획을 집지 못한 것이고, 측정 자체는 됐다. 그 스크립트를 한 번 더 돌리면 된다(00_자료준비/04_일상_운용과_문제해결.md). ⚠️ 스케줄러 잡은 쓰지 않는다(이 DB는job_queue_processes= 1). '옆 세션'은 셸이 두 번째 sqlplus를 띄워 만든다(일꾼.sql). 일꾼은 지휘 스크립트가 끝에 보내는EXIT를 받으면 끝나고, 60초 동안 지시가 없으면 스스로 끝난다.
STEP 0 — 직전 리비전을 연다 · 전제를 확인한다 (5분) ★ 매주 처음 하는 일
bash
docker start ora-hanorder
cat 리비전/SDT_rev00.sql # 오늘 고칠 물건 — 정산일 함수 (6/14 금요일 반영판)
MSYS_NO_PATHCONV=1 docker exec -i ora-hanorder sqlplus -s / as sysdba < 실습/91_결과캐시_권한_SYSDBA.sql
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 실습/00_준비와_점검.sqlpowershell
Get-Content 실습\91_결과캐시_권한_SYSDBA.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s / as sysdba
Get-Content 실습\00_준비와_점검.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1| 확인 | 기대 | ☐ |
|---|---|---|
91 주기 전 · 뒤 | DBA 하나 → DBA · SQLP · ENABLED | ☐ |
| [전제] big_rows · big_idx · hist · stlm_rows · last_no · pkg · drv · c15 | 1000000 · 3 · FREQUENCY · 100000 · 0 · VALID · VALID · 0 | ☐ |
| 데이터 지문 | 255,398,623,700 · 25,519,833,100 | ☐ |
| [설정] | result_cache_mode MANUAL · max_size 8126464 · max_result 5 · integrity TRUSTED · execution_threshold 2 · auto_blocklist ON · cpu_count 2 · job_queue_processes 1 · STATUS ENABLED | ☐ |
| 만든 것 | 2000 · 100000 · 무효 0 | ☐ |
USER_PROCEDURES | FN_STLM_DT_RC_C15B RESULT_CACHE YES · DETERMINISTIC YES(← 금요일 판) · FN_STLM_DT_DET_C15B NO · YES(← 2019 원본) · FN_STLM_DT_C15B NO · NO(← rev01) | ☐ |
| 메모리 보고 | 1984 blocks · 49 blocks | ☐ |
91 없이 00을 돌리면
[권한]에서ORA-20001 … run 91 … first로 멈춘다.sqlp는V$RESULT_CACHE_*는 읽지만DBMS_RESULT_CACHE는 부르지 못한다. 15화의DBMS_HPROF와 같은 경우다.
SDT_rev00.sql에서 두 단어에 밑줄을 긋는다. DETERMINISTIC(누가 · 언제 · 왜)와 RESULT_CACHE(누가 · 언제 · 왜)다. 그리고 본문 한 줄에서 이 함수의 진짜 입력을 찾는다.
STEP 1 — 예측을 봉인한다 (5분)
Q1. 정산일 함수에 RESULT_CACHE 를 붙였다. 정산이 계속 등록되면 그 캐시는 지워질까? 예 / 아니오
Q2. 목록 SQL (정산 10만 행) 이 정산일 함수를 행마다 부른다. 2019 원본(DETERMINISTIC)은 몇 번 부를까? ______
Q3. 예상 보정액 (정산 10만 행 · 고객 2,000명) 을 RESULT_CACHE 함수로 두 번 돌린다. 두 번째 판의 함수 실행은? ______
Q4. 고객 한 명의 등급을 고쳐 커밋하면, 보정률 결과 1,500칸 중 몇 칸이 지워질까? ______
Q5. RESULT_CACHE_MODE = FORCE 로 '시계를 숨긴 함수' 를 부르는 문장을 힌트 없이 돌리면? 시각이 간다 / 멈춘다
Q6. 결과 캐시에 있는 답 하나를 100만 번 찾는다. 혼자일 때와 둘이 동시일 때, 각자의 시간은? 같다 / 조금 는다 / 두 배 가까이▶ 적고 나서 펼치기
- Q1: 아니오. 정산일 답의 의존 객체는 함수 자신 하나다. 진짜 입력(시계)은 객체가 아니다.
- Q2: 대부분 10만이라고 쓴다. 실측은 1이다(인자 없는
DETERMINISTIC함수는 문장에 한 번만 불린다). 그래서 금요일의RESULT_CACHE가 번 것은 없다. - Q3: 대부분 0이라고 쓴다. 실측은 12,551이다. 결과 캐시 1,984칸에서 답 하나가 칸 하나를 차지하므로 고객 2,000명이 안 들어간다. 1,500명이면 0이다.
- Q4: 1,500칸 전부. 테이블 단위로 기억한다.
- Q5: 멈춘다. 힌트 한 줄 없이 금요일 사고가 난다.
- Q6: 두 배 가까이(20 ~ 21 → 34 ~ 42cs). 캐시 없는 함수는 둘이어도 그대로다.
STEP 2 — 얼어붙은 정산일 (15분) ★ 오늘의 사건
bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 실습/01_얼어붙은_정산일.sql
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 실습/01b_옆세션.sql # 바로 이어서 — 다른 세션'날짜가 바뀐 상황'은 이렇게 만든다. 컨테이너 시계는 바꾸지 않는다. ① 같은 모양 · 단위만 초: 정산일 함수에서
TRUNC만 뺀RETURN SYSDATE. 진짜SYSDATE로 진짜 2초를 기다린다. ② 세션 시간대:-11:00과+13:00(24시간 차이).SYSDATE는 세션 시간대를 따르지 않으므로 이 판만CURRENT_DATE로 썼다.FIXED_DATE는 쓰지 않는다. 되돌린 뒤에도 PDB 설정에 한 줄이 남았기 때문이다(2_파고들기 §5.2).
[A] 무효화가 고장 났나
| 확인 | 기대 | ☐ |
|---|---|---|
| 정산일 결과의 의존 객체 | SQLP.FN_STLM_DT_RC_C15B 하나 | ☐ |
| 보정률 결과의 의존 객체 | 함수 · SQLP.M_CUS_C15B | ☐ |
| [A1] 정산 한 줄 등록 · 커밋 뒤 | 둘 다 Published | ☐ |
[A2] 고객 마스터 같은 값 UPDATE · 커밋 뒤 | 보정률 Invalid · 정산일 Published | ☐ |
[B] ~ [E] 숨은 입력 넷
| 판 | 캐시 없는 판 | RESULT_CACHE 판 | ☐ |
|---|---|---|---|
| [B] '초' 판 2초 간격 세 번 | 시각이 간다 | 첫 값에 멈춘다 · 본문 1 · 0 · 0 | ☐ |
| [C] 목록 · 2초 뒤 한 줄 등록 | 100,001 | 100,000 · MINUS 1 / 0 · 빠진 것 900002 | ☐ |
| [C] 그 한 줄을 지우면 | 100,000 | 100,000. 경계 데이터가 없으면 대조가 통과한다 | ☐ |
[D] -11:00('어제') → +13:00('오늘') | D-1 → D | D-1 → D-1 | ☐ |
[D] sysdate · current_date | 두 세션 sysdate 같은 시각 · current_date만 24시간 이동 | ☐ | |
[E1] NLS MM-DD → DD/MM | 06-14 → 14/06 | 06-14 → 06-14 | ☐ |
[E2] 직원 kim → lee(CLIENT_IDENTIFIER) | owner=kim → owner=lee | owner=kim → owner=kim | ☐ |
[F] 오라클이 거절하는 것 · 받아 주는 것
문장 (/*+ RESULT_CACHE */ · 2초 간격 두 번) | 계획에 RESULT CACHE | 시각 | ☐ |
|---|---|---|---|
F1 SYSDATE 직접 · F4 SYSTIMESTAMP 직접 · F5 CURRENT_DATE 직접 | 없다(거절) | 간다 | ☐ |
F2 SYSDATE를 숨긴 그냥 함수 | 있다 | 멈춘다 | ☐ |
F3 같은 함수 + DETERMINISTIC | 있다 | 멈춘다 | ☐ |
| 캐시에 남은 문장 결과 | F2 · F3만 | ☐ |
[G] 녹이기 — 그리고 다시 언다
☐ 다시 컴파일 → Invalid 1 → 다음 호출이 그 순간으로 다시 언다 (2초 뒤에도 같은 값)
☐ DBMS_RESULT_CACHE.INVALIDATE → 다시 그 순간으로[H] 선배는 왜 붙였나 — 목록 SQL 네 판 (정산 10만 행 · 두 번)
| 판 | body | find | 경과 | 논리 읽기 | ☐ |
|---|---|---|---|---|---|
| H plain(그냥 함수) | 100,000 | 0 | 12 ~ 13cs | 549(첫 판 584 ~ 669) | ☐ |
| H det(2019) | 1 | 0 | 0 ~ 1cs | 549 | ☐ |
| H rc(rev00 · 금요일 판) | 0 | 1 | 0 ~ 1cs | 549 | ☐ |
| H rev01 call(스칼라 서브쿼리) | 1 | 0 | 0cs | 549 | ☐ |
☐ [H5] 금요일 판의 계획 — TABLE ACCESS FULL · Starts 1 · A-Rows 100K · Buffers 549 · filter("STLM_DT"<="FN_STLM_DT_RC_C15B"())
☐ 계획 해시 — plain · det · rc 셋 다 3721302985 · rev01 call 1560056510 (교재 실측값 · 해시는 판마다 같았다)
☐ RESULT_CACHE 가 이 목록에서 번 것 = 없다 (2019 의 DETERMINISTIC 이 이미 1 번)[I] 고친 판(rev01)의 시험 — 경계
| 시각 | 원장 | rev01 | rev00 | ☐ |
|---|---|---|---|---|
| 06-14 FRI 17:59:59 | 06-14 FRI | 06-14 FRI | D (now) | ☐ |
| 06-14 FRI 18:00:00 | 06-15 SAT | 06-15 SAT | D (now) | ☐ |
| 06-14 FRI 23:59:59 · 06-15 SAT 00:00:00 | 06-15 SAT · 06-15 SAT | 같다 | D (now) | ☐ |
| 06-16 SUN 18:30:00 · 06-17 MON 09:00:00 | 06-17 MON · 06-17 MON | 같다 | D (now) | ☐ |
| 06-17 MON 18:00:00 | 06-18 TUE | 06-18 TUE | D (now) | ☐ |
| 한 주 7분 간격 1,440점 | MINUS 0 / 0 | ☐ |
01b — 다른 세션
| 확인 | 기대 | ☐ |
|---|---|---|
| [A] 새 세션(PL/SQL) · SQL에서 | result_cache가 지금보다 3 ~ 4초 앞 · body 0(01 [G]가 마지막으로 얼린 값) | ☐ |
| [A] 캐시에 있는 '초' 판 결과 | 앞의 것들 Invalid · 마지막 하나 Published | ☐ |
[B] +13:00으로 붙은 직원 | plain D · result_cache D-1 | ☐ |
[C] NLS DD.MM · 직원 park | plain 14.06 · owner=park / result_cache 06-14 · owner=kim | ☐ |
STEP 3 — 캐싱 4계층 (15분) ★ 오늘의 표
bash
H="docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1"
$H < 실습/일꾼.sql > /dev/null & $H < 실습/02_캐싱_4계층.sql; waitpowershell
$j = Start-Job -ArgumentList (Resolve-Path 실습\일꾼.sql) -ScriptBlock { param($p)
Get-Content $p -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 }
Get-Content 실습\02_캐싱_4계층.sql -Encoding UTF8 | docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1
$j | Wait-Job | Remove-Job맨 앞의 worker_ready가 1이어야 한다(0이면 일꾼이 뜨지 않은 것이다. 이때는 옆 세션 줄만 빠지고 나머지는 돈다).
| 판 | 기대 | ☐ |
|---|---|---|
| [A] Q2 버퍼 캐시만 × 10 | 한 번 5,868 · 열 번 lio 약 58,700 · 112 ~ 117cs | ☐ |
[B] Q2 RESULT_CACHE 계획: 첫 번 | RESULT CACHE 연산자 · Buffers 5,867 | ☐ |
| [B] 두 번째 | 아래 줄 Starts 0 · Buffers 칸이 사라진다 | ☐ |
| [B] W1 #2 ~ #10 | lio 0 · find 9 | ☐ |
| [B] 옆 세션 | lio 0 · 같은 답 A 10000 / 2564322100 | ☐ |
| [C] plain / 스칼라 서브쿼리 / DETERMINISTIC | 100,000 / 57,664 / 57,664(두 번 다) | ☐ |
| [C] RESULT_CACHE 2000 #1 · #2 | 14,467 · 12,551(evict 12,497 · 12,551) | ☐ |
| [C] 합계 | 네 판 모두 73,753,436 | ☐ |
| [D] 메모리 보고 | 1984 blocks · Used 1974 · PLSQL 1970 blocks (1970 count) | ☐ |
| [D] 1500 #1 · #2 · plain | 1,500 · 0 · 75,000 | ☐ |
| [D] 옆 세션 1500 | body 0 · 답 55,294,096 | ☐ |
| [E] Q2 after grade | 남을 수도, 밀려날 수도 있다(판마다 다르다. 교재 여덟 번: 커서 모양만 밀림 4 · 둘 다 밀림 1 · 둘 다 남음 3) | ☐ |
| [F] DETERMINISTIC | PL/SQL 직접 100,000 · 커서 루프 99,938 · 집계 한 번 57,664 · arraysize 15/500/5000 → 100,000 / 97,991 / 86,143 | ☐ |
| [G] 큰 결과 | Bypass · row_count 0 · 두 번 다 lio 1,548 | ☐ |
☐ 스칼라 서브쿼리와 DETERMINISTIC 이 같은 수(57,664)인 까닭을 07화 §9 의 '칸' 으로 설명할 수 있다
☐ RESULT_CACHE 두 번째 판이 0 이 아닌 까닭을 메모리 보고의 숫자 둘(1,984 · 1,970)로 설명할 수 있다
☐ Q2 의 0 을 운영에 옮기지 못하는 까닭 — 의존 테이블(주문)의 커밋 빈도 (STEP 5 [E] 에서 확인)STEP 4 — 무효화 (10분)
bash
$H < 실습/일꾼.sql > /dev/null & $H < 실습/03_무효화.sql; wait| 판 | 기대 | ☐ |
|---|---|---|
| [A] fill · hit | 5,867 · 0 | ☐ |
| [B] 커밋 전: 옆 세션 | lio 0 · 옛 답 A 10000 / 2564322100 | ☐ |
| [B] 커밋 전: 내 세션 | lio 5,867 · find 0 · create 0 · 새 답 A 10500 / 2688614600 · 결과는 Published | ☐ |
| [C] 커밋 뒤 | Q2 result Invalid · SQLP.M_CUS_C15B 줄 둘(INVALIDATIONS 1 · 0) · 옆 세션 5,868 · 새 답 · 그다음 내 세션 0 | ☐ |
[D] ROLLBACK 뒤 | lio 0 · find 1 | ☐ |
[E] 같은 값 UPDATE 커밋 뒤 | lio 5,867 · create 1(지워졌다) | ☐ |
| [F] 정산 복사본 커밋 뒤 · 고객 한 명 커밋 뒤 | Result Published 1500 · Result Invalid 1500 → body 1,500 · 보정률 .001 | ☐ |
[G] RELIES_ON | SP2-0806 경고 · PLW-05019 … deprecated beginning with version 11.2 | ☐ |
| [H] 없는 고객 × 5 | body 5 · NO_DATA_FOUND 5 | ☐ |
| [I] 재컴파일 | Result Invalid 1500 | ☐ |
| 끝 | 복사본 = 원본(MINUS 0 · 0) | ☐ |
[G]의SP2-0806: Function created with compilation warnings는 기대한 출력이다. 실패가 아니다.
STEP 5 — RESULT_CACHE_MODE = FORCE (5분)
bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 실습/04_FORCE_세션.sql| 판 | MANUAL | FORCE | ☐ |
|---|---|---|---|
| [A] 힌트 없는 Q2 | — | RESULT CACHE 생김 · 두 번째 Starts 0 | ☐ |
[B] NO_RESULT_CACHE | — | 빠짐 · Buffers 5,868 | ☐ |
| [C] PK 조회 2,000개 | create 0 | create 2,001 · evict 약 1,011 ~ 1,018 · fa_left 0 또는 1(판마다) | ☐ |
| [D] 그냥 함수 10만 행 #1 | 39 ~ 41cs | 61 ~ 69cs · create 75,587 · evict 74,597 | ☐ |
| [D] #2 | 39 ~ 41cs | 0(바깥 문장째) | ☐ |
| [D] 바깥 문장의 의존 객체 | FN_ADJ_RATE_C15B · M_CUS_C15B · T_ORD_STLM_C15B | ☐ | |
| [D] 고객 한 명 고친 뒤 FORCE | body 100,000 · force 73741702 / manual 73741702 (same) | ☐ | |
| [E] 고치고 읽기 × 1000 | create 0 | create 1,000 · find 0 · invalidations +999 | ☐ |
| [F] '초' 판을 부르는 문장 (힌트 없음) | 시각이 멈춘다 · SYSDATE 직접 문장은 간다 | ☐ | |
| [G] 원복 | session MANUAL · system MANUAL | ☐ |
STEP 6 — 경합 (5분)
bash
$H < 실습/일꾼.sql > /dev/null & $H < 실습/05_경합.sql; wait| 판 | 기대 | ☐ |
|---|---|---|
| A solo plain · solo rc | 7 ~ 8cs · 20 ~ 21cs | ☐ |
| [C] solo rc 래치 | Result Cache: Set · Blocklist Latch gets 1,000,001 · misses 0 | ☐ |
| B pair plain(me · other) | 8 ~ 9cs(혼자와 같다) | ☐ |
| B pair rc(me · other) | 34 ~ 42cs(각자 두 배 가까이) | ☐ |
| [C] pair rc 래치 | misses 수천 ~ 수만(Set 15,445 ~ 58,104 · Blocklist 7,681 ~ 58,381) · sleeps 0 | ☐ |
⚠️ Free는
cpu_count가 2다. 세션을 셋 넘게 띄우면 CPU 차례 기다림이 섞여 래치만 따로 읽을 수 없다. 수백 세션의 경합은 대규모 EE 환경을 다루는 별권에서 본다.
STEP 7 — (선택) ENFORCED는 무엇을 믿나 (3분)
bash
MSYS_NO_PATHCONV=1 docker exec -i ora-hanorder sqlplus -s / as sysdba < 실습/93_결과캐시_무결성_SYSDBA.sql| 문장 (2초 간격 두 번) | 기대 | ☐ |
|---|---|---|
E1 SYSDATE 직접 | 시각이 간다(거절) | ☐ |
E2 SYSDATE를 숨긴 그냥 함수 | 시각이 간다(TRUSTED와 달리 거절) | ☐ |
E3 같은 함수 + DETERMINISTIC | 시각이 멈춘다(받아 준다) | ☐ |
| 캐시에 남은 것 | E3만 | ☐ |
| 끝 | TRUSTED · spfile_rows 0 | ☐ |
이 파라미터는 세션 단위로 바꿀 수 없다. 그래서 PDB를
SCOPE = MEMORY로 몇 초 동안 바꾸고 같은 스크립트 끝에서 되돌린다. 연습 DB에서만 하는 일이다.
STEP 8 — T1 단건 SQL 튜닝 리포트 11호 ★ 오늘의 산출물
양식: T1_단건SQL_튜닝리포트.md · 붙이는 것: 정합성_회귀_체크리스트.md(08화)
┌────────────────────────────────────────────────────────────────────────────────┐
│ T1 단건 SQL 튜닝 리포트 — 11호 정산일 함수 (정산 등록 화면) T1-011 │
├────────────────────────────────────────────────────────────────────────────────┤
│ 측정 환경 : 연습 DB 복사본 · 26ai Free · cpu 2 · 결과 캐시 7,936 KB (4 KB × 1,984) │
│ 정산 복사본 10만 (정산일 전부 과거) · 고객 복사본 2,000 · 단독 + 옆 세션 1 │
└────────────────────────────────────────────────────────────────────────────────┘| # | 항목 | 내용 |
|---|---|---|
| 1 | 대상 | ① 정산일 함수 FN_STLM_DT: SDT_rev00 → rev01(diff: 리비전/SDT_rev01.sql 머리말) + 부르는 쪽(목록 SQL) 한 줄 ② 보정률 함수 FN_ADJ_RATE의 RESULT_CACHE(6/14): 6/17 09:40 되돌림 · 재적용 보류 |
| 2 | 측정 환경 | 위 머리말 · result_cache_mode MANUAL · integrity TRUSTED · max_result 5(49블록) · 통계는 00이 복사본에 수집 |
| 3 | 부하 조건 | 단일 세션(+ 옆 세션 1: 일꾼.sql) · 워크로드 프로파일 없음 |
| 4 | 현상 | 운영: 6/14 17:30 ~ 6/17 09:40 등록분 정산일이 6/14로 고정. 연습 DB 재현: '초' 판이 첫 값에 멈춤 · 목록 100,001 vs 100,000(MINUS 1/0) · 시간대 -11:00 → +13:00에서 D-1 → D-1. 4수치: TABLE ACCESS FULL · Starts 1 · E-Rows 5000 / A-Rows 100K · Buffers 549 · filter("STLM_DT"<="FN_STLM_DT_RC_C15B"()). 4수치로는 보이지 않는다. 계획, Buffers, 계획 해시(3721302985)가 그냥 함수 판과 같다 |
| 5 | 원인 | 정산일 답의 의존 객체 = 함수 자신 하나(V$RESULT_CACHE_DEPENDENCY). 진짜 입력(시계)은 의존 객체가 아니라서 아무도 지우지 않는다. 판단 근거: 6/14 윤세라. 작성자의 DETERMINISTIC 표시(2019-04-08 hd.kang)를 '언제나 같은 답'으로 읽음 · 대조는 같은 날 오후 안에서만(시각 경계 없음). [I권 Result 캐시 p.472 · Deterministic 함수 사용 시 주의사항 p.512] [lnpls PL/SQL Function Result Cache · tgdba Tuning the Result Cache] |
| 6 | 개선안 | 채택: rev01. 시각을 인자로(p_at DATE DEFAULT SYSDATE) · RESULT_CACHE · DETERMINISTIC 제거 · 목록은 (SELECT FN_STLM_DT FROM dual). 채택하지 않은 대안: ⓐ 매일 자정 INVALIDATE: 18시 마감은 자정이 아니다 · 잊는 날 같은 사고 · 이 DB job_queue_processes 1 ⓑ 시각을 인자로 받고 RESULT_CACHE 유지: 답은 맞지만 인자가 초마다 달라 칸만 먹는다 ⓒ 2019 원본에서 멈춤: 답은 맞다 · 거짓 DETERMINISTIC이 남고 시험할 수 없다 |
| 7 | 효과 | 목록 Buffers 549 → 549(변화 없음) · 정산일 본문 0 → 1 · 결과 캐시 찾기 1 → 0 · 경과 0 → 0cs. 정합성 불일치 → 통과(8번). 이 리포트의 효과는 속도가 아니라 답이다 |
| 8 | 정합성 회귀 검증 | 아래 표 |
| 9 | 환경 의존성 | 결과 캐시 크기(연습 DB 7,936KB = 1,984칸 · 함수의 답 하나 = 칸 하나) · result_cache_integrity(ENFORCED여도 DETERMINISTIC이면 받아 준다: 93) · 단일 인스턴스(RAC는 별권) |
| 10 | 리스크 | ① 목록 SQL을 고치지 않은 호출부는 행마다 부른다(12cs · 답은 맞다) ② 운영에 RESULT_CACHE가 붙은 다른 함수가 있을 수 있다 ③ 보정률 함수 재적용 시 칸 부족(2,000 > 1,984, 연습 DB 기준) ④ 정정 대상(6/14 17:30 ~ 6/17 09:40 등록분): 정산팀 |
| 11 | 롤백 절차 | rev01 → 2019 원본(DETERMINISTIC만 · 인자 없음)으로 CREATE OR REPLACE. rev00(RESULT_CACHE 판)으로는 되돌리지 않는다. 다시 만들면 그 함수의 결과 캐시도 지워진다(01 [G]) |
| 12 | 재측정 계획 | ① 운영 result_cache_max_size · 고객 수 확인 → 보정률 재적용 판단 ② 운영 USER_PROCEDURES.RESULT_CACHE = 'YES' 전수 목록: 각 함수가 시계 · 세션 설정 · 세션 상태 · 패키지 변수를 읽는가 ③ 정산 등록 화면을 배포할 때마다 01 [I]의 경계 대조 ④ V$RESULT_CACHE_STATISTICS의 Delete Count Valid(밀려남)를 주 1회 확인 |
8. 정합성 회귀(08화 체크리스트)
| # | 항목 | 대상 / 방법 | 결과 | 판정 |
|---|---|---|---|---|
| A | 기준 | 원장 = 규칙을 그대로 옮긴 식(HH24 >= 18이면 다음 날). 직전 판 rev00과 같은 날 안에서 대조하면 전부 같으므로 기준으로 쓰지 않는다 | — | 정함 |
| B | 건수 · MINUS · 중복 | rev01 vs 원장: 한 주 7분 간격 1,440점 · rev00 목록 vs 캐시 없는 판(01 [C]) | rev01: MINUS 0 / 0 · rev00: 100,000 vs 100,001 · MINUS 0 / 1 | rev01 통과 · rev00 불일치 |
| C | 잘라 내기 전 전체 | 해당 없음. 목록의 페이징, FETCH FIRST와 무관한 함수 | — | 해당 없음 |
| D | 조인마다 1 쪽 | 해당 없음. 조인이 없다 | — | 해당 없음 |
| E | 경계 데이터 | ★ 시각(금 17:59:59 · 18:00:00 · 자정 · 일 18:30 · 월 09:00 · 월 18:00) · ★ 세션(다른 세션 · 시간대 -11:00/+13:00 · NLS · 직원) · 복사본에 '방금 등록한 정산' 한 줄 | rev01 일곱 경계 = 원장 · rev00은 다른 세션 · 시간대 · NLS · 직원 전부 처음 세션의 것 | rev01 통과 |
| F | 계획 해시와 무관하게 | rev00 · 그냥 함수 · 2019 원본의 목록 계획 해시 3721302985(셋이 같다) | 계획이 같아도 답은 달랐다 | 통과(해시를 근거로 쓰지 않음) |
| G | 자동 장치를 근거로 쓰지 않았나 | "오라클이 자동으로 무효화한다"를 근거로 쓰지 않았다. 무효화는 의존 객체만 본다 | — | 통과 |
| H | 영향 분석 | FN_STLM_DT를 부르는 곳 전부(USER_DEPENDENCIES · 소스 검색) · RESULT_CACHE가 붙은 함수 전부 | 개발팀 · 운영 확인(12번 ②) | 조치 필요 |
STEP 9 — 「SQL 코딩 가이드」에 한 줄 (5분)
SQL_코딩가이드.md의 조문 11(15b화 · 근거 1회)을 읽고, 「개정 이력」의 15b화 줄을 확인한다.
11 결과 캐시는 답이 '테이블과 인자' 에만 기대는 곳에만 붙인다.
시계 · 세션 시간대 · NLS · 세션 상태 · 패키지 변수에 기대는 함수에는 붙이지 않는다 — 숨은 입력은 인자로 꺼낸다.
DETERMINISTIC 은 아무도 검사하지 않는 약속이다. 거짓이면 쓰지 않는다.☐ 우리 스키마에서 결과 캐시를 쓰는 함수 · 약속을 한 함수를 찾는 쿼리를 써 봤다
SELECT object_name, procedure_name, result_cache, deterministic FROM user_procedures
WHERE result_cache = 'YES' OR deterministic = 'YES';
☐ 그 함수들의 본문에서 SYSDATE · SYSTIMESTAMP · CURRENT_DATE · SYS_CONTEXT · TO_CHAR(날짜) 형식 없음 · 패키지 변수를 찾았다STEP 10 — 서술 답안 (400~600자)
박팀장에게 보내는 회신. "빠르게 하려다 날짜가 틀렸다는 거군요."
▶ 선배의 첨삭 포인트 (쓰고 나서)
① 무엇이 틀렸는지를 먼저 쓰고, 왜 빠르게 하려 했는지는 나중에 쓴다. 정산일이 6/14로 고정된 기간, 정정 대상, 되돌린 시각이 첫 문단에 온다.
② "캐시 버그"라고 쓰지 않는다. 결과 캐시는 설계대로 동작했다. 테이블이 바뀌면 지운다. 날짜는 테이블이 아니다. 고장이 아니라 붙이면 안 되는 자리였다. 그렇게 써야 "그럼 캐시를 다 끄자"는 결론으로 가지 않는다.
③ 얻은 것이 없었다는 사실을 숨기지 않는다. 목록에서는 이미 2019년 판이 한 번만 불리고 있었다. "빠르게 하려다"가 아니라 "재지 않고 붙였다"가 정확하다.
④ 다음 사고를 막는 내용으로 끝낸다. 결과 캐시를 쓰는 함수 전수 점검, 코딩 가이드 조문, 배포 때 경계 시각 대조를 적는다.
STEP 11 — 파인만 자가설명
1단 백지 설명 → 2단 막힘 태그(최소 1개) → 3단 원천 복귀 → 4단 재설명.
| 태그 | 전형적인 문장 |
|---|---|
#인과 | "정산 테이블이 계속 바뀌었는데 왜 캐시가 안 지워졌는지 설명하다 막혔다" → §4.1 |
#용어 | "DETERMINISTIC과 RESULT_CACHE의 차이를 한 문장으로 말 못 하겠다" → §1 · §6 |
#수치 | "두 번째 판이 왜 12,551인지 숫자로 못 나눠 보겠다" → §3.2 |
4단 되묻기 3문 (오늘 버전)
- 조건 반전: "정산일 규칙이 '18시 마감'이 아니라 '정산팀이 테이블에 적어 두는 마감일'이라면,
RESULT_CACHE는 맞는 자리가 되나?" - 반례 요구: "
DETERMINISTIC을 붙여도 되는 함수 하나와, 붙이면 안 되는데 붙여도 한 문장 안에서는 틀리지 않는 함수 하나는?" - 측정 지목: "운영의 어떤 함수가 결과 캐시에서 '얼어 있는지'를 무엇으로 찾을까?"
태그는 _막힘로그.md로 옮긴다.
정리 — 한빛유통을 15화 종료 상태로
bash
docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1 < 실습/99_정리.sql
MSYS_NO_PATHCONV=1 docker exec -i ora-hanorder sqlplus -s / as sysdba < 실습/92_결과캐시_권한회수_SYSDBA.sql| 확인 | 기대 | ☐ |
|---|---|---|
99 남은 세션 | 0행(일꾼이 남아 있으면 먼저 EXIT를 보낸다) | ☐ |
99 c15b_left · c15b_results · big_rows · big_idx · hist · stlm_rows · last_no · pkg · drv · rc_mode | 0 · 0 · 1000000 · 3 · FREQUENCY · 100000 · 0 · VALID · VALID · MANUAL | ☐ |
99 지문 | 255,398,623,700 · 25,519,833,100 · A 20 · B 180 · C 1800 | ☐ |
92 권한 · 결과 캐시 · 파라미터 | DBA 하나 · rc_objects 0 · MANUAL · TRUSTED · 8126464 · ENABLED | ☐ |
완료 체크리스트
- [ ] STEP 0:
SDT_rev00의 두 단어와 각각의 「그때의 근거」를 읽었고, 본문에서 진짜 입력을 찾았다 - [ ] STEP 1: 보기 전에 예측 6개를 적었다
- [ ] STEP 2: 정산일 결과의 의존 객체가 함수 하나인 것을 봤다. '초' 판이 멈추고, 목록에서 한 줄이 빠지고, 어제의 답을 오늘도 돌려주는 것을 봤다.
01b의 다른 세션이 같은 얼음을 받는 것도 봤다 - [ ] STEP 2: 목록 네 판에서 금요일의 한 단어가 번 것이 없음을 봤고, rev01의 경계 대조에서
MINUS0 / 0을 확인했다 - [ ] STEP 3: 네 겹을 나란히 쟀고, 두 번째 판이 0이 아닌 까닭을 칸으로 설명했다
- [ ] STEP 4: 커밋 전 남의 세션이 옛 답을 받는 것이 맞는 까닭을 적었다(선택)
- [ ] STEP 5:
FORCE에서 힌트 없이 시계가 멈추는 것을 봤다(선택) - [ ] STEP 6: 둘이 동시에 찾으면 각자 두 배 가까이 걸리는 것을 봤다(선택)
- [ ] STEP 8: T1 11호를 쓰면서 4번(4수치로 안 보임), 5번(판단 근거), 8번(정합성 E 시각 · 세션), 11번(롤백) 칸을 채웠다
- [ ] STEP 11: 막힘 태그 최소 1개를
_막힘로그.md에 옮겼다 - [ ] 정리:
99→92로 15화 종료 상태를 확인했다
완결성 게이트: STEP 2의 [C](100,001 vs 100,000), STEP 8의 정합성 표 E 줄, 11번 롤백 칸이 비어 있으면 이 화는 미완성이다.
다음 화로 넘기는 상태
| 무엇 | 값 |
|---|---|
ora-hanorder 원본 | 변화 없음. 15화 종료 상태(T_ORD_BIG 인덱스 3 · ORD_ST 히스토그램 · T_ORD_STLM 10만 · T_STLM_NUM 0 · PKG_ORD_STLM 원본 · PKG_LOAD_DRIVER VALID · C15B 0) |
| 결과 캐시 | 비어 있음 · MANUAL · TRUSTED · sqlp의 DBMS_RESULT_CACHE 권한 회수 |
| 정산일 함수 | SDT_rev01. 리비전 파일(연습 DB의 원본 스키마에는 정산일 함수를 만들지 않았다) |
| Q2 | 결과 캐시 없이 rev01 그대로(보류 사유: 의존 테이블의 커밋 빈도) |
| 새 산출물 | T1 11호 · 「SQL 코딩 가이드」 조문 11(근거 1회) |
| 열어 둔 것 | 보정률 함수 결과 캐시 재적용(운영 크기 확인) · RESULT_CACHE 함수 전수 점검 → 18화 |
다음 → 16화 「처음으로 시스템 전체를 본다」: 15화의 부하 발생기가 처음으로 연습 DB를 바쁘게 돌린다. 오늘 Q2에 결과 캐시를 붙이지 않은 결정이 거기서 어떻게 보이는지 확인한다.