버퍼 핸들이란 무엇인가

Kyojun Jin·2022년 9월 26일
0

SQLP

목록 보기
3/34

원문: JL Computer Consultancy - What is a buffer handle?

많은 사람들이 시스템 성능을 평가할 때 논리적 I/O를 보는데 시간을 보내며, 논리적 I/O가 CPU 사용량의 큰 지표라고 생각한다.

이는 옳지 않다. 내가 이렇게 생각하는 데는 몇 가지 이유가 있다.

일단 그렇게 가정한다면 더 큰 오류가 발생할 수 있다. 오라클은 논리적 I/O 를 하지 않고도 데이터 버퍼를 방문할 수 있기 때문이다.

물론, 왜 중요한지는 알기 이전에 논리적 I/O가 정확히 무엇인지를 알아야 할 것이다.
이에 대한 두 가지 정의가 있다. x$kcbwh에 있는 802개의 서브루틴들 중 하나를 콜하는 것, 혹은 cache buffers chains를 필요로 하는 버퍼를 방문하는 것이다.

캐시에 버퍼 된 블록을 처음에 래치를 사용하지 않고 어떻게 안전하게 방문할 수 있을까?
정답은 래치를 여러번 쓰는 것이다. 처음에 버퍼를 얻기 위해 래치를 사용하고, 작업이 끝나면 래치를 놓는 것이다. 하지만 버퍼를 해두면, 래치를 얻기 위해 경쟁하느라 CPU를 과도하게 사용할 일이 없어진다.

버퍼를 핀하기 위해 버퍼 핸들이라는 오브젝트를 사용한다. (정확히는 버퍼 자체가 아니라 버퍼 헤더다)

가용 버퍼 핸들의 총량은 시작 시에 정해지며, (아마도) 프로세스의 수에 따라 다르다. 대략 프로세스 수 곱하기 5 정도인데 여기서 5는 _db_handles 기본값이 5인 것이랑 관련이 있을 수도 있다. 이 핸들은 x$kcbbf 구조체를 가진다.

버퍼 헤더를 핀해놓기 위해선 현 세션은 버퍼 핸들을 얻어야 하고 이를 위해선 cache buffer handles 래치를 잡아서 x$kcbbf 배열의 무결성을 지켜야 한다.

물론 세션이 이 래치를 버퍼를 핀하고 싶을 때마다 매번 잡아야 한다면 래치가 주요 경합 지점이 될 것이다. 그래서 각 세션이 약간의 "예약된 세트"나 핸들 캐시를 만들어야 할 것이다. 세션이 관리할 수 있는 예약된 핸들은 최대 5개로, 이는 _db_handles 파라미터로 결정된다. 이렇게 하면 세션에서 래치가 이 핸들들을 사용할 필요가 없다.

물론 몇몇 쿼리는 복잡할테고, 따라서 많은 버퍼들이 핀될 수 있으면 굉장히 효율적으로 작동할 것이다. 이 때 가용 버퍼가 남아있다면 세션은 제한된 값인 5개 보다 더 많은 버퍼를 얻을 수 있다. 세션에게 버퍼 수를 제한해두기 위해 오라클은 각 프로세스들이 버퍼들을 공정하게 나눠서 핀할 수 있도록 몇몇 연산 작업을 한다. 그렇게 해서 세션이 영구적으로 가지고 있을 수 있는 핸들의 총량이 (대략) db_block_buffers 나누기 프로세스 수가 되게 된다. (여기서 9i 이상 버전에서의 동적인 캐시 리사이징에 대한 질문들이 생겨난다) 이 제한은 _cursor_db_buffers_pinned 파라미터 값이다.

버퍼를 핀해두는 것의 비용과 효율성은 두 통계치에서 확인할 수 있다. 래치를 사용하는 비용 없이 버퍼를 방문하는 횟수를 나타내는 버퍼가 핀 되어 있던 빈도핀할 버퍼가 없는 횟수가 그것이다. 후자는 "내가 원하는 버퍼에 사용할 수 있는 핀이 없는 빈도"라고도 불릴 수 있는데, 버퍼를 핀하고 싶지만 수가 초과됐다거나, 배열에 핀할 수 있는 여유 공간을 찾지 못할 때가 그것이다.

