본문 바로가기

# 미사용

오라클 DB Buffer Cache

DB Cache Buffer

오라클을 포함한 여러 DBMS는 대부분의 경우에,

성능향상을 위해서 먼저 캐시 메모리에 블럭(여러 레코드) 단위로 데이터를 캐싱한 뒤 작업을 진행한다.


다음의 상황을 가정해보자,

  • 데이터를 읽어야 할 때,

똑같은 데이터를 읽어야 하는 경우가 발생할 때,

매번 디스크에서 읽는 것 보다 메모리에서 읽는 것이 훨씬 빠름.

  • 데이터를 수정해야 할 때,

UPDATE가 발생될 때 마다 파일을 매번 수정하는 것보다,

메모리에서 먼저 수정한 뒤, 나중에 한꺼번에 파일에 쓰는 것이 훨씬 빠름.


Note.

Block 단위 IO


오라클에서 IO는 블록(여러 레코드)단위로 이루어지는데,

데이터 파일에서 캐시 버퍼로 옮길때도 블록단위로 적재하고,

캐시 버퍼에서 데이터 파일로 옮길때도 블록단위로 쓴다.


파일에서 데이터를 읽거나 쓰는 작업은 굉장히 고비용인데,

단 하나의 레코드만을 읽기 위해 IO Call을 넣는것은 굉장한 성능저하를 일으키기 때문이다.


이것은 SQL 튜닝에서 중요한 의미를 가지는데,

하나의 컬럼만 읽으려고 해도 어쩔 수 없이 블록 전체를 읽게됨을 의미한다.


다시 말하자면, 하나의 칼럼을 읽든 수백개의 레코드를 읽든

둘 다 1개의 블록만을 읽었다면 성능상 차이는 없다고 볼 수 있다.


즉, SQL의 성능을 평가하는 가장 중요한 기준은 "읽은 블럭의 개수"이며,

옵티마이저의 행동에 영향을 미치는 가장 큰 판단기준이다.


다시 한번 강조하지만, SQL의 성능평가 및 튜닝기준은

읽어야 할 레코드 수가 아니라 블록 개수이다.



Note.

Direct IO


데이터를 캐시에 먼저 저장하고 작업하는 근본적인 이유는

"해당 데이터가 재활용될 가능성"이 있기 때문이다.


따라서 해당 데이터가 재활용될 가능성이 없다고 판단되는 경우에는

예외적으로 캐시에 저장하지 않고 파일수준에서 곧바로 사용할 수 있다.


이렇게 캐시를 거치지 않는 방식을 "Direct IO, Bypass IO"라고 하며,

캐시를 거치는 방식을 "Buffer IO, Conventional IO"라고 부른다.




DB Cache Buffer Status

DB Buffer Cache 내에 캐시된 데이터 블럭들은 

반드시 다음 상태 중, 1가지의 상태를 가진다.

  • Pinned Buffer

아직 특정 세션이 사용중인 블럭.

다른 세션이라도 해당 블럭을 자유롭게 읽을 수 있지만,

다른 세션은 이 블럭의 내용을 수정하지 못하고,

작업이 끝날 때 까지 재활용(삭제)될 수 없다.

  • Dirty Buffer

버퍼 내 데이터가 갱신되었지만 아직 파일에 쓰여지지 않은 상태.

변경점이 파일에 쓰여지기 전까지 재활용될 수 없다.

  • Free Buffer

데이터베이스가 기동된 이후로 아직 한번도 사용되지 않았거나, 

Dirty Buffer의 변경점이 파일에 정상적으로 저장되어 재활용 가능한 상태.


모든 버퍼가 Dirty 상태거나 Pinned 상태라면,

Free 상태의 버퍼를 확보하기 위해 DBWn 프로세스가 호출된다.


Note.

DB Buffer Cache 시나리오


처음 데이터베이스가 기동되면

DB Buffer Cache 내 블럭들은 전부 Free 상태에서 시작한다.


