# 3-1-1. 데이터베이스 아키텍처

## 과목3. SQL 고급 활용 및 튜닝

## 제1장 SQL 수행 구조

## 제1절 데이터베이스 아키텍처

### 1. 데이터베이스 구조

#### 가. Oracle 구조

DBMS마다 데이터베이스에 대한 정의가 조금씩 다른데, Oracle에서는 디스크에 저장된 데이터 집합(Datafile, Redo Log File, Control File 등)을 데이터베이스(Database)라고 부른다. 그리고 SGA 공유 메모리 영역과 이를 액세스하는 프로세스 집합을 합쳐서 인스턴스(Instance)라고 부른다.(\[그림 Ⅲ-1-1] 참조)<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-0e923106713897b8df7178a6c93bb8fd2cd6450f%2FSQL_260.jpg?alt=media)

기본적으로 하나의 인스턴스가 하나의 데이터베이스만 액세스하지만, RAC(Real Application Cluster) 환경에서는 여러 인스턴스가 하나의 데이터베이스를 액세스할 수 있다. 하나의 인스턴스가 여러 데이터베이스를 액세스할 수는 없다.

#### 나. SQL Server 구조

\[그림 Ⅲ-1-1]과 대비해 SQL Server 아키텍처를 간단히 표현하면 \[그림 Ⅲ-1-2]와 같다. SQL Server는 하나의 인스턴스 당 최고 32,767개의 데이터베이스를 정의해 사용할 수 있다. 기본적으로 master, model, msdb, tempdb 등의 시스템 데이터베이스가 만들어지며, 여기에 사용자 데이터베이스를 추가로 생성하는 구조다.<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-21bfe357e8f8e25460f6d6108a61c7bf379a476f%2FSQL_261.jpg?alt=media)

데이터베이스 하나를 만들 때마다 주(Primary 또는 Main) 데이터 파일과 트랜잭션 로그 파일이 하나씩 생기는데, 전자는 확장자가 mdf이고 후자는 ldf이다. 저장할 데이터가 많으면 보조(Non-Primary) 데이터 파일을 추가할 수 있으며, 확장자는 ndf이다.

### 2. 프로세스

SQL Server는 쓰레드(Thread) 기반 아키텍처이므로 프로세스 대신 쓰레드라는 표현을 써야 한다. SQL Server 뿐만 아니라 Oracle도 Windows 버전에선 쓰레드(Thread)를 사용하지만, 프로세스와 일일이 구분하면서 설명하려면 복잡해지므로 특별히 쓰레드를 언급해야 할 경우가 아니라면 간단히 '프로세스'로 통칭하기로 한다. 잠시 후 표로써 정리해 보이겠지만, 주요 쓰레드의 역할은 Oracle 프로세스와 크게 다르지 않다. 프로세스는 서버 프로세스(Server Processes)와 백그라운드 프로세스(Background Processes) 집합으로 나뉜다. 서버 프로세스는 전면에 나서 사용자가 던지는 각종 명령을 처리하고, 백그라운드 프로세스는 뒤에서 묵묵히 주어진 역할을 수행한다.

#### 가. 서버 프로세스(Server Processes)

서버 프로세스는 사용자 프로세스와 통신하면서 사용자의 각종 명령을 처리하며, SQL Server에선 Worker 쓰레드가 같은 역할을 담당한다. 좀 더 구체적으로 말해, SQL을 파싱하고 필요하면 최적화를 수행하며, 커서를 열어 SQL을 실행하면서 블록을 읽고, 읽은 데이터를 정렬해서 클라이언트가 요청한 결과집합을 만들어 네트워크를 통해 전송하는 일련의 작업을 모두 서버 프로세스가 처리해 준다. 스스로 처리하도록 구현되지 않은 기능, 이를테면 데이터 파일로부터 DB 버퍼 캐시로 블록을 적재하거나 Dirty 블록을 캐시에서 밀어냄으로써 Free 블록을 확보하는 일, 그리고 Redo 로그 버퍼를 비우는 일 등은 OS, I/O 서브시스템, 백그라운드 프로세스가 대신 처리하도록 시스템 Call을 통해 요청한다. 클라이언트가 서버 프로세스와 연결하는 방식은 DBMS마다 다르지만 Oracle을 예로 들면, 전용 서버 방식과 공유 서버 방식, 두 가지가 있다.

