본문 바로가기
자격증/SQLP

데이터 저장 구조 및 I/O 메커니즘 (2)

by 철제백조 2022. 10. 10.

 Single Block I/O vs Multi Block I/O

데이터를 모두 캐시에 적재할 수는 없다.

캐시에서 찾지 못한 데이터 블록은 I/O Call을 통해 디스크에서 DB 버퍼캐시로 적재하고서 읽는다.

I/O Call 할 때, 한 번에 한 블록씩 요청하기도 하고, 여러 블록씩 요청하기도 한다.

 

1. Single Block I/O

- 한 번에 한 블록씩 요청해서 메모리에 적재하는 방식

 

인덱스를 이용할 때는 기본적으로 인덱스와 테이블 블록 모두 해당 방식을 이용한다.

인덱스는 소량 데이터를 읽을 때 주로 사용하므로 이 방식이 효율적이다.

 

2. Multi Block I/O

한 번에 여러 블록씩 요청해서 메모리에 적재하는 방식

 

많은 데이터 블록을 읽을 때는 해당 방식이 효율적이다.따라서 인덱스를 이용하지 않고 테이블 전체를 스캔할 때 이 방식을 사용한다.테이블이 클수록 Multi Block I/O 단위도 크면 좋다.

 

ex) 건재상 - 운반공 - 미장공[벽돌이 다 떨어지고 운반공이 벽돌을 건재상에게 구하러 가는 동안 미장공은 잠을 잔다]

 

>> 읽고자 하는 블록을 DB 버퍼캐시에서 찾지 못하면 해당 블록을 디스크에서 읽기 위해 I/O Call을 한다.

그 동안 프로세스는 대기 큐(Wait Queue)에서 잠을 잔다.

대용량 테이블이면 수많은 블록을 디스크에서 읽는 동안 여러 차례 잠을 잘 텐데, 한꺼번에 많은 양을 요청해야 잠자는 횟수를 줄이고 성능을 높일 수 있다.

 

Multiblock I/O는 캐시에서 찾지 못한 특정 블록을 읽으려고 I/O Call 할 때 디스크 상에 그 블록과 '인접한' 블록들을 한꺼번에 읽어 캐시에 미리 적재하는 기능이다.

 

 

보통 OS 단에서 1MB(1MB 크기의 손수레) 단위로 I/O를 수행한다.

테이블 전체 블록을 읽을 때는 손수레에 한 번에 담을 수 있는 만큼 최대한 많이 담아야 유리하다.

 

일반적으로 OS 레벨 I/O 단위가 1MB.

오라클 레벨 I/O 단위가 8KB.

따라서 8KB x 128 = 1MB가 최대 크기가 된다.

 

 

인접한 블록

- '인접한 블록'이란 같은 익스텐트에 속한 블록을 의미한다.

그 말은 Multiblock I/O 방식으로 읽더라도 익스텐트 경계를 넘지 못한다는 뜻이다.

(손수레를 다 채우지 않았는데 건재상 벽돌이 다 떨어졌다고 다른 건재상을 찾아 나서지 않는다.)

 

 

 Table Full Scan vs Index Range Scan

테이블에 저장된 데이터를 읽는 방식은 두 가지이다.

 

1. Table Full Scan

- 테이블 전체를 스캔해서 읽는 방식

 

테이블에 속한 블록 '전체'를 읽어서 사용자가 원하는 데이터를 찾는 방식.

 

2. Index Range Scan

- 인덱스를 이용해 읽는 방식 (= 인덱스를 이용한 테이블 액세스)

 

인덱스에서 '일정량'을 스캔하면서 얻은 ROWID로 테이블 레코드를 찾아가는 방식.

ROWID는 테이블 레코드가 디스크 상에 어디에 저장됐는지를 가리키는 위치 정보다.

 

※ Table Full Scan은 붉은 색으로 표기되나, Table Full Scan이 있는지 없는지 체크하는 건 SQL 성능 향상에 큰 도움이 되지 않는다. 많은 개발자들의 인식과 달리 오히려 인덱스가 SQL의 성능을 떨어뜨리는 경우도 상당하기 때문이다.

 

 

 

그렇다면 집계용 SQL이나 배치 프로그램처럼 
인덱스를 이용해 많은 데이터를 읽을 때 성능이 왜 더 저하되는 것일까?

 

 

Table Full Scan시퀀셜 액세스Multiblcok I/O 방식으로 디스크 블록을 읽는다.

한 블록에 속한 모든 레코드를 한 번에 읽어들이고, 캐시에서 못 찾으면 한 번의 I/O Call을 통해 인접한 수십~수백 개 블록을 한꺼번에 I/O하는 메커니즘이다. 이 방식을 이용하는 SQL은 스토리지 스캔 성능이 좋아지는 만큼 성능도 좋아진다.

 

