Cache Fusion과 블록 전송
Cache Fusion과 GRD의 블록 상태, 인스턴스 간 읽기 및 쓰기 전송과 V$BH 조회 실습
#핵심 요약
Cache Fusion은 필요한 블록이 다른 인스턴스의 버퍼 캐시에 있으면 공유 디스크에 쓰고 다시 읽지 않고 인터커넥트로 전달하는 방식임
GCS는 GRD(Global Resource Directory)로 각 블록이 어느 인스턴스에 어떤 모드로 있는지 관리함
쓰기용 현재 블록(xcur)은 클러스터에서 한 노드만 가짐
다른 노드가 쓰기를 요청하면 xcur가 넘어가고 이전 노드에는 과거 이미지(pi)가 남을 수 있음
미커밋 변경 블록을 다른 노드가 읽으면, 보유 노드가 Undo로 조회 시점의 CR 블록을 만들어 보냄
커밋을 빠뜨린 트랜잭션은 다른 노드의 조회마다 CR 전송을 늘림
같은 Hot block을 여러 노드가 번갈아 바꾸면 블록 왕복과 gc 대기가 늘어남
업무나 데이터를 노드별로 나누는 것을 검토함
#개념과 동작 원리
RAC 구조 일반은 고가용성과 재해복구 분야에서 다룸
V$BH.STATUS |
의미 |
|---|---|
xcur |
배타적 현재 블록, 쓰기 접근 |
scur |
공유 현재 블록, 여러 인스턴스의 읽기 |
null |
블록 자원 권한 없음 |
pi |
과거 이미지 다른 노드가 갱신한 뒤 복구 목적으로 남는 이미지 |
cr |
읽기 일관성용 사본 |
| 전송 상황 | 동작 | 노드별 상태 |
|---|---|---|
| ① 최초 읽기, 전송 없음 | 어느 인스턴스에도 없는 블록을 디스크에서 읽음 | 조회 노드 scur |
| ② Read → Read | 다른 노드의 scur 블록을 인터커넥트로 받음 |
양쪽 scur |
| ③ Read → Write | 읽기 상태 블록을 한 노드가 변경 | 갱신 노드 xcur, 다른 노드 권한 해제 |
| ④ Write → Read(커밋 후) | 처음 요청 때는 보유 노드 xcur 유지 가능, 반복 요청 후 양쪽 scur |
|
| ⑤ Write → Read(커밋 전) | 보유 노드가 Undo로 CR 블록을 만들어 전송 | 보유 노드 xcur 유지, 요청 노드 cr |
| ⑥ Write → Write | 보유 노드가 리두를 먼저 기록하고 블록 전송 | 요청 노드 xcur, 이전 노드 pi |
실습의 "4가지 경우" 설명에는 세 가지만 나열되어 있어 나머지 한 가지는 확인 불가scur 전환 시점은 버전과 환경에 따라 다를 수 있음
⑥에서 블록을 보내기 전에 리두를 기록해야 하므로 리두 쓰기 성능이 블록 전송 속도에 영향을 줌
실습에서는 gcs log flush sync가 늘면 리두 저장소의 쓰기 성능을 확인하라고 적었음
#필수 명령어 또는 SQL
#블록 위치와 락 모드 조회 스크립트
CREATE TABLE rac_test (id NUMBER);
INSERT INTO rac_test VALUES (1);
COMMIT;SELECT DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS fno,
DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS blkno
FROM rac_test;
-- lock_mode.sql로 저장해 두 노드에서 사용
SELECT b.lock_element_addr, b.status, e.mode_held, e.local
FROM v$bh b, v$gc_element e
WHERE b.file# = <FNO> AND b.block# = <BLKNO>
AND b.lock_element_addr = e.gc_element_addr;결과 해석:
실습 결과(순서대로): DB 재기동 직후 두 노드 모두 no rows selected → 1번 노드 조회 후 1번 scur(MODE_HELD 1) → 2번 노드 조회 후 양쪽 scur → 2번 노드가 UPDATE, COMMIT하자 2번 xcur(MODE_HELD 2), 1번은 no rows selected(남아 있다면 V$BH에서 cr)
실습에서는 flush buffer_cache 뒤에도 GRD 정보가 남을 수 있어 재기동으로 초기 상태를 만들었다고 적었음
#커밋 전 읽기와 CR 블록
-- 커밋하지 않음
UPDATE rac_test SET id = id + 1;SELECT name, value
FROM v$mystat m JOIN v$statname n USING (statistic#)
WHERE name = 'gc cr blocks received';
-- 여러 번 반복 후 위 조회 재실행
SELECT * FROM rac_test;SELECT inst_id, status, COUNT(*)
FROM gv$bh
WHERE file# = <FNO> AND block# = <BLKNO>
GROUP BY inst_id, status
ORDER BY inst_id;결과 해석: gc cr blocks received가 조회할 때마다 늘고, GV$BH에는 1번 노드 xcur, 2번 노드 cr 여러 개가 보임
실습에서는 실습 뒤 1번 노드에서 ROLLBACK했음
#Write → Write와 pi
같은 블록에 행을 하나 더 넣고, 1번 노드가 첫 행을, 2번 노드가 두 번째 행을 각각 UPDATE, COMMIT함
SELECT inst_id, status, dirty
FROM gv$bh
WHERE file# = <FNO> AND block# = <BLKNO>
AND status IN ('xcur', 'pi')
ORDER BY inst_id;
SELECT inst_id, event, total_waits
FROM gv$system_event
WHERE event LIKE 'gcs log flush%';결과 해석:
실습 결과: INST_ID 1, pi, Y(이전에 고친 노드), INST_ID 2, xcur, Y(마지막에 고친 노드)
행이 달라도 같은 블록이면 전송이 일어남
#확인 문제: 번갈아 고친 결제 블록이 두 노드에 남은 상태
상황: 결제 승인 테이블을 두 노드가 번갈아 고칠 때 블록이 디스크를 거치는지, 두 노드가 차례로 고친 뒤 각 노드에 그 블록이 어떤 상태로 남는지 한 번의 조회로 보여 달라는 요청
-- 1번 노드
CREATE TABLE t_pay (pay_id NUMBER, status VARCHAR2(10));
INSERT INTO t_pay VALUES (100, 'READY');
COMMIT;
SELECT dbms_rowid.rowid_relative_fno(rowid) AS fno,
dbms_rowid.rowid_block_number(rowid) AS blkno
FROM t_pay;
-- 2번 노드
UPDATE t_pay SET status = 'APPROVED' WHERE pay_id = 100;
COMMIT;
-- 1번 노드
UPDATE t_pay SET status = 'SETTLED' WHERE pay_id = 100;
COMMIT;SELECT inst_id, status, dirty
FROM gv$bh
WHERE file# = &fno AND block# = &blkno
ORDER BY inst_id;결과 해석:
예상값: INST_ID 1은 xcur(마지막에 고친 노드), INST_ID 2는 pi(이전에 고친 노드), 둘 다 DIRTY = Y
1번 노드는 최신 블록을 디스크가 아니라 2번 노드에게서 인터커넥트로 받아 고침
pi는 인스턴스 복구에 쓰일 수 있는 이전 이미지이며, 최신 블록이 디스크에 기록되면 복구 조건에 따라 지워짐
실습이 끝나면 DROP TABLE <SCHEMA>.t_pay PURGE로 지움
실행 결과 확인 불가
#확인 문제: 커밋을 빠뜨린 프로시저 때문에 2번 노드 조회가 느려짐
상황: 배포 뒤 2번 노드의 조회 화면이 느려졌지만 락에 걸린 것처럼 멈추지는 않음
배포한 것은 1번 노드에서 도는 보너스 계산 프로시저 하나뿐임
CREATE OR REPLACE PROCEDURE p_bonus IS
BEGIN
UPDATE emp SET comm = nvl(comm, 0) + 100 WHERE deptno = 30;
-- COMMIT 누락
END;
/
EXEC p_bonus결과 해석:
PL/SQL procedure successfully completed가 나와 끝난 것처럼 보여도 트랜잭션은 열려 있음
SELECT empno, ename, comm FROM emp WHERE deptno = 30;
SELECT name, value
FROM v$mystat m JOIN v$statname n USING (statistic#)
WHERE name = 'gc cr blocks received';SELECT s.inst_id, s.sid, s.serial#, s.username, s.prev_sql_id, t.start_time
FROM gv$transaction t
JOIN gv$session s ON s.inst_id = t.inst_id AND s.taddr = t.addr
ORDER BY t.start_time;결과 해석:
업무 담당자가 변경 확정을 승인한 경우 해당 트랜잭션을 연 재현 세션에서 커밋함
COMMIT;결과 해석:
ALTER SYSTEM KILL SESSION '<SID>,<SERIAL#>,@1' IMMEDIATE;결과 해석:
| 점검과 조치 | 기준 |
|---|---|
| TX 락 대기 없이 조회만 느림 | 미커밋 Write → Read로 CR 블록 전송이 반복되는지 확인 |
| 미커밋 트랜잭션 추적 | GV$TRANSACTION으로 클러스터 전체 조회 |
| 트랜잭션 종료와 프로시저 수정 | 세션을 끊으면 그 변경은 롤백됨 커밋해도 되는지 업무 확인 뒤 판단하고 프로시저에 COMMIT을 넣음 |
예상값: 2번 노드 세션의 gc cr blocks received가 조회할 때마다 늘고, GV$TRANSACTION에 INST_ID 1, <SCHEMA>의 오래 열린 트랜잭션이 나옴
수업 자료의 정리 단계는 UPDATE emp SET comm = comm - 100 WHERE deptno = 30 후 커밋인데, 세션을 끊어 변경이 롤백된 경우에는 이 문장이 원래 값을 100씩 줄이고, 커밋한 경우에도 원래 NULL이던 comm은 0으로 남음
정리 전에 실제로 커밋됐는지와 원래 값을 확인함
실행 결과 확인 불가