첫 사용자가 SQL을 호출하면,

해당 작업에 필요한 데이터를 디스크에서 읽어와 DB Buffer Cache에 캐시한다.

블럭 상태도 Free 상태에서 Pinned 상태로 바꾼다.


첫 번째 사용자가 아직도 작업을 진행하고 있을 때,

두 번째 사용자가 갱신작업을 시작했다.


두 번째 사용자에 의해 갱신이 이루어진 블럭은 Dirty 상태로 변경되며,

갱신이 이루어지지 않은 블럭은 Free 상태가 유지된다.


새로운 데이터를 사용하기 위해 DB Buffer Cache 에 캐시해야 하지만,

Free Buffer가 없어 더 이상 새로운 데이터를 캐시할 수 없다.


오라클은 DBWn 프로세스를 호출하여

Dirty Buffer의 변경점을 디스크에 쓴 뒤, Free 버퍼를 확보한다.



오라클은 효과적으로 Dirty Buffer와 Free Buffer를 효과적으로 탐색하기 위해

Cache Buffer LRU Chain (Working Set) 오브젝트를 관리하고 있는데, 

내부적으로 두 가지의 List를 다룬다.


위에서 언급한 두 가지의 내부적인 리스트는

각각 Dirty Buffer와 Free Buffer의 메타정보(Buffer Header)를 관리하고 있으며,

Pinned 되지않은 모든 캐시버퍼의 메타정보는 두 리스트 중 하나의 관리대상이 된다.

  • LRU List (Least Recently Used, Free List)

Free 상태의 버퍼 헤더를 관리한다.

  • LRUW List (Least Recently Used Write, Dirty List)

Dirty 상태의 버퍼 헤더를 관리한다.



위의 리스트는 각각 LRU 알고리즘에 의하여 관리되며, 

Free 버퍼를 확보해야 할 때 오랫동안 사용되지 않은 블럭이 우선적으로 디스크에 쓰여지게 된다. 


또한 하나의 Working set만 사용한다면 성능부하가 심각해질 수 있으므로,

오라클은 여러개의 Working set Object를 운용한다는 것을 기억하자.


Note.

MRU end, LRU end


대부분의 경우에는 캐시블럭이 MRU end 쪽에 생성되어 

상당히 오랜 시간동안 DB Buffer Cache Area에 남게 되지만,


"재사용 가능성이 있지만, 그 확률이 희박하다."고 판단한 경우에는

LRU end 쪽에 캐시 블럭을 생성하여 최대한 빨리 캐시에서 사라지도록 조절한다.



DB Buffer Cache Architecture

기본적으로 DB Cache Buffer는 테이블 형태의 자료구조이기 때문에,

효율적인 탐색을 위해 Hash Bucket 이라는 Multiable Hash List를 내부적으로 관리하고 있다.


같은 해쉬값을 갖는 블럭의 메타정보(Buffer Header)를 하나의 리스트에서 관리하여,

DB Buffer Cache 를 전부 뒤지지 않아도 빠르게 찾을 수 있도록 하는 것 이다.


아래 사진에는 표현되어 있지 않지만,

각 Hash Bucket 내의 버퍼 헤더들은 양방향 링크로 연결되어 있으며,

양방향 리스트와 Hash Bucket을 포함하여 Hash Chain이라고 부른다.



각 메타정보(Buffer Header)는 Hash Bucket과 다수의 LRU Chain(Working Set)에 관리되고 있으므로,

DB Buffer의 아키텍쳐는 다음 사진과 같이 표현될 수 있다.



Working Set은 여러개가 있을 수 있다는 것을 상기하기 바라며,

래치에 대해서는 바로 다음 절에서 설명한다.


DB Cache Buffer Lacth (동시접근 제어)

제일 먼저 DB Cache Buffer는 SGA에 위치하고 있다는 것을 기억하기 바라며,

바로 이전 장에서 보았던 기본적인 아키텍쳐 다이어그램을 살펴보자.



