본문 바로가기

DB/Oracle

[Oracle] Chapter 7. 오라클의 데이터 구조

7.1 오라클의 데이터 구조를 왜 배워야 하는가?

 

오라클은 전달받은 데이터를 그대로 저장하는 것이 아니라, 몆 개의 상자로 나누어서 저장하도록 고안됐다. 그래서 상자와 관련된 이야기(데이터의 구조)는 평상시 업무에서도 빈번하게 화두에 오르기 때문에 몰라서는 안 된다. 또한, 애플리케이션 개발 팀에서도 테이블이나 인덱스의 생성과 생성 요청을 해야 하므로 어느 정도의 지식은 필요하다. 그리고 성능과 관련된 부분에서도 데이터 구조와 관련된 지식은 필요하다.

다음의 키워드는 이번 장에서 설명할 데이터의 구조에 관한 용어이다. 들어본 적이 없을 수도 있지만, 실제로 데이터베이스를 관리할 때는 빈번하게 등장하는 키워드이므로 이번 장에서 제대로 이해하고 넘어가자.

 

- 테이블스페이스(tablespace)

- 세그먼트(segment)

- 익스텐트(extent)

- 블록(block)

- 데이터 파일(datafile)

 

예를 들어, '테이블스페이스가 가득 차면 데이터 파일을 추가하자'나 'ORA-30036 unable to extend segment by 8 in undo tablespace' 같은 일반적인 운영에서 발생할 수 있는 에러의 대응에서도 이런 용어들을 많이 사용하므로 데이터 구조는 반드시 이해해 두어야 한다.

당연한 이야기이지만 오라클은 데이터를 관리하는 시스템이므로 데이터를 어떻게 저장하고 있는지를 이해하는 것은 오라클을 알기 위해서 절대 빼놓을 수 없는 부분이다. 데이터 구조는 복잡하므로 어렵다고 느끼실 수도 있지만, 이해할 때까지 몆 번이든 반복해서 학습하자.

 

7.2 가변 길이 데이터를 관리할 프로그램을 만들기 위해서는?

 

오라클의 데이터 구조를 배운 적이 있는 사람 중에 '오라클의 데이터 구조는 어쨰서 이렇게 복잡한 것일까?'라는 의문을 가지는 사람들이 있다. 그래서 먼저 왜 복잡한지 설명하고자 다음의 질문을 해보겠다.

 

Q. 여러 테이블의 데이터를 파일로 관리하기 위한 프로그램을 처음부터 만든다고 가정하고, 해당 프로그램의 구조를 떠올려보자. 관리할 데이터는 가변 길이이며, 길어지는 경우도 있지만 짧아지는 경우도 있고, 삭제하거나 생성하는 경우도 있다고 하자.

 

단순하게 생각해보면 어떤 테이블이든 상관없이 파일의 선두에서부터 데이터를 차례대로 넣어 두면 쉽게 구현할 수 있을 것처럼 보인다. 하지만 이런 방식으로 구현하면 이미 입력된 데이터의 크기를 크게 변경할 때 문제가 발생한다. 더욱이 데이터를 동시에 처리할 수도 없다.

 

- 데이터를 변경하려 할 때 곤란할 수 있음

1. INSERT문으로 첫 번째 로우에 데이터 'abcde'가 입력됨

2. INSERT문으로 두 번째 로우에 데이터 '12345'가 입력됨

3. UPDATE문을 사용해서 첫 번째 로우의 데이터에 'f'를 추가해서 'abcdef'로 변경하고 싶지만, 바로 뒤에 다른 데이터가 존재하므로 변경할 수가 없음

 

- 동시에 처리할 수 없음

1. 어떤 서버 프로세스가 INSERT문을 사용해서 데이터에 'fgh'를 추가하려고 함

2. 1과 같은 시점에 다른 서버 프로세스가 다른 데이터 '1234'를 추가하려고 함

장애 발생 : 데이터를 덮어써서는 안 되므로 다른 프로세스가 끝날 때까지 어느 쪽이든 기다려야 함. 이래서는 키워드 중 하나인 '병렬 처리를 가능케 하고 높은 처리량을 실현한다'는 목표를 실현할 수 없음

 