**1) 전용 서버(Dedicated Server) 방식**

\[그림 Ⅲ-1-3]은 전용 서버 방식으로 접속할 때 내부적으로 어떤 과정을 거쳐 세션을 수립하고 사용자 명령을 처리하는지 잘 보여준다<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-685bd55c19824674a943acabdfa089c52219828f%2FSQL_262.jpg?alt=media)

처음 연결요청을 받는 리스너가 서버 프로세스(Window 환경에서는 쓰레드)를 생성해 주고, 이 서버 프로세스가 단 하나의 사용자 프로세스를 위해 전용(Dedicated) 서비스를 제공한다는 점이 특징이다. 만약 SQL을 수행할 때마다 연결 요청을 반복하면 서버 프로세스의 생성과 해제도 반복하게 되므로 DBMS에 매우 큰 부담을 주고 성능을 크게 떨어뜨린다. 따라서 전용 서버 방식을 사용하는 OLTP성 애플리케이션에선 Connection Pooling 기법을 필수적으로 사용해야 한다. 예를 들어, 50개의 서버 프로세스와 연결된 50개의 사용자 프로세스를 공유해서 반복 재사용하는 방식이다.

**2) 공유 서버(Shared Server) 방식**

공유 서버는 말 그대로 하나의 서버 프로세스를 여러 사용자 세션이 공유하는 방식으로서, 앞서 설명한 Connection Pooling 기법을 DBMS 내부에 구현해 놓은 것으로 생각하면 쉽다. 즉 미리 여러 개의 서버 프로세스를 띄어 놓고 이를 공유해서 반복 재사용한다.<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-dffb7811fe2cbad8f60760912bfcd083bf11a1f8%2FSQL_263.jpg?alt=media)

\[그림 Ⅲ-1-4]에서 보이듯, 공유 서버 방식으로 Oracle에 접속하면 사용자 프로세스는 서버 프로세스와 직접 통신하지 않고 Dispatcher 프로세스를 거친다. 사용자 명령이 Dispatcher에게 전달되면 Dispatcher는 이를 SGA에 있는 요청 큐(Request Queue)에 등록한다. 이후 가장 먼저 가용해진 서버 프로세스가 요청 큐에 있는 사용자 명령을 꺼내서 처리하고, 그 결과를 응답 큐(Response Queue)에 등록한다. 응답 큐를 모니터링하던 Dispatcher가 응답 결과를 발견하면 사용자 프로세스에게 전송해 준다.

#### 나. 백그라운드 프로세스(Background Processes)

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-a48cff23f559b4a2d66f6435941d73e031396b98%2FSQL_264.jpg?alt=media)

### 3. 파일 구조

#### 가. 데이터 파일

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-1609168470ca9de1eaf989b3da5c0cfb716d2ea3%2FSQL_265.jpg?alt=media)

Oracle과 SQL Server 모두 물리적으로는 데이터 파일에 데이터를 저장하고 관리한다. 공간을 할당하고 관리하기 위한 논리적인 구조도 크게 다르지 않지만 약간의 차이는 있다

**1) 블록(=페이지)**

대부분 DBMS에서 I/O는 블록 단위로 이루어진다. 데이터를 읽고 쓸 때의 논리적인 단위가 블록인 것이다. Oracle은 '블록(Block)'이라고 하고, SQL Server는 '페이지(Page)'라고 한다. Oracle은 2KB, 4KB, 8KB, 16KB, 32KB, 64KB의 다양한 블록 크기를 사용할 수 있지만, SQL Server에선 8KB 단일 크기를 사용한다. 블록 단위로 I/O 한다는 것은, 하나의 레코드에서 하나의 칼럼만을 읽으려 할 때도 레코드가 속한 블록 전체를 읽게 됨을 뜻한다. SQL 성능을 좌우하는 가장 중요한 성능지표는 액세스하는 블록 개수이며, 옵티마이저의 판단에 가장 큰 영향을 미치는 것도 액세스해야 할 블록 개수다. 예를 들어, 옵티마이저가 인덱스를 이용해 테이블을 액세스할지 아니면 Full Table Scan 할지를 결정하는 데 있어 가장 중요한 판단 기준은 읽어야 할 레코드 수가 아니라 읽어야 하는 블록 개수다.

