지난 글에서는 InnoDB가 데이터를 테이블스페이스(tablespace) → 세그먼트(segment) → 익스텐트(extent) → 페이지(page) 순의 계층 구조로 저장한다는 개념을 정리했다. 각 구성요소의 역할과 관계를 살펴봤지만, 이는 어디까지나 논리적인 구조일 뿐이다.
실제로 디스크에 어떤 페이지가 할당되었는지, 어떤 익스텐트가 사용 중인지 등은 메타데이터 페이지에 저장된다. InnoDB의 저장 구조와 내부 동작 방식을 깊이 이해하려면 이 메타데이터 페이지들을 반드시 살펴봐야 한다.
이번 글에서는 그 중에서도 핵심적인 시스템 페이지인 FSP_HDR, XDES, INODE 페이지의 구조와 역할을 분석한다.
본격적으로 메타데이터 페이지 구조를 보기 전에, 어떤 정보가 어디에서 어떻게 관리되는지 한 번 정리해보자.
아래는 계층별 메타데이터가 저장되는 위치와 관련 구조체를 키워드 중심으로 정리한 표다.
| 계층 | 메타데이터 위치, 구조체 |
|---|---|
| Filespace | FSP_HDR Page, FSP Header |
| Segment | INODE Page, INODE Entry |
| Extent | XDES Entry, FSP_HDR/XDES Page |
InnoDB는 익스텐트와 세그먼트를 상태별로 효율적으로 관리하기 위해 이중 연결 리스트(Double Linked List)를 사용한다.
단순 포인터 배열이 아니라 List base node라는 구조체를 통해 관리되며, 여기엔 next, prev 포인터가 함께 포함된다.

연결 리스트를 사용하면 아래와 같은 장점이 있다:
FSP_HDR Page는 항상 페이지 번호 0번에 위치하며, 테이블스페이스의 전역 메타데이터를 담고 있는 페이지다.
즉, 이 페이지는 테이블스페이스 전체를 관리하는 출발점이다.

이전 글에서 다룬 것처럼, 모든 InnoDB 페이지는 앞에 FIL Header, 뒤에 FIL Trailer가 공통적으로 붙는다.
FSP_HDR Page는 그 안에 FSP Header와 256개의 XDES Entry를 포함한다.
FSP Header는 테이블스페이스의 전반적인 상태를 나타내는 메타데이터 필드로 구성된다.
InnoDB가 어떤 페이지를 할당할지, 얼마나 공간이 남았는지, 세그먼트나 익스텐트를 어떻게 관리할지 등 대부분의 결정이 이 정보를 기반으로 이루어진다.

FSP Header의 필드는 다음과 같다.
| 필드 | 설명 |
|---|---|
| Space ID | 테이블스페이스 고유 식별자 |
| File size | 현재 파일에 존재하는 가장 큰 페이지 번호 |
| Free limit | 초기화된 마지막 페이지 번호 |
| Flags | 압축 여부 등 다양한 설정 값 |
| FREE_FRAG 사용 페이지 수 | 조각 페이지 공간 상태 추적 |
| XDES List base nodes | 익스텐트 상태별 연결 리스트 헤더들 |
| Next Unused Segment ID | 다음 세그먼트 할당 시 사용할 ID |
| INODE List base nodes | 세그먼트(INODE) 상태별 연결 리스트 헤더들 |
File size는 테이블스페이스가 지금까지 확장된 최대 페이지 번호다.
하지만 확장됐다고 해서 그 페이지들이 다 초기화돼서 쓸 수 있는 상태인 건 아니다.
실제로 쓸 수 있을 정도로 초기화까지 완료된 마지막 페이지 번호가 Free limit이다.
이 두 값은 새로운 데이터를 저장할 공간이 필요할 때, InnoDB가 파일을 더 확장할지 말지 판단하는 기준이 된다.
XDES Entry는 익스텐트(Extent)의 상태를 나타내는 구조체인데, 이들을 서로 연결하는 리스트들이 존재한다.
FSP Header는 각 상태별로 아래와 같은 리스트의 시작 지점을 갖고 있다.
이 리스트를 따라가면서 InnoDB는 어떤 익스텐트를 공간 할당에 쓸지 판단할 수 있다.
FREE_FRAG 사용 페이지 수는 무엇일까?
FSP Header에는 FREE_FRAG 상태인 익스텐트의 사용 가능한 페이지 수가 별도로 캐싱된다.
리스트를 순회하면서 "여유 페이지 몇 개 남았지?"를 매번 확인하는 건 느릴 수밖에 없다.
FSP Header에 미리 값이 캐싱돼 있다면 빠르게 조건을 판단하고 바로 할당 여부를 결정할 수 있다.
종합해보면 공간을 할당하는 로직은 다음과 같다.
각 XDES Entry는 하나의 Extent(64 페이지)에 대한 메타데이터를 담고 있으며, 해당 익스텐트의 상태를 관리한다.