한 테이블에 저장된 데이터가 100만 건이라고 가정하고, 데이터를 차례대로 저장할경우 어떤 문제가 발생하는지 살펴보자. 데이터베이스를 관리하기 위해서는 어떤 테이블이 어떤 데이터를 가졌는지에 대한 정보가 필요하다. 하지만 파일의 선두에서부터 차례대로 데이터를 입력해 나가는 방식이라면 데이터가 100만 건 있을 때는 100만 개의 관리 정보가 필요하다. 100만 건의 데이터를 읽어오는 데 100만 번의 I/O가 필요해질지도 모르며, 공간이 잘게 나누어지기 때문에 공간을 관리하는 것도 어려워진다.

 

- 데이터 관리나 I/O가 힘듦

어디에 어떤 테이블의 데이터가 들어가 있는지를 누군가 어딘가에서 관리할 수밖에 없음. 하지만 몆 십, 몆백 만이 되는 양이라면 관리 효율이 매우 낮으며, 그 관리 정보를 사용해서 I/O를 해야 하므로 I/O횟수도 많아지게 됨. 1장에서 설명했던 것처럼, 디스크를 읽고 쓰기 위해서는 '탐색'과 '회전 대기 시간'이 필요하므로 I/O 횟수가 많으면 효율이 낮아짐

 

- 사용하지 않는 공간의 관리가 힘듦

데이터가 들어 있지 않은 공간이 있으므로 다음에 데이터를 입력하기 위해서는 어딘가에서 누군가 관리하지 않으면 안 됨. 하지만 몆십, 몆백 만이 되는 양이라면 관리 효율이 매우 낮음

 

7.2.1 필요한 데이터 구조

 

이런 부분에 대해서 완전하지는 않지만 유효한 대처 방법이 존재한다. 그것은 '적당한 크기로 정리한다(뭉친다)'라는 접근 방식이다. 예를 들면, 100만 건을 각각 관리하는 것은 매우 힘들지만, 1만 건을 한 개의 집합으로 관리하면 100개의 관리 정보만 있으면 된다. 또한, I/O의 횟수도 100회로 충분하다. 데이터를 변경하는 것도 일정 데이터양(집합)마다 변경에 필요한 공간을 확보해두면 쉽게 처리할 수 있다. 물론, 빠르게 데이터를 입력할 수 있도록 비어 있는 공간에 대한 관리도 필요하다.

 

- 관리를 '어디서부터 어느 정도의 크기가 어떤 테이블의 데이터인가?'라는 방식으로 하면 '관리 정보'의 수를 줄일 수 있음. 또한, I/O를 수행할 때도 한 번에 데이터를 읽어올 수 있음(디스크 I/O의 오버헤드가 감소한다). 단, 이 방식을 구현하기 위해서는 테이블에 잘 정리된 공간을 할당해야 함

- 실제 환경에서는 오라클의 제한이나 OS, 볼륨 매니저(volume manager)의 제한으로 인해 한 개의 관리 영역에 I/O가 한 번 이상 발생할 수도 있다.

 

정리해보면 다음의 세 가지를 구현할 수 있는 구조가 필요하다.

 

- 관리 및 I/O의 효율을 고려해 공간을 어느 정도의 크기로 뭉쳐서 할당한다.

- 데이터 변경에 필요한 공간을 확보한다.

- 비어 있는 공간을 관리한다.

 

7.3 오라클의 데이터 구조

 

그러면 오라클의 데이터 구조에 관해 구체적으로 살펴보도록 하자. 오라클의 데이터 구조는 크게 물리 구조, 논리 구조로 나눌 수 있다. 무엇을 물리 구조라고 하고 무엇을 논리 구조라고 부르는지 보는 관점에 따라 다를 수 있지만, 일반적으로 사용하는 물리 구조/논리 구조에 맞추어 설명한다면 물리 구조는 데이터 파일 등의 OS에서 보이는 구조를 의미한다.

 