**2) 익스텐트(Extent)**

데이터를 읽고 쓰는 단위는 블록이지만, 테이블 스페이스로부터 공간을 할당하는 단위는 익스텐트다. 테이블이나 인덱스에 데이터를 입력하다가 공간이 부족해지면 해당 오브젝트가 속한 테이블 스페이스(물리적으로는 데이터 파일)로부터 추가적인 공간을 할당받는데, 이때 정해진 익스텐트 크기의 연속된 블록을 할당받는다. 예를 들어, 블록 크기가 8KB인 상태에서 64KB 단위로 익스텐트를 할당하도록 정의했다면, 공간이 부족할 때마다 테이블 스페이스로부터 8개의 연속된 블록을 찾아(찾지 못하면 새로 생성) 세그먼트에 할당해 준다. 익스텐트 내 블록은 논리적으로 인접하지만, 익스텐트끼리 서로 인접하지는 않는다. 예를 들어, 어떤 세그먼트에 익스텐트 2개가 할당됐는데, 데이터 파일 내에서 이 둘이 서로 멀리 떨어져 있을 수 있다. 참고로 Oracle은 다양한 크기의 익스텐트를 사용하지만, SQL Server에선 8개 페이지의 익스텐트만을 사용한다. 페이지 크기도 8KB로 고정됐으므로 익스텐트는 항상 64KB인 셈이다. 또한 Oracle은 한 익스텐트에 속한 모든 블록을 단일 오브젝트가 사용하지만, SQL Server에서는 2개 이상 오브젝트가 나누어 사용할 수도 있다. SQL Server는 다음 2가지 타입의 익스텐트를 사용한다.<br>

* 균일(Uniform) 익스텐트 : 64KB 이상의 공간을 필요로 하는 테이블이나 인덱스를 위해 사용되며, 8개 페이지 단위로 할당된 익스텐트를 단일 오브젝트가 모두 사용한다.<br>
* 혼합(Mixed) 익스텐트 : 한 익스텐트에 할당된 8개 페이지를 여러 오브젝트가 나누어 사용하는 형태다. 모든 테이블이 처음에는 혼합 익스텐트로 시작하지만 64KB를 넘으면서 2번째부터 균일 익스텐트를 사용하게 된다.

**3) 세그먼트**

SQL Server에서는 세그먼트라는 용어를 사용하지 않지만, 힙 구조 또는 인덱스 구조의 오브젝트가 여기에 속한다. 세그먼트는 테이블, 인덱스, Undo처럼 저장공간을 필요로 하는 데이터베이스 오브젝트다. 저장공간을 필요로 한다는 것은 한 개 이상의 익스텐트를 사용함을 뜻한다. 테이블을 생성할 때, 내부적으로는 테이블 세그먼트가 생성된다. 인덱스를 생성할 때, 내부적으로 인덱스 세그먼트가 생성된다. 다른 오브젝트는 세그먼트와 1:1 대응 관계를 갖지만 파티션은 1:M 관계를 갖는다. 즉 파티션 테이블(또는 인덱스)을 만들면, 내부적으로 여러 개의 세그먼트가 만들어진다. 한 세그먼트는 자신이 속한 테이블 스페이스 내 여러 데이터 파일에 걸쳐 저장될 수 있다. 즉 세그먼트에 할당된 익스텐트가 여러 데이터 파일에 흩어져 저장되는 것이며, 그래야 디스크 경합을 줄이고 I/O 분산 효과를 얻을 수 있다.

**4) 테이블 스페이스**

