sungyup's.

PostgreSQL / Postgres Deep Dive / 4.1 Understanding the Internals of PostgreSQL

4.1Understanding the Internals of PostgreSQL

포스트그레스에서 데이터를 어디에 저장하는지

성능에 대한 고민을 하며, 일반적인 SQL을 넘어 PostgreSQL만의 특성들을 탐구할 수 있다. 이번 포스팅에선 로우레벨에서 PostgreSQL이 어떻게 작동하는지 살펴보자.

Where does Postgres Store Data?

포스트그레스는 하드디스크의 어디에 데이터를 저장할까? 아래의 쿼리를 실행해보자.

sql
SHOW data_directory;

내 경우, 아래와 같은 결과가 나온다.

-data_directory
1/Users/sungyupju/Library/Application Support/Postgres/var-17

Finder로 해당 경로를 들어가보면, 여러가지 폴더가 보인다. 이 중 포스트그레스 데이터가 저장되는 곳은 base 폴더다. base 폴더 안에는 숫자가 제목인 폴더들이 있다. 이 폴더들의 정체는 뭘까? 아래의 쿼리로 확인해보자.

sql
SELECT oid, datname FROM pg_database;
-oiddatname
15postgres
216385sungyupju
31template1
44template0
516390Validation
616414Instagram

이 결과에 따르면, 16414라는 oid가 우리가 최근에 만든 Instagram 데이터베이스로 보인다. 파인더로 찾은 base 폴더 안에 있는 16414 폴더를 열면 아래와 같은 결과를 볼 수 있다.

16414 folder
데이터베이스에 저장된 raw 데이터는 이렇게 생겼다.

이 파일들은 각 테이블이라기엔 너무 많고, 각 데이터 행이라기엔 너무 적다. 이 파일들이 의미하는게 뭘까? 역시 쿼리로 조회해볼 수 있다.

sql
SELECT * FROM pg_class;

이 결과는 아주 큰데, 대략적으로 아래와 같다.

pg_class result
아주 큰 테이블이지만 익숙한 이름들이 보인다. relname 컬럼의 users, posts, comments, likes_id_seq...

익숙한 이름을 찾아보면, 우리의 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짜리 블록들로 구성되어 있는데 이 블록들은 테이블의 행들 정보를 여러개 포함한다.

how block is saved in hard disk
블록들은 하드디스크에 이런 식으로 저장된다. 물론, 정보들은 0과 1의 이진수로 저장된다.

구체적으로, 8kb의 블록들은 위의 이미지와 같이 저장된다. 우선 해당 블록의 정보가 가장 위에 저장된다. 이후, 각 아이템(테이블의 각 행)이 어디에 저장되어 있는지 위치 정보가(초록색 상자) 저장된다.

맨 마지막엔 실제 데이터(파란 상자)가 저장되어 있다. 그리고 실제 데이터와 위치 정보 사이는 추가될 데이터의 위치 정보/실제 데이터 정보를 위한 빈 공간이 있다.

Heap File Layout

포스트그레스큐엘 공식 문서에서 데이터베이스 파일들이 low level에서 어떻게 구현되었는지 확인할 수 있다.

앞서 말한 우리의 users 테이블을 Hex Editor로 열어보자. VS Studio Code에서 Hex Editor Extension을 설치한 후, 16423 파일을 열면 아래와 같은 결과가 출력된다.

16423 file opened in hex editor
16진수들의 행렬이 보이는데, Decoded Text 쪽을 보면 왠지 식별할 수 있는 문자들이 보인다. Gerardo Kautzer는 사람 이름 같고, https://로 시작되는 부분은 아바타 url로 보인다. 이후, 전화번호와 status로 보이는 내용들이 보이고, 일정 간격 후 Amari Walter로 이어진다.

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에는 이런 표가 있다:

ItemDescription
PageHeaderData24 bytes long. Contains general information about the page, including free space pointers.
ItemIdDataArray of item identifiers pointing to the actual items. Each entry is an (offset, length) pair. 4 bytes per item.
Free spaceThe unallocated space. New item identifiers are allocated from the start of this area, new items from the end.
ItemsThe actual items themselves.
Special SpaceIndex access method specific data. Different methods store different data. Empty in ordinary tables.

이 중 맨 마지막에 있는 Special Space를 제외하면 앞서 본 이미지와 동일한 내용이라는 것을 알 수 있다.

PageHeaderData

여기서, 24 바이트라고 하는 PageHeaderData의 구성 요소에 대해서는 공식 문서에서 약간 더 스크롤을 내리면 볼 수 있다:

FieldTypeLengthDescription
pd_lsnPageXLogRecPtr8 bytesLSN: next byte after last byte of WAL record for last change to this page
pd_checksumuint162 bytesPage checksum
pd_flagsuint162 bytesFlag bits
pd_lowerLocationIndex2 bytesOffset to start of free space
pd_upperLocationIndex2 bytesOffset to end of free space
pd_specialLocationIndex2 bytesOffset to start of special space
pd_pagesize_versionuint162 bytesPage size and layout version number information
pd_prune_xidTransactionId4 bytesOldest 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를 통해 종료점을 찾아보았고, 그렇게 찾은 영역들을 표시하면 아래 이미지와 같다.

hexcode demystified
0C 0D에 있는 것이 pd_lower다. 여기서 E4라는 숫자를 찾아 228칸 내려가보면 00 00 00 ...하며 00이 반복되는 Free Space를 찾을 수 있다.

ItemIdData

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

각각의 ItemId는 4바이트다. Free Space 전까지 4칸 단위로 ItemId가 하나씩 있는 것이다. 이 ItemId는 해당 Item이 이 16진법 행렬에서(실제로는 0과 1로 된 데이터에서) 어디부터 저장되어 있으며 얼마만큼의 길이를 가진 데이터인지에 대한 정보다.

Items

ItemId를 통해 Item을 찾아가면, 첫 23바이트는 고정 헤더고, 이후가 실제 데이터다. 앞서 Hex Editor로 맨 처음에 파일을 열었을 때를 기억한다면, 실제 식별할 수 있는 데이터를 보기 전에 알 수 없는 문자들이 좀 있었다. 그 식별할 수 없는 문자열이 헤더였고, 나머지가 데이터였던 것으로 이해할 수 있다.