논리 구조는 OS에서 식별할 수 없는 오라클 내부의 구조를 의미한다. 예를 들어, 데이터 파일 안에 보관된 '테이블'이나 '로우(행)'는 논리 구조이다.

 

7.3.1 데이터 파일과 테이블의 관계

 

테이블이나 인덱스는 익스텐트로 구성되어 있고, 익스텐트는 블록으로 구성되어 있다. 데이터 파일은 OS에서 보이는 물리구조이다. 그리고 테이블은 여러 개의 로우를 가지며, OS에서 보이지 않는다는 의미로 논리적인 구조이다. 논리적인 구조인 테이블은 물리적인 구조인 데이터 파일에 어떤 형태로 보관될까?

 

작은 구조부터 차례대로 살펴보자. 가장 작은 구조(집한)은 '(오라클) 블록)'이다. 블록은 8KB 같은 크기로 나뉜 공간을 말한다. 이 블록 안에 한 건 이상의 데이터가 보관된다.

 

그러면 테이블은 여러 개의 블록으로 구성된 것일까? 여러 개의 블록으로 구성은 되어 있지만, 이 상태로는 한 개의 테이블이 여러 장소에 분산된 상태로 존재하며, 몆만 몆십 만에 가까운 블록을 관리하는 사태가 발생하기 때문에 효율적이지 않다. 그래서 '익스텐트'라고 하는 한 가지 구조를 더 도입했다.

익스텐트는 연속된 블록의 집합이다. 익스텐트 덕분에 각 블록의 위치가 아니라 각 익스텐트의 첫 위치와 블록의 개수만으로 데이터를 관리할 수 있으며, 관리 정보도 줄일 수 있게 되었다. 또한, 데이터를 한 번에 읽어올 수 있으므로 테이블의 풀 스캔 성능을 향상할 수도 있다. 더욱이 테이블이나 인덱스 등의 데이터를 한번 더 모은 익스텐트의 집합을 '세그먼트'라고 부른다.

 

7.4 데이터 구조에는 어떤 것들이 있는가?

 

7.4.1 세그먼트

 

단순하게 말하자면, 세그먼트는 '많은 데이터를 보관하기 위한 구조'를 의미한다. 세그먼트를 '구조'라고 말한 이유는 조작 가능한지 여부를 제쳐 놓고서라도, 데이터베이스 내에 존재하고 있는 것처럼 보이기 떄문이다. 다르게 이야기하자면 '익스텐트의 집합'이라고 할 수 있다.

사용자를 위한 테이블이나 인덱스 같은 세그먼트는 사용자의 조작 대상이다. 이미 알고 계시듯이 테이블이나 인덱스는 DROP 명령어를 이용해 삭제할 수 있다. 그때 익스텐튼느 필요 없게 되며, 테이블스페이스의 빈 공간으로 돌아가게 된다. 실은 사용자용 세그먼트인 테이블이나 인덱스 외에도 오라클이 자동으로 생성하는 세그먼트도 있다 데이터를 정렬하기 위한 세그먼트나 UNDO라고 불리는 과거 데이터를 보관하는 세그먼트 등이다.

 

7.4.2 테이블스페이스

 

테이블스페이스도 오라클 내부의 구조이다. '테이블' 스페이스라고 부르긴 하지만, '세그먼트를 분류해서 보관하기 위한 상자'라고 생각하시면 된다. 테이블스페이스는 한 개 이상의 데이터 파일로 구성되어 있다.

테이블스페이스에는 오라클이 데이터베이스를 관리하기 위해 사용하는 테이블스페이스와 사용자가 사용하는 테이블스페이스 등 몆 가지 종류가 존재한다. 테이블스페이스의 집합(물리적으로는 데이터 파일의 집합)과 REDO 로그 파일, 컨트롤 파일이 모이면 하나의 데이터베이스가 된다.

 

7.4.3 블록 안의 공간

 

