enq: TX - allocate ITL entry vs enq: HW - contention
- 두 대기 이벤트 모두 "Enqueue 계열"이고 "동시성 문제"라는 점은 같지만, 근본 원인이 완전히 다른 레이어**에서 발생합니다.
- 하나는 "세그먼트 확장" 문제이고, 다른 하나는 "블록 내부 트랜잭션 슬롯 부족" 문제입니다.
핵심 개념
- enq: HW - contention
→ 세그먼트 레벨 문제 → "새로운 블록 영역"으로 확장할 때 발생 → 원인: HWM(High Water Mark) 이동 경쟁
- enq: TX - allocate ITL entry
→ 블록(Block) 레벨 문제 → "이미 존재하는 블록" 안에서 발생 → 원인: 블록 헤더의 ITL(Interested Transaction List) 슬롯 고갈
vpn_key **한 줄 비유**:
- HW Contention = "건물에 새 층을 짓는데 한 사람만 공사할 수 있어서 나머지가 기다림"
- ITL Contention = "이미 지어진 층(블록)에 들어갈 수 있는 엘리베이터 슬롯이 꽉 차서 못 들어감"
---
- 2. ITL(Interested Transaction List)이 무엇인가
``` 모든 Data Block의 헤더 구조:
┌─────────────────────────────────────┐ │ Block Header │ │ ┌─────────────────────────────────┐ │ │ │ ITL (Interested Transaction List)│ │ │ │ ITL#1: Txn A (활성) │ │ │ │ ITL#2: Txn B (활성) │ │ │ │ ITL#3: (빈 슬롯) │ │ │ │ ... │ │ │ │ ITL#N: (INITRANS로 초기 개수 결정)│ │ │ └─────────────────────────────────┘ │ ├─────────────────────────────────────┤ │ Row Data (Row1, Row2, Row3...) │ └─────────────────────────────────────┘
역할: "지금 이 블록의 어떤 row를 어떤 트랜잭션이
건드리고 있는지"를 블록 자체가 기억하는 슬롯
```
- 동작 원리**: 한 블록 안의 서로 다른 row를 동시에 여러 트랜잭션이 수정하려면, **각 트랜잭션마다 ITL 슬롯을 하나씩 배정**받아야 합니다. 이 슬롯이 부족하면:
``` 1차 시도: 기존 ITL 슬롯 중 하나 재사용 시도
→ 커밋된 트랜잭션의 슬롯이면 재사용 가능 (대기 없음)
2차 시도: 슬롯이 부족하고 블록에 여유 공간(PCTFREE 확보분)이 있으면
→ 새 ITL 슬롯을 동적으로 추가 (MAXTRANS 한도까지, 대기 거의 없음)
3차 시도: 여유 공간도 없고 슬롯도 부족하면
→ enq: TX - allocate ITL entry 대기 발생!
→ 앞선 트랜잭션이 커밋/롤백해서 슬롯을 반납할 때까지 대기
```
---
- 3. 비교표로 정리
| 구분 | enq: HW - contention | enq: TX - allocate ITL entry | |---|---|---| | 발생 레벨 | 세그먼트(Segment) | 블록(Block) | | 근본 원인 | HWM 확장 경쟁 | 블록 내 ITL 슬롯 고갈 | | 관련 파라미터 | 없음 (구조적으로 항상 존재) | `INITRANS`, `MAXTRANS`, `PCTFREE` | | 주요 발생 상황 | 대량 동시 INSERT (특히 LOB) | 소수 Hot Block에 다수 세션이 서로 다른 row를 동시 UPDATE/INSERT | | ASSM으로 해결? | 부분적으로만 완화 (근본 해결 X) | ASSM이 상당 부분 자동 완화 (동적 슬롯 확장) | | 대표 시나리오 | 대용량 배치 INSERT 폭주 | 코드성 테이블, 소수 PK 블록에 집중된 동시 UPDATE | | P1/P2 의미 | Tablespace#, File#(세그먼트 식별) | 파일#+블록#(정확히 어느 블록인지 특정 가능) |
---
- 4. 실습으로 ITL Contention 재현하기
- 4.1 의도적으로 ITL이 부족한 테이블 생성
```sql CREATE TABLE itl_contention_test (
id NUMBER, val VARCHAR2(100)
) PCTFREE 0 -- 여유공간 없음 → 동적 ITL 확장 불가능하게 만듦 INITRANS 1 -- 초기 ITL 슬롯 단 1개만 MAXTRANS 1; -- 최대로도 1개까지만 허용 (11g 이후 무시되는 경우도 있으나 개념 실습용)
-- 한 블록에 여러 row가 들어가도록 소량만 삽입 INSERT INTO itl_contention_test SELECT level, 'DATA' FROM dual CONNECT BY level <= 20; COMMIT; ```
```sql -- 몇 개 row가 같은 블록에 있는지 확인 SELECT DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid) AS block_no,
COUNT(*) AS row_cnt
FROM itl_contention_test GROUP BY DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid); ```
``` BLOCK_NO ROW_CNT
-------
1847 20 ← 20개 row가 전부 한 블록에 몰려있음
```
- 4.2 세션1: 첫 번째 row UPDATE 후 커밋 안 함
```sql -- Session 1 UPDATE itl_contention_test SET val = 'SESSION1' WHERE id = 1; -- COMMIT 하지 않고 대기 상태 유지 ```
- 4.3 세션2: 같은 블록의 다른 row UPDATE 시도
```sql -- Session 2 (다른 row, 같은 블록) UPDATE itl_contention_test SET val = 'SESSION2' WHERE id = 2; -- PCTFREE 0 + INITRANS 1 때문에 새 ITL 슬롯 만들 공간이 없어서 대기 발생! ```
- 4.4 대기 상태 실시간 확인
```sql SELECT sid, event, state, seconds_in_wait,
blocking_session, row_wait_obj#, row_wait_row#
FROM v$session WHERE event = 'enq: TX - allocate ITL entry'; ```
``` SID EVENT STATE SECONDS_IN_WAIT BLOCKING_SESSION ROW_WAIT_OBJ# ROW_WAIT_ROW#
----------------------------- -------- --------------- ---------------- ------------- -------------
152 enq: TX - allocate ITL entry WAITING 5 145 123458 1
```
- 결정적 차이점 발견**: `blocking_session`이 명확히 145로 특정됩니다. HW Contention은 특정 블로킹 세션을 지목하기보다 "세그먼트 확장 권한" 자체를 다투는 반면, ITL Contention은 **정확히 어떤 세션이 어떤 row를 잡고 있어서 막고 있는지 100% 특정 가능**합니다.
- 4.5 세션1이 커밋하면 즉시 해소
```sql -- Session 1 COMMIT; ```
```sql -- Session 2 즉시 진행됨 (별도 조치 없이 자동 해소) ```
---
- 5. 근본적 차이: 왜 하나는 파라미터로 예방 가능하고 다른 하나는 안 되는가
``` enq: HW - contention → "세그먼트를 확장하는 행위" 자체가
본질적으로 한 번에 한 세션만 가능한 물리적 작업
→ INITRANS/MAXTRANS 같은 파라미터로 예방 불가능 → 오직 "확장을 여러 세그먼트로 분산"(파티셔닝)해야 완화
enq: TX - allocate ITL entry → "미리 슬롯을 넉넉히 예약"해두면 원천 차단 가능 → INITRANS를 높여서 애초에 충돌 여지를 없앨 수 있음 ```
- 5.1 예방: INITRANS를 높여서 재현 실험
```sql CREATE TABLE itl_fixed_test (
id NUMBER, val VARCHAR2(100)
) PCTFREE 10 -- 여유 공간 확보 (동적 확장 가능하도록) INITRANS 10; -- 초기 슬롯을 10개로 넉넉히
INSERT INTO itl_fixed_test SELECT level, 'DATA' FROM dual CONNECT BY level <= 20; COMMIT; ```
동일한 시나리오(Session1 UPDATE 후 미커밋 + Session2 다른 row UPDATE)를 재현하면:
```sql -- Session 2 실행 결과 -- 즉시 성공! 대기 없음 (INITRANS 10 덕분에 여유 슬롯 충분) ```
```sql SELECT event FROM v$session WHERE sid = <session2_sid>; ```
``` EVENT
SQL*Net message from client ← 정상 처리 완료, 대기 이벤트 없음 ```
---
- 6. 실전에서 각각을 진단하는 방법 비교
- 6.1 HW Contention 진단 (이전 답변 참고)
```sql -- P2 값 → 세그먼트(파일/테이블스페이스) 단위로만 특정 가능 -- 어떤 row인지는 알 수 없음 (row 개념 자체가 없는 단계이므로) SELECT p1, p2, p3 FROM v$session WHERE event = 'enq: HW - contention'; ```
- 6.2 ITL Contention 진단 (row 단위까지 특정 가능)
```sql -- 정확히 어떤 row/블록에서 경합 중인지, -- 누가 블로커인지까지 완벽하게 추적 가능 SELECT
s1.sid AS waiting_sid, s1.row_wait_obj# AS obj_id, s1.row_wait_row# AS row_num, s2.sid AS blocking_sid, s2.username AS blocking_user, s2.sql_id AS blocking_sql
FROM v$session s1, v$session s2 WHERE s1.event = 'enq: TX - allocate ITL entry' AND s1.blocking_session = s2.sid; ```
``` WAITING_SID OBJ_ID ROW_NUM BLOCKING_SID BLOCKING_USER BLOCKING_SQL
------ ------- ------------ ------------- -------------
152 123458 1 145 APP_USER 3xk29fjq8...
```
- 진단 편의성 차이**: ITL Contention은 "누가 블로커인지" AWR/ASH 없이도 `v$session`만으로 즉시 특정 가능하지만, HW Contention은 **명시적 블로커가 없고(순수 자원 경쟁) 오직 같은 세그먼트를 다투는 세션 그룹만 파악 가능**합니다.
---
- 7. 해결 방안 비교
| 상황 | HW Contention 해법 | ITL Contention 해법 | |---|---|---| | 근본 대응 | 파티셔닝으로 세그먼트 분산 | `INITRANS` 상향 (테이블/인덱스 생성 시 미리 설정) | | 사후 대응(운영 중) | 병렬도/동시 세션 수 제한 | `ALTER TABLE ... MOVE` + INITRANS 재설정 | | 근본적 회피 불가능 여부 | 구조적으로 완전 제거 불가 (완화만 가능) | 완전 예방 가능 (파라미터만 잘 잡으면 발생 자체 안 함) | | ASSM 효과 | 미미함 | ASSM 사용 시 Oracle이 필요시 자동으로 ITL을 동적 확장 시도 (PCTFREE 여유 있으면) |
- 7.1 기존 테이블에 INITRANS 사후 적용
```sql -- 이미 만들어진 테이블도 재구성으로 적용 가능 ALTER TABLE itl_contention_test MOVE INITRANS 10 PCTFREE 10;
-- 인덱스도 별도로 재구성 필요 (Table Move는 인덱스에 영향 없음) ALTER INDEX pk_itl_contention_test REBUILD INITRANS 10; ```
---
- 8. 언제 어떤 이벤트를 의심해야 하는가 (실전 판단 기준)
``` 증상: "특정 소수 row(코드테이블, 마스터 데이터 등)에
다수 세션이 몰려서 UPDATE할 때 대기 발생"
→ enq: TX - allocate ITL entry 의심 → v$session의 blocking_session이 뚜렷하게 잡힘 → INITRANS 부족이 원인일 확률 높음
증상: "대량 배치 INSERT, 특히 LOB/파티션 INSERT 시
여러 세션이 동시에 몰릴 때 대기 발생"
→ enq: HW - contention 의심 → blocking_session이 특정되지 않고 여러 세션이
동일 세그먼트 확장을 놓고 순번 대기하는 형태
→ 파티셔닝/세그먼트 분산이 해법 ```
---
- 9. 최종 정리
``` 공통점:
둘 다 "enq:" 계열 대기 이벤트로 Oracle 내부 직렬화(Serialization) 메커니즘에서 발생
결정적 차이:
┌─────────────────────┬──────────────────────┬──────────────────────────┐ │ │ HW - contention │ TX-allocate ITL entry │ ├─────────────────────┼──────────────────────┼──────────────────────────┤ │ 대기 발생 위치 │ 세그먼트 레벨 │ 블록 레벨 │ │ 블로커 특정 가능 여부 │ 불가능 (자원 경쟁) │ 가능 (v$session에 명시) │ │ 사전 예방 파라미터 │ 없음 │ INITRANS/PCTFREE 있음 │ │ 근본 해결책 │ 파티셔닝(분산) │ INITRANS 상향(예방) │ │ ASSM으로 완전 해결? │ 아니오 (완화만) │ 상당 부분 자동 완화 가능 │ └─────────────────────┴──────────────────────┴──────────────────────────┘
한 줄 요약: "HW Contention은 건물을 넓히는 경쟁,
ITL Contention은 이미 있는 방에 들어갈 열쇠고리 부족 문제"
```
---
궁금하시면 이어서: - **enq: TX - row lock contention (일반적인 Row Lock)과 ITL Contention의 차이 - 둘 다 blocking_session이 잡히는데 뭐가 다른가** - **INITRANS를 무조건 높게 잡으면 안 되는 이유 - 블록 공간 낭비와의 트레이드오프** - **파티션 테이블에서 Local Index INITRANS 설계 시 고려사항**
도 다뤄드릴 수 있습니다.