테이블 스페이스는 세그먼트를 담는 콘테이너로서, 여러 데이터 파일로 구성된다. SQL Server의 파일 그룹이 Oracle 테이블 스페이스에 해당한다. 데이터는 물리적으로 데이터 파일에 저장되지만, 사용자가 데이터 파일을 직접 선택하진 않는다. 사용자는 세그먼트를 위한 테이블 스페이스를 지정할 뿐, 실제 값을 저장할 데이터 파일을 선택하고 익스텐트를 할당하는 것은 DBMS의 몫이다. 각 세그먼트는 정확히 한 테이블 스페이스에만 속하지만, 한 테이블 스페이스에는 여러 세그먼트가 존재할 수 있다. 특정 세그먼트에 할당된 모든 익스텐트는 해당 세그먼트와 관련된 테이블 스페이스 내에서만 찾아진다. 한 세그먼트가 여러 테이블 스페이스에 걸쳐 저장될 수는 없는 것이다. 하지만 앞서 얘기했듯이 한 세그먼트가 여러 데이터 파일에 걸쳐 저장될 수는 있다. 한 테이블 스페이스가 여러 데이터 파일로 구성되기 때문이다.<br>

지금까지 설명한 내용을 그림으로 요약하면 \[그림 Ⅲ-1-6]과 같다.<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-2bbc531a112975bcdb820e2bfa260f340d8b1d98%2FSQL_266.jpg?alt=media)

SQL Server에서는 한 익스텐트에 속한 모든 페이지를 2개 이상 오브젝트가 나누어 사용할 수 있으므로(혼합 익스텐트) \[그림 Ⅲ-1-7]과 같다.<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-59b89c41b14a032ddede4bf5093cef6daf4a9de9%2FSQL_267.jpg?alt=media)

#### 나. 임시 데이터 파일

임시(Temporary) 데이터 파일은 특별한 용도로 사용된다. 대량의 정렬이나 해시 작업을 수행하다가 메모리 공간이 부족해지면 중간 결과집합을 저장하는 용도다. 임시 데이터 파일에 저장되는 오브젝트는 말 그대로 임시로 저장했다가 자동으로 삭제된다. Redo 정보를 생성하지 않기 때문에 나중에 파일에 문제가 생겼을 때 복구되지 않는다. 따라서 백업할 필요도 없다. Oracle에선 임시 테이블 스페이스를 여러 개 생성해 두고, 사용자마다 별도의 임시 테이블 스페이스를 지정해 줄 수도 있다.

> ```sql
> CREATE temporary tablespace big_temp
> tempfile '/usr/local/oracle/oradata/ora10g/big_temp.dbf' size 2000m;
>
> ALTER USER SCOTT TEMPORARY TABLESPACE BIG_TEMP; 
> ```

SQL Server는 단 하나의 tempdb 데이터베이스를 사용한다. tempdb는 전역 리소스로서 시스템에 연결된 모든 사용자의 임시 데이터를 여기에 저장한다.

#### 다. 로그 파일

DB 버퍼 캐시에 가해지는 모든 변경사항을 기록하는 파일을 Oracle은 'Redo 로그'라고 부르며, SQL Server는 '트랜잭션 로그'라고 부른다. 변경된 메모리 버퍼 블록을 디스크 상의 데이터 블록에 기록하는 작업은 random I/O 방식으로 이루어지기 때문에 느리다. 반면 로그 기록은 Append 방식으로 이루어지기 때문에 상대적으로 매우 빠르다. 따라서 대부분 DBMS는 버퍼 블록에 대한 변경사항을 건건이 데이터 파일에 기록하기보다 우선 로그 파일에 Append 방식으로 빠르게 기록하는 방식을 사용한다. 그러고 나서 버퍼 블록과 데이터 파일 간 동기화는 적절한 수단(DBWR, Checkpoint)을 이용해 나중에 배치(Batch) 방식으로 일괄 처리한다. 사용자의 갱신내용이 메모리상의 버퍼 블록에만 기록된 채 아직 디스크에 기록되지 않았더라도 Redo 로그를 믿고 빠르게 커밋을 완료한다는 의미에서, 이를 'Fast Commit' 메커니즘이라고 부른다. 인스턴스 장애가 발생하더라도 로그 파일을 이용해 언제든 복구 가능하므로 안심하고 커밋을 완료할 수 있는 것이다. Fast Commit은 빠르게 트랜잭션을 처리해야 하는 모든 DBMS의 공통적인 메커니즘이다.<br>

