DBMS 아키텍처 개요
DBMS 아키텍처
DBMS 내부의 기능
•
쿼리 평가 엔진: 실사용자로부터 입력받은 SQL 구문을 분석하고, 어떤 순서로 기억장치의 데이터에 접근할지 결정한다. 실행 플랜은 이 때 결정되는 계획이고, 접근 메서드는 실행 계획에 기반을 둬서 데이터에 접근하는 방법이다.
•
버퍼 매니저: 버퍼라는 특정 메모리 영역을 관리한다.
•
디스크 용량 매니저: 어디에 어떻게 데이터를 저장할지를 관리하며, 데이터의 읽고 쓰기를 제어한다. 버퍼 매니저와 함께 연동되어 작동한다.
•
트랜잭션 매니저와 락 매니저: 트랜잭션의 정합성을 유지하면서 실행시키고, 필요한 경우 데이터에 락을 걸어 다른 사람의 요청을 대기시킨다.
•
리커버리 매니저: 데이터를 정기적으로 백업하고, 문제가 일어났을 때 복구한다.
DBMS와 버퍼
기억장치의 계층
기억장치는 기억 비용(데이터를 저장하는 데 소모되는 비용)에 따라 위와 같은 계층으로 분류한다.
많은 데이터를 영속적으로 저장하려 하면 속도를 잃고,
속도를 얻고자 하면 많은 데이터를 영속적으로 저장하기 힘들다.
DBMS와 기억장치의 관계
DBMS는 주로 용량, 비용, 성능의 관점에서 평균적인 수치를 갖는 HDD에 데이터를 저장한다. 또한, 일반적인 DBMS는 메모리에도 데이터를 저장한다.
메모리
메모리는 디스크에 비해 기억 비용이 굉장히 비싸지만, 성능 향상(SQL 구문의 실행 속도 향상)을 위해 DBMS는 데이터의 일부라도 메모리에 올린다.
메모리와 디스크는 대략 수십만 배에서 수백만 배의 성능 차이가 있다.
일반적인 SQL 구문의 실행 시간 대부분은 저장소 I/O에 사용되기 때문에 디스크 접근을 줄이면 성능을 크게 향상시킬 수 있다. 성능 향상을 목적으로 데이터를 저장하는 메모리를 버퍼 또는 캐시라고 한다.
메모리 위에 있는 2개의 버퍼
DBMS가 데이터를 유지하기 위해 사용하는 메모리는 크게 데이터 캐시와 로그 버퍼 두 종류이다.
DBMS의 버퍼 메모리의 제어 매개변수
•
데이터 캐시: 디스크에 있는 데이터의 일부를 메모리에 유지하기 위해 사용하는 메모리 영역.
•
로그 버퍼
데이터베이스의 갱신 처리는 SQL 구문의 실행 시점과 저장소에 갱신하는 시점에 차이가 있는 비동기 처리이다.
DBMS는 갱신(INSERT, DELETE, UPDATE, MERGE)과 관련된 SQL 구문을 사용자로부터 받으면, 곧바로 저장소에 있는 데이터를 변경하지 않는다. 일단 로그 버퍼 위에 변경 정보를 보내고 이후 디스크에 변경을 수행한다.
한 번 메모리에 갱신 정보를 받은 시점에서 사용자에게는 해당 SQL 구문이 끝났다고 통지하고, 내부적으로 관련된 처리를 계속 수행함으로써 성능을 향상시킨다.
메모리의 휘발성
메모리가 가진 단점으로는 비싼 가격 뿐 아니라 휘발성이 있다.
메모리에는 데이터의 영속성이 없다. 하드웨어의 전원을 꺼버리면 메모리 위에 올라가 있는 모든 데이터가 사라진다. 즉, DBMS를 껐다 켜면 버퍼 위의 모든 데이터가 사라진다.
이러한 메모리의 휘발성은 장애가 발생했을 때 메모리에 있던 데이터가 모두 사라져버려 데이터 부정합을 발생시킨다.
데이터 캐시라면 장애로 인해 메모리 위의 데이터가 사라져버려도, 원본 데이터는 디스크 위에 남아있으므로 시간은 더 걸리겠지만 결과에는 문제가 없다. 하지만 로그 버퍼 위에 존재하는 데이터가 디스크 위의 로그 파일에 반영되기 전 장애가 발생해서 사라지면 문제가 된다. 이를 해결하는 방식이 커밋이다.
커밋이란 갱신 처리를 확정하는 것이다. DBMS는 커밋 시점에 반드시 갱신 정보를 로그 파일(영속적인 저장소 위에 존재)에 씀으로써, 장애가 발생해도 정합성을 유지할 수 있게 한다. 커밋 때는 반드시 디스크에 동기 접근이 일어난다.
디스크에 동기 처리를 한다면 데이터 정합성은 높아지지만 성능은 낮아진다.
반대로 비동기 처리를 통해 성능을 높이려면 데이터 정합성이 낮아진다.
시스템 특성에 따른 데이터 캐시와 로그 버퍼의 크기
위의 표에 있는 3개의 DBMS에서 데이터 캐시와 로그 버퍼의 크기를 비교해보면 공통으로 데이터 캐시에 비해 로그 버퍼의 초기값이 굉장히 작다. 이는 데이터베이스가 기본적으로 검색을 메인으로 처리한다고 가정하기 때문이다.
갱신 처리에 값비싼 메모리를 많이 사용하는 것보다는, 자주 검색하는 데이터를 캐시에 올려놓는 것이 좋다고 생각.
내가 만든 시스템이 검색에 비해 갱신이 많다면, 로그 버퍼의 크기를 늘려주는 최적화가 필요하다.
메모리는 비싼 희소 자원이다.
검색과 갱신 중에서 어떤 것이 더 우선되어야 하는지 고민하자.
최근의 DBMS는 리소스를 자동으로 조정하는 기능을 가지고 있다. 이를 사용해 메모리 할당을 스스로 조정하는 DBMS도 있다. 하지만 자동 설정에 모든 것을 의지하는 것은 위험하다.
워킹 메모리
DBMS는 2개의 버퍼 데이터 캐시와 로그 버퍼 외에도 일반적으로 메모리 영역을 하나 더 가지고 있다. 정렬 또는 해시 관련 처리에 사용되는 작업용 영역으로 working memory라고 부른다. 정렬은 ORDER BY 구, 집합 연산, 윈도우 함수 등의 기능을 사용할 때 실행된다. 반면 해시는 주로 테이블 등의 결합에서 해시 결합이 사용될 때 실행된다.
워킹 메모리는 SQL에서 정렬 또는 해시가 필요할 때 사용되고 종료되면 해제되는 임시 영역으로, 일반적으로 데이터 캐시와 로그 버퍼와는 다른 영역으로 관리된다.
OS의 swap과 같은 개념으로, 워킹 메모리가 해당 영역에서 다루려는 데이터양보다 작다면 대부분의 DBMS는 저장소를 사용한다.
많은 DBMS는 워킹 메모리가 부족할 때 사용하는 임시적인 영역을 가지고 있다. 임시 영역들은 저장소 위에 있으므로 접근 속도가 느리다.
속도가 느려진다는 것 자체가 심각한 문제는 아니다. 하지만 메모리에서 작동하고 있을 때는 빠르게 움직이다가, 메모리가 부족해지는 순간 갑자기 느려지는 순간적인 변화가 일어나는 것이 문제이다.
DBMS와 실행 계획
데이터 접근 절차를 알아보자.
쿼리 평가 엔진은 RDB에서 데이터 접근 절차를 결정하는 모듈이자 사용자로부터 입력받은 SQL 구문을 처음 읽어들이는 모듈이다. 쿼리 평가 모듈은 추가로 파서 또는 옵티마이저와 같은 여러 개의 서브 모듈로 구성된다.
DBMS의 쿼리 처리 흐름
1.
파서가 구문을 분석하고 정형적인 형식으로 변환해준다.
2.
옵티마이저는 인덱스 유무, 데이터 분산 또는 편향 정도, DBMS 내부 매개변수 등의 조건을 고려해서 선택 가능한 많은 실행 계획을 작성한다.
3.
각 실행 계획들의 비용을 연산하고, 가장 낮은 비용을 가진 실행 계획을 선택한다.
4.
옵티마이저에서 실행 계획을 세울 때 카탈로그 매니저에서 정보를 제공한다. 카탈로그란 테이블 또는 인덱스의 통계 정보와 같은 DBMS의 내부 정보를 모아놓은 테이블들이다.
5.
옵티마이저가 세운 여러 개의 실행 계획 중 최적의 실행 결과를 선택한다. 이 때, 실행 계획은 인간이 읽기 쉽게 만들어진 계획서이다. 따라서 성능이 좋지 않은 SQL 구문이 있을 때 실행 계획을 읽고, 수정 방안 등을 고려할 수 있다.
⇒ 하나의 실행 계획을 선택하면, 이후에 DBMS는 실행 계획을 절차적인 코드로 변환하고 데이터 접근을 수행한다.
데이터베이스 사용자는 옵티마이저, 특히 카탈로그 매니저가 관리하는 통계 정보를 잘 사용해야 한다.
카탈로그에 포함되어 있는 통계 정보는 다음과 같다.
•
각 테이블의 레코드 수
•
각 테이블의 필드 수와 필드 크기
•
필드의 카디널리티
•
필드값의 히스토그램
•
필드 내부에 있는 NULL 수
•
인덱스 정보
테이블에 데이터 삽입/갱신/제거가 수행될 때 카탈로그 정보가 갱신되지 않는다면, 옵티마이저는 오래된 정보를 바탕으로 실행 계획을 세우게 된다. 따라서 테이블의 데이터가 많이 바뀌면 카탈로그의 통계 정보도 함께 갱신해야 한다.
실행 계획이 SQL 구문의 성능을 결정
데이터양이 많은 테이블에 접근하거나 복잡한 SQL 구문을 실행하면 반응 지연이 발생하기도 한다. 이럴 땐 먼저 실행 계획을 살펴봐야 한다.
모든 DBMS는 실행 계획을 조사하는 수단을 제공한다.
거의 모든 DBMS의 실행 계획에는 다음 3가지 내용이 포함되어 있다.
•
조작 대상 객체
: 테이블 이외에도 인덱스, 파티션, 시퀀스처럼 SQL 구문으로 조작할 수 있는 객체라면 무엇이든 올 수 있다.
•
객체에 대한 조작의 종류
•
조작 대상이 되는 레코드 수
: 카탈로그 매니저로부터 얻은 통계 정보에서 파악한 숫자이므로, 실제 SQL 구문을 실행한 시점의 테이블 레코드 수와 차이가 있을 수 있다.
SQL에서 지연이 일어나는 경우는 대부분 결합과 관련된 것이다. 일반적으로 DBMS는 결합을 할 때는 3가지 종류의 알고리즘을 사용한다.
•
Nested Loops: 한 쪽 테이블을 읽으면서 레코드 하나마다 결합 조건에 맞는 레코드를 다른 쪽 테이블에서 찾는 방식.
•
Sort Merge: 결합 키로 레코드를 정렬하고, 순차적으로 두 개의 테이블을 결합하는 방식. 결합 전에 전처리로 정렬을 수행하는데, 이 때 작업용 메모리로 워킹 메모리를 사용한다.
•
Hash: 결합 키값을 해시값으로 매핑하는 방법. 워킹 메모리를 이용해 해시 테이블을 만든다.
SELECT shop_name FROM Shops S
INNER JOIN Reservations R ON S.shop_id=R.shop_id;
SQL
복사
위와 같은 SQL 구문을 수행할 때 실행 결과는 다음과 같다.
PostgreSQL
Oracle
실행 계획은 일반적으로 트리 구조이고, 중첩 단계가 깊을수록 먼저 실행된다. PostgreSQL의 결과의 경우, Nested Loop보다도 Seq Scan과 Index Scan의 단계가 깊으므로, 결합 전에 테이블 접근이 먼저 수행된다는 것을 알 수 있다.
또한, 같은 중첩 단계에서는 위에서 아래로 실행한다.
실행 계획의 중요성
옵티마이저는 우수하지만 완벽하진 않다. 옵티마이저가 선택한 실행 계획의 성능이 좋지 않을 경우, 수동으로 실행 계획을 변경한다.
힌트 구를 사용해 SQL 구문에서 옵티마이저에게 강제적으로 명령할 수 있다.
select /*+ HINT */ name
from emp
where id =1;
SQL
복사
ref