본문 바로가기
스터디/오라클 성능고도화 원리와 해법1

CH1.오라클 아키텍처 - 3.버퍼 LOCK

by 취미툰 2019. 12. 18.
반응형

1)버퍼 lock이란?
아주 짧은 순간일지라도 두 개 이상의 프로세스가 동시에 버퍼 내용을 읽고 쓴다면, 문제가 생길 수 있습니다. 이를 막기 위해 캐시된 버퍼 블록을 읽거나 변경하려는 프로세스는 먼저 버퍼 헤더로부터 버퍼 lock을 획득해야 합니다. 버퍼 lock을 획득했다면 래치를 곧바로 해제합니다. 또 다른 안정장치를 마련해 두었기때문에 이제 안심하고 블록을 읽고 쓸 수 있습니다. 버퍼 내용을 읽기만 할때Share 모드, 변경할 때Exclusive 모드로 lock을 설정합니다.
액세스를 직렬화하기 위한 매커니즘이므로 당연히 Exclusive 모드 lock은 한 시점에 하나의 프로세스만 얻을 수 있습니다. Select문이더라도 블록 클린아웃이 필요할 때는 버퍼 내용을 변경하는 작업이므로 Exclusive lock을 요구하게 됩니다. 이는 추후에 설명하도록 하겠습니다.

만약 해시 체인 래치를 획득하고 목적한 버퍼를 찾았는데 다른 프로세스가 버퍼 lock을 Exclusive 모드로 점유한 채 내용을 갱신 중이라면 래치를 쥔 채 기다릴 수는 없습니다.
그럴때에는 버퍼 헤더에 있는 버퍼 lock 대기자 목록(waiter list)에 자신을 등록해 놓고 래치를 해제합니다. 이때 버퍼 lock 대기자 목록에 등록되어 있는 동안 buffer busy waits 대기 이벤트가 발생합니다.
후에 대기자 목록에서 기다리다가 버퍼 lock이 해제되면 버퍼 lock을 획득하고, 원했던 작업을 진행합니다.
목적한 읽기/쓰기 작업을 완료하면 버퍼 헤더에서 버퍼 lock을 해제해야 하는데 이때도 버퍼 헤더를 액세스하려는 다른 프로세스와 충돌이 생길 수 있으므로 해당 버퍼가 속한 체인 래치를 다시 한번 획득합니다. 버퍼 lock을 해제하고 래치를 해제해야 비로소 버퍼 블록 읽기가 완료됩니다. (대부분의 블록읽기는 두번의 래치 획득이 필요합니다!)
위의 과정을 보면 하나의 블록읽기가 얼마나 고비용의 작업인지를 알 수 있습니다.

2)버퍼 핸들
버퍼 lock을 다른 말로 버퍼 Pin이라고 표현하기도 합니다. 앞에서 말한 Pinned 버퍼가 여기에 해당합니다. 변경 시에는 하나의 프로세스만 Pin을 설정할 수 있지만 읽기작업을 위해서라면 여러 개 프로세스가 동시에 Pin을 설정할 수 있습니다. 버퍼 헤더에 Pin을 설정하려고 사용하는 오브젝트버퍼 핸들(buffer handle)이라고 부르고 버퍼 핸들을 얻어 버퍼 헤더에 있는 소유자 목록(Holder list)에 연결시키는 방식으로 Pin을 설정합니다.
버퍼 핸들도 공유된 리소스이므로 버퍼 핸들을 얻으려면 또 다른 래치가 필요해지는데 바로 cache buffer handles 래치입니다. 버퍼를 pin하는 오퍼레이션이 많을수록 해당 래치가 경합지점이 될 것이므로 오라클은 각 프로세스마다 _db_handles_cached 개수만큼의 버퍼 핸들을 미리 할당해주며 기본은 5개입니다. 각 세션은 캐싱하고 있다가 버퍼를 pin할때마다 사용하며 그 이상의 버퍼 핸들이 필요할 때만 cache buffer handles 래치를 얻고 추가로 버퍼 핸들을 할당받습니다. 시스템 전체적으로 사용할 수 있는 총 버퍼 핸들의 개수는 processes 파라미터와 _db_handles_cached 파라미터의 곱으로 알 수 있습니다.