* Online Redo 로그\
  캐시에 저장된 변경사항이 아직 데이터 파일에 기록되지 않은 상태에서 정전 등으로 인스턴스가 비정상 종료되면, 그때까지의 작업내용을 모두 잃게 된다. 이러한 트랜잭션 데이터의 유실에 대비하기 위해 Oracle은 Online Redo 로그를 사용한다. 마지막 체크포인트 이후부터 사고 발생 직전까지 수행되었던 트랜잭션들을 Redo 로그를 이용해 재현하는 것이며, 이를 '캐시 복구'라고 한다. Online Redo 로그는 최소 두 개 이상의 파일로 구성된다. 현재 사용 중인 파일이 꽉 차면 다음 파일로 로그 스위칭(log switching)이 발생하며, 계속 로그를 써 나가다가 모든 파일이 꽉 차면 다시 첫 번째 파일부터 재사용하는 라운드 로빈(round-robin) 방식을 사용한다<br>
* 트랜잭션 로그\
  트랜잭션 로그는 Oracle의 Online Redo 로그와 대응되는 SQL Server의 로그 파일이다. 주 데이터 파일마다, 즉 데이터베이스마다 트랜잭션 로그 파일이 하나씩 생기며, 확장자는 ldf이다. 트랜잭션 로그 파일은 내부적으로 '가상 로그 파일'이라 불리는 더 작은 단위의 세그먼트로 나뉘며, 이 가상 로그 파일의 개수가 너무 많아지지 않도록(즉 조각화가 발생하지 않도록) 옵션을 지정하는 게 좋다. 예를 들어, 로그 파일을 애초에 넉넉한 크기로 만들어 자동 증가가 발생하지 않도록 하거나, 어쩔 수 없이 자동 증가한다면 증가하는 단위를 크게 지정하는 것이다.<br>
* Archived(=Offline) Redo 로그\
  Archived Redo 로그는 Oracle에서 Online Redo 로그가 재사용되기 전에 다른 위치로 백업해 둔 파일을 말한다. 디스크가 깨지는 등 물리적인 저장 매체에 문제가 생겼을 때 데이터베이스(또는 미디어) 복구를 위해 사용된다. 참고로, SQL Server에는 Archived Redo 로그에 대응되는 개념이 없다.

### 4. 메모리 구조

메모리 구조는 시스템 공유 메모리 영역과 프로세스 전용 메모리 영역으로 구분된다.<br>

* 시스템 공유 메모리 영역\
  시스템 공유 메모리는 말 그대로 여러 프로세스(또는 쓰레드)가 동시에 액세스할 수 있는 메모리 영역으로서, Oracle에선 'System Global Area(SGA)', SQL Server에선 'Memory Pool'이라고 부른다. 공유 메모리를 구성하는 캐시 영역은 매우 다양하지만, 모든 DBMS가 공통적으로 사용하는 캐시 영역으로는 DB 버퍼 캐시, 공유 풀, 로그 버퍼가 있다. 공유 메모리 영역은 그 외에 Large 풀(Large Pool), 자바 풀(Java Pool) 등을 포함하고, 시스템 구조와 제어 구조를 캐싱하는 영역도 포함한다.시스템 공유 메모리 영역은 여러 프로세스에 공유되기 때문에 내부적으로 래치(Latch), 버퍼 Lock, 라이브러리 캐시 Lock/Pin 같은 액세스 직렬화 메커니즘이 사용된다.<br>
* 프로세스 전용 메모리 영역\
  Oracle은 프로세스 기반 아키텍처이므로 서버 프로세스가 자신만의 전용 메모리 영역을 가질 수 있는데, 이를 'Process Global Area(PGA)'라고 부르며, 데이터를 정렬하고 세션과 커서에 관한 상태 정보를 저장하는 용도로 사용한다.쓰레드(Thread) 기반 아키텍처를 사용하는 SQL Server는 프로세스 전용 메모리 영역을 갖지 않는다. 쓰레드는 전용 메모리 영역을 가질 수 없고, 부모 프로세스의 메모리 영역을 사용하기 때문이다. 참고로, Windows 버전 Oracle도 쓰레드를 사용하지만 프로세스 기반의 Unix 버전과 같은 인터페이스를 제공하고 구조에 대한 개념과 설명도 구별하지 않는다.<br>