오라클은 블록 안에 데이터 변경에 대비한 공간을 남겨 둔다. 바꿔 말하면, 데이터를 입력할 때 가득 채워 넣는 것이 아니라 어느 정도의 공간을 남겨 두는 것이다. 데이터가 계쏙 삭제되어 블록 안의 공간이 늘어나면 다시 해당 블록으로 데이터를 입력한다. 어떤 블록에 공간이 남아 있는지를 빠르게 파악하기 위해서 빈 블록을 세그먼트 단위로 관리하고 있다. 세그먼트 안에 공간이 모자란 상황이 오면 세그먼트에 새로운 익스텐트를 추가하고 빈 블록을 늘린다.

 

7.4.4 ROWID

 

오라클에서 데이터 로우의 주소를'ROWID'라고 부른다. ROWID는 데이터 파일의 번호나 데이터 파일 안의 블록 번호, 블록 안에 로우 번호와 같은 정보로 구성되어 있다. 이것을 보면 로우 데이터는 블록에 보관된 것을 알 수 있다.

 

7.5 실제 흐름을 따라 각 동작을 확인

 

7.5.1 공간을 할당하기 및 비어 있는 공간의 관리

 

실제 업무에서 수행하는 순서로 공간을 할당하는 것과 비어 있는 공간을 관리하는 동작을 확인해보자.

 

1. 데이터베이스의 생성

우선 데이터베이스의 생성이다. 이때 SYSTEM 테이블스페이스를 시작으로 몆 가지 테이블스페이스가 생성된다.

 

2. 사용자용 테이블스페이스의 생성

당므으로 사용자용 테이블스페이스를 생서한다. 사용자용 테이블 스페이스를 생성하는 것은 데이터베이스를 생성할 때 같이 해도 상관없다. 이 시점에서 테이블스페이스는 비어 있는 공간을 가지게 된다는 점에 유의하자.

 

3. 테이블을 테이블스페이스에 생성

이어서 테이블스페이스에 테입르을 생성한다. 이 시점에서 내부가 비어 있는 상태로 익스텐트가 생성된다. 익스텐트가 생성된 후에 데이터 입력(INSERT)이 수행되며, 익스텐트의 비어 있는 블록에 데이터를 입력한다. 'PCTFREE'라는 임계치에 도달하면 해당 블록에 입력하는 것을 멈추고 '이 블록에는 공간이 없다'고 인식한다. 그리고 다음으로 비어 있는 블록에 데이터를 입력한다.

데이터의 입력이 계속되어 익스텐트가 가득 차게 되면 테이블스페이스가 가지고 있는 빈 공간을 사용해 새로운 익스텐트를 테이블에 할당하고 데이터를 입력할 수 있게 한다. 그 후에 데이터를 삭제하여 'PCTUSED'라는 임계치(ASSM일 떄는 오라클이 자동 계산)보다 낮아지면 다시 '이 블록은 비어 있다'라고 인식하게 된다. 테이블이나 인덱스가 DROP(또는 TRUNCATE)되면 익스텐트 안의 데이터는 불필요하므로 테이블스페이스의 빈 공간으로 돌아간다.

 

7.6 프로세스에서 본 데이터 구조

 

오라클의 메모리 내부(특히 캐시)에서는 대부분의 데이터를 블록(오라클 블록)이라는 단위로 관리하고 있다. 메모리 내부의 동작에 대해 검색(SELECT)과 변경(UPDATE)을 예로 들어 설명하겠다. 여기서 검색은 풀 스캔이라고 가정한다. 따라서 검색할 때 테이블의 전체 데이터를 읽어올 필요가 있다. 오라클은 해당 테이블의 익스텐트를 조사하고 버퍼 캐시에 존재하지 않은 블록을 처음부터 읽어온다.

 

다음은 데이터의 변경이다. 데이터의 변경은 인덱스를 사용해 한 건만을 변경한다고 가정해보자. 우선 인덱스에 접근하고 인덱스의 관리 정보를 토대로 인덱스의 루트 블록(가장 위의 블록)을 찾아간다. 캐시에 없다면 블록을 읽어온다. 이어서 루트 블록에서 다음 블록의 주소를 조사하고, 그 블록이 캐시에 적재되어 있지 않다면 디스크에서 읽어온다. 이렇게 반복해서 필요한 데이터의 ROWID를 확인하고, 해당되는 데이터 블록을 찾는다. 대상 테이블 데이터의 블록이 캐시에 적재되어 있지 않다면 해당 블록만을 읽어온다. 그리고 캐시상에서 블록의 데이터를 변경한다. 지금까지 설명한 처리는 서버 프로세스가 수행한다.

 

