4.1Understanding the Internals of PostgreSQL
포스트그레스에서 데이터를 어디에 저장하는지
성능에 대한 고민을 하며, 일반적인 SQL을 넘어 PostgreSQL만의 특성들을 탐구할 수 있다. 이번 포스팅에선 로우레벨에서 PostgreSQL이 어떻게 작동하는지 살펴보자.
Where does Postgres Store Data?
포스트그레스는 하드디스크의 어디에 데이터를 저장할까? 아래의 쿼리를 실행해보자.
sqlSHOW data_directory;
내 경우, 아래와 같은 결과가 나온다.
- | data_directory |
---|---|
1 | /Users/sungyupju/Library/Application Support/Postgres/var-17 |
Finder로 해당 경로를 들어가보면, 여러가지 폴더가 보인다. 이 중 포스트그레스 데이터가 저장되는 곳은 base 폴더다. base 폴더 안에는 숫자가 제목인 폴더들이 있다. 이 폴더들의 정체는 뭘까? 아래의 쿼리로 확인해보자.
sqlSELECT oid, datname FROM pg_database;
- | oid | datname |
---|---|---|
1 | 5 | postgres |
2 | 16385 | sungyupju |
3 | 1 | template1 |
4 | 4 | template0 |
5 | 16390 | Validation |
6 | 16414 |
이 결과에 따르면, 16414라는 oid
가 우리가 최근에 만든 Instagram 데이터베이스로 보인다. 파인더로 찾은 base 폴더 안에 있는 16414 폴더를 열면 아래와 같은 결과를 볼 수 있다.

이 파일들은 각 테이블이라기엔 너무 많고, 각 데이터 행이라기엔 너무 적다. 이 파일들이 의미하는게 뭘까? 역시 쿼리로 조회해볼 수 있다.
sqlSELECT * FROM pg_class;
이 결과는 아주 큰데, 대략적으로 아래와 같다.

익숙한 이름을 찾아보면, 우리의 users
테이블은 oid 16423번에 있는 것으로 보인다. 이 파일은 document 형태로, 더블클릭해서 직접 열어보면 알아볼 수 있는 문자열과 이상한 문자열들이 이리저리 섞여있다.(이 파일들은 이후 hex editor로 열어볼 예정이다)
Heaps, Blocks and Tuples
Heap(=Heap File)
테이블의 모든 데이터를 포함하고 있는 파일이다. 예를 들면, 앞서 언급한 16423 파일은 users
의 모든 데이터를 포함하는 Heap, 또는 Heap File이다.
Tuple(=Item)
테이블의 각 행이다. 아이템이라고도 불린다.
Block(=Page)
힙 파일들은 여러개의 'Block' 또는 'Page'로 나뉜다. 각 페이지/블록은 여러 행들을 저장한다. 용량은 8kb다.
즉, 우리의 데이터들은 아까 파인더에서 우리가 본 힙 파일들에 저장되어 있고, 이 힙 파일들은 여러개의 8kb짜리 블록들로 구성되어 있는데 이 블록들은 테이블의 행들 정보를 여러개 포함한다.

구체적으로, 8kb의 블록들은 위의 이미지와 같이 저장된다. 우선 해당 블록의 정보가 가장 위에 저장된다. 이후, 각 아이템(테이블의 각 행)이 어디에 저장되어 있는지 위치 정보가(초록색 상자) 저장된다.
맨 마지막엔 실제 데이터(파란 상자)가 저장되어 있다. 그리고 실제 데이터와 위치 정보 사이는 추가될 데이터의 위치 정보/실제 데이터 정보를 위한 빈 공간이 있다.
Heap File Layout
포스트그레스큐엘 공식 문서에서 데이터베이스 파일들이 low level에서 어떻게 구현되었는지 확인할 수 있다.
앞서 말한 우리의 users
테이블을 Hex Editor로 열어보자. VS Studio Code에서 Hex Editor Extension을 설치한 후, 16423 파일을 열면 아래와 같은 결과가 출력된다.

