본문으로 건너뛰기
  1. Posts/

[MySQL] InnoDB에서 테이블은 정말 파일 하나로 저장될까? Tablespace, Segment, Extent, Page, Row

·6 분
NineKoo9
작성자
NineKoo9
목차

이 문서는 의문을 가지고 있던 지점을 AI 에이전트와 대화하며 정리한 메모입니다. InnoDB 테이블이 디스크에 저장되는 구조를 tablespace, segment, extent, page, row 순서로 정리합니다.

먼저 전제
#

이 글은 innodb_file_per_table=ON 인 일반적인 InnoDB 비파티션 테이블을 기준으로 설명한다. 이 전제가 있으면 .ibd 파일 하나를 중심으로 구조를 이해하기 쉽다.

다만 아래 경우에는 설명이 달라진다.

  • innodb_file_per_table=OFF 이면 새 테이블은 시스템 tablespace에 저장된다.
  • general tablespace를 쓰면 여러 테이블이 하나의 tablespace를 공유할 수 있다.
  • partitioned table은 파티션별로 별도 tablespace/file이 생길 수 있다.

각 테이블은 하나의 파일인가?
#

보통은 그렇지만, 정확히는 “테이블은 tablespace에 저장된다"가 맞다.

innodb_file_per_table=ON 일 때, 일반적인 InnoDB 테이블은 single-table tablespace에 저장되고, 그 tablespace가 보통 하나의 .ibd 파일로 존재한다.

/var/lib/mysql/mydb/
├── user.ibd
├── orders.ibd
└── product.ibd

이 모드에서는 .ibd 하나 안에 해당 테이블의 클러스터드 인덱스(실제 행 데이터)세컨더리 인덱스 가 함께 들어간다.

핵심은 다음이다.

  • Table = File 은 엄밀한 원리가 아니라 file-per-table 구성에서 흔히 보이는 결과다.
  • 더 정확한 표현은 Table -> Tablespace -> backing file(.ibd) 이다.

하나의 파일 안에는 여러 페이지가 있는가?
#

Yes. .ibd 파일은 동일 크기의 database page가 연속으로 배치된 구조다.

user.ibd
┌─────────┬─────────┬─────────┬─────────┬─────┐
│ Page 0  │ Page 1  │ Page 2  │ Page 3  │ ... │
└─────────┴─────────┴─────────┴─────────┴─────┘
  • page 크기 기본값은 16KB
  • innodb_page_size 에 따라 4KB, 8KB, 16KB, 32KB, 64KB가 가능
  • 디스크 I/O와 Buffer Pool 캐싱도 결국 이 page 단위로 일어난다

전체 계층 구조
#

file-per-table 기준으로 보면 다음 식으로 이해하는 게 가장 덜 헷갈린다.

Table / Index
 └── Tablespace
      ├── backing file: .ibd
      └── Segment
           └── Extent
                └── Page
                     └── Record(Row)

여기서 중요한 점은 다음 두 가지다.

  • tablespace가 상위 개념이고, 파일은 그 tablespace를 담는 물리적 저장소다.
  • segment / extent / page 는 tablespace 내부 공간 관리 구조다.

각 계층 상세
#

1) Tablespace와 File
#

InnoDB는 데이터를 먼저 tablespace에 배치하고, 그 tablespace를 하나 이상의 파일로 유지한다.

  • single-table tablespace: 테이블 1개 전용
  • system tablespace: 여러 내부 구조와 테이블이 공유
  • general tablespace: 사용자가 여러 테이블을 함께 넣을 수 있음
  • undo tablespace: undo 로그용

즉, .ibd 파일을 중심으로 설명할 수는 있지만, 본질은 파일보다 tablespace 다.

2) Segment
#

segment는 tablespace 내부의 논리적 저장 단위다. 여기서 헷갈리기 쉬운 포인트가 있다.

  • InnoDB는 각 인덱스마다 2개의 segment 를 둔다.
  • 하나는 non-leaf node용, 다른 하나는 leaf node용 이다.
  • 여기서 말하는 segment는 rollback segment와 다른 개념이다.

즉, 기존 설명처럼 rollback segment를 같은 .ibd 안의 일반 segment로 묶어 말하면 부정확하다.

또한 leaf node에 저장되는 내용도 인덱스 종류에 따라 다르다.

인덱스leaf page에 저장되는 것
Clustered Index실제 행 데이터
Secondary Index보조 인덱스 키 + 기본 키 값