지금부터 시스템 공유 메모리 영역의 구성요소인 DB 버퍼 캐시, 공유 풀, 로그 버퍼를 순서대로 살펴보고, 마지막으로 프로세스 전용 메모리 영역인 Process Global Area에 대해 살펴본다.

#### 가. DB 버퍼 캐시(DB Buffer Cache)

DB 버퍼 캐시는 데이터 파일로부터 읽어 들인 데이터 블록을 담는 캐시 영역이다. 인스턴스에 접속한 모든 사용자 프로세스는 서버 프로세스를 통해 DB 버퍼 캐시의 버퍼 블록을 동시에(내부적으로는 버퍼 Lock을 통해 직렬화) 액세스할 수 있다. 일부 Direct Path Read 메커니즘이 작동하는 경우를 제외하면, 모든 블록 읽기는 버퍼 캐시를 통해 이루어진다. 즉 읽고자 하는 블록을 먼저 버퍼 캐시에서 찾아보고 없을 때 디스크에서 읽는다. 디스크에서 읽을 때도 먼저 버퍼 캐시에 적재한 후에 읽는다. 데이터 변경도 버퍼 캐시에 적재된 블록을 통해 이루어지며, 변경된 블록(Dirty 버퍼 블록)을 주기적으로 데이터 파일에 기록하는 작업은 DBWR 프로세스의 몫이다. 디스크 I/O는 물리적으로 액세스 암(Arm)이 움직이면서 헤드를 통해 이루어지는 반면, 메모리 I/O는 전기적 신호에 불과하기 때문에 디스크 I/O에 비교할 수 없을 정도로 빠르다. 디스크에서 읽은 데이터 블록을 메모리 상에 보관해 두는 기능이 모든 데이터베이스 시스템에 필수적인 이유다.

**1) 버퍼 블록의 상태**

모든 버퍼 블록은 아래 세 가지 중 하나의 상태에 놓인다.<br>

* Free 버퍼 : 인스턴스 기동 후 아직 데이터가 읽히지 않아 비어 있는 상태(Clean 버퍼)이거나, 데이터가 담겼지만 데이터 파일과 서로 동기화돼 있는 상태여서 언제든지 덮어 써도 무방한 버퍼 블록을 말한다. 데이터 파일로부터 새로운 데이터 블록을 로딩하려면 먼저 Free 버퍼를 확보해야 한다. Free 상태인 버퍼에 변경이 발생하면 그 순간 Dirty 버퍼로 상태가 바뀐다.<br>
* Dirty 버퍼 : 버퍼에 캐시된 이후 변경이 발생했지만, 아직 디스크에 기록되지 않아 데이터 파일 블록과 동기화가 필요한 버퍼 블록을 말한다. 이 버퍼 블록들이 다른 데이터 블록을 위해 재사용되려면 디스크에 먼저 기록되어야 하며, 디스크에 기록되는 순간 Free 버퍼로 상태가 바뀐다.<br>
* Pinned 버퍼 : 읽기 또는 쓰기 작업이 현재 진행 중인 버퍼 블록을 말한다.

**2) LRU 알고리즘**

버퍼 캐시는 유한한 자원이므로 모든 데이터를 캐싱해 둘 수 없다. 따라서 모든 DBMS는 사용빈도가 높은 데이터 블록 위주로 버퍼 캐시가 구성되도록 LRU(least recently used) 알고리즘을 사용한다. 모든 버퍼 블록 헤더를 LRU 체인에 연결해 사용빈도 순으로 위치를 옮겨가다가, Free 버퍼가 필요해질 때면 액세스 빈도가 낮은 쪽(LRU end) 데이터 블록부터 밀어내는 방식이다. \[그림 Ⅲ-1-8]과 같은 컨베이어 벨트를 연상하면 LRU 알고리즘을 쉽게 이해할 수 있다.<br>

