All Articles

Space 파일 레이아웃 기초

원문: The basics of InnoDB space file layout

InnoDB 핵심을 이해하기 위한 여행 시작 에서, InnoDB 내부 구조 문서화를 위한 innodb_diagrams 프로젝트를 소개했습니다. 이 포스트의 다이어그램들은 innodb_diagrams를 이용해 그렸습니다.

InnoDB의 데이터 스토리지 모델은 MySQL 컨텍스트 상에서 “tablespace”라고 불리는 “space”를 사용하며, 이는 InnoDB 자체에서 “file spaces”라고도 합니다. Space는 OS 수준의 여러 실제 파일 (예를 들어, ibdata1, ibdata2 등)로 구성될 수 있지만, 결국 논리적으론 단일 파일입니다. 즉, 별개의 파일들이 서로 물리적으로 연결된 것처럼 처리됩니다.

InnoDB의 각 space에는 32 bit의 정수인 space ID가 할당되어 있으며, 이 ID는 해당 space를 참조할 목적으로 다른 곳에서 많이 사용됩니다. InnoDB에는 항상 “system space”가 있으며, system space의 space ID는 항상 0입니다. System space는 InnoDB가 필요로 하는 다양한 특수 부기에 사용됩니다. 현재 InnoDB는 “테이블 당 파일 (file per table)” 형태로만 추가 space를 지원하며, 이 space는 각 MySQL 테이블에 대한 .ibd 파일을 생성합니다. 내부적으로 이 .ibd 파일은 실제로 여러 테이블을 포함할 수 있을만큼 완전한 기능을 갖춘 space지만, MySQL 구현 상에서는 단일 테이블만 포함합니다.

Pages

각 space는 보통 16KiB의 페이지로 나뉘어집니다 (UNIV_PAGE_SIZE를 정의하는 컴파일 타임 변수가 변경되거나 InnoDB 압축이 사용되는 경우 다를 수 있음). 한 space 내의 각 페이지에는 “오프셋”이라고 하는 32 bit 정수 페이지 번호가 할당됩니다. 이 페이지 번호는 파일의 시작 부분부터 해당 페이지 위치까지의 실제 오프셋 (다중 파일 space의 경우 반드시 파일이 기준일 필요는 없음)입니다. 따라서 페이지 0은 파일 오프셋 0에, 페이지 1은 파일 오프셋 16,384에 위치합니다. (이해가 빠르신 분들은 InnoDB가 최대 64TiB의 데이터만 저장할 수 있다는 걸 눈치 채셨을 겁니다. 이는 실제로 space 당 한도이며, 기본적으로 32 bit 정수인 페이지 번호에 디폴트 페이지 크기를 곱하면 다음과 같은 결과가 나오기 때문입니다: 2^32 x 16 KiB = 64 TiB)

간단한 페이지 레이아웃은 아래와 같습니다:

basic-page-overvie

모든 페이지에는 38 byte FIL 헤더와 8 byte FIL 트레일러가 있습니다 (FIL은 “파일”의 축약어입니다). 헤더에는 페이지의 나머지 구조를 결정하는 Page Type 필드가 있습니다. FIL 헤더 및 트레일러의 구조는 다음과 같습니다:

fil-header-trailer

FIL 헤더 및 트레일러에는 아래와 같은 구조가 포함됩니다 (위 그림 순서와 다름):

  • Page Type은 헤더에 저장됩니다. Page Type은 페이지 데이터의 나머지 부분을 파싱하는 데에 필요합니다. 페이지는 파일 공간 관리, extent 관리, 트랜잭션 시스템, 데이터 딕셔너리, undo 로그, blob은 물론 index (테이블 데이터)를 위해 할당됩니다.
  • Space ID는 헤더에 저장됩니다.
  • 페이지를 초기화할 때, 페이지 번호를 헤더에 저장합니다. Offset (Page Number) 필드에서 읽은 페이지 번호가 파일 오프셋에 기반해 일치하는지 확인하는 것은 페이지를 읽는 작업이 제대로 수행되었는지 구별하는 데에 사용됩니다. 그리고 이 필드가 초기화 값을 가지면, 페이지가 초기화 되었음을 나타냅니다.
  • 32 bit Checksum이 헤더에 저장되고, 예전 형식 (및 깨진) 32 bit Old-style Checksum이 트레일러에 저장됩니다. 오래된 checksum은 더 이상 사용되지 않고, 그 공간은 추후에 재사용됩니다.
  • 이 페이지 타입에 대한 논리적인 이전 (Previous Page) 및 다음 (Next Page) 페이지에 대한 포인터는 헤더에 저장됩니다. 이렇게 하면 페이지의 이중 연결 리스트 (doubly-linked list)를 구성할 수 있으며, INDEX 페이지에서 동일한 레벨의 모든 페이지를 연결하는 데 사용됩니다. 이 경우, 효율적으로 full table scan을 할 수 있습니다. 많은 페이지 타입은 이 필드를 사용하지 않습니다.
  • 페이지가 마지막으로 수정된 시점 (last page modification)을 나타내는 64 bit 로그 시퀀스 번호 (LSN)가 헤더에 저장되고 동일한 LSN의 하위 32 bit가 트레일러에 저장됩니다.
  • 64 bit Flush LSN 필드는 헤더에 저장되며, 실제로 전체 시스템 중 하나의 페이지 (space 0의 페이지 0)에서만 값이 변합니다. 이때, 전체 시스템 (모든 space)을 통틀어 flush 된 페이지의 LSN 중 가장 높은 LSN 값을 저장합니다. 이 필드는 space의 나머지 필드 중에서도 용도에 맞게 재사용할 수 있는 훌륭한 후보입니다.

Space 파일