그래서 “leaf node = 실제 row"는 클러스터드 인덱스에 대해서만 정확한 표현이다.

3) Extent
#

extent는 InnoDB가 공간을 묶어서 관리하는 단위다.

Page 크기Extent 크기구성
4KB1MB256 pages
8KB1MB128 pages
16KB1MB64 pages
32KB2MB64 pages
64KB4MB64 pages

보완할 점은 두 가지다.

  • extent는 tablespace 안에서 연속된 page 묶음이지, 파일시스템/디스크 물리 섹터까지 반드시 연속이라는 뜻은 아니다.
  • segment는 처음부터 extent 단위로만 커지지 않는다. 처음 32 page는 한 장씩, 그 이후부터 extent 단위 할당이 시작된다.

즉, “extent = 항상 실제 물리 디스크에서 완전히 연속된 큰 덩어리"라고 이해하면 과하다. 다만 InnoDB는 extent 단위 관리를 통해 논리적 지역성(locality) 과 순차 접근 가능성을 높이려 한다.

4) Page
#

page는 InnoDB의 기본 디스크 I/O 단위이자 Buffer Pool 캐싱 단위다.

정확성을 위해 page 구조는 “페이지 종류에 따라 조금씩 다르다"는 전제를 두는 게 맞다. 아래는 index page 기준의 단순화된 그림이다.

┌──────────────────────────────────┐
│ FIL Header (38 bytes)            │
├──────────────────────────────────┤
│ Page Header (index page 전용)    │
├──────────────────────────────────┤
│ Infimum / Supremum records       │
├──────────────────────────────────┤
│ User Records                     │
├──────────────────────────────────┤
│ Free Space                       │
├──────────────────────────────────┤
│ Page Directory                   │
├──────────────────────────────────┤
│ FIL Trailer (8 bytes)            │
└──────────────────────────────────┘

자주 보는 page 타입은 다음 정도다.

타입역할
FIL_PAGE_INDEXB-tree node page
FIL_PAGE_TYPE_FSP_HDRtablespace header page
FIL_PAGE_IBUF_BITMAPchange buffer / allocation bitmap
FIL_PAGE_INODEsegment inode 정보
FIL_PAGE_UNDO_LOGundo log page

5) Row / Record
#

InnoDB에서 “row"는 결국 clustered index leaf record 로 저장된다.

큰 가변 길이 컬럼은 항상 같은 방식으로 처리되지 않는다.

  • row format과 page size에 따라 일부는 페이지 내부, 일부는 off-page/overflow page 로 갈 수 있다.
  • DYNAMIC / COMPRESSED row format에서는 긴 컬럼이 20-byte pointer만 남기고 외부 page로 빠질 수 있다.
  • COMPACT / REDUNDANT앞부분 768 byte를 inline으로 두고 나머지를 외부 page에 저장할 수 있다.

즉, “BLOB/TEXT는 무조건 overflow page로 간다” 혹은 “항상 20byte 포인터만 남는다"는 식으로 단정하면 row format 차이를 놓치게 된다.


실제 쿼리 실행 흐름
#

SELECT * FROM user WHERE id = 5;

대략적인 흐름은 다음과 같다.

1. Buffer Pool에 필요한 page가 있는지 확인
2. 없으면 clustered index의 root/branch/leaf page를 따라 내려감
3. 필요한 page를 디스크에서 읽어 Buffer Pool에 적재
4. leaf page 안의 record를 찾아 반환

여기서 핵심은 쿼리가 row 단위로 보이더라도, 실제 저장 엔진은 page 단위로 읽고 page 단위로 캐시한다는 점이다.


정리
#

단위성격핵심 역할
Table논리사용자 관점의 테이블
Tablespace논리+물리 연결테이블/인덱스가 배치되는 저장 공간
File (.ibd)물리single-table tablespace의 backing file
Segment논리인덱스 leaf/non-leaf 공간 관리
Extent공간 할당 단위page 묶음
PageI/O 단위디스크 읽기/쓰기와 Buffer Pool 캐싱의 최소 단위
Record(Row)데이터 단위실제 행 레코드

핵심 포인트만 다시 묶으면:

  • 테이블 = 파일“은 file-per-table 환경에서 자주 보이는 결과이지 본질 그 자체는 아니다.
  • InnoDB는 실제로 tablespace -> segment -> extent -> page 구조로 공간을 관리한다.
  • 성능 관점에서 가장 중요한 단위는 결국 page 다.

References
#