다른 명령
새 문서: == enq: HW - Contention == * LOB Segment 이슈와 밀접하게 연결되는 대기 이벤트입니다. * 특히 동시성 높은 환경에서 LOB 컬럼에 INSERT가 몰릴 때 자주 발생하는 이슈 ---- === HW(High Water Mark) Enqueue가 무엇인가? === * 개념: 세그먼트(Table, Index, LOB Segment 등)가 "새로운 블록 영역으로 확장" 될 때 걸리는 직렬화 락(Lock) * HWM(High Water Mark)란? := 이 세그먼트가 지금까지 한 번이라도... |
|||
| 8번째 줄: | 8번째 줄: | ||
* HWM(High Water Mark)란? | * HWM(High Water Mark)란? | ||
: | : :이 세그먼트가 지금까지 한 번이라도 사용한 적 있는 블록의 최대 경계선 | ||
<source> | <source> | ||
2026년 9월 22일 (화) 08:34 기준 최신판
enq: HW - Contention
- LOB Segment 이슈와 밀접하게 연결되는 대기 이벤트입니다.
- 특히 동시성 높은 환경에서 LOB 컬럼에 INSERT가 몰릴 때 자주 발생하는 이슈
HW(High Water Mark) Enqueue가 무엇인가?
- 개념: 세그먼트(Table, Index, LOB Segment 등)가 "새로운 블록 영역으로 확장" 될 때 걸리는 직렬화 락(Lock)
- HWM(High Water Mark)란?
- :이 세그먼트가 지금까지 한 번이라도 사용한 적 있는 블록의 최대 경계선
[세그먼트 내부 이미지]
┌────────┬────────┬────────┬────────┬─────────────┐
│ 사용중 │ 사용중 │ 사용중 │ 미사용 │ 미사용 │
│ Block1 │ Block2 │ Block3 │ Block4 │ Block5 │
└────────┴────────┴────────┴────────┴─────────────┘
▲
HWM (여기까지가 "사용해본 적 있는" 경계)
→ HWM을 넘어 Block4, Block5로 최초 진입(확장)하려면
"누군가 한 명만" HWM을 이동시키는 작업을 해야 함
→ 그 "한 명만 하도록 강제"하는 직렬화 장치가 바로 enq: HW 이다
- 핵심: HW 이동 자체는 원자적(Atomic)이어야 하므로, 여러 세션이 동시에 HWM을 넘기려고 시도하면 한 세션만 실제로 이동시키고 나머지는 대기합니다.
이 대기가 `enq: HW - contention`입니다.
어떤 상황에서 자주 발생하는가?
- 발생 빈발 시나리오 TOP 3
1순위: LOB Segment 동시 INSERT (가장 빈번) : → 여러 세션이 동시에 CLOB/BLOB 컬럼에 대량 데이터를 INSERT : → 특히 NOCACHE + 병렬 세션 조합에서 극심 2순위: 병렬 Direct-Path INSERT (APPEND Hint) : → 여러 병렬 슬레이브가 동시에 같은 테이블 뒤쪽에 데이터 적재 3순위: 시퀀스 없는 대량 동시 INSERT (일반 Heap 테이블) : → ASSM(Automatic Segment Space Management)이라도 극단적으로 짧은 시간에 너무 많은 세션이 몰리면 발생 가능- SecureFile LOB에 다수 세션이 동시에 대용량 데이터를 넣는 배치/API 환경이라면, LOB Segment의 HWM 확장 경쟁이 바로 이 대기 이벤트의 주요 원인
실습으로 재현해보기
- 테스트 테이블
CREATE TABLE hw_contention_test ( id NUMBER, data_lob CLOB ) LOB (data_lob) STORE AS SECUREFILE hw_lob_seg ( DISABLE STORAGE IN ROW NOCACHE CHUNK 8192 );
- 다수 세션에서 동시 INSERT 발생시키기 (개념적 재현)
- 여러 세션에서 동시에 아래를 실행 (SQL*Plus 여러 창 or 병렬 Job):
-- Session 1~10 동시 실행 DECLARE l_clob CLOB; BEGIN FOR i IN 1..1000 LOOP l_clob := gen_clob(50000, 'X'); -- 50KB LOB INSERT INTO hw_contention_test VALUES (i, l_clob); COMMIT; END LOOP; END; /
- 실시간 대기 이벤트 모니터링
SELECT sid, event, state, seconds_in_wait, p1, p2, p3 FROM v$session WHERE event = 'enq: HW - contention';
SID EVENT STATE SECONDS_IN_WAIT P1 P2 P3 ---- ----------------------- ------- ---------------- ---------- ------ ------ 145 enq: HW - contention WAITING 2 1213908993 65543 0 152 enq: HW - contention WAITING 2 1213908993 65543 0 168 enq: HW - contention WAITING 1 1213908993 65543 0
-
- P1, P2 해석:
- - `P1 = 1213908993` → `Type='HW'(0x48 0x57), Mode` 조합 인코딩 값
- - `P2 = 65543` → 대상 세그먼트의 **Tablespace# + Relative File#** 조합 (같은 세그먼트를 다투고 있다는 증거)
- P1, P2 해석:
-
- 동일한 P2 값을 가진 세션들이 정확히 같은 세그먼트(여기서는 hw_lob_seg)에서 경쟁 중임을 의미합니다.
- 원인이 되는 세그먼트를 정확히 찾아내는 방법
-- P2 값을 이용해 실제 경쟁 중인 세그먼트 역추적 SELECT s.sid, s.event, o.owner, o.object_name, o.object_type FROM v$session s, v$lock l, dba_extents e, dba_objects o WHERE s.event = 'enq: HW - contention' AND l.sid = s.sid AND l.type = 'HW' AND e.relative_fno = MOD(l.id2, 65536) AND e.file_id = e.relative_fno -- 환경에 맞게 조정 필요 AND o.object_id = e.segment_id AND o.object_name = e.segment_name;- * 더 실무적으로 간단한 방법: P1/P2로 직접 조회하는 대신, 실시간으로 어느 세그먼트가 확장 중인지 ASH로 확인
SELECT sample_time, session_id, current_obj#, (SELECT object_name FROM dba_objects WHERE object_id = ash.current_obj#) AS obj_name, event FROM v$active_session_history ash WHERE event = 'enq: HW - contention' ORDER BY sample_time DESC FETCH FIRST 20 ROWS ONLY;SAMPLE_TIME SESSION_ID CURRENT_OBJ# OBJ_NAME EVENT ----------------------- ----------- ------------- --------------- --------------------- 2024-01-15 14:23:01.123 145 123457 HW_LOB_SEG enq: HW - contention 2024-01-15 14:23:01.456 152 123457 HW_LOB_SEG enq: HW - contention 2024-01-15 14:23:01.789 168 123457 HW_LOB_SEG enq: HW - contention
- 확인 완료 : 세 세션 모두 `HW_LOB_SEG`에서 경쟁 중임이 명확히 드러납니다.
왜 이런 구조적 한계가 존재하는가? (ASSM이라도 못 피하는 이유)
오해: "ASSM(자동 세그먼트 공간 관리)이면
Freelist Contention이 해결됐으니 HW Contention도 없겠지?"
실제: ASSM은 "이미 HWM 아래에 있는 블록들 사이의 공간 찾기 경쟁"만 해결한다.
"HWM 자체를 넘어서 새 영역으로 처음 진입하는 순간"의
직렬화는 ASSM 여부와 무관하게 항상 존재한다.
즉, Freelist Contention(구시대) ≠ HW Contention(구조적으로 항상 존재)
- LOB Segment는 특히 취약한 이유*:
일반 Table Segment → 여러 세션이 각기 다른 블록에 흩어져서 쓸 수 있음 (ASSM Bitmap 분산 배정) → HWM 확장 빈도 자체가 상대적으로 낮음 LOB Segment (특히 NOCACHE 대용량 INSERT) → 각 LOB 값이 통째로 CHUNK 단위(8K, 16K...)를 즉시 소비 → 대용량 LOB을 빠르게 연속 INSERT하면 HWM이 "매우 자주" 이동해야 함 → HWM 이동 빈도가 높을수록 Contention 발생 확률 급증
해결 방안 (실전 우선순위 순)
- SECUREFILE의 경우 - Concurrency 관련 파라미터 확인
-- 현재 LOB의 상세 스토리지 옵션 확인 SELECT table_name, column_name, securefile, cache, logging, retention FROM user_lobs WHERE table_name = 'HW_CONTENTION_TEST';TABLE_NAME COLUMN_NAME SECUREFILE CACHE LOGGING RETENTION -------------------- ----------- ---------- --------- ------- ---------- HW_CONTENTION_TEST DATA_LOB YES NO YES AUTO
-
- 개선안: SecureFile은 내부적으로 Space Management를 위한 여러 최적화가 있지만, 극단적 동시성에서는 여전히 근본적 해결이 안 됨. 아래 항목들과 병행 필요.
- 파티셔닝으로 세그먼트 자체를 분산 (가장 효과적)
-- Hash Partition으로 LOB Segment 자체를 물리적으로 분산 CREATE TABLE hw_contention_test_part ( id NUMBER, data_lob CLOB ) LOB (data_lob) STORE AS SECUREFILE ( DISABLE STORAGE IN ROW NOCACHE ) PARTITION BY HASH (id) PARTITIONS 8;-- 파티션별로 별도의 LOB Segment가 생성되는지 확인 SELECT table_name, partition_name, segment_name FROM user_lob_partitions WHERE table_name = 'HW_CONTENTION_TEST_PART';
TABLE_NAME PARTITION_NAME SEGMENT_NAME ------------------------- --------------- ------------------------ HW_CONTENTION_TEST_PART SYS_P101 SYS_LOB_P101_00001$$ HW_CONTENTION_TEST_PART SYS_P102 SYS_LOB_P102_00002$$ HW_CONTENTION_TEST_PART SYS_P103 SYS_LOB_P103_00003$$ ...
- 효과 : 세션들이 `id` 값에 따라 8개의 서로 다른 파티션(=서로 다른 LOB Segment)으로 흩어지므로, 동일 세그먼트를 다투는 확률이 1/8로 감소합니다.
- 동시 INSERT 세션 수 자체를 조절 (애플리케이션 레벨)
-- Resource Manager로 동시 병렬도 제한 (배치 작업의 경우) BEGIN DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE( plan => 'BATCH_PLAN', group_or_subplan => 'BATCH_GROUP', comment => 'LOB Insert 동시성 제한', parallel_degree_limit_p1 => 4 -- 동시 병렬도 제한 ); END; /
- CACHE 옵션 재검토 (단, 대용량이면 신중히)
-- NOCACHE → CACHE READS로 변경 시 HWM 확장 패턴이 달라질 수 있음 -- (단, Buffer Cache 압박이 커지므로 LOB이 매우 크면 비추천) ALTER TABLE hw_contention_test MOVE LOB(data_lob) STORE AS ( CACHE READS );
- 배치 커밋 간격 조정 (Commit 빈도와 HWM 확장 빈도는 별개지만 병행 효과 있음)
-- 매 row마다 COMMIT → 500 row마다 COMMIT으로 변경 -- (트랜잭션 오버헤드 감소로 전체 처리시간 단축, HW Contention 절대량은 불변이나 -- 세션 간 타이밍이 분산되어 충돌 확률 감소하는 부수 효과) DECLARE l_clob CLOB; BEGIN FOR i IN 1..1000 LOOP l_clob := gen_clob(50000, 'X'); INSERT INTO hw_contention_test VALUES (i, l_clob); IF MOD(i, 500) = 0 THEN COMMIT; END IF; END LOOP; COMMIT; END; /
- 실전 진단 체크리스트
-- AWR에서 HW Contention이 Top Wait Event로 잡히는지 확인 SELECT event, total_waits, time_waited_micro/1000000 AS time_waited_sec FROM dba_hist_system_event WHERE event = 'enq: HW - contention' AND snap_id BETWEEN (SELECT MIN(snap_id) FROM dba_hist_snapshot WHERE begin_interval_time > SYSDATE-1) AND (SELECT MAX(snap_id) FROM dba_hist_snapshot) ORDER BY time_waited_sec DESC;EVENT TOTAL_WAITS TIME_WAITED_SEC ----------------------- ------------ ---------------- enq: HW - contention 8421 342.56
- 이 정도 수치(하루 5분 이상 누적 대기)라면 명백히 튜닝 대상 입니다.
최종 정리
enq: HW - contention 핵심 요약
원인: 세그먼트의 High Water Mark를 넘어 새 영역으로 확장할 때
발생하는 필연적 직렬화 경쟁
가장 흔한 실전 상황: 다수 세션의 동시 LOB INSERT (특히 NOCACHE 대용량)
ASSM으로도 해결 안 되는 구조적 이슈 (Freelist Contention과는 다른 문제)
가장 효과적인 해결책 우선순위:
1. 파티셔닝으로 세그먼트 자체를 분산 (근본 해결)
2. 동시 세션 수/병렬도 제한 (부하 조절)
3. CACHE 옵션, 배치 커밋 조정 (보조적 완화)