반면 시퀀셜 액세스 Multiblcok I/O가 아무리 좋아도 수십~수백 건의 소량 데이터를 찾을 때 수백만~수천만 건의 데이터를 스캔하는 건 비효율적이다. 큰 테이블에서 소량 데이터를 검색할 때는 반드시 인덱스를 이용해야 한다.

 

Index Range Scan을 통한 테이블 액세스는 랜덤 액세스Single Block I/O  방식으로 디스크 블록을 읽는다.

캐시에서 블록을 찾지 못하면, '레코드 하나를 읽기 위해 매번 잠을 자는 I/O 메터니즘'이다.

따라서 많은 데이터를 읽을 때는 Table Full Scan보다 불리하다.

이 방식을 사용하는 SQL은 스토리지 스캔 성능이 수십 배 좋아져도 성능이 조금 밖에 좋아지지 않는다. 

 

게다가 이 방식은 읽었던 블록을 반복해서 읽는 비효율이 있다. 즉, 많은 데이터를 읽을 때 물리적인 블록 I/O 뿐만 아니라 논리적인 블록 I/O에서도 불리하다. 한 블록에 평균 500개 레코드가 있으면, 같은 블록을 최대 500번 읽는다.

 

만약 인덱스를 이용해 전체 레코드를 액세스한다면, 모든 블록을 평균 500번씩 읽는 셈이다.

이는 각 블록을 단 한 번만 읽는 Table Full Scan 보다 훨씬 불리하다.

 

따라서 인덱스는 큰 테이블에서 아주 적은 일부 데이터를 빨리 찾기 위한 도구일 뿐이므로 너무 맹신해서는 안된다.

읽을 데이터가 일정량을 넘으면 Table Full Scan이 유리하다.

 

 

캐시 탐색 메커니즘

Direct Path I/O를 제외한 모든 블록 I/O는 메모리 버퍼캐시를 공유한다.

DBMS 버퍼캐시를 해시 구조로 관리한다.

 

버퍼캐시에서 블록을 찾을 때 이처럼 해시 알고리즘으로 버퍼 헤더를 찾고, 거기서 얻은 포인터로 버터 블록을 액세스하는 방식을 사용한다. 해시 구조의 특징은 아래와 같다.

  • 같은 입력 값은 항상 동일한 해시 체인(=버킷)에 연결됨
  • 다른 입력 값(예를 들어 4, 9)이 동일한 해시 체인(=버킷)에 연결될 수 있음
  • 해시 체인 내에서는 정렬이 보장되지 않음

 

 

메모리 공유자원에 대한 액세스 직렬화

버퍼캐시는 SGA 구성요소이므로 버퍼캐시에 캐싱된 버퍼블록은 모두 공유자원이다.

공유자원은 모두에게 권한이 있기 때문에 누구나 접근할 수 있다.

문제는 하나의 버퍼블록을 두 개 이상 프로세스가 '동시에' 접근하려 할 때 발생한다. 동시에 접근할 경우 블록 정합성에 문제가 생길 수 있기 때문이다.

 

따라서 자원을 공유하는 것처럼 보여도 내부에선 한 프로세스씩 순차적으로 접근하도록 구현해야 하며, 이를 위해 직렬화 메커니즘이 필요하다. 이른바 '줄 세우기'라 할 수 있다.

 

카 쉐어링이 결국 차를 나눠쓰더라도 탈 땐 한 사람밖에 타지 못하는 것처럼 공유캐시 역시 특정 자원을 두 개 이상 프로세스가 같이 사용할 수 없다. 같이 사용하는 것처럼 보여도 특정 순간에는 한 프로세스만 사용할 수 있다.

이런 줄서기가 가능하도록 하는 메커니즘이 래치(Latch)이다. (자물쇠를 열 수 있는 key를 획득한 프로세스만이 체인으로 진입할 수 있다.)

 

SGA를 구성하는 서브 캐시마다 별도의 래치가 존재한다. 버퍼캐시에는 캐시버프 체인 래치 등이 그것이다.

빠른 DB를 구현하려면 BCHR를 높여야 하지만, 캐시 I/O도 생각만큼 빠르지 않을 수 있다. 이들 래치에 의한 경합이 생길 수도 있기 때문이다.

 

캐시버퍼 체인뿐만 아니라 버퍼블록 자체에도 직렬화 메커니즘이 존재한다. 바로 '버퍼 락'(후행 프로세스가 같은 블록에 접근해 데이터를 읽고 쓸 때 정합성 문제가 발생할 수 있기 때문)이다.

이런 직렬화 메커니즘에 의한 캐시 경합을 줄이려면, SQL 튜닝을 통해 쿼리 일량(논리적 I/O) 자체를 줄여야 한다.

 

 

 

'자격증 > SQLP' 카테고리의 다른 글

인덱스 구조 및 탐색  (0) 2022.10.12
데이터 저장 구조 및 I/O 메커니즘 (1)  (0) 2022.10.09
SQL 공유 및 재사용  (0) 2022.10.08
SQL INDEX & INDEX HINT  (0) 2022.10.07
3. Update 심화  (1) 2022.10.06

댓글