Space 파일은 여러 페이지 (최대 2^32 페이지)가 연결된 것일 뿐입니다. 보다 효율적인 관리를 위해 페이지는 1 MiB (16 KiB의 디폴트 페이지 크기를 갖는 64개의 인접한 페이지들)의 블록으로 그룹화되며, 이는 “extent”라고 불립니다. 많은 구조체는 space 내에 페이지를 할당하기 위해 extent로만 참조합니다.

InnoDB는 모든 페이지, extents 및 space 자체를 추적하기 위해 부기를 수행해야 하기 때문에 space 파일에는 몇 가지 필수적인 슈퍼-구조가 있습니다:

space-file-overview

Space의 **첫 번째 페이지 (페이지 0)**는 항상 FSP_HDR 또는 “file space header” 페이지입니다. FSP_HDR 페이지는 space의 크기 및 여유 공간, fragmented 및 전체 extent 목록을 추적하는 FSP 헤더 구조를 (복잡하게) 포함하고 있습니다. (여유 공간 관리에 대한 자세한 설명은 추후 다른 포스트에서 다룰 예정입니다.)

FSP_HDR 페이지는 내부적으로 256 extents (또는 16,384 페이지, 256 MiB)에 대한 부기 정보를 저장하기에 충분한 공간이 있으므로, XDES 페이지 형식으로 부기 정보를 얻으려면 16,384 페이지마다 추가 공간을 예약해놔야 합니다. XDESFSP_HDR 페이지의 구조는 FSP 헤더 구조가 XDES 페이지에서는 줄어든다는 점을 제외하고는 동일합니다. 이 추가 페이지는 space 파일이 커짐에 따라 자동으로 할당됩니다.

각 space의 **세 번째 페이지 (페이지 2)**는 INODE 페이지로, 파일 세그먼트 (extent의 그룹 및 단일 할당된 “fragment” 페이지의 배열)와 관련된 목록을 저장하는 데 사용됩니다. 각 INODE 페이지는 85개의 INODE 항목을 저장할 수 있으며, 각 index는 2개의 INODE 항목이 필요합니다. (INODE 항목과 파일 세그먼트에 대한 자세한 설명은 추후 다른 포스트에서 다룰 예정입니다.)

FSP_HDR 또는 XDES 페이지 다음에 등장하는 페이지는 IBUF_BITMAP 페이지인데, 이 페이지는 insert buffering과 관련된 부기 정보로 사용됩니다. 그러나 이 페이지와 관련된 내용은 이 게시물이 다루는 범위를 벗어나므로 여기선 따로 설명하지 않겠습니다.

System space

System space (space 0)는 InnoDB에서 특별한 space이며, 고정된 페이지 번호를 갖는 상당수의 페이지를 포함하고 있어 InnoDB의 작업에 중요한 정보를 광범위하게 저장하고 있습니다. System space도 다른 space와 마찬가지로 하나의 space이기 때문에 처음 3개의 페이지로 FSP_HDR, IBUF_BITMAPINODE 페이지를 가지고 있습니다. 그 뒤부터는 약간 특별합니다 (다릅니다):

ibdata1-file-overview

다음 페이지가 할당됩니다:

  • Page 3, type SYS: Insert buffering 관련 헤더 및 부기 정보
  • Page 4, type INDEX: Insert buffering에 사용되는 index 구조의 root 페이지
  • Page 5, type TRX_SYS: 최신 트랜잭션 ID, MySQL 바이너리 로그 정보, double write buffer extent의 위치와 같은 InnoDB의 트랜잭션 시스템 작동 관련 정보
  • Page 6, type SYS: 첫 번째 롤백 세그먼트 페이지. 롤백 세그먼트 데이터를 저장하기 위해 필요에 따라 추가 페이지 (또는 extent)가 할당됩니다.
  • Page 7, type SYS: 데이터 딕셔너리를 구성하는 index의 root 페이지 번호를 포함하는 데이터 딕셔너리 관련 헤더. Root 페이지 번호가 데이터 딕셔너리 자체에 저장되므로, 이 페이지는 다른 index (테이블)를 찾기 위해 필요합니다.
  • Pages 64-127: Double write buffer의 64 페이지 (extent)로 구성된 첫 번째 블록. Double write buffer는 InnoDB 복구 메커니즘의 일부로 사용됩니다.
  • Pages 128-191: Double write buffer의 두 번째 블록

다른 모든 페이지는 필요에 따라 index, 롤백 세그먼트, undo 로그 등에 할당됩니다.

테이블 당 (per-table) space 파일

InnoDB는 “file per table” 모드를 제공합니다. 이 모드는 생성된 MySQL 테이블 각각에 대해 파일 (위에서 설명했다시피 실제로는 하나의 space)을 생성합니다. 이 기능의 더 나은 이름은 “file per table” 보다는 “space per table” 일 수 있습니다. 각 테이블에 대해 생성된 .ibd 파일은 일반적인 space 파일 구조를 가집니다:

ibd-file-overview

런타임에 index를 추가하는 “fast index creation”을 무시하면, 필수 3개 페이지 다음에 오는 페이지는 테이블 생성 시 정의된 순서대로 테이블의 각 index의 root 페이지가 됩니다. 페이지 3은 clustered index의 root가 되고, 페이지 4는 첫 번째 secondary 키의 root가 됩니다.

대부분의 InnoDB 부기 구조는 system space에 저장되기 때문에, per-table space에 할당된 대부분의 페이지는 INDEX 유형이고 테이블 데이터를 저장합니다.

다음 포스트

다음 포스트에서는 InnoDB 내의 여유 공간 관리 방법 (extent descriptor, 파일 세그먼트 (inode) 및 리스트)에 대해 살펴볼 것입니다.

Published Aug 11, 2019

Mijin An
Working with two cats 🐱🦁