전자로 세션이 여태 완료한 작업의 수를 알 수 있다. 후자는 프로세스의 수가 버퍼 사이즈에 비해 너무 크다거나, 반대로 db_block_buffers가 돌리고 싶은 프로세스의 수에 비해 너무 작게 설정되어 있음을 알려주는 힌트가 될 수 있다.

버퍼는 데이터베이스가 콜 되는 동안 핀 될 수 있다. 즉, 핀들은 콜이 끝나면 릴리즈 되어야 한다.
버퍼가 핀 되어 있다면 메모리에서 해제될 수 없다. LRU 리스트의 마지막에 있더라도 말이다.
버퍼는 (정확히 말해 버퍼 헤더는) exclusive 모드나 shared 모드로 핀될 수 있다. exclusive 모드로 핀하면 (x$bhmode_held를 2로 설정하면) 해당 버퍼를 핀하고 싶은 다른 세션은 버퍼 헤더에 있는 "waiters list"에 핀을 부착할 것이고, "buffer busy waits" 상태로 돌입할 것이다.

대부분의 버퍼는 래치를 두 번 얻는다. 하나는 위치를 알아내 핀하기 위해서이고 하나는 핀을 해제하기 위해서이다. 하지만 버퍼를 읽기 위해 래치를 잡고 있는 때면 한 번만 필요하기도 하다. 인덱스의 root block을 읽는다거나, consistent read data block을 생성하는 도중에 undo block를 읽는다거나, single table hash cluster의 블록을 읽는 것이 그 예이다. collision flag가 set 되어 있지 않는다면 말이다.

래치를 얻는 것은 CPU가 많이 드는 작업이라서, 오라클 커널은 버퍼가 핀 될 수 있는 코드 지점들의 수를 늘려오고 있다. 가장 많이 핀되는 버퍼는 아마 범위 검색에서 쓰이는 인덱스들의 리프 블록들일 것이다. 하지만 나는 undo blocks (undo segment header blocks 말고) 도 DML문이 실행되는 동안 핀된다고 본다. nested loop joins의 inner table의 인덱스들의 branch block이나 index skip scan에서 쓰이는 branch block 또한 마찬가지다.

요약

  1. 메모리 버퍼 캐시의 버퍼에 접근하려면 래치를 얻어야 한다. (읽을 땐 한 번, 쓸 땐 두 번)
  2. 하지만 래치를 자주 얻게 되면 CPU에 부하가 걸린다.
  3. 한 세션은 사용 중인 버퍼를 '핀' 해둘 수 있다. 이렇게 하면 쓸 때마다 래치를 얻거나 릴리즈 하는 동작을 취하지 않아도 된다.
  4. 버퍼를 핀하기 위해 버퍼 핸들이라는 오브젝트를 사용한다. (정확히는 버퍼 자체가 아니라 버퍼 헤더다)
  5. 버퍼 핸들을 사용하기 위해선 cache buffer handles라는 래치를 잡아야 한다.
  6. 세션이 핀 할 수 있는 버퍼(프로세스마다 잡을 수 있는 cache buffer handles)는 기본 5개이나, 설정값이나 여유 공간에 따라 달라질 수 있다.
  7. 버퍼는 exclusive, shared의 두 가지 모드로 핀 될 수 있다.
  8. exclusive 모드로 핀 된 버퍼에 다른 세션이 접근하면, 해당 버퍼 헤더의 waiters list에 자신을 등록하고 busy buffer waits 상태에 들어간다.

-> 결론: 블록 I/O 감소 효과를 위해서 버퍼를 핀하는 것 = 또는 쿼리에서 한도 내의 버퍼를 핀 되게 유도하여 핀 된 버퍼를 읽도록 유도하는 것 = 액세스 한 버퍼가 핀 되어 있던 횟수를 증가시키는 것이 중요하다.

0개의 댓글