![](https://1900845109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MiU6Ywdw7__X0wsBtyv%2Fuploads%2Fgit-blob-ac8e7e9c3e016a288a6c7c9ab1436cde14b174c2%2FSQL_268.jpg?alt=media)

#### 나. 공유 풀(Shared Pool)

공유 풀은 딕셔너리 캐시와 라이브러리 캐시로 구성되며, 버퍼 캐시처럼 LRU 알고리즘을 사용한다. SQL Server에서 같은 역할을 하는 메모리 영역을 '프로시저 캐시(Procedure Cache)'라고 부른다.<br>

* 딕셔너리 캐시\
  데이터베이스 딕셔너리(Dictionary)는 테이블, 인덱스 같은 오브젝트는 물론 테이블 스페이스, 데이터 파일, 세그먼트, 익스텐트, 사용자, 제약에 관한 메타 정보를 저장하는 곳이다. 그리고 딕셔너리 캐시는 말 그대로 딕셔너리 정보를 캐싱하는 메모리 영역이다. '주문' 테이블을 예로 들면, 입력한 주문 데이터는 데이터 파일에 저장됐다가 버퍼 캐시를 경유해 읽히지만, 테이블 메타 정보는 딕셔너리에 저장됐다가 딕셔너리 캐시를 경유해 읽힌다.<br>
* 라이브러리 캐시\
  라이브러리 캐시(Library Cache)는 사용자가 수행한 SQL문과 실행계획, 저장 프로시저를 저장해 두는 캐시영역이다. 사용자가 SQL 명령어를 통해 결과집합을 요청하면 이를 최적으로(→가장 적은 리소스를 사용하면서 가장 빠르게) 수행하기 위한 처리 루plan)이라고 한다. 빠른 쿼리 수행을 위해 내부적으로 생성한 일종의 프로시저와 같은 것이라고 이해하면 쉽다. 쿼리 구문을 분석해서 문법 오류 및 실행 권한 등을 체크하고, 최적화(Optimization) 과정을 거쳐 실행계획을 만들고, SQL 실행엔진이 이해할 수 있는 형태로 포맷팅하는 전 과정을 하드 파싱(Hard Parsing)이라고 한다. 특히 최적화 과정은 하드 파싱을 무겁게 만드는 가장 결정적 요인인데, 같은 SQL을 처리하려고 이런 무거운 작업을 반복 수행하는 것은 매우 비효율적이다. 그??하기 위한 캐시 공간을 따로 두게 되었고, 그것이 바로 라이브러리 캐시 영역이다. 캐싱된 SQL과 그 실행계획의 재사용성을 높이는 것은 SQL 수행 성능을 높이고 DBMS 부하를 최소화하는 핵심 원리 중 한가지다.

#### 다. 로그 버퍼(Log Buffer)

DB 버퍼 캐시에 가해지는 모든 변경사항을 로그 파일에 기록한다고 앞서 설명했는데, 로그 엔트리도 파일에 곧바로 기록하는 것이 아니라 먼저 로그 버퍼에 기록한다. 건건이 디스크에 기록하기보다 일정량을 모았다가 기록하면 훨씬 빠르기 때문이다. 좀 더 자세히 설명하면, 서버 프로세스가 데이터 블록 버퍼에 변경을 가하기 전에 Redo 로그 버퍼에 먼저 기록해 두면 주기적으로 LGWR 프로세스가 Redo 로그 파일에 기록한다. Oracle의 Redo 로그, Redo 로그 버퍼와 대비되는 개념이 SQL Server의 트랜잭션 로그, 로그 캐시다. 변경이 가해진 Dirty 버퍼를 데이터 파일에 기록하기 전에 항상 로그 버퍼를 먼저 로그 파일에 기록해야만 하는데, 그 이유는 인스턴스 장애가 발생할 때면 로그 파일에 기록된 내용을 재현해 캐시 블록을 복구하고, 최종적으로 커밋되지 않은 트랜잭션은 롤백해야 한다. 이때, 로그 파일에는 없는 변경내역이 이미 데이터 파일에 기록돼 있으면 사용자가 최종 커밋하지 않은 트랜잭션이 커밋되는 결과를 초래하기 때문이다. 정리해 보면, 버퍼 캐시 블록을 갱신하기 전에 변경사항을 먼저 로그 버퍼에 기록해야 하며, Dirty 버퍼를 디스크에 기록하기 전에 해당 로그 엔트리를 먼저 로그 파일에 기록해야 하는데, 이를 'Write Ahead Logging'이라고 한다. 그리고 로그 버퍼를 주기적으로 로그 파일에 기록한다고 했는데, 늦어도 커밋 시점에는 로그 파일에 기록해야 한다(Log Force at commit). 메모리상의 로그 버퍼는 언제든 유실될 가능성이 있기 때문이다. 로그를 이용한 Fast Commit이 가능한 이유는 로그를 이용해 언제든 복구 가능하기 때문이라고 설명한 것을 상기하기 바란다. 다시 말하지만, 로그 파일에 기록했음이 보장돼야 안심하고 커밋을 완료할 수 있다.