Hex Editor로 열어보면, 16진수의 행렬이 보인다. 각 16진수는 1byte로, 8개의 bit(0 또는 1)로 구성된다. 예를 들어, 위의 이미지에서 마우스 커서는 19라는 숫자에 가 있는데 맨 오른쪽에 Data Inspector의 binary를 보면 00011001이라고 되어 있다. 이는 19라는 숫자가 16진법으로 표기되어 10진법으로는 16^1 + 9 = 25임을 다시 확인할 수 있는 부분인데, 00011001은 2^4 + 2^3 + 1 = 25이기 때문이다. 즉, 이 16진수 행렬은 0과 1만으로 구성된 비트 단위 이진 코드를 보다 쉽게 읽을 수 있도록 바이트 단위로 묶어서 표현한 것이라고 할 수 있다.
한 블록은 8kb이므로, 이 파일의 각 바이트들을 모두 세 2^10 * 8개를 내려가면 하나의 블록(페이지)이 끝나고, 그 다음은 또 새로운 페이지가 이어지고...하는 것을 알 수 있다.
앞서 언급한 포스트그레스큐엘 공식 문서의 Overall Page Layout에는 이런 표가 있다:
Item | Description |
---|---|
PageHeaderData | 24 bytes long. Contains general information about the page, including free space pointers. |
ItemIdData | Array of item identifiers pointing to the actual items. Each entry is an (offset, length) pair. 4 bytes per item. |
Free space | The unallocated space. New item identifiers are allocated from the start of this area, new items from the end. |
Items | The actual items themselves. |
Special Space | Index access method specific data. Different methods store different data. Empty in ordinary tables. |
이 중 맨 마지막에 있는 Special Space를 제외하면 앞서 본 이미지와 동일한 내용이라는 것을 알 수 있다.
PageHeaderData
여기서, 24 바이트라고 하는 PageHeaderData의 구성 요소에 대해서는 공식 문서에서 약간 더 스크롤을 내리면 볼 수 있다:
Field | Type | Length | Description |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8 bytes | LSN: next byte after last byte of WAL record for last change to this page |
pd_checksum | uint16 | 2 bytes | Page checksum |
pd_flags | uint16 | 2 bytes | Flag bits |
pd_lower | LocationIndex | 2 bytes | Offset to start of free space |
pd_upper | LocationIndex | 2 bytes | Offset to end of free space |
pd_special | LocationIndex | 2 bytes | Offset to start of special space |
pd_pagesize_version | uint16 | 2 bytes | Page size and layout version number information |
pd_prune_xid | TransactionId | 4 bytes | Oldest unpruned XMAX on page, or zero if none |
이 페이지 헤더 내용을 활용해 Free Space를 찾아보자. pd_lower
는 Free Space의 시작점이 어딘지, pd_upper
는 종료점이 어딘지 보여주는 offset 값이라고 한다. Hex Editor로 열어본 이 파일에서 pd_lower
에 있는 숫자는 E4였고, 이는 228이다. 228은 16으로 나눴을 때 14.25이므로, 14줄 내려가고 4칸(0.25 = 4/16)을 더 가면 Free Space의 시작이 나와야 한다는 의미다. 그렇게 찾아가보면 00 00 00 ...하며 00이 반복되는 구간을 찾을 수 있는데, 이것이 Free Space다. 마찬가지 방법으로 pd_upper
를 통해 종료점을 찾아보았고, 그렇게 찾은 영역들을 표시하면 아래 이미지와 같다.

ItemIdData
포스트그레스큐엘 공식 문서에서 PageHeaderData 다음에 나오는 ItemId를 설명하기 위해 올려둔 그림이 있다.

각각의 ItemId는 4바이트다. Free Space 전까지 4칸 단위로 ItemId가 하나씩 있는 것이다. 이 ItemId는 해당 Item이 이 16진법 행렬에서(실제로는 0과 1로 된 데이터에서) 어디부터 저장되어 있으며 얼마만큼의 길이를 가진 데이터인지에 대한 정보다.
Items
ItemId를 통해 Item을 찾아가면, 첫 23바이트는 고정 헤더고, 이후가 실제 데이터다. 앞서 Hex Editor로 맨 처음에 파일을 열었을 때를 기억한다면, 실제 식별할 수 있는 데이터를 보기 전에 알 수 없는 문자들이 좀 있었다. 그 식별할 수 없는 문자열이 헤더였고, 나머지가 데이터였던 것으로 이해할 수 있다.