SGA는 여러개의 Process에 의하여 동시 액세스 문제가 발생할 수 있기 때문에,

각 오브젝트의 접근을 직렬화하는 매커니즘이 필요해진다.


이를 위해 구현된 가벼운 Lock을 래치(latch)라고 하는데,

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


Note.

Latch Lock


Latch는 접근순서를 보장하지 않는 가벼운 잠금장치이며,

프로그래밍 언어로 표현하자면 다음과 유사하다.


//! Latch Lock이 있어야만 접근가능한 오브젝트 모델.

boolean execute( )

{

    if( ! thisProcess.latch ) return false;

    // to do execute.

    return true;

}


//! Lacth Lock에 접근하는 프로세스 모델.

int max = 2000;

while(max--)

{

    if( execute() ) break;

}


동일 오브젝트에 접근하려는 프로세스가 여러개인 상황에서,

래치는 프로세스의 대한 순서가 보장되지 않는다.


즉, 처음에 래치를 얻으려고 시도했던 프로세스가

맨 마지막에서야 작업을 시작할 가능성이 존재한다.



하나의 래치가 여러개의 Hash Bucket을 보호하지만,

단 하나의 래치만 사용한다면 병렬성이 급감하기 때문에,

오라클은 여러개의 래치 오브젝트를 사용하여 병렬성을 유지한 상태에서 각종 자료구조를 보호한다.


Note.

래치와 성능 (1)


래치의 개수가 적은 오라클 인스턴스는 SQL의 응답시간이 길어질 수 있으며,

버전이 낮은 오라클 인스턴스는 실질적으로 래치의 개수도 적다.

 

각 래치가 너무 많은 량의 오브젝트를 보호하는 상황에서

인스턴스에서 성능이 저하되는 이유를 추즉해보자.




Note.

래치와 성능 (2)


윗 노트에서 설명했듯이 하나의 래치가 너무 많은 량의 오브젝트를 관리하면,

많은 프로세스들이 하나의 래치를 두고 경쟁을 벌이는 "경합상태"가 발생한다.


하지만 래치의 개수를 조절하는 방법은 존재하지 않을 뿐 더러,

제어권을 얻었다면 해당 프로세스는 곧바로 래치를 해제하기 때문에.


순수한 래치경합으로 인한 성능저하는 지극히 적다고 할 수 있으며,

오히려 제어권한(Lock)을 얻기 위한 자연스러운 현상이라고 할 수 있다.


Plus Note.


래치는 오브젝트의 접근권한(Access rights)을 얻기 위해 사용되고,

Lock은 오브젝트의 제어권한(Control rights)을 얻기 위해 사용된다.



DBMS 성능악화의 주범은 래치가 아닌 락이며, 

락에 대해서는 다음 장에서 자세히 설명한다.

 


Note.

래치를 얻지 못한 프로세스


래치경합으로 인해 래치를 얻지 못한 프로세스는

계속해서 래치획득을 시도하는데 이것을 spin 이라고 한다.

(횟수제한 있음, 보통 2000번)


spin 중에 래치를 얻었다면 다행이지만 끝까지 래치를 얻지 못했다면

나중을 기약하면서 일정시간 sleep 상태로 들어간다.


일정시간이 지나 깨어난 프로세스는 다시 래치획득을 시도하는데,

래치는 순서가 보장되지 않는 잠금장치이기 때문에,

sleep 에서 깨어났다고 해도 래치를 얻는다는 보장이 없다.


또 다시 래치를 얻지 못한 프로세스는 sleep에 빠진다.




'# 미사용' 카테고리의 다른 글

오라클 Redo  (0) 2018.05.21
오라클 Buffer Lock  (0) 2018.05.19
오라클 기본 아키텍쳐  (0) 2018.05.15
알고리즘 개요  (0) 2018.05.04
[4-4-2]: 물리 요소 조사 및 분석  (0) 2018.03.23