728x90

01 | 오라클 아키텍처

03 ) 버퍼 Lock

(1) 버퍼 Lock ? 

DB 버퍼 캐시 내에서 버퍼 블록을 찾고, 래치를 빠르게 해제하지 않으면 cache buffers chains 래치에 여러 개의 해시 체인이 달렸으므로 래치에 대한 경합 발생 가능성이 증가하게 된다. 

 

캐시된 버퍼 블록을 읽거나 변경하려는 프로세스는 먼저 버퍼 헤더로부터 버퍼 Lock(buffer pin) 을 흭득해야 한다. 

버퍼 Lock (buffer pin)을 흭득햇다면 래치를 곧바로 해제해야한다.  

 

버퍼 내용을 읽기만 할 때는 Share 모드, 변경할 때는 Exclusive 모드로 Lock을 설정한다.

액세스를 직렬화하기 위한 메커니즘이므로 당연히 Exclusive 모드 Lock은 한 시점에 하나의 프로세스만 얻을 수 있다.  Select 문이더라도 Block Cleanout이 필요할 때는 버퍼 내용을 변경하는 작업이므로 Exclusive 모드 Lock을 요구한다.

 

💡 Block Cleanout → 성능 최적화를 위해 사용

- 트랜잭션 완료정보를 지연해서 처리하는 최적화기법

- 트랜잭션 완료여부를 블록 내에 표시(clean)하지 않고 남겨뒀다가, 이후 누군가 그 블록을 읽을 때 처리(cleanout)하는 메커니즘

- 성능을 위해서 트랜잭션이 커밋되었을때 모든 블록을 즉시 수정하지 않는다. 대신 해당 트랜잭션이 어떤 블록을 변경했는지, undo, redo 로그에만 저장한다. 

- 블록내의 트랜잭션 메타 데이터(Interested Transaction List)는 "나중에" 정리한다. 나중에 정리하는 것이 Block Cleanout이다.

 

📌 Why 성능 최적화? 

▶️  트랜잭션이 커밋될때마다 모든 관련 블록을 다시 찾아가서 "커밋됨"을 표시하면 디스크 I/O 비용과 latch 경합이 매우 커진다.

 ex) 트랜잭션 A가 1000개의 데이터 블록을 변경 → COMMIT → 1000개의 데이터 블록을 모두 찾아가 "COMMIT됨 표시" ▶️ COST 가 큼

트랜잭션 A가 1000개의 데이터 블록을 변경 (ITL 기록; 이 트랜잭션이 수정 중 표시) + UNDO 생성 → COMMIT(redo 기록, SCN 부여) → 트랜잭션 B가 트랜잭션 A가 변경한 데이터블록 접근(ITL확인) → UNDO에 연결된 트랜잭션 상태를 확인한 후 Block Cleanout  처리

 

📌 버퍼 Lock 대기모드

- 찾아낸 버퍼 캐시상의 대상 버퍼가 타 프로세스에 의해 exclusive lock이 흭득된 상태인 경우 버퍼 헤더에 있는 버퍼 Lock 대기자 목록에 등록 후 대기 (해당 요청을 했던 프로세스에 할당된 cache buffers chain latch는 이 시정에 해지된다.)

- 이렇게 동작해야 또 다른 프로세스가 해당 버퍼 캐시에 요청을 했을때 latch wait이 발생하지 않고 버퍼 lock 대기자 목록에 자신의 정보를 기록할 수 있다.

- buffer busy waits 대기 이벤트 발생

- 목적한 읽기/쓰기 작업을 완료하면 버퍼 헤더에서 버퍼 Lock 을 해제해야 하는데, 이때도 버퍼 헤더를 액세스하려는 다른 프로세스와 충돌이 생길 수 있으므로 해당 버퍼가 속한 체인 래치를 다시 한번 흭득한다. ▶ 버퍼 Lock 해제의 경우 버퍼헤더에서 해당 버퍼 블록에 대한 상태를 수정하는 작업임(pin count 수정), 다른 프로세스가 버퍼 체인 스캔을 하며 버퍼 헤더에 접근하게 되면 충돌이 발생할 수 있다. 그렇기 때문에 Lock 해지할 때에도 Buffer Chains Latch를 재흭득해야 함.

