디스크에 저장된 데이터 집합을 데이터베이스라고 합니다. 일반적으로 하나의 인스턴스가 하나의 데이터베이스에 엑세스하지만 REAL APPLICATION CLUSTER 환경에서는 여러 인스턴스가 하나의 데이터베이스에 엑세스합니다. 반대의 경우는 불가합니다.
프로세스
프로세스는 서버 프로세스와 백그라운드 프로세스로 구분됩니다.
서버 프로세스
서버프로세스는 사용자 프로세스와 통신하면서 사용자의 각종 명령을 처리합니다. SQL을 파싱하고 최적화 하고 커서를 열어 SQL을 실행합니다. 이를 통해 블록을 읽고 읽은 데이터를 정렬해 클라이언트가 요청한 결과 집합을 만들어 네트워크를 통해 전송합니다.
사용자가 직접 처리하지 않는 기능, 예를들어 데이터 파일로부터 DB 버퍼 캐시로 데이터 블록을 적재하는 일, DIRTY 블록을 캐시에서 밀어내 FREE 블록을 확보하는 일 등을 백그라운드 프로세스가 대신 처리하도록 요청합니다. 클라이언트가 서버프로세스와 통신하는 방식은 전용 서버방식과 공유 서버 방식이 있습니다.
전용서버 방식
전용서버방식은 프로세스 기반 아키텍쳐입니다. 클라이언트로부터 연결 요청을 받는 리스너가 서버 프로세스를 생성하고 서버 프로세스가 단 하나의 클라이언트를 위한 전용 서비스를 제공합니다. SQL을 수행할때 마다 연결 요청을 반복하면 서버프로세스의 생성과 해제도 반복되어 성능이 저하됩니다. 따라서 전용 서버 방식을 사용하는 에플리케이션 DBMS CONNECTION POOLING 기법을 필수로 사용해야 합니다.
공유서버 방식
공유버서방식은 쓰레드 기반 아키텍쳐 입니다. 하나의 서버 프로세스를 여러 사용자 세션이 공유합니다. CONNECTION POOLING 기법을 DBMS 내부에 구현한 형태입니다. 미리 여러 개의 서브프로세스를 띄워놓고 클라이언트가 이를 공유합니다.
클라이언트는 서버 프로세스와 직접 통신하지 않고 DISPATCHER 프로세스에 요청합니다. DISPATCHER는 이를 SGA에 있는 요청 큐에 등록합니다. 가용한 서버 프로세스는 요청 큐에 있는 명령을 꺼내 처리하고 결과를 응답 큐에 등록합니다. DISPATCHER 가 응답 결과를 발견하면 사용자에게 전달합니다.
연결요청에 대한 부하는 쓰레드 기반 아키텍쳐인 공유서버방식 보다 프로세스 기반 아키텍쳐인 전용서버방식에서 더 심하게 발생합니다.
백그라운드 프로세스
SYSTEM MONITOR
장애가 발생한 시스템을 재기동할 때, 인스턴스 복구를 수행하고 임시 세그먼트와 익스텐트를 모니터링
프로세스 모니터
이상이 생긴 프로세스가 사용하던 리소스를 복구
DATABASE WRITERS(DBWR)
버퍼 케시에 있는 DIRTY 버퍼를 데이터 파일에 기록합니다.
LOG WRITER(LGWR)
로그 버퍼를 REDO 로그 파일에 기록합니다.
ARCHIVER
REDO 로그 파일이 꽉차 덮어써지기 전에 ARICHIVE 로그 디렉터리로 백업합니다.
CHECKPOINT
마지막 CHECKPOINT 이후 일어난 데이터베이스 변경사항을 데이터 파일에 기록하도록 트리거링 합니다. 기록이 완료되면 현재 어디까지 기록됐는지를 컨트롤 파일과 데이터 파일 헤더에 기록합니다.
WRITE AHEAD LOGGING
REDO 로그에 기록해 둔 버퍼 블록에 대한 변경사항 중 현재 어디까지 데이터 파일에 기록했는지 체크포인트 정보를 관리합니다. 버퍼 캐시와 데이터 파일이 동기화된 시점을 기록해 장애가 발생하면 마지막 체크포인트 이후 로그 데이터만을 디스크에 기록함으로써 인스턴스를 복구하는 용도로 사용합니다. 이 정보를 갱신하는 주기가 길어질 수록 장애 발생시 인스턴스 복구 시간도 길어집니다.
RECOVERER
분산 트랜잭션 중 발생한 문제를 해결합니다.
데이터 저장구조
ORACLE은 물리적으로 데이터 파일에 데이터를 저장해 관리합니다.
데이터 파일의 구조
블록
DBMS에서 입출력은 블록 단위입니다. 블록은 데이터를 읽고 쓸 때의 논리적 단위입니다. 블록단위 I/O는 하나의 레코드를 읽기위해 레코드가 속한 블록 전체를 읽는 것을 의미합니다. SQL 성능은 엑세스 하는 블록의 수에 따라 달라집니다. 옵티마이저의 판단을 결정하는 것은 읽어야할 레코드의 수가 아닌 블록의 수입니다.
익스텐트
데이터를 읽고 쓰는 단위는 블록이지만 테이블스페이스에 할당받는 공간의 단위입니다. 데이터를 입력하다가 공간이 부족해지면 해당 데이터오브젝트(=세그먼트)가 속한 테이블스페이스는 정해진 크기의 익스텐트 크기만큼의 연속된 블록을 할당받습니다. 익스텐트를 구성하는 블록은 논리적으로 인접하지만 동일한 데이터오브젝트(=세그먼트)에 할당된 익스텐트는 서로 인접하지 않습니다.
ORACLE의 경우 한 익스텐트에 속한 블록은 단일 오브젝트(=세그먼트)가 사용합니다. SQL SERVER에 경우 한 익스텐트에 속한 블록을 여러 데이터오브젝트(=세그먼트) 나눠서 사용할 수 있습니다.
세그먼트
테이블, 인덱스, UNDO로그 처럼 저장공간을 필요로하는 데이터베이스 오브젝트입니다. 이는 한 개 이상의 익스텐트를 사용함을 의미합니다. 파티션을 제외한 데이터 오브젝트는 세그먼트와 1:1로 대응합니다. 파티션은 1:M 관계로 대응합니다. 파티션 테이블을 만들면, 내부적으로 여러 개의 세그먼트가 만들어집니다. 한 세그먼트는 자신이 속한 테이블스페이스 내 여러 데이터 파일에 걸쳐 저장될 수 있습니다. 세그먼트가 저장된 익스텐트가 여러 파일 내에 흩어져 저장되는 것이며, 이를 통해 경합을 줄이고 I/O분산 효과를 얻을 수 있습니다.
테이블 스페이스
테이블스페이스는 세그먼트를 담는 컨테이너로, 여러 데이터 파일로 구성됩니다. 데이터를 물리적으로 저장할때 사용자가 저장될 파일을 지정하지 않습니다. 사용자는 세그먼트를 위한 테이블스페이스를 지정할 뿐, 실제 값을 저장할 데이터 파일을 선택하고 익스텐트를 할당하는 것은 DBMS의 역할입니다.
각 세그먼트는 정확히 한 테이블스페이스에 속하지만, 한 테이블스페이스에는 여러 세그먼트가 존재합니다. 즉, 세그먼트와 테이블스페이스는 M:1관계입니다. 특정 세그먼트에 할당된 모든 익스텐트는 세그먼트가 속한 테이블 스페이스 내에서만 찾을 수 있습니다. 한 세그먼트가 여러 테이블스페이스에 걸쳐 저장될 수 없습니다. 한 테이블 스페이스가 여러 데이터 파일로 구성되기 때문에 한 세그먼트가 여러 데이터 파일에 걸쳐 저장될 수는 있습니다.
임시 데이터 파일
임시 데이터 파일은 대량 데이터의 정렬혹은 해시작업 중 공간이 부족해지면 중간 결과 집합을 저장하는 용도입니다. 나중에 자동으로 삭제됩니다. REDO 정보를 생성하지 않기에 복구되지 않습니다. ORACLE의 경우, 임시 테이블스페이스를 여러 개 생성해 두고, 사용자마다 별도의 임시테이블스페이스를 지정할 수 있습니다.
create temporary tablespace big_temp
tempfile '/usr/local/oracle/oradata/ora10g/big_temp.dbf', size 200m;
alter user scott temporary tablespace big_temp;
로그파일
DB 버퍼 캐시의 모든 변경사항을 기록파는 파일을 Redo 로그라고 합니다. 디스크 상의 데이터 블록에 버퍼 캐시의 데이터를 기록하는 작업은 RANDOM I/O 방식으로 이루어지기 때문에 느립니다. 반대로, 로그는 APPEND 방식으로 기록되기 때문에 상대적으로 속도가 빠릅니다. 대부분의 DBMS는 버퍼 블록의 변경사항을 데이터 파일에 기록하기보다 우선 로그파일에 빠르게 기록하는 한 후, 일괄처리(=BATCH) 방식으로 처리합니다.
사용자의 갱신내용을 REDO 로그에 선저장하고 COMMIT 처리하는 것을 FAST COMMIT 메커니즘이라고 합니다. 인스턴스 장애가 발생하더라도 로그파일을 이용해 언제든 복구가 가능하므로 안심하고 커밋을 완료할 수 있습니다. 빠른 트랜잭션을 처리해야 하는 모든 DBMS의 공통적인 COMMIT 메커니즘입니다.
ONLINE REDO
캐시에 저장된 변경사항이 아직 데이터 파일에 기록되지 않은 상태에서 인스턴스가 비정상 종료되면 작업내용을 잃게됩니다. 이런 트랜잭션 데이터의 유실을 대비하기위해 ONLINE REDO LOG를 사용합니다. 마지막 체크포인트 이후부터 사고 발생 직전까지 수행된 트랜잭션을 REDO 로그로 재현합니다.
ONLINE REDO LOG는 최소 두 개 이상의 파일로 구성됩니다. 사용중인 파일이 꽉 차면 다음 파일로 로그스위칭 하고 모든 파일이 차면 다시 첫 파일부터 재사용하는 ROUND-ROBIN 방식을 사용합니다.
ARCHIVED REDO (OFF LINE REDO)
ONLINE REDO LOG가 재사용되기 전에 다른 위치로 백업해 둔 파일을 말합니다. 디스크가 깨지는 등 물리적인 문제가 생겼을때 데이터베이스 복구를 위해 사용합니다.
메모리 구조
DBMS의 메모리 구조는 공유 메모리 영역(SGA)과 프로세스 전용 메모리 영역(PGA)로 구분됩니다.
공유 메모리 영역(SGA)
여러 프로세스가 공용으로 엑세스할 수 있는 메모리영역으로 SYSTEM GLOBAL AREA 입니다. 공유 메모리를 구성하는 다양한 캐시 영역중, 모든 DBMS가 공통적으로 사용하는 캐시영역은 DB 버퍼 캐시, SHARED POOL, LOG BUFFER 가 있습니다. 그외에도 LARGE POOL, JAVA POOL 등을 포함하고, 시스템 구조와 제어 구조를 캐싱하는 영역도 포함됩니다.
시스템 공유 메모리 영역은 여러 프로세스에의해 공유되기 때문에 내부적으로 LATCH, BUFFER LOCK, LIBRARY LOCK/PIN 같은 엑세스 직렬화 메커니즘이 사용됩니다.
프로세스 전용 메모리 영역
서버 프로세스의 전용 메모리 영역입니다. 데이터를 정렬하고 세션과 커서의 상태 정보를 저장하는 용도로 사용합니다.
DB버퍼캐시
데이터 파일로부터 읽어 들인 데이터 블록을 담는 캐시 영역입니다. 사용자 인스턴스는 서버 프로세스를 통해 DB 버퍼 캐시의 버퍼 블록에 엑세스합니다. 일부 DIRECT PATH READ 메커니즘이 작동하는 경우를 제외한 모든 블록 읽기는 버퍼 캐시를 경우합니다. 조회할 블럭을 먼저 버퍼 캐시에서 찾아보고 없으면 디스크에서 읽습니다.
데이터 변경 또한, 버퍼 캐시에 적재된 블록을 통해 이뤄집니다. 변경된 블록을 주기적으로 데이터 파일에 기록하는 작업은 DB WRITER 프로세스가 합니다. 캐싱은 메모리 I/O로서 전기적 신호이기 때문에 속도가 빠릅니다. 따라서 데이터 파일에서 읽은 블록을 메모리 상에 보관하는 기능은 필수적입니다.
버퍼블록
FREE 버퍼 : 인스턴스 기동 후 아직 비어있는 상태의 버퍼이거나, 데이터가 담겼지만 파일과 동기화되 언제든 덮어써보 무방한 버퍼블록을 말합니다. FREE 상태에서 변경이 발생하면 DIRTY 버퍼로 상태가 바뀝니다.
DIRTY 버퍼 : 버퍼에 캐시된 이후 변경이 발생했지만, 아직 디스크에 기록되지 않아 데이터 파일 블록과 동기화가 필요한 버퍼블록입니다. 디스크 파일과 동기화 되는 순간 FREE 버퍼 상태가 됩니다.
PINNED 버퍼 : 읽기 또는 쓰기 작업이 진행 중인 버퍼블록을 말합니다.
LRU 알고리즘
버퍼 캐시의 크기는 유한하므로 모든 데이터를 캐싱할 수 없습니다. DBMS는 LRU 알고리즘을 통해 사용빈도가 높은 데이터 블록 위주로 버퍼 캐시가 구성되도록 합니다. 모든 버퍼 블록 헤더를 LRU 체인에 연결해 사용빈도 순으로 위치를 옮겨가다가 FREE 버퍼가 필요해지면 엑세스 빈도가 낮은 데이터 블록부터 밀어내는 방식입니다.
TABLE FULL SCAN 한 데이터 블록은 LRU END에 위치하기 때문에 버퍼 캐시에 오래머물지 않습니다.
SHARED POOL(공유 풀)
공유 풀은 딕셔너리 캐시와 라이브러리 캐시로 구성되며, 버퍼 캐시처럼 LRU 알고리즘을 사용합니다.
딕셔너리 캐시
데이터베이스 딕셔너리는 테이블나 인덱스와 같은 데이터 오브젝트, 테이블 스페이스, 데이터 파일, 세그먼트, 익스텐트, 사용자 제약에 관한 메타 정보를 저장하는 공간입니다. 딕셔너리 캐시는 딕셔너리 정보를 캐싱하는 메모리 영역입니다.
라이브러리 캐시
사용자가 수행한 sql 문과 실행계획, 저장 프로시저를 저장해 두는 캐시영역입니다. SQL에 대한 반복적인 하드파싱을 최소화하기 위해 사용하는 캐시영역입니다. 캐싱된 SQL과 실행계획의 재사용성을 높이는 것은 SQL의 수행성능을 높이고 DBMS 부하를 최소화하기 위해 반드시 필요합니다.
로그 버퍼
DB 버퍼 캐시에 가해지는 모든 변경 사항을 로그 파일에 기록합니다. 오라클에서는 로그파일을 REDO LOG, SQL SERVER에선 트랜잭션 로그 라고 합니다.
로그 또한 곧바로 기록하는 것이 아니라 로그 버퍼에 우선 기록합니다. 건건이 디스크에 기록하기보다 일정량을 모았다가 기록하면 훨씬 빠르기 때문입니다. 서버 프로세스가 데이터 블록 버퍼에 변경을 가하기 전에 REDO 로그 버퍼에 먼저 기록해두면 LGWR 프로세스가 REDO 로그 파일에 REDO 로그 버퍼에 기록된 사향을 기록합니다.
변경사항을 로그파일에 기록하는 이유는 인스턴스 장애가 발생하면 로그 파일에 기록된 내용을 재연해 캐시블록을 복구하고, 최종적으로 커밋되지 않은 트랜잭션은 롤백해야합니다. 로그 파일에는 없는 변경내역이 이미 데이터파일에 기록되 있으면 사용자가 최종 커밋하지 않은 트랜잭션이 커밋되는 결과를 초래합니다.
WRITE HEAD LOGGING
버퍼 캐시 블록을 갱신하기 전에 변경사항을 먼저 로그 버퍼에 기록해야 합니다. DIRTY 버퍼를 디스크에 기록하기 전에 해당 로그 엔트리를 먼저 로그 파일에 기록해야 하는데 이를 WRITE HEAD LOGGING 이라고 합니다.
FAST COMMIT
LGWR가 로그버퍼를 로그 파일에 기록하는 시점은 커밋 이전 시점이어야 합니다. 메모리 상의 로그 버퍼는 유실 가능성이 있기 때문입니다. 로그를 이용한 FAST COMMIT이 가능한 이유는 로그에 변경사항이 저장되있음이 보장되므로 이를 이용해 언제든지 데이터의 복구가 가능하기 때문입니다.
PGA
ORACLE 서버 프로세스는 PGA 메모리 영역을 할당받아 프로세스에 종속적인 고유 데이터를 저장하는 용도로 사용합니다. PGA는 다른 프로세스와 공유되지 않는 독립적인 공간으로, 래치 메커니즘이 필요 없어 같은 수의 블록을 읽더라도 SGA 버퍼 캐시에서 읽는 것보다 훨씬 빠릅니다.
USER GLOBAL AREA(UGA)
전용 서버 방식으로 연결할 때는 프로세스와 세션이 1:1 관계이지만 공유서버 방식은 1:M 관계입니다. 세션이 프로세스의 수보다 많을 수 있는 구조로 하나의 프로세스가 여러 세션을 위해 일합니다. 따라서 각 세션을 위한 독립적인 메모리 공간이 필요한데 이를 UGA라고 합니다. 전용서버방식에서는 UGA를 PGA에 할당하고 공유서버 방식으로 연결할 때는 SGA에 할당합니다.
CALL GLOBAL AREA
PGA에 할당하는 메모리 공간에는 CGA도 있습니다. ORACLE은 하나의 데이터베이스 CALL을 넘어 다음 CALL 까지 참조돼야하는 정보를 UGA에 담고, CALL이 진행되는동안 필요한 데이터는 CGA에 담습니다. CGA는 PARSE CALL, EXECUTE CALL, FETCH CALL 마다 매번 할당받습니다. RECURSIVE CALL이 발생하면 그 안에서도 PARSE CALL, EXECUTE CALL, FETCH CALL 단계별로 CGA를 추가로 할당받습니다. CGA에 할당된 공간은 하나의 CALL이 끝나자마자 PGA에 반환됩니다.
SORT AREA
데이터의 정렬을 위해 사용되는 SORT AREA는 소트 오퍼레이션이 진행되는 동안 공간이 부족해질때마다 CHUNK 단위로 조금씩 할당됩니다. 세션마다 사용할 수 있는 최대 크기를 예전에는 SORT_AREA 파라미터로 설정했으나 WORKAREA_SIZE_POLICY 파라미터를 auto로 설정하면 ORACLE 내부적으로 결정합니다.PGA 내에서 SORT AREA가 할당되는 위치는 SQL의 종류와 소트 수행 단계에 따라 다릅니다. DML 문장은 하나의 EXECUTE CALL 내에서 모든 데이터 처리를 완료하므로 SORT AREA가 CGA에 할당됩니다.
SELECT 문장의 경우 수행 중간 단계에 필요한 SORT AREA는 CGA에 할당되고, 최종 결과 집합을 출력하기 직전 단계에 필요한 SORT AREA는 UGA에 할당합니다.
바인드 변수(BIND VARIABLE)
파라미터 Driven 방식으로 SQL을 작성하는 방식으로 SQL과 실행계획을 캐싱하지 않고 하나를 반복 재사용하므로 파싱소요시간과 메모리 사용량을 줄여줍니다. 바인드 변수를 사용하면 컬럼 히스토 그램을 활용하지 못하지만 레코드 건수, 컬럼 값의 종류, NULL 값 개수 등을 활용해 실행계획을 수립합니다.
데이터베이스 CALL
select 문장을 수행할 때는 Parse, Execute, Fetch 순으로 CALL 이 발생합니다. 대부분의 I/O는 FETCH 단계에서 발생합니다. Group by 결과집합을 만드는 과정에서의 I/O는 첫 번째 FETCH CALL 에서발생합니다. INSERT, UPDATE DELETE 문에서는 FETCH CALL이 발생하지 않습니다.
사용자지정함수 사용시 발생하는 시스템 부하
- 가상머신 상에서 실행되므로 매번 바이트 코드를 해석하는 부하
- 쿼리 문장의 조회 건수 만큼 반복적으로 호출하는 부하
- 함수에 내장된 쿼리가 있다면, 해당 문장을 RECURSICE 하게 반복 수행하는 부하
- CONTEXT SWITCHING
블록 I/O
Direct path I/O는 일반적으로 병렬로 FULL SCAN을 할때 발생합니다. RANDOM I/O는 인덱스를 통해 테이블을 엑세스할때 주로 발생합니다. SINGLE BLOCK I/O는 인덱스를 통해 테이블을 액세스 할때 주로 발생합니다.
MULTI BLOCK I/O는 테이블 전체를 FULL SCAN할때 발생합니다. MULTI BLOCK I/O를 하더라도 EXTENT 범위를 넘어 스캔하지 않습니다. 따라서 EXTENT의 크기가 작으면 FULL SCAN 시 더 많은 입출력이 발생합니다. 반면, 인덱스를 통핸 테이블 액세스는 SINGLE BLOCK I/O 방식을 사용하므로 EXTENT 크기가 입출력횟수에 영향을 주지 않습니다.