#### 라. PGA(Process Global Area)

각 Oracle 서버 프로세스는 자신만의 PGA(Process/Program/Private Global Area) 메모리 영역을 할당받고, 이를 프로세스에 종속적인 고유 데이터를 저장하는 용도로 사용한다. PGA는 다른 프로세스와 공유되지 않는 독립적인 메모리 공간으로서, 래치 메커니즘이 필요 없어 똑같은 개수의 블록을 읽더라도 SGA 버퍼 캐시에서 읽는 것보다 훨씬 빠르다.<br>

* User Global Area(UGA)\
  전용 서버(Dedicated Server) 방식으로 연결할 때는 프로세스와 세션이 1:1 관계를 갖지만, 공유 서버(Shared Server) 방식으로 연결할 때는 1:M 관계를 갖는다. 즉 세션이 프로세스 개수보다 많아질 수 있는 구조로서, 하나의 프로세스가 여러 개 세션을 위해 일한다. 따라서 각 세션을 위한 독립적인 메모리 공간이 필요해지는데, 이를 'UGA(User Global Area)'라고 한다. 전용 서버 방식이라고 해서 UGA를 사용하지 않는 것은 아니다. UGA는 전용 서버 방식으로 연결할 때는 PGA에 할당되고, 공유 서버 방식으로 연결할 때는 SGA에 할당된다. 구체적으로 후자는, Large Pool이 설정됐을 때는 Large Pool에, 그렇지 않을 때는 Shared Pool에 할당하는 방식이다.<br>
* Call Global Area(CGA)\
  PGA에 할당되는 메모리 공간으로는 CGA도 있다. Oracle은 하나의 데이터베이스 Call을 넘어서 다음 Call까지 계속 참조되어야 하는 정보는 UGA에 담고, Call이 진행되는 동안에만 필요한 데이터는 CGA에 담는다. CGA는 Parse Call, Execute Call, Fetch Call마다 매번 할당받는다. Call이 진행되는 동안 Recursive Call이 발생하면 그 안에서도 Parse, Execute, Fetch 단계별로 CGA가 추가로 할당된다. CGA에 할당된 공간은 하나의 Call이 끝나자마자 해제돼 PGA로 반환된다.<br>
* Sort Area\
  데이터 정렬을 위해 사용되는 Sort Area는 소트 오퍼레이션이 진행되는 동안 공간이 부족해질 때마다 청크(Chunk) 단위로 조금씩 할당된다. 세션마다 사용할 수 있는 최대 크기를 예전에는 sort\_area\_size 파라미터로 설정하였으나, 9i부터는 새로 생긴 workarea\_size\_policy 파라미터를 auto(기본 값)로 설정하면 Oracle이 내부적으로 결정한다.<br>

PGA 내에서 Sort Area가 할당되는 위치는 SQL문 종류와 소트 수행 단계에 따라 다르다. DML 문장은 하나의 Execute Call 내에서 모든 데이터 처리를 완료하므로 Sort Area가 CGA에 할당된다. SELECT 문장의 경우, 수행 중간 단계에 필요한 Sort Area는 CGA에 할당되고, 최종 결과집합을 출력하기 직전 단계에 필요한 Sort Area는 UGA에 할당된다.<br>

앞에서 이미 설명한 것처럼, 쓰레드(Thread) 기반 아키텍처를 사용하는 SQL Server는 프로세스 전용 메모리 영역을 갖지 않는다. 대신, 데이터 정렬은 Memory Pool 안에 있는 버퍼 캐시에서 수행하며, 세션 관련 정보는 Memory Pool 안에 있는 Connection Context 영역에 저장한다.<br>

\
\ <br>

> 출처 : 데이터온에어 – 한국데이터산업진흥원(<https://dataonair.or.kr>)