⭐️ 버퍼 블록을 읽을때 대부분 두번의 래치 흭득을 요구한다. (버퍼 헤더 검색 후 pin / Un-pin)

일부 작업의 경우 래치를 흭득한 상태로 버퍼 블록을 읽기 때문에 래치 흭득이 한번만 일어난다. (v$sysstat 뷰에서 consistent gets - examination 통계항목으로 측정)

버퍼 Lock을 해제하고 Latch Lock을 해제해야 비로소 버퍼 블록 읽기가 완료된다.

 

(2) 버퍼 핸들

버퍼 Lock을 설정하는 것은 자신이 현재 그 버퍼를 사용중임을 표시해 두는 것으로서, 그 버퍼 헤더에 Pin을 걸었다고도 표현한다.

읽기 작업 : 여러 프로세스가 Pin 설정 가능

변경 작업 : 하나의 프로세스만 Pin 설정 가능

 

버퍼 헤더에 Pin을 설정하려고 사용하는 오브젝트를 버퍼 핸들(Buffer Handle)이라고 부르며, 버퍼 핸들을 얻어 버퍼 헤더에 있는 소유자 목록(Holder Lost)에 연결시키는 방식으로 Pin을 설정한다.

 

버퍼 핸들도 공유된 리소스이므로 버퍼 핸들을 얻으려면 또 다른 래치가 필요해지는데, 바로 cache buffer handles 래치이다. 

버퍼를 Pin하는 작업이 많을수록 cache buffer handles 래치가 경합 지점이 될것이므로, 오라클은 각 프로세스마다 _db_handles_cached 개수만큼의 버퍼 핸들을 미리 할당해 주며, 기본값은 5이다. 

각 세션은 이를 캐싱하고 있다가 버퍼를 Pin할때마다 사용하며, 그 이상의 버퍼 핸들이 필요할 때만 cache buffer handles 래치를 얻고 추가로 버퍼 핸들을 할당받는다. 

시스템 전체 버퍼 핸들 개수 (_db_handles) = processes * _db_handles_cached

 

(3) 버퍼 Lock의 필요성

  • 단일 레코드 갱신시의 버퍼 Lock 필요성
    • 단일 레코드를 갱신 → 사용자 데이터 변경 시 DML Lock 을 통해서 정합성을 유지
    • DML Lock 만으로는 정합성 유지에는 불충분하다. → 하나의 레코드를 갱신하더라도 블록 단위로 I/O를 수행하기 때문에 , 블록 안에 저장된 10개의 레코드를 읽는 짧은 순간 동안 다른 프로세스에 의해 변경이 발생하면 잘못된 결과를 얻게 된다.
    • 버퍼 Lock 필요
  • Row-Level Lock에서의 버퍼 Lock 필요성
    • row-level lock 설정 자체도 레코드의 속성을 변경한다. 
    • 두 프로세스가 동시에 row-level lock을 설정할 경우 문제가 발생한다. ( 대상 로우가 다르더라도 )
    • 블록 SCN 변경 /  ITL 슬롯 변경도 블록 헤더 내용을 변경하는 작업으로 동시 액세스가 발생하면 Lost Update 문제가 생겨 블록 자체의 정합성이 깨질 수 있다.
    • 블록 진입 자체를 직렬화 해야한다.

 

(4) 버퍼 Pinning

"버퍼 Pinning" 버퍼를 읽고 나서 버퍼 Pin을 즉각 해제하지 않고 데이터베이스 Call이 진행되는 동안 유지하는 기능

같은 블록을 반복적으로 읽을  때 버퍼 Pinning을 통해 래치 흭득 과정을 생략하면 논리적인 블록 읽기(Logical Reads) 횟수를 획기적으로 줄일 수 있다. 

같은 블록을 재방문할 가능성이 큰 몇몇 작업들을 수행할 때만 사용한다.

 

v$sysstat, v$sesstat, v$mystat 등을 조회해 보면, 래치 흭득 과정을 통해 블록을 액세스 할 때는 session logical reads 항목이 증가하고, 래치 흭득 과정 없이 버퍼 Pinning 을 통해 블록을 곧바로 액세스 할 때는 buffer is pinned count 항목의 수치가 증가한다.

 

