본문 바로가기

Dot Database/Concept

[DB] RDB 데이터 구조에 대해 알아보자

    RDB 데이터 구조를 배워야 하는 이유

    이 글은 오라클 기준으로 작성하였다.

     

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

     

    데이터 구조에 관한 용어는 다음과 같다. 미리 익숙해지도록 하자.

    • 테이블스페이스(tablespace)
    • 세그먼트(segment)
    • 익스텐트(extent)
    • 블록(block)
    • 데이터 파일(datafile)

     

    기존 데이터 저장방식 문제점

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

    • 데이터 관리나 I/O가 힘듬
    • 사용하지 않는 공간의 관리가 힘듬

     

    ex) 한 테이블에 저장된 데이터가 100만 건이라고 가정하고, 데이터를 차례대로 저장할 경우 어떤 문제가 발생하는지 살펴보자.

    • 데이터베이스를 관리하기 위해서는 어떤 테이블이 어떤 데이터를 가졌는지에 대한 정보가 필요하다. 하지만 파일의 선두에서부터 차례대로 데이터를 입력해 나가는 방식이라면 데이터가 100만 건 있을 때는 100만개의 관리 정보가 필요하다. 100만 건의 데이터를 읽어오는 데 100만 번 I/O가 필요해질지도 모르며, 공간이 잘게 나누어지기 때문에 공간을 관리하기도 어렵다.

     

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

    1. 관리 및 I/O의 효율을 고려해 공간을 어느 정도의 크기로 뭉쳐서 할당한다.
    2. 데이터 변경에 필요한 공간을 확보한다.
    3. 비어 있는 공간을 관리한다.

     

    오라클 데이터 구조

    오라클 데이터 구조는 크게 물리 구조, 논리 구조로 나눌 수 있다. 무엇을 물리 구조라고 하고 무엇을 논리 구조라고 하는지 보는 관점에 따라 다를 수 있지만, 일반적으로 사용하는 물리 구조/논리 구조에 맞추어 설명한다면 다음과 같다.

    • 물리 구조는 데이터 파일 등의 OS에서 보이는 구조를 의미한다.
    • 논리 구조는 OS에서는 식별할 수 없는 오라클 내부의 구조를 의미한다. 

    예를 들어, 데이터 파일 안에 보관된 '테이블'이나 '로우(행)'는 논리 구조이다.

     

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

    테이블이나 인덱스는 익스텐트로, 익스텐트는 블록으로 구성되어 있다. 위의 그림만으로는 물리적인 구조에 대해 이해하기 어렵기 때문에 그래서 떠올리기 쉽도록 데이터 파일과 테이블의 관계에 대해 설명하고자 한다.

    • 데이터 파일은 OS에서 보이는 물리 구조이다.
    • 그리고 테이블은 여러 개의 로우를 가지며, OS에서 보이지 않는다는 의미로 논리적인 구조이다.

     

    ls명령어로 본 데이터 파일

    ls명령어로 본 데이터 파일

     

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

    로우는 블록에 저장됨

     

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

     

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

     

    익스텐트를 이용해 블록을 정리

     

    테이블과 데이터 파일의 관계를 정리하면 다음과 같다.

    • 연속된 블록의 집합 > 익스텐트, 익스텐트의 집합 > 세그먼트

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

     

    오라클 데이터 구조에 어떤 것들이 있는가?

    세그먼트

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

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

     

    테이블스페이스

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

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

     

    블록 안의 공간

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

     

    블록 안에 비어 있는 공간이 있음

    ROWID

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

     

     

     

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

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

     

    버퍼 캐시에 블록 읽어오기

     

    다음은 데이터 변경이다. 데이터의 변경은 인덱스를 사용해 한 건만을 변경한다고 가정한다.

    1. 우선 인덱스에 접근하고 인덱스의 관리 정보를 토대로 인덱스의 루트 블록(가장 위의 블록)을 찾아간다.
    2. 캐시에 없다면 블록을 읽어온다.
    3. 이어서 루트 블록에서 다음 블록의 주소를 조사하고, 그 블록이 캐시에 적재되어 있지 않다면 디스크에 읽어온다.

    이렇게 반복해서 필요한 데이터의 ROWID를 확인하고, 해당되는 데이터 블록을 찾는다. 대상 테이블 데이터의 블록이 캐시에 적재되어 있지 않다면 해당 블록만을 읽어온다. 그리고 캐시상에서 블록의 데이터를 변경한다.

    * 지금까지 설명한 처리는 서버 프로세스가 수행한다.

     

    인덱스 액세스 시 버퍼 캐시에 블록 읽어오기

     

    그런 후에 DBWR(Database WRiter)이라고 불리는 백그라운드 프로세스가 잠시 후에 데이터 파일에 변경된 데이터를 기록한다. 이 설명에서 알 수 있듯이, 캐시의 데이터와 파일의 데이터가 일치하지 않는 시간이 존재한다. 데이터를 변경할 때마다 일일이 기록한다면 그만큼 사용자가 느끼는 응답 시간은 나빠질 것이기 때문이다. 물론 데이터 보증은 REDO 로그라는 별도의 장치를 통해 지켜지기 때문에 COMMIT한 데이터에 대한 안전성은 유지된다.

     

    정리

    이제까지 설명한 구조와 동작을 정리하면 다음과 같다.

    • 테이블스페이스는 세그먼트를 집어넣기 위한 용기로서 하나 이상의 데이터 파일로 구성된다.
    • 일반적으로 테이블이나 인덱스는 세그먼트이다.
    • 세그먼트는 익스텐트를 구성되며, 익스텐트는 연속된 블록으로 구성되어 있다.
    • 세그먼트는 테이블스페이스 여러 개에 걸쳐서 존재할 수 없다.(세그먼트는 테이블스페이스에 소속되므로)
    • 익스텐트는 데이터 파일 여러 개에 걸쳐서 존재할 수 없다.(익스텐트는 연속된 블록이므로)
    • 일반적으로 테이블이나 인덱스는 테이블스페이스가 가지고 있는 공간에서 새로운 익스텐트를 할당받음으로써 크기가 커진다.
    • 블록 안의 데이터 변경용 공간은 PCTFREE라는 파라미터로 제어한다.
    • 로우(행)는 블록에 보관되어 있다.

     


    참고

    스기타 아츠시 외 4인 - 오라클 구조