| 필드 | 설명 |
|---|---|
| Segment ID | 해당 익스텐트를 사용하는 세그먼트의 ID |
| List Node | 이중 연결 리스트를 위한 포인터 (prev/next) |
| State | 익스텐트의 상태를 나타냄. FREE, FREE_FRAG, FULL_FRAG, FSEG 중 하나 |
| Page Bitmap | 각 페이지 정보를 2비트에 담음 (64개 페이지 × 2비트 = 총 128bit) |
State는 익스텐트가 어떤 상태에 있는지를 나타낸다. 각 상태는 다음과 같은 의미를 가진다
FREE, FREE_FRAG, FULL_FRAG 상태의 익스텐트는 각각 전역적으로 관리되는 이중 연결 리스트를 통해 연결되어 있다.
FSEG 상태인 익스텐트는 세그먼트가 통째로 소유하고 있는 익스텐트이기 때문에, 전역 리스트로 연결할 필요가 없다.
세그먼트와 세그먼트가 소유하는 익스텐트들을 자신만의 리스트(FREE, NOT_FULL, FULL)로 관리하는 주체는 INODE Entry인데 이건 뒷쪽에서 다룬다.
FSP_HDR에는 최대 256개의 XDES Entry를 저장할 수 있다.
즉, 하나의 FSP_HDR 페이지는 총 256개 익스텐트(= 16,384 페이지 = 256MB)까지 관리할 수 있다.
그렇다면 하나의 테이블스페이스는 256개 익스텐트를 넘길 수 없나?
물론 그럴 리 없다. FSP_HDR의 용량을 초과하는 경우, InnoDB는 자동으로 XDES 전용 페이지(XDES Page)를 추가한다.
XDES Page는 FSP_HDR 페이지의 확장 역할을 수행한다.
FSP_HDR 페이지와 기본 구조는 동일하지만, 상단의 FSP Header 영역은 대부분 0으로 채워진다.
(FSP_HDR 페이지) - (FSP Header) 라고 생각하면 된다.

테이블스페이스가 256MB 이상 커질 경우 256MB마다 하나의 XDES 페이지가 추가된다고 이해하면 된다.
INODE Page는 세그먼트가 어떤 익스텐트들이 소유하고 있는지, 어떤 상태인지 등을 추적하는 역할을 한다.
앞서 FSEG 상태의 인스텐트들은 여기서 관리된다.

기본적으로 INODE Page는 페이지 번호 2번에 존재하며, 하나의 페이지당 최대 85개의 INODE Entry를 포함할 수 있다.
각 Entry는 하나의 세그먼트를 의미하며, 해당 세그먼트가 관리 중인 익스텐트 리스트와 조각 페이지들을 추적한다.

| 필드 | 설명 |
|---|---|
| Segment ID | 해당 세그먼트의 고유 식별자 |
| NOT_FULL 사용 페이지 수 | 현재 NOT_FULL 익스텐트에서 사용 중인 페이지 수 |
| 리스트 포인터들 | FREE, NOT_FULL, FULL 리스트의 이중 연결 리스트 base node |
| Fragment Array | 최대 32개의 조각 단위로 할당된 페이지 정보 |
앞서 말했듯이, 세그먼트는 처음 생성되었을 때 익스텐트를 한 번에 할당받지 않는다.
대신 최대 32개의 페이지를 조각(fragment) 단위로 받아서 쓴다.
이게 바로 Fragment Array에 저장된다.
이후 공간이 더 필요해지면 InnoDB는 익스텐트를 통째로 할당하게 되고, 그때부터는 조각 대신 익스텐트 기반으로 동작한다.
각 세그먼트는 자신이 소유한 익스텐트들을 상태에 따라 리스트로 관리한다.
익스텐트 상태가 바뀌면 이 리스트 간에 이동이 발생한다.
예를 들어 어떤 익스텐트가 가득 차면 NOT_FULL -> FULL, 다시 공간이 생기면 FULL -> NOT_FULL로 옮겨진다.
완전히 비게 되면 FREE로도 이동할 수 있다.
이 구조 덕분에 InnoDB는 각 세그먼트 단위로 공간을 세밀하게 관리하고, 빠르게 할당 여부를 판단할 수 있다.
하나의 INODE 페이지는 85개의 세그먼트(INODE Entry)만 관리할 수 있다.
XDES Page와 동일한 원리로 85개의 세그먼트를 초과하면 추가 INODE Page가 생긴다.
지금까지 내용을 요약해보자.
InnoDB는 파일 전체를 관리하는 메타데이터 구조를 여러 페이지에 걸쳐 계층적으로 유지한다.
InnoDB의 메타데이터 구조는 처음 보면 꽤 복잡해 보이지만, 한 번 전체 흐름을 잡고 나면 오히려 예측 가능하고 합리적인 구조였다.
아래는 내가 JCole의 블로그 자료를 바탕으로 전체 구조를 한눈에 볼수 있게 만든 도식이다.
