테마
12화 · 아무도 안 건드렸는데 전체가 멈췄다
🗓️ 5월 20일 (월) · 11화에서 일주일 뒤 · 무대
ora-hanorder+ora-newshop· 3막의 첫 화오늘 여는 것: 줄. 누가 누구를 막는가, 그리고 그 줄은 정당한가. 고칠 조각을 고르기도 전에 장애가 먼저 온다. 처음으로 장애를 맞고, 처음으로 포스트모템을 쓴다. 오늘의 조각은 N1(채번)으로, 01화부터 판이 없던 마지막 조각 중 하나다.
파일 세 개
| 파일 | 무엇을 하나 | 시간 | |
|---|---|---|---|
| 읽는다 | 1_사건.md | 정산 화면이 10분 멈춘 날. 블로킹 트리를 찍고, 후보 넷을 증거로 지우고, 총무팀 발번 대장 앞에 줄을 서고, 데드락 트레이스를 읽고, 첫 포스트모템을 쓰는 하루 | 40분 |
| 한다 | 3_실습과_기록.md | 락의 모양 · N1 혼자 · 세 사람 동시(일꾼 셋 + 지휘자) · DDL의 줄 · 블록 안의 줄 · 트레이스 꺼내 읽기 · FK를 건 쪽 · T4 포스트모템 1호 | 90~120분 |
| 찾는다 | 2_파고들기.md | Latch · Mutex · Enqueue · TX 이름표(USN · SLOT · WRAP) · TM 6모드 매트릭스 · 실측 REQUEST 지도 · 블로킹 트리 · 채번 세 칸 · 데드락 그래프 · 무인덱스 FK: 이 버전에서 잰 것 · DDL · ITL · 락 에스컬레이션 없음 | 필요할 때 |
1을 읽고 3을 따라 한다. 2는 막힐 때 펼친다. 시간이 없는 주에는 1_사건을 중단 지점 1까지 읽고 3_실습은 STEP 0, 1, 4, 7, 10만 한다(약 60분).
지난 화에서 들고 오는 것
| 11화의 마지막 말 | "하드 파싱이 3만에서 1이 되면 같은 시간에 더 많은 클릭이 처리돼. 그동안 하드 파싱 뒤에 줄 서 있던 게 앞으로 나와. 이번엔 데이터 쪽에서." |
N1 | 판이 없다. 01화에 "1번 고친다 (12화)"라고 적기만 했다. 원본은 SELECT NVL(MAX(STLM_SEQ),0)+1 FROM T_ORD_STLM이고, 주석에 "시퀀스는 구멍이 생겨서 안 씀 (2019.03 강도현)"이라고 적혀 있다 |
T_STLM_NUM | 채번 테이블. 있는데 안 쓴다. LAST_NO 0(01화 메모) |
| 한빛유통 주문 시스템의 FK | 0개(01화). "FK가 있으면 무인덱스 FK가 자식 테이블에 락을 건다 → 12화 (ora-newshop)"라고 적어 두었다 |
| 05화 · 07화가 넘긴 질문 | ITL 칸이 모자라면? · 래치는 spin하고 락은 줄을 세운다 · 버퍼 Lock과 buffer busy waits |
그리고 박팀장에게서 전화가 한 통 온다.
"지금 정산 화면이 안 떠요. 정산팀 전체가요. 배포한 사람 없대요. 오늘 아무도 아무것도 안 건드렸답니다."
오늘 여는 코드
sql
-- N1_rev00 · 강도현 (2019.03) — 리비전 파일에 있는 것
SELECT NVL(MAX(STLM_SEQ), 0) + 1 FROM T_ORD_STLM;sql
-- 운영 · 5월 17일 (금) 18:40 반영 · 개발팀 "정산번호 중복 버그 수정" — 리비전 파일에 없는 것
SELECT LAST_NO + 1 INTO v FROM T_STLM_NUM WHERE NUM_TP = 'STLM' FOR UPDATE;
UPDATE T_STLM_NUM SET LAST_NO = v WHERE NUM_TP = 'STLM';매주 처음 하는 일(
diff)로 오늘은 장애의 원인을 찾는다. 아무도 안 건드린 것이 아니라, 건드린 것을 아무도 장애와 이어 붙이지 않았을 뿐이다.
오늘의 전개 — 세 번 틀린다
| 내가 믿은 것 | 측정 | |
|---|---|---|
| 10:40 | "무인덱스 FK다" | 한빛유통 주문 시스템에는 FK가 0개. 설 자리가 없다(오후에 FK 7개짜리 플랫폼에서 다시 본다) |
| 11:10 | "MAX+1이라 다들 줄을 선다" | 줄을 서지 않는다. 세 명이 동시에 받아도 기다림은 0~1cs. 대신 셋 다 100001을 받는다(겹친다). 막은 SQL은 MAX+1이 아니라 핫픽스의 FOR UPDATE |
| 13:00 | "채번 테이블 전체에 TM 락" | TM은 셋 다 3이다(서로 안 막는다). 화면은 배치의 TX를 REQUEST 6으로 기다렸다. 줄은 테이블이 아니라 트랜잭션에 선다 · 316~321cs |
| 14:00 | "데드락이 나면 풀린다" | ORA-00060은 문장 하나만 되돌린다. 배치는 여전히 막혀 있었다. 트레이스의 TX-0002000F-00000350 = USN 2 · SLOT 15 · WRAP 848 = V$LOCK |
| 15:00 | "MAX+1에 PK만 걸면" | 겹침은 막는다. 대신 TX REQUEST 4로 줄을 서고, 줄 끝에서 ORA-00001 |
| rev01 | 시퀀스. 감사 지적 원문은 "결번 금지"가 아니라 "결번 사유 기록"이다 | 번호 하나 Buffers 568 → 0 · 기다림 0 · 겹침 0 · 빈 번호는 규정에 |
| 16:30 | (사례집 · 공식문서) "부모를 지우면 자식 테이블 전체에 테이블 락" | 이 컨테이너에서는 자식 TM 락이 보이지 않았다. 부모 DELETE가 자식 쪽 진행 중 트랜잭션을 TX REQUEST 4로 하나씩 기다렸다. 주문 INSERT는 안 막혔다. 인덱스를 만들면 아무도 안 기다린다 |
왜 이렇게 짰을까? 3막은 "고친 것이 다음 문제를 만드는" 막이다. 오늘 장애의 원인은 진짜 버그를 고친 정당한 수정이었고, 그 수정은 강도현이 이미 한 번 겪고 접은 길이었다. 그래서 오늘의 산출물은 SQL 한 줄이 아니라 그 전제를 다음 사람이 읽을 수 있게 남기는 문서다.
오늘 배우는 한 문장
줄은 행이 아니라 앞사람의 트랜잭션에 선다. 그래서 기다리는 시간은 무엇을 잠갔느냐가 아니라 언제 COMMIT하느냐로 정해진다. 누가 누구를 막는지는
V$를 보면 알 수 있다. 그 줄이 정당한지는 업무가 정한다.
| 번호를 주는 방법 | 겹침 | 빈 번호 | 기다림 | 번호 하나 Buffers |
|---|---|---|---|---|
rev00 MAX+1 | 있다 | 없다 | 없다 | 568 |
핫픽스 FOR UPDATE | 없다 | 없다 | 앞사람이 COMMIT할 때까지 | 5 |
| rev01 시퀀스 | 없다 | 있다 | 없다 | 0 |
🔑 오늘의 비유 — 총무팀 문서 발번 대장
11화의 결재 서식함을 지나면 총무팀 데스크가 있다. 공문이나 계약서에 붙는 번호는 이 바인더에 손으로 적어서 받는다.
| 발번 대장 | 오라클 |
|---|---|
| 대장 앞에 이름을 적고 순서대로 선다 · 새치기 없음 | enqueue: 요청한 순서대로(FIFO) |
| 김대리가 계약서 서른 건에 번호를 따려고 대장을 회의실로 가져갔다 · 계약서를 다 읽을 때까지 안 돌아온다 | 핫픽스: FOR UPDATE로 잡은 행 락은 트랜잭션이 끝날 때까지 안 풀린다 |
| 10초짜리 일에 7분을 섰다 | 번호 받기 5블록 · 기다림 = 배치의 10분 |
| 대장 대신 발송함 맨 위 공문의 번호 + 1 · 둘이 동시에 보면 같은 번호 · 맨 위가 반송되면 그 번호를 또 씀 | MAX+1: 겹침 · 다시 씀 |
| 1층 은행 번호표 기계 · 뽑고 그냥 나가면 그 번호는 아무도 안 부른다 | 시퀀스: 빈 번호 |
| 07화의 탕비실 집게. 줄 없이 손을 계속 뻗는다 | 래치 (spin) |
(01화 커피 캐리어 = 블록 · 02화 방문객 출입대장 = 인덱스 · 06화 4층 거래처 카드함 = 조인 · 07화 탕비실 픽업 선반 = 버퍼 캐시 · 08화 회식 정산서 = 조인 카디널리티 · 09화 출입대장 포스트잇 = 키 기반 페이징 · 10화 구내식당 식수표 = 히스토그램 · 11화 결재 서식함 = 라이브러리 캐시)
☠️ 오늘의 함정 — "책에 그렇게 써 있다"로 재발방지책을 쓴다
공식문서 cncpt는 무인덱스 FK가 "자식 테이블에 full table lock"을 건다고 쓴다. 그런데 이 컨테이너(26ai Free 23.26.2)의 V$LOCK에는 그 락이 한 번도 찍히지 않았다. 성능고도화 I권이 RS(2)라고 적은 SELECT ... FOR UPDATE의 TM 모드도 실측은 3이었다.
| 같은 질문 | 적혀 있는 것 | 이 컨테이너에서 잰 것 |
|---|---|---|
| 무인덱스 FK 부모 DELETE가 자식에 거는 락 | 자식 테이블 full table lock (문장 동안) | 자식 TM 없음 · 자식 쪽 진행 중 TX를 REQUEST 4로 하나씩 대기 |
SELECT ... FOR UPDATE의 TM 모드 | RS (2) | 3 |
LMODE와 REQUEST는 재서 적는다. 교과서 값을 옮겨 적지 않는다. 결론(외래 키에는 인덱스)은 같아도 기다림의 모양이 다르면 장애 대응이 달라진다. 무엇을 찍어야 하는지, 누가 멈추는지가 달라지기 때문이다.
실습 준비
bash
docker start ora-hanorder
docker start ora-newshop # STEP 8 (FK 를 건 쪽) 에서만11화를 마친 상태에서 시작한다. 00_준비와_점검.sql이 이 상태를 확인한다(T_ORD_STLM 10만 · LAST_NO 0 · 주문 시스템 FK 0). ora-newshop은 L4_ora-newshop의 2단계(ORA_SQL_TEST + FK 7개)까지 마쳐 두면 된다. ECOMMERCE 스키마는 쓰지 않는다. 18화에서 만난다.
PowerShell에서는 먼저
$OutputEncoding = New-Object System.Text.UTF8Encoding $false한 줄을 실행한다.
이 화의 새 방식 — 일꾼 셋 + 지휘자
동시성은 세션 하나로 잴 수 없다. 일꾼_1 · 2 · 3.sql을 뒤에 먼저 띄우고, 지휘자 스크립트를 앞에서 돌린다. 일꾼은 지휘자가 수첩에 적은 지시를 그대로 따르고, 60초 동안 지시가 없으면 스스로 ROLLBACK하고 끝난다.
bash
H="docker exec -i ora-hanorder sqlplus -s sqlp/sqlp@localhost/FREEPDB1"
$H < 00_준비와_점검.sql
for w in 1 2 3; do $H < 일꾼_$w.sql > /dev/null & done; $H < 03_N1_지휘자.sql; wait(PowerShell 방식은 3_실습과_기록.md 맨 위에 있다)
| 스크립트 | 무엇 | 컨테이너 · 걸리는 시간 |
|---|---|---|
실습/00_준비와_점검.sql | [점검] 남은 세션 · 락 · [전제] 11화 상태 · [준비] 복사본 · 함수 넷 · 수첩 · 보는 뷰 | hanorder · 3초 |
실습/01_락의_모양.sql | DML의 락 · 10만 행에도 2줄 · TX 풀기 · TM 5 x 5 매트릭스 · DDL의 암시적 COMMIT (실패해도) | hanorder · 5초 |
실습/02_N1_혼자_재기.sql | 번호 하나 Buffers 568 · 5 · 0 · 겹침 · 다시 씀 · 빈 번호 · 자율 트랜잭션 대안 | hanorder · 2초 |
실습/일꾼_1.sql · 일꾼_2 · 일꾼_3 | 세션 한 명씩. 03 · 04 · 05 · 11과 함께 뒤에 띄운다 | 두 컨테이너 공용 |
실습/03_N1_지휘자.sql | ★ 세 사람 동시 다섯 판: rev00 · rev00 + PK · 핫픽스 · rev01 · 데드락 · 트레이스 파일 이름 | hanorder · 30초 |
실습/04_블록_안의_줄_지휘자.sql | (선택) ITL 슬롯 부족 · INITRANS · 인덱스 맨 오른쪽 블록 | hanorder · 15초 |
실습/05_DDL_의_줄_지휘자.sql | DDL이 줄 맨 앞에 서면: ORA-00054 · TM 6 · 뒤의 TM 3 · CREATE INDEX TM 4 | hanorder · 15초 |
실습/10_FK_준비.sql · 11_FK_지휘자.sql · 19_FK_정리.sql | FK 7개 중 무인덱스 3 · 부모 DELETE가 무엇을 기다리나 · 인덱스를 만들면 · FK가 없으면 고아 33건 | newshop · 20초 |
실습/91_시퀀스_캐시_SYSDBA.sql | (선택 · SYSDBA) 공유 풀을 비우면 캐시 18개 소실 | hanorder · 1초 |
실습/99_정리.sql | _C12 전부 삭제 · 11화 상태 그대로인지 확인 | hanorder · 1초 |
어느 스크립트든 몇 번을 돌려도 판정이 같다(두 번씩 돌려 확인했다). 원본 테이블에는 쓰지 않는다. 정산, 채번, 고객, 주문은 모두 _C12 복사본에서 다룬다. PKG_ORD_STLM도 바꾸지 않는다.
ℹ️ 이 교재의 실습은 Docker로 띄운 Oracle Database Free 한 대로 모두 끝낸다. Free는 CPU가 2개다. 운영에서 보는 세션 수십 개가 매달린 트리나 수백 세션이 한 줄에 서는 규모의 경합은 이 컨테이너로 재현하지 않았다. 대신 세 세션으로 줄의 모양(누가 누구를 막는지, LMODE / REQUEST)을 쟀다. 대규모 EE 환경은 별권에서 다룬다.
이 화의 산출물
| 무엇 | 어디 |
|---|---|
N1_rev00: 강도현 원본 · 그때의 근거(2019.03 커밋: 채번 테이블 시험 → 대기 → 철회) | 리비전/N1_rev00.sql |
N1_rev01: 시퀀스. 번호 하나 Buffers 568 → 0 · 세 명 동시 기다림 0 · 겹침 3 → 0 · 「채택하지 않은 대안」 넷 | 리비전/N1_rev01.sql |
| T4 장애 포스트모템 ★ 새 양식. blameless · 타임라인 · 블로킹 체인 증거 · 5 Whys · 재발방지(리스크 칸) | 40_보고서와_표준/템플릿/T4_장애_포스트모템.md |
| 포스트모템 1호: 5월 20일 정산 등록 대기 · 정합성 회귀 체크리스트 A~H | 3_실습과_기록.md STEP 10 |
이 화가 다음으로 넘기는 것
| 무엇 | 어디로 |
|---|---|
| 포스트모템 1호 재발방지 2번: 주간 보정 배치를 N건마다 COMMIT(오과장이 다음 주 월요일 보정부터 1,000건 단위로 적용) | 13화 |
| TX의 이름표가 가리키는 언두 세그먼트 헤더의 칸 · 블록의 ITL · 행의 lock byte | 13화 |
| 신규 플랫폼 FK 인덱스 셋 · 인덱스 맨 오른쪽 블록 경합. 적재 비용과 함께 | 14화 |
V$는 장애가 끝나면 사라진다. 지나간 시간을 보는 도구 | 16 · 17화 |
| "채번은 업무 트랜잭션에서 떼어 낸다": SQL 코딩 가이드 후보 조문(근거 1회) | 18화 |
← 11화 「같은 SQL이 수만 개」 · 교재 전체 안내 · 다음 → 13화 「안전 조치의 대가」