버퍼 pinning은 하나의 데이터베이스 Call(Parse call, execute call, fetch call) 내에서만 유효하다. 즉 Call이 끝나고 사용자에게 결과를 반환하고 나면 Pin 은 해제되어야 한다.

 

전통적으로 버퍼 Pinning이 적용되던 지점은 인덱스를 스캔하면서 테이블을 액세스할 때의 인덱스 리프 블록이다. 

Index Range Scan하면서 인덱스와 테이블 블록을 교차 방문할 때 블록 I/O를 보면, 테이블 블록에 대한 I/O만 증가하는 이유가 여기있다. → 클러스터링 팩터가 좋은 인덱스의 경우 효과는 극대화 된다. (= 인덱스 레코드가 가리키는 테이블 rowid 정렬 순서가 인덱스 키 값 정렬 순서와 거의 일치한다면) 

이외에도 DML 수행시 Undo 레코드를 기록하는 Undo 블록도 Pinning 적용

 

'Database > Oracle' 카테고리의 다른 글

[Oralce] 오라클 성능 최적화 (2)  (0) 2026.03.18
[Oracle] 오라클 성능 고도화 (1)  (0) 2026.03.18
728x90

01 | 오라클 아키텍처

02 ) DB 버퍼 캐시

빠른 데이터 입출력을 위하 SGA 공유 메모리를 이용한다. 

사용자가 입력한 데이터를 데이터파일에 저장하고 이를 다시 읽는 과정에서 거쳐 가는 캐시 영역이 SGA 구성 요소 중 하나인 DB 버퍼 캐시이다. 

 

(1) 블록 단위 I/O

오라클에서 I/O는 블록(block)단위로 이루어진다. 

 

1. DB buffer Cache → Datafile

2. Datafile → DB buffer Cache 

 

두 과정 모두 블록 단위로 I/O 가 이루어진다. 

인덱스를 경유한 테이블 액세스 시에는 한 번에 한 블록씩(single block read) 읽어들이지만, Full Scan 시에는 성능 향상을 위해 한 번에 여러 개 블록(multi block)을 읽어들인다. 

 

DBWR 프로세스는 버퍼 캐시로부터 Dirty block을 주기적으로 데이터파일에 기록하는 작업을 수행하느데, 이때도 성능 향상을 위해서 한 번에 여러 블록을 처리한다. 

▶ DBWR은 주기적으로  DB Buffer Cache에서 변경된 블록을 데이터파일에 기록 (동기화 과정)

 

블록 단위로 읽는다는 것은 하나의 레코드에서 하나의 컬럼만 읽고자 하는 경우에도, 레코드가 속한 블록 전체를 읽는다는 것이다.

 

SQL의 성능을 좌우하는 가장 중요한 성능지표는 블록 개수이며, 옵티마이저의 판단에 가장 영향을 미치는 것도 액세스해야 할 블록 개수이다. 옵티마이저가 인덱스를 이용해 테이블을 액세스할지 아니면 Full Table Scan 할지를 결졍하는 데 있어 가장 중요한 판단 기준은, 읽어야 할 레코드 수가 아니라 블록 개수이다.

 

(2) 버퍼 캐시 구조

DB Buffer Cache 의 가장 직관적인 그림은 바둑판 모양이다. 

SGA 내에서 가장 많이 사용되는 자료구조는 해시 테이블(Hash Table)이다.

 

DB Buffer Cache 도 Hash Table 구조로 관리된다. 

Hash Key : 데이터 블록 주소(DBA; Data Block Address)

Hash Function : DBA 입력한 해쉬 값 리턴

Hash Bucket : 해시 함수에 의해 리턴 받은 해시 값을 저장

Hash Chain : 동일한 해시 값에 의한 Buffer Header 를 Linked List 로 연력한 구조 (실제 데이터 값은 버퍼 헤더에 있는 포인터에 의해 버퍼 블록을 찾아감)