3)버퍼 lock의 필요성
사용자 데이터를 변경할때는 DML lock을 통해 보호하도록 되어있는데, 그것을 담는 블록에 또 다른 lock을 획득해야 하는 이유가 무엇일까요?
이유는, 오라클이 하나의 레코드를 갱신하더라도 블록 단위의 I/O를 수행하기 때문입니다. 예를 들어, 블록 안에 저장된 10개의 레코드를 읽는 짧은 순간 동안 다른 프로세스에 의해 변경이 발생하면 잘못된 결과를 얻게되기 때문입니다.
그리고 값을 변경하기 전에 레코드에 로우 단위 lock을 설정하는 일 자체도 레코드의 속성을 변경하는 작업이므로 두 개의 프로세스가 동시에 로우 단위 lock을 설정하려고 시도한다면(대상 로우가 서로 다르다 하더라도) 문제가 될 수 있습니다. 블록 SCN을 변경하거나 ITL슬록에 변경을 가하는 등의 블록 헤더 내용을 변경하는 작업도 동시에 일어날 수 있는데, 이런 동시 액세스가 실제로 발생한다면 lost update문제가 발생하여 궁극적으로 데이터의 정합성이 깨지는 상황이 발생합니다. 따라서 블록 자체로의 진입을 직렬화해야 합니다.

4)버퍼 Pinning
버퍼를 읽고나서 버퍼 Pin을 즉각 해제하지 않고 데이터베이스 call이 진행되는 동안 유지하는 기능입니다. 같은 블록을 반복적으로 읽을 때 버퍼 Pinning을 통해 래치 획득 과정을 생략한다면(계속 pinned 버퍼 상태이기 떄문에 다시 free버퍼를 획득하기위한 래치 등) 논리적인 블록 읽기 횟수를 획기적으로 줄일 수 있습니다. 모두 그런것은 아니고 같은 블록을 재방문할 가능성이 큰 몇몇 오퍼레이션을 수행할 때만 사용합니다.
v$sysstat,v$sesstat,v$mystat등을 조회해보면 래치 획득 과정을 통해 블록을 액세스 하면 session logical reads 항목이 증가하고, 래치 획득 과정 없이 버퍼 Pinning을 통해 블록을 액세스할때는 buffer is pinned count 항목의 수치가 증가합니다.
버퍼 Pinning은 하나의 데이터베이스 call(Parse,Execute,Fetch call)내에서만 유효하고 call이 끝나고 사용자에게 결과를 반환하고 나면 Pin은 해제되어야 합니다. 따라서 첫 번째 Fetch call에서 Pin된 블록이라 할지라도 두 번째 Fetch call이되면 다시 래치 획득 과정을 거처 pin되어야 합니다.
전통적으로 버퍼 Pinning이 적용되던 지점은 인덱스를 스캔하면서 테이블을 액세스할 때의 인덱스 리프블록입니다. index range scan을 하면서 인덱스와 테이블 블록을 교차 방문할때의 블록 I/O를 체크해 보면 테이블 블록에 대한 I/O만 계속 증가하는 이유가 여기 있습니다. index range scan을 할때 인덱스 클러스터링 팩터가 좋다면 같은 테이블 블록을 반복해서 액세스할 가능성이 그만큼 커집니다. 그래서 오라클은 8i부터 인덱스로부터 액세스되는 하나의 테이블 블록을 Pinning하기 시작했습니다. 실제 클러스터링 팩터가 좋은 인덱스를 경유해 테이블을 액세스해보면 논리적 블록 읽기 횟수가 매우 적게 나타나는 것을 확인할 수 있습니다. 버전이 점점 높아지면서 버퍼 Pinning을 적용하는 지점을 점차 확대시켜나가고 있습니다.
9i
NL조인 시 inner테이블을 룩업하기 위해서 사용되는 인덱스 루트 블록을 Pinning하기 시작했습니다.
index skip scan에서 브랜치 블록을 거쳐 리프 블록을 액세스하는 동안에도 브랜치 블록을 계속 Pinning하고 있다가 그 다음 방문할 리프 블록을 찾으려 할때 추가적인 래치 획득과정 없이 브랜치 블록을 곧바로 읽습니다.
11g
NL조인시 inner 테이블의 인덱스 루트 블록뿐 아니라 다른 인덱스블록에 대해서도 Pinning을 함으로써 논리적 블록 읽기를 획기적으로 감소하였습니다.
이외에도, DML수행 시 UNDO 레코드를 기록하는 undo 블록에 대해서도 Pinning을 적용합니다.

버퍼 Pinning을 통한 블록 I/O감소효과는 SQL을 튜닝하는 데있어 워낙 중요한 내용입니다

반응형

댓글