테마
13화 · 안전 조치의 대가
🗓️ 5월 27일 (월) · 12화에서 일주일 뒤 · 무대
ora-hanorder오늘 배울 것: 커밋 주기만으로는 두 장애를 다 피할 수 없다. 그리고 지난주에 내가 낸 처방이 주말에 장애로 돌아왔다. 오늘 다루는 조각은 처음 등장하는 P1(정산 적재 배치)이다. 고치지는 않고, 재어 보고 근거를 남긴다.
파일 세 개
| 파일 | 무엇을 하나 | 시간 | |
|---|---|---|---|
| 읽는다 | 1_사건.md | 1,000건 커밋 처방이 ORA-01555를 부른 날. 방향이 반대인 장애 두 개를 재 보고, 정정 전 사본함 앞에 서고, 블록 덤프로 ITL을 읽고, 지난주에 비워 둔 칸을 채우는 하루 | 35분 |
| 한다 | 3_실습과_기록.md | 커밋 주기 측정 · 변경 벡터 · ORA-30036·01555 직접 재현 · 지연 클린아웃 · 블록 덤프 · 반쯤 반영 · 커밋 주기 기준 | 90~120분 |
| 찾는다 | 2_파고들기.md | redo·WAL·Fast Commit · Undo 3층(슬롯·ITL·lock byte) · CR 블록 · 01555 원인 3종 · 30036 대조 · 커밋 주기 · IMU | 필요할 때 |
1을 읽고 3을 따라 한다. 2는 막힐 때 펼친다. 시간이 없는 주에는 1_사건을 중단 지점 1까지 읽고 3_실습은 STEP 0, 1, 2, 6, 8만 한다(약 50분, SYS 계정이 없어도 된다).
지난 화에서 들고 오는 것
| 12화 포스트모템 1호 재발방지 2번 | "주간 보정 배치: 트랜잭션을 짧게. 배치는 N건마다 COMMIT" · 「리스크」 칸이 비어 있다 |
| 오과장의 답 | "P1처럼 1,000건 단위로 다음 주부터 적용하겠다" |
| 05화가 넘긴 것 | 블록의 ITL 칸이 모자라면 어떻게 되나 · 칸에 무엇이 적히나 |
| 07화가 넘긴 것 | COMMIT 뒤에도 남은 dirty 31 · 커밋이 무엇을 쓰기에 안전한가(Redo·WAL) |
| 12화가 넘긴 것 | TX 이름표(usn·slot·wrap)가 가리키는 언두 세그먼트 헤더의 칸 · 블록 ITL · 행 lock byte |
그리고 오과장에게서 메신저가 한 줄 와 있다.
"주말 보정 배치가 토요일 새벽에 죽었어요. ORA-01555. 1,000건마다 커밋으로 바꾸고 나서 처음 돌린 날."
오늘 여는 코드
sql
-- P1_rev00 · 강도현 — 야간 정산 적재 배치 (리비전 파일에 처음 남는다)
IF MOD(n, 1000) = 0 THEN COMMIT; END IF; -- 트랜잭션은 짧게 (2020.07 강도현)sql
-- 주간 보정 배치 (오과장, 12화 처방 적용) — 토요일에 죽은 것
FOR r IN (SELECT ... FROM 정산 ORDER BY ...) LOOP -- 긴 커서
UPDATE 정산 SET ... WHERE ...; -- 읽는 그 테이블을 고치고
IF MOD(n,1000)=0 THEN COMMIT; END IF; -- 커밋 → 과거를 풀어 준다 = fetch across commit
END LOOP;매주 처음 하는 일(
diff)이 오늘은 내가 쓴 처방을 되짚는다. "짧게 하라"는 맞는 말이었다. 다만 ORA-30036에만 맞았다. 바로 그 '짧게'가 ORA-01555를 불렀다.
오늘의 전개 — 두 번 틀린다
| 내가 믿은 것 | 측정 | |
|---|---|---|
| 09:40 | "언두가 부족한 거다. 늘리면 된다" | 한 번 커밋하면 ORA-30036(언두 부족, 맞다). 그런데 1,000건마다 커밋하면 ORA-01555(반대쪽). 언두를 늘려도 01555는 안 풀린다 |
| 10:20 | "그럼 더 자주 커밋하면 싸지겠네" | 매 건 커밋하면 redo 약 1.9배 · undo 약 2배 · LGWR 약 13배. 더 비싸지고 01555도 더 빨리 난다 |
| 11:10 | (비유) 정정 전 사본함: 완료 도장이 사본함을 비운다 | 드물게 찍으면 함이 꽉 차고(30036), 자주 찍으면 과거가 사라진다(01555) |
| 13:00 | "커밋하면 디스크에 저장된다" | dirty 31(← 07화). 커밋은 redo(WAL)만 디스크로 민다. 블록은 DBWn이 나중에 쓴다 |
| 14:00 | (측정) 블록 덤프로 본 ITL | --U-(활성) → C---(커밋). Undo 3층이 서로를 가리킨다(← 12화) |
| 15:00 | "5,842는 지연 클린아웃이다" (01화 가설) | 아니다. APPEND 적재에는 지연 클린아웃이 없다. 추가분은 첫 파싱과 재귀 조회다 |
| 16:30 | "그럼 1,000 말고 5,000건?" | 주기 문제가 아니다. fetch across commit이 문제다 → 읽기와 쓰기를 떼어 낸다 |
왜 이렇게 짰을까? 3막은 "고친 것이 다음 문제를 만드는" 막이다. 12화에서 '나'가 낸 재발방지책이 13화의 장애가 된다. 그리고 그 장애의 뿌리는 빈칸 하나, 리스크를 적지 않은 데 있다.
오늘 배우는 한 문장
커밋 주기만으로는 두 장애를 다 피할 수 없다. 드물게 하면 ORA-30036, 자주 하면 ORA-01555다. 진짜 문제는 주기가 아니라 읽으면서 고치고 커밋하는 구조(fetch across commit)다. 읽기와 쓰기를 떼어 내야 한다.
| 커밋을 덜 | 커밋을 더 | 언두를 늘리면 | |
|---|---|---|---|
| ORA-30036 (지금 쓸 언두 없음) | 가까워진다 | 멀어진다 | 풀린다 |
| ORA-01555 (과거 언두 사라짐) | 멀어진다 | 가까워진다 | 안 풀린다 |
🔑 오늘의 비유 — 총무팀 정정 전 사본함
11화의 결재 서식함, 12화의 발번 대장을 지나 이번에는 총무팀 데스크 옆 캐비닛이다. 장부를 고치기 전에 원래 값을 슬립에 베껴 이 함에 넣는다(= undo). 한 묶음을 끝내면 완료 도장(= COMMIT)을 찍고, 도장이 찍힌 슬립은 버려도 된다(재사용 가능).
| 정정 전 사본함 | 오라클 |
|---|---|
| 고치기 전 값을 슬립에 베껴 함에 넣는다 | undo: 바꾸기 전 값(롤백과 과거 읽기에 쓴다) |
| 완료 도장을 찍으면 그 슬립은 버려도 된다 | COMMIT: 그 트랜잭션의 언두는 재사용할 수 있다 |
| 도장을 안 찍고 다 고치면 함이 꽉 찬다 | 한 번 커밋 → ORA-30036 |
| 도장을 자주 찍어 함을 비우는데, 긴 감사가 '시작할 때의 장부'를 찾으면 슬립이 없다 | fetch across commit → ORA-01555 |
| 고치기 전에 출납 일지에 먼저 적는다 | redo / WAL: 일지가 먼저, 장부는 나중 |
| 도장을 찍어도 장부는 아직 책상 위에 있다 | dirty: 커밋해도 블록은 메모리에 있다(← 07화) |
☠️ 오늘의 함정 — "짧게 하라"를 모든 배치에 똑같이 적용한다
"트랜잭션을 짧게"는 한 번에 다 커밋하는 큰 트랜잭션(ORA-30036 위험)에는 맞는 처방이다. 그런데 읽으면서 고치고 커밋하는 배치(fetch across commit)에 그대로 적용하면, 그 짧은 커밋들이 긴 커서의 과거를 지워 ORA-01555가 난다.
| 같은 처방 | 맞는 곳 | 틀리는 곳 |
|---|---|---|
| "N건마다 COMMIT" | 읽는 테이블 ≠ 고치는 테이블 (P1: T_ORD를 읽어 빈 테이블에 적재) | 읽는 테이블 = 고치는 테이블 (보정 배치: 긴 커서로 읽으며 고친다) |
처방은 코드의 '모양'을 보고 고른다. 같은 문장이 어떤 배치에서는 안전하고 다른 배치에서는 장애가 된다. → 「커밋 주기 기준」
실습 준비
bash
docker start ora-hanorder12화를 마친 상태에서 시작한다. 실습/00_준비와_점검.sql이 이 상태를 확인한다(T_ORD_BIG 100만 건, 인덱스 3개 · T_ORD_STLM 10만 건 · LAST_NO 0 · PKG_ORD_STLM VALID).
PowerShell에서는 먼저
$OutputEncoding = New-Object System.Text.UTF8Encoding $false한 줄을 실행한다.
| 스크립트 | 무엇 | 계정 |
|---|---|---|
실습/00_준비와_점검.sql | 전제 확인 · 복사본·배치 함수·수첩 | sqlp |
실습/01_P1_커밋주기.sql | ★ 커밋 주기별 redo·undo·log file sync (본체) | sqlp |
실습/02_변경벡터와_커밋.sql | 변경 벡터 · COMMIT · dirty 31 (WAL) | sqlp |
실습/03_작은언두_ORA30036_SYSDBA.sql | 작은 언두 + 한 번 커밋 → ORA-30036 | SYS |
실습/04_churner.sql·04_reader.sql | ★ 긴 읽기 + 동시 커밋 → ORA-01555 | sqlp (두 세션) |
실습/05_원복_SYSDBA.sql | ★★ 언두 원복. 반드시 돌린다 | SYS |
실습/06_지연클린아웃과_ITL_SYSDBA.sql | 지연 클린아웃 · 블록 덤프 ITL | SYS |
실습/07_반쯤_반영.sql | 쪼갠 배치가 중간에 실패하면 반쯤 반영 | sqlp |
실습/09_정리.sql | _C13 삭제 · 12화 상태 · 언두 원복 확인 | sqlp |
어느 스크립트든 몇 번을 돌려도 결과와 판정이 같다(두 번씩 돌려 확인했다). redo 바이트나 죽는 행 번호는 조금씩 흔들리므로 '몇 배인가, 어떤 에러인가, 몇 줄인가'로 읽는다. ⚠️ 03을 돌렸으면 반드시 05로 원복한다. 03은 인스턴스의 언두를 작게 바꾼다. 본체(01, 02, 07)는 SYS 없이 끝난다. ℹ️ Docker로 띄운 Oracle Database Free 한 대에서 모든 실습을 마친다. 수백 세션이 공유 언두를 압박하는 규모는 이 컨테이너로 만들 수 없어서, 작은 언두(6M)로 메커니즘만 쟀다. 대규모 EE 환경은 별권에서 다룬다.
이 화의 산출물
| 무엇 | 어디 |
|---|---|
P1_rev00: 강도현 원본 · 2020.07 근거 · 고치지 않는다(재어 보고 근거만 남긴다. 전진은 14화) | 리비전/P1_rev00.sql |
| 「커밋 주기 기준」 ★ 새 표준. 집합 연산 → fetch across commit → 언두 크기 순으로 묻는 결정표 | 40_보고서와_표준/템플릿/커밋주기_기준.md |
| T4 포스트모템 2호 (+T1): 토요일 보정 배치 ORA-01555 · 반쯤 반영 (정합성 E·H) | 3_실습과_기록.md STEP 8 |
| 12화 리스크 칸 회수: 포스트모템 1호 재발방지 2번의 빈칸을 채우는 장면 | 1_사건.md 오후 5시 30분 · 3_실습 STEP 7 |
이 화가 다음으로 넘기는 것
| 무엇 | 어디로 |
|---|---|
"배치를 어떻게 빠르고 안전하게 돌리나": 집합 연산(INSERT…SELECT·MERGE) · APPEND · 파티션 · 병렬 · P1 다시 쓰기 | 14화 |
| 04화 인덱스와 05화 HWM의 적재 비용 청구서. redo/undo로 잰다 | 14화 |
| PL/SQL 루프 자체의 비용(컨텍스트 스위칭): 한 건씩 도는 구조 | 15화 |
V$는 장애가 끝나면 사라진다. redo/undo/대기의 지나간 값은 어디서 보나 | 16·17화 |
| 「SQL 코딩 가이드」·「커밋 주기 기준」: 채번과 배치는 읽기와 쓰기를 분리한다 (근거 2회 · 12·13화) | 18화 |
← 12화 「아무도 안 건드렸는데 전체가 멈췄다」 · 교재 전체 안내 · 다음 → 14화 「야간 배치가 아침을 넘긴다」