그런 후에 DBWR(DataBase WRiter)이라고 불리는 백그라운드 프로세스가 잠시 후에 데이터 파일에 변경된 데이터를 기록한다. 이 설명에서 알 수 있듯이, 캐시의 데이터와 파일의 데이터가 일치하지 않는 시간이 존재한다. 이것은 1장에서 나열한 키워드인 '응답 시간(respose time)을 중시한다'에 해당한다. 데이터를 변경할 떄마다 일일이 기록한다면 그만큼 사용자가 느끼는 응답 시간은 나빠질 것이기 떄문이다. 키워드 중 하나인 '커밋(COMMIT)한 데이터는 지킨다'와 모순되어 보일 수도 있지만, 데이터의 보증은 REDO 로그라고 불리는 별도의 장치를 통해 지켜지고 있다. REDO 로그에 관해서는 앞으로 나올 장에서 설명하겠다.

 

7.7 요약

 

- 테이블스페이스는 세그먼트를 집어넣기 위한 용기로서 하나 이상의 데이터 파일로 구성된다.

- 일반적으로 테이블이나 인덱스는 세그먼트다.

- 세그먼트는 익스텐트로 구성되며, 익스텐트는 연속된 블록으로 구성되어 있다.

- 세그먼트는 테이블스페이스 여러 개에 걸쳐서 존재할 수 없다(세그먼트는 테이블스페이스에 소속되므로).

- 익스텐트는 데이터 파일 여러 개에 걸쳐서 존재할 수 없다(익스텐트는 연속된 블록이므로).

- 일반적으로 테이블이나 인덱스는 테이블스페이스가 가지고 있는 공간에서 새로운 익스텐트를 할당받음으로써 크기가 커진다.

- 블록 안의 데이터 변경용 공간은 PCTFREE라는 파라미터로 제어한다.

- 로우(행)는 블록에 보관되어 있다.

 

다음 장에서는 '각종 Lock과 대기'에 대해서 설명하겠다. 어쨰서 SQL문의 처리가 대기하는지, 대기에는 어떤 종류가 있는지, 어떤 Lock이 오라클에 존재하며, 어떤 부분을 신경 쓰지 않으면 안 되는지 등을 살펴보도록 하자.

 

Tip

테이블스페이스가 가득 차면 어떻게 되지?

 

본문에서도 설명했듯이, 익스텐트가 가득 차면 테이블스페이스가 가진 공간에서 세그먼트에 새로운 익스텐트를 할당한다. 그러면 할당할 공간이 없으면 어떻게 될까?

 

이때는 익스텐트를 할당하지 못하며 에러가 발생한다. 따라서 운영 환경에서 테이블스페이스가 가진 여유 공간을 모니터링하는 것은 매우 중요한 작업이다. 또한, 여유 공간이 부족할 떄는 데이터 파일을 추가하여 대처한다. 단, 데이터 파일을 자동 확장할 수 있도록 설정하였다면 상한선(예를 들면, OS의 파일 크기 상한이나 한 개의 데이터 파일에 넣을 수 있는 블록 수의 상한 등)에 도달하지 않은 이상 자동으로 공간을 확장하기 떄문에 공간이 부족하다고 해서 너무 서둘러 대응할 필요는 없다. 단 파일 시스템의 여유 공간을 모니터링할 필요가 있으며, 큰 데이터 파일은 문제가 발생할 여지가 있으므로 너무 크게 할당하지 않는 편이 좋다.

또한, 이런 이유 떄문에 관리용으로 사용하는 테이블스페이스(SYSTEM 테이블스페이스)에는 사용자용의 세그먼트를 생성해서는 안 된다. 잘못해서 SYSTEM 테이블스페이스가 가득 차게 되면 데이터베이스 전체에 영향을 주기 떄문이다.