즉, 사용자가 찾고자하는 데이터 블록 주소를 해시값으로 변환해서 해당 해시 버킷에서 체인을 따라 스캔하여 데이터를 찾고, 찾고자한 데이터블록이 있는 경우 그대로 읽고, 없는 경우 디스크에서 읽어 해시 체인에 연결한 후 읽는다. 

이렇게 체인에 연결된 데이터는 다른 사용자들도 나중에 읽을 수 있도록 캐싱하는 것이다.

 

▶ Buffer Header만 Hash Chain에 연결되며, 실제 데이터 값이 필요해지면 버퍼 헤더에 있는 포인터를 이용해 버퍼 블록을 찾아가는 구조이다. 

 

(3) 캐시 버퍼 체인

각 해시 체인은 래치(Latch)에 의해서 보호된다. DB 버퍼 캐시는 공유 메모리 영역인 SGA에 존재하므로 여러 프로세스에 의한 동시 액세스 요청이 일어날 가능성이 높다. 

같은 리소스에 대한 액세스를 반드시 직렬화해야 하고, 이를 위해 구현된 일종의 Lock 매커니즘이 Latch이다.

 

래치를 흭득한 프로세스만이 그 래치에 의해 보호되는 자료구조로의 진입이 허용된다.

 

두 개 이상의 프로세스가 같은 해시 체인으로 진입해 새로운 버퍼 블록을 연결하고 해제하는 작업을 동시에 진행한다면 문제가 발생할 수 있다. 이를 방지하기 위해서 사용하는 것이 cach buffers chains 래치이다.

 

cache buffers cahins : 여러개의 해시 체인을 동시에 관리

Oracle 9i 부터 Shared Mode와 Exclusive Mode로 관리

Shared Mode : 해시 체인을 스캔하여 필요한 블록 검색

Exclusive Mode : 체인 구조 변경(체인 추가, 삭제) 혹은 Buffer Pin 설정

 

해시 버킷 : 해시 체인 = 1 : 1 을 목표 ▶ 해시 체인을 찾고난 후 추가적인 스캔에 의한 비용 최소화

✔︎ Latch 는 데이터 자체를 보호하는 것이 아닌, SGA에 공유되어 있는 자료구조를 보호하는 것이며, 그 중 cache buffers chains 래치는 버퍼 캐시에 연결된 체인구조를 보호하는 것이다.

 

(4) 캐시 버퍼 LRU 체인

버퍼 헤더는 해시 체인뿐만 아니라 LRU체인에 의해서도 연결되어 있다.

버퍼 캐시는 한번 읽은 데이터 블록을 캐싱해두지만, 유한한 자원이 아니기 때문에 읽은 모든 데이터를 캐싱할 수 없다.

버퍼 캐시가 사용도가 높은 데이터 블록들 위주로 구성될 수 있도록 LRU(Least Recently Used) 알고리즘을 사용해 관리한다.

 

모든 버퍼 블록 헤더를 LRU체인에 연결해 사용빈도 순으로 위치를 옮겨가다가, Free 버퍼가 필요해질 때마다 액세스 빈도가 낮은 데이터 블록들을 우선하여 알아냄으로써 자주 액세스 되는 블록들이 캐시에 더 오래 남아 있도록 관리한다.

 

📌 LRU알고리즘

- 가장 최근에 사용한 버퍼 캐시를 MRU(Most Recently Used) End에 위치

- Free Buffer가 필요할 때 LRU(Least Recently Used) End에 위치한 버퍼 캐시부터 밀어냄

 

📌 LRU List

- Dirty List : 캐시 내에서 변경되었지만, 아직 디스크에 기록되지 않은 Dirty Buffer Block 관리, LRUW(LRU Write) 리스트라고도 한다.

- LRU List : 아직 Dirty 리스트로 옮겨지지 않은 나머지 버퍼 블록들을 관리 ( Pinned Buffer, Free Buffer, Dirty List에 옮겨 가지 않은 Dirty Buffer)

- 모든 버퍼 블록은 둘 중 하나에 반듯 속해 있다.

- LRU List 를 보호하기 위한 래치 : cache buffers lru chain

 

⭐️ Buffer 상태

1. Free Buffer : Instance 기동 후 한번도 사용 하지 않은 버퍼 / 데이터를 사용하였으나 변경되지 않은 버퍼 / 변경 후 데이터 파일과 동기화된 버퍼 

