Interconnect와 RAC 대기
GV$ 뷰로 좁히는 RAC 대기 진단, 인터커넥트의 MTU와 오류 및 블록 수신 시간 점검
#핵심 요약
V$ 뷰는 접속한 인스턴스 정보만, GV$ 뷰는 INST_ID를 포함한 전체 인스턴스 정보를 보여 줌
한 노드 세션의 대기 원인이 다른 노드의 블록이나 락일 수 있어 RAC에서는 GV$로 비교함
추적 순서: 대기 이벤트와 클래스(GV$SYSTEM_EVENT, GV$SESSION.EVENT) → 인스턴스(INST_ID) → 세션과 SQL(GV$SESSION.SQL_ID → GV$SQL) → 경합 자원(ROW_WAIT_OBJ# → DBA_OBJECTS)
두 노드가 같은 블록의 서로 다른 행을 바꾸면 TX 락 대기는 없지만 블록을 서로 가져가야 해서 gc 계열 대기(Cluster 클래스)가 생김
인터커넥트는 노드 사이의 사설 네트워크로 Cache Fusion 블록, 글로벌 자원 메시지, 노드 상태 통신에 쓰임
패킷 지연이나 손실이 있으면 gc 대기가 음
실습 점검 순서: 대역폭, 프로토콜 → MTU(양쪽과 스위치 일치) → 오류, 지연(UDP 수신 오류, 블록 평균 수신 시간) → LMS 프로세스의 CPU 확보
#개념과 동작 원리
RAC 네트워크 구성(Public, Private, VIP, SCAN) 자체는 고가용성과 재해복구 분야에서 다룸
| 네트워크 | 오가는 데이터 | 쓰는 주소 |
|---|---|---|
| Public | 업무 트래픽(SQL 요청과 결과) | Public IP, VIP, SCAN IP |
| Private | 클러스터 트래픽(Cache Fusion, 하트비트) | Private IP |
| 점검 항목 | 내용 |
|---|---|
| 대역폭 | 1Gb, 10Gb Ethernet, InfiniBand 등 실제 링크 속도 |
| 프로토콜 | 설명: Linux RAC는 UDP, Exadata는 RDS |
| MTU | 기본 1500, 점보 프레임 예 9000 양쪽 노드와 경유 스위치가 같아야 함 |
| 오류, 지연 | UDP 수신 오류, 재조립 실패, 블록 평균 수신 시간 |
| LMS CPU | 네트워크가 정상이어도 LMS 스케줄링이 늦으면 gc 대기 증가 가능 |
실습에서는 11gR2 이후 Private NIC에 169.254.x.x 대역의 HAIP 주소가 자동으로 붙어 인터커넥트 통신에 쓰인다고 적었음
대기 이벤트와 대기 클래스 일반은 세션과 블로킹 진단, 블록 상태는 Cache Fusion과 블록 전송에 있음
#필수 명령어 또는 SQL
#V$와 GV$ 비교, 인스턴스별 대기
SELECT inst_id, instance_name, host_name, status FROM gv$instance ORDER BY inst_id;
SELECT *
FROM (SELECT inst_id, event, wait_class,
ROUND(time_waited_micro / 1000000) AS wait_sec,
RANK() OVER (PARTITION BY inst_id ORDER BY time_waited_micro DESC) AS rk
FROM gv$system_event
WHERE wait_class <> 'Idle')
WHERE rk <= 5
ORDER BY inst_id, rk;
SELECT inst_id, wait_class, ROUND(SUM(time_waited_micro) / 1000000) AS wait_sec
FROM gv$system_event
WHERE wait_class IN ('Cluster', 'User I/O', 'Concurrency', 'Commit')
GROUP BY inst_id, wait_class
ORDER BY inst_id, wait_sec DESC;결과 해석: GV$SYSTEM_EVENT는 기동 후 누적값이라 현재 구간을 보려면 두 시점의 차이나 GV$SESSION의 현재 대기를 봄
같은 이벤트의 노드별 편차가 크면 그 노드의 부하와 경합을 확인함
#Hot block 경합 추적
실습에서는 두 행이 한 블록에 들어가는 t_hot을 만들고 1번 노드는 id = 1, 2번 노드는 id = 2를 20만 번씩 UPDATE, COMMIT했음
SELECT inst_id, event, wait_class, COUNT(*)
FROM gv$session
WHERE username = '<SCHEMA>' AND state = 'WAITING'
GROUP BY inst_id, event, wait_class
ORDER BY inst_id;
SELECT s.inst_id, s.sql_id, q.sql_text, o.object_name
FROM gv$session s
JOIN gv$sql q
ON q.inst_id = s.inst_id AND q.sql_id = s.sql_id AND q.child_number = s.sql_child_number
LEFT JOIN dba_objects o ON o.object_id = s.row_wait_obj#
WHERE s.username = '<SCHEMA>'
AND s.wait_class = 'Cluster' AND s.state = 'WAITING';결과 해석:
실습 결과: 1번 노드 gc current block busy, 2번 노드 gc buffer busy acquire(실행 시점마다 다를 수 있음), 원인 SQL은 양쪽 모두 UPDATE T_HOT..., 오브젝트 T_HOT
#인터커넥트 경로와 프로토콜
oifcfg getif결과 해석:
실습 결과: 공용 인터페이스는 public, 사설 인터페이스는 cluster_interconnect,asm으로 표시되었음
SELECT inst_id, name, ip_address, is_public, source
FROM gv$cluster_interconnects
ORDER BY inst_id;결과 해석:
실습 결과: 두 인스턴스 모두 사설 NIC의 별칭 인터페이스에 HAIP 주소(169.254 대역), IS_PUBLIC = NO
실습에서는 이어서 ORADEBUG SETMYPID, ORADEBUG IPC, ORADEBUG TRACEFILE_NAME으로 트레이스를 남기고 파일에서 udp를 찾아 프로토콜을 확인했음ORADEBUG는 진단용 내부 명령이라 사용 조건은 기재 없음
#MTU, 오류, 블록 수신 시간
ip link show <PRIVATE_NIC> | grep mtu
ping -c 3 -M do -s 1472 <PRIVATE_HOST>
netstat -su | grep -i -E "receive errors|buffer errors"결과 해석: -M do는 조각화를 금지해 1472 + 헤더 28 = 1500바이트 패킷이 그대로 가는지 봄(점보 프레임이면 -s 8972)
실패하면 경로의 MTU와 ICMP 허용을 확인하고, UDP 오류 값이 계속 늘면 점검 대상임
SELECT inst_id,
ROUND(SUM(DECODE(name, 'gc cr block receive time', value)) * 10 /
NULLIF(SUM(DECODE(name, 'gc cr blocks received', value)), 0), 2) AS avg_cr_ms,
ROUND(SUM(DECODE(name, 'gc current block receive time', value)) * 10 /
NULLIF(SUM(DECODE(name, 'gc current blocks received', value)), 0), 2) AS avg_cur_ms
FROM gv$sysstat
WHERE name IN ('gc cr block receive time', 'gc cr blocks received',
'gc current block receive time', 'gc current blocks received')
GROUP BY inst_id
ORDER BY inst_id;결과 해석:
주석: receive time 통계는 1/100초 단위라 10을 곱해 ms로 바꿈(확인 불가)
#LMS 개수
SHOW PARAMETER cpu_count
SHOW PARAMETER gcs_server_processes
SELECT inst_id, pname FROM gv$process WHERE pname LIKE 'LMS%' ORDER BY 1, 2;결과 해석:
ALTER SYSTEM SET gcs_server_processes = 4 SCOPE = SPFILE SID = '*';srvctl stop database -db <DB_NAME>
srvctl start database -db <DB_NAME>SELECT inst_id, COUNT(*) AS lms_count
FROM gv$process WHERE pname LIKE 'LMS%'
GROUP BY inst_id ORDER BY inst_id;결과 해석:
자동 산정 값 복원은 SPFILE 설정을 지운 뒤 다시 재기동하고 실제 프로세스 개수를 확인해야 완료됨
인스턴스별로 별도 설정한 값이 있으면 그것도 확인함
ALTER SYSTEM RESET gcs_server_processes SCOPE = SPFILE SID = '*';srvctl stop database -db <DB_NAME>
srvctl start database -db <DB_NAME>SELECT inst_id, value FROM gv$parameter WHERE name = 'gcs_server_processes';
SELECT inst_id, COUNT(*) AS lms_count
FROM gv$process WHERE pname LIKE 'LMS%'
GROUP BY inst_id ORDER BY inst_id;결과 해석:
gcs_server_processes를 정하지 않으면 CPU 수로 LMS 개수를 정함
수업 자료 기준: 13코어 1개, 415코어 2개, 16~127코어 2 + CPU_COUNT/32(소수점 버림), 128코어 이상이면서 SGA 100GB 이상이면 CPU_COUNT/6
예: 64코어는 2 + 64/32 = 4개
정적 파라미터라 SPFILE에만 쓰고 DB를 다시 시작해야 적용되며, SID = '*'로 두 인스턴스에 같이 줌
실습 기록: 4로 바꾸고 재기동하자 두 노드 모두 LMS0~`LMS3`이 떴음
자동 산정으로 돌아가도록 RESET했으나, 복원 재기동 뒤 실제 LMS 개수는 확인 불가
LMS를 늘리기 전에 gc 대기와 LMS의 CPU 사용률을 먼저 보며, 프로세스가 너무 많으면 CPU 경합이 생길 수 있음
#LMS 우선순위
ps -ef | grep lms | grep -v grep
ps -o pid,cls,ni,pri,cmd -p <LMS_PID>renice -20 -p <LMS_PID>결과 해석:
주의: nice 값이 낮을수록 우선순위가 높고 음수 조정에는 root가 필요함renice는 현재 프로세스에만 적용되어 인스턴스를 재시작하면 원래대로 돌아감
LMS가 실시간 클래스(CLS=RR)로 도는 환경이면 스케줄링 클래스를 따로 확인함
#확인 문제: 주문 접수가 느려진 원인 찾기
상황: 주문 서버가 두 노드에 붙어 있고 CPU는 한가한데 주문 접수가 느려짐
어떤 대기 때문에, 어느 노드에서, 어떤 SQL과 테이블이 원인인지 밝혀야 함
CREATE TABLE t_order_seq (branch_id NUMBER PRIMARY KEY, last_no NUMBER);
INSERT INTO t_order_seq SELECT level, 0 FROM dual CONNECT BY level <= 4;
COMMIT;결과 해석:
준비가 끝난 뒤 아래 갱신 블록만 두 노드에서 동시에 실행함
-- 1번 노드: 지점 1, 2 / 2번 노드: mod(i,2) + 3 으로 지점 3, 4
BEGIN
FOR i IN 1..200000 LOOP
UPDATE t_order_seq SET last_no = last_no + 1 WHERE branch_id = mod(i,2) + 1;
COMMIT;
END LOOP;
END;
/SELECT inst_id, wait_class, event, COUNT(*)
FROM gv$session
WHERE username = '<SCHEMA>' AND state = 'WAITING'
GROUP BY inst_id, wait_class, event
ORDER BY inst_id;
SELECT s.inst_id, s.sql_id, q.sql_text
FROM gv$session s
JOIN gv$sql q
ON q.inst_id = s.inst_id AND q.sql_id = s.sql_id AND q.child_number = s.sql_child_number
WHERE s.username = '<SCHEMA>' AND s.wait_class = 'Cluster' AND s.state = 'WAITING';
SELECT DISTINCT s.inst_id, o.owner, o.object_name
FROM gv$session s
JOIN dba_objects o ON o.object_id = s.row_wait_obj#
WHERE s.username = '<SCHEMA>' AND s.wait_class = 'Cluster' AND s.state = 'WAITING';결과 해석:
예상값: 대기 클래스가 Cluster인 gc 계열 대기, 원인 오브젝트 T_ORDER_SEQ
노드마다 갱신하는 행은 다르지만 네 행이 같은 블록에 있어 블록이 노드 사이를 계속 오감
같은 블록을 갱신하는 작업을 한 노드로 모으거나, 노드별 갱신 대상이 서로 다른 블록에 저장되도록 데이터 배치를 분리하는 방안을 검토함
지점별 접속 노드만 나누고 블록을 공유하면 블록 왕복이 남음
CPU가 한가해도 세션의 Cluster 대기 시간이 길면 RAC 경합을 의심하며, 2번 노드의 대기 세션과 SQL도 GV$로 함께 봄
실행 결과 확인 불가
실습이 끝나면 DROP TABLE t_order_seq PURGE로 지움
#확인 문제: 2번 노드 사용자만 조회가 느린 원인
상황: 2번 노드에 붙은 사용자만 느리고, 전날 서버 담당자가 2번 노드에서 프로세스 우선순위를 만졌으나 무엇을 바꿨는지 모름
실습에서는 root로 2번 노드의 모든 LMS를 renice 19(가장 낮은 우선순위)로 바꿔 이 상황을 재현함
for p in $(pgrep -f "_lms"); do renice 19 -p $p; done결과 해석:
점검 순서: 인터커넥트 경로와 프로토콜 → MTU와 조각화 → 노드별 평균 블록 수신 시간 → LMS 우선순위
oifcfg getif
ip link show <PRIVATE_NIC> | grep mtu # 1번 노드에서도 같은 명령으로 비교
ping -c 3 -M do -s 1472 <RAC1_PRIVATE_HOST>
ps -o pid,cls,ni,cmd -p $(pgrep -d, -f _lms)SELECT inst_id, name, ip_address FROM gv$cluster_interconnects;결과 해석:
노드별 평균 블록 수신 시간은 위 "MTU, 오류, 블록 수신 시간" 절의 GV$SYSSTAT 쿼리를 그대로 씀
for p in $(pgrep -f "_lms"); do renice -20 -p $p; done
ps -o pid,cls,ni,cmd -p $(pgrep -d, -f _lms)결과 해석:
예상값: 인터커넥트는 사설 대역을 쓰고 MTU도 같은데 2번 노드의 블록 수신 시간만 길며, LMS의 NI가 19로 나옴
복원 뒤 NI -20 확인
경로와 MTU가 정상인데 한 노드의 블록 수신만 느리면 LMS 스케줄링 지연을 의심함
renice로 바꾼 값은 인스턴스를 다시 시작하면 사라지므로, 재기동 뒤에도 같은 증상이 나오면 우선순위를 바꾸는 자동 실행 스크립트가 있는지 찾음
복원 값 -20은 수업 자료의 기대값이며, LMS가 실시간 스케줄링 클래스로 도는 환경에서 nice 값이 실제로 쓰이는지는 확인 불가
실행 결과 확인 불가