02. Memory vs. Disk I/O
(1) I/O 효율화 튜닝의 중요성
- 디스크 : 입출력은 물리적으로 액세스 암(Arm)이 움직이면서 헤드를 통해 데이터를 읽고 쓰기 때문에 느림
- 메모리 : 입출력은 전기적 신호에 불과하기 때문에 디스크 I/O에 비해 비교할 수 없을 정도로 빠름(10,000배이상 빠름)
- 모든 DBMS는 버퍼 캐시를 경유해 I/O를 수행
(읽고자 하는 블록을 먼저 캐시에 찾아보고, 찾지 못할 때만 디스크에서 읽는 것)
- 물리적인 디스크 I/O가 필요할 때면 서버 프로세스는 I/O 서브시스템에 I/O Call을 발생시키고 잠시 대기하게 되므로 비용이 큰 것
- 디스크에 발생하는 경합이 심할수록 대기 시간도 길어짐
- 유한한 메모리 자원을 좀 더 효율적으로 사용해야 하므로 자주 액세스하는 블록들이 캐시에 더 오래 남아 있도록 LRU 알고리즘을 사용
- 결국 디스크 I/O를 최소화하고, 대부분 처리를 메모리에서 할 수 있도록 버퍼 캐시 효율성을 높이는 것이 데이터베이스 성능을 좌우하는 열쇠
(2) 버퍼 캐시 히트율(Buffer Cache Hit Ratio)
- 히트율(BCHR) : 버퍼 캐시 효율을 측정하는 지표로서 전통적으로 가장 많이 사용되 온것
(전체 읽은 블록 중에서 얼만큼을 메모리 버퍼 캐시에서 찾았는지를 나타내는 것)
구하는 공식
BCHR = ( 캐시에서 곧바로 찾은 블록 수 / 총 읽은 블록 수 ) X 100
= ( (논리적 블록읽기 - 물리적 블록읽기) / 논리적 블록읽기 ) X 100
= ( 1 - (물리적 블록읽기) / (논리적 블록읽기) ) X 100
- '논리적 블록읽기' = '총 읽은 블록 수'
- '캐시에서 곧바로 찾은 블록 수' = '논리적 블록읽기' - '물리적 블록읽기'
- 공식에서 알 수 있듯 BCHR는 물리적인 디스크 읽기를 수반하지 않고 곧바로 메모리에서 블록을 찾은 비율
- Direct Path Read 방식으로 읽는 경우를 제외하면 모든 블록 읽기는 버퍼 캐시를 통해 이루어짐
- 읽고자 하는 블록을 먼저 버퍼 캐시에서 찾아보고 없을 때 디스크로부터 읽어들이며, 이때도 디스크로부터 곧바로 읽는 게 아니라 먼저 버퍼 캐시에 적재한 후에 읽음
- 따라서 SQL을 수행하는 동안 캐시에서 읽은 총 블록 수를 '논리적 블록읽기(Logical Reads)'
- 그리고 '캐시에서 곧바로 찾은 블록 수'는 디스크를 경유하지 않고 버퍼 캐시에서 찾은 블록 수를 말하므로 '총 읽은 블록 수(=논리적 블록읽기)'에서 '물리적 블록읽기(Physical Reads)'를 차감해서 구한다.
Call Count Cpu Elapsed Disk Query Current Rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.000 0.001 0 0 0 0
Execute 1 0.010 0.006 0 0 0 0
Fetch 2 138.680 1746.630 601458 1351677 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 138.690 1746.637 601458 1351677 0 1
- 물리적 블록읽기 : Disk 항목
- 논리적 블록읽기 : Query + Current 항목
(Direct Path Read 방식으로 읽은 블록이 없다면 이 두 값을 더한 것이 총 읽은 블록 수) - 총 1,351,677개 블록을 읽었는데, 그중 601,458개는 디스크에서 버퍼캐시로 적재한 후에 읽은 것
따라서 위 샘플에서 BCHR는 56%다.
BCHR = ( 1 - (Disk / (Query + Current))) X 100
= ( 1 - (601,458 / (1,351,677 + 0))) X 100
= 55.5 %
- 논리적으로 100개 블록 읽기를 요청하면 56개는 메모리에서 찾고, 44개는 메모리에 없어 디스크 I/O를 발생시켰다는 의미
(다른 대기 이벤트가 없었다면 CPU time과 Elapsed time 간에 발생한 시간차는 대부분 디스크 I/O 때문이라고 이해하면 됨)
BCHR를 구하는 공식을 통해 알 수 있는 것처럼 '논리적 블록 읽기'를 '메모리 블록읽기'로 이해하기 보다 '블록 요청횟수' 또는 '총 읽은 블록 수'로 이해하는 것이 정확
모든 블록을 메모리를 경유해 읽기 때문에 결과적으로 같은 것일 수 있지만 '논리적'을 '메모리로부터'로 해석해 Call 통계를 잘못 해석하는 경우를 자주 보기 때문에 의미를 명확히 하고자 하는 것
즉, 많은 분들이 위 Call 통계를 보고 디스크에서 601,458개, 메모리에서 1,351,677개 블록을 읽어 총 1,953,135개 블록을 읽었다고 잘못 해석
아래는 위 Call 통계의 원본 SQL을 튜닝한 결과다.
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.000 0 0 0 0
Execute 1 0.00 0.000 0 0 0 0
Fetch 2 0.00 0.001 0 27 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.001 0 27 0 1
튜닝후 결과
튜닝 전에는 논리적으로 1,351,677개의 블록을 요청했고, 많은 블록을 읽다 보니 디스크 블록 읽기도 601,458번이나 발생했었다.
하지만 튜닝을 통해 논리적인 블록 요청 횟수가 27번만 발생하니까 메모리에서 읽은 블록 수도 27개에 그치고, 설령 BCHR가 0%여서 27개를 모두 디스크에서 읽더라도 쿼리는 항상 빠르게
수행될 것이다.
- 이처럼 논리적인 블록 요청 횟수를 줄이고, 그럼으로써 물리적으로 디스크에서 읽어야 할 블록 수를 줄이는 것이 I/O 효율화 튜닝의 핵심 원리
- 같은 블록을 반복적으로 엑세스는 형태의 애플리케이션이라면 논리적인 I/O 요청이 비효율적으로 많이 발생하는데도 BCHR는 매우 높게 나타난다.
BCHR가 성능지표로서 갖는 한계점이 바로 여기에 있다.
작은 테이블을 자주 액세스하면 모든 블록이 메모리에서 찾아지므로 BCHR는 높겠지만 블록을 찾는 과정에서 래치를 얻어야 하므로 의외로 큰 비용을 수반한다. - 같은 블록을 여러 세션이 동시에 액세스함으로 인해 래치 경합과 버퍼 Lock 경합까지 발생한다면 메모리 I/O 비용이 오히려 디스크 I/O 이상으로 커질 수 있다.
따라서 BCHR가 100%라고 하더라도 논리적으로 읽어야 할 블록 수의 절대량이 많다면 반드시 SQL 튜닝을 실시해서 논리적인 블록 읽기를 최소화해야 한다.
대량의 데이터를 기준으로 NL 조인 방식을 사용해 작은 테이블을 반복적으로 Lookup 하는 경우가 대표적이다.
(3) 네트워크, 파일시스템 캐시가 I/O 효율에 미치는 영향
- 메모리 I/O, 디스크 I/O 발생량뿐 아니라 최근에는 네트워크 속도가 I/O 성능에 지대한 영향을 미치고 있음
- 예전에는 서버에 전용 케이블로 직접 연결된 외장형 저장 장치를 사용했지만 이제는 데이터베이스 서버와 스토리지 간에 NAS 서버나 SAN을 통해 연결되는 아키텍처를 사용
- 대용량 데이터 저장과 관리 기법에 이런 네트워크 기술이 사용되기 시작한지 이미 오래고, 따라서 디스크 속도뿐 아니라 네트워크 속도까지 맞물려 I/O 튜닝은 더 복잡해진 느낌
- 오라클은 CPU, RAM, 디스크를 일체형으로 개발한 MPP 방식의 어플라이언스 제품들을 이용하여 네트워크 속도를 줄일려고 함
- RAC 서버에서 INSTANCE 끼리 네트워크를 통해 캐시된 블록들을 서로 공유하므로 메모리 I/O성능에도 네트워크 속도가 영향을 미침
- I/O 성능에 관한 가장 확실하고 근본적인 해결책은 논리적인 블록 요청 횟수를 최소화 하는 것이다.