2. Dirty Buffer : 캐시된 후 데이터의 변경이 발생 되었으나 데이터파일에 아직 기록되지 않은 버퍼

3. Pinned Buffer : 읽기 혹은 변경을 위해 현재 액세스 되고 있는 버퍼 

 

 

 

 

 

 

'Database > Oracle' 카테고리의 다른 글

[Oracle] 오라클 성능 고도화 (3)  (0) 2026.03.18
[Oracle] 오라클 성능 고도화 (1)  (0) 2026.03.18
728x90

01 | 오라클 아키텍처

01 ) 기본 아키텍처

오라클 성능 고도화 그림 1-3

오라클은 데이터베이스와 이를 액세스하는 프로세스 사이에 SGA(System Global Area)라고 하는 메모리 캐시 영역을 두고 있다.

인스턴스 : 메모리 + 프로세스

데이터베이스 : 데이터파일 + redo log file + control file

 

디스크를 경유하는 I/O는 물리적으로 액세스 암(Arm)이 움직이면서 헤드를 통해 데이터를 읽고 쓴다.

메모리를 통한 I/O는 전기적 신호를 통해 이루어지기 때문에 디스크를 경유하는 경우와 비교할 수 없을 정도로 속도가 빠르다. 

디스크 I/O 속도 < 메모리 I/O 속도 (캐시)

기억장치 - 액세스 암

데이터베이스는 많은 사용자들이 동시에 데이터에 액세스하기 때문에 데이터를 보호하기 위해 Lock은 물론 SGA상에 위치한 데이터 구조에 대한 액세스를 직렬화하기 위한 Lock 매커니즘(=래치, Latch)도 필요하다.

 

트랜잭션의 직렬성 : 여러 트랜잭션이 동시에 병행 수행되더라도 각 트랜잭션이 하나씩 차례대로 수행되는 것과 같은 일관성을 보장하는 수행특성

 

인스턴스를 구성하는 프로세스에는 서버 프로세스와 백그라운드 프로세스가 존재한다.

서버 프로세스 : 사용자가 던진 명령을 처리

백그라운드 프로세스 : 서버 프로세스가 스스로 처리하지 못하는 작업 (버퍼 캐시로 블록 적재 등)을 처리

 

오라클에 접속하면 각 클라이언트를 위한 전용 서버 프로세스가 뜨고, 사용자에게 필요한 서비스를 제공한다. SQL을 파싱하고 필요하면 최적화를 수행하며, 커서를 열어 SQL을 실행하면서 블록을 정렬해서 클라이언트가 요청한 결과집합을 만들어 네트워크를 통해 전송하는 일련의 작업들을 모두 서버 프로세스가 처리해 준다. 

 

 오라클에서 세션이 수립되는 과정은 다음과 같다.

1. 사용자가 오라클 접속 연결 요청

2. 사용자로부터 연결 요청을 받은 리스너가 서버 프로세스를 생성 

3. 서버 프로세스는 PGA를 할당 받음

4. 서버 프로세스 생성 완료에 대한 패킷을 사용자에게 전송

5. 사용자 해당 서버 프로세스로 연결하여 DB접속

 

▶ 리스너에 연결요청을 하는 순산 하나의 프로세스를 띄우고 (fork) PGA메모리를 할당한다. 

프로세스 하나를 띄우고 PGA를 할당하는 과정은 cost가매우 큰 작업으로 SQL문을 실행할 때마다 연결요청을 받아 프로세스를 할당받아 실행하는 것은 성능적은 측면에서 좋지 않다. 그래서 반드시 Connection Pool 기능이 필요하다.

 

Connection Pool ▶ 한번 커넥션을 맺으면 작업을 완료하더라도 이를 해제하지 않고 애플리케이션 서버에 Pooling 하고 있다가 세션을 반복 재사용을 한다. 

 

'Database > Oracle' 카테고리의 다른 글

[Oracle] 오라클 성능 고도화 (3)  (0) 2026.03.18
[Oralce] 오라클 성능 최적화 (2)  (0) 2026.03.18

+ Recent posts