[2026] Linux 시리즈 #03 — 파일·inode 내부: ext4/XFS 온디스크 구조와 익스텐트
이 글의 핵심
온디스크 inode가 메타데이터와 데이터 위치를 어떤 자료구조로 표현하는지(ext4 익스텐트·XFS 포크·B+트리)를 운영·성능 튜닝 관점에서 풀어 씁니다.
시리즈 안내
#03 | 실무 트러블슈팅: Linux 디스크 full vs inode · 블록·저널·I/O 스케줄러: #09 디스크·블록 계층 · 개요: Linux 완전 가이드
1. 들어가며: 왜 “inode 내부”를 알아야 하는가
운영자가 df -i, debugfs, xfs_bmap 같은 도구를 쓸 때, 그 아래에는 온디스크 메타데이터 레이아웃과 할당기·저널·블록 계층이 있습니다. 증상은 inode 고갈 트러블슈팅과 같아 보여도, 랜덤 작은 I/O인지 메타데이터 갱신 폭주인지 익스텐트 단편화인지에 따라 대응이 갈립니다. 이 글은 ext4와 XFS에 한정해, inode 레코드가 데이터 위치를 어떻게 인코딩하는지를 커널·온디스크 관점에서 정리합니다. 저널 리플레이, 블록 할당 알고리즘, I/O 스케줄러는 범위가 커서 #09에서 다룹니다.
2. VFS inode와 온디스크 inode
2.1 VFS struct inode
리눅스 VFS의 inode는 캐시 슬래브·락·카운터·i_mapping 등을 포함한 인메모리 객체입니다. i_op, i_fop로 파일시스템별 연산으로 연결되고, 디스크 블록 주소는 파일시스템이 address_space·익스텐트 캐시 등으로 풀어 줍니다.
2.2 “온디스크 inode”란
mkfs 시점에 정해지는 inode 크기(예: 128B·256B·그 이상)와 슈퍼블록 필드 안에서, 각 inode 슬롯은 연속 테이블 또는 동적 청크에 기록됩니다. 모드·UID/GID·크기·타임스탬프·링크 수·데이터 위치가 이 레코드에 들어 갑니다. 경로의 마지막 이름 조각은 inode가 아니라 디렉터리의 데이터 블록(해시·트리 등 파일시스템별)에 저장됩니다.
핵심은 한 가지입니다. “파일이 어디에 얼마나 연속으로 놓였는가”를 효율적으로 표현하기 위해, 현대 로컬 파일시스템은 익스텐트(extent) — (논리 시작, 길이, 물리 시작) 튜플의 트리 또는 배열 — 를 씁니다.
3. ext4: 온디스크 inode와 i_block 공간
3.1 레이아웃 개요
ext4는 블록 그룹 단위로 inode 비트맵·블록 비트맵·inode 테이블을 배치합니다( flex_bg 등으로 메타데이터를 모을 수 있음). 온디스크 inode 안에는 고정 길이 필드와, 데이터 위치를 담는 i_block 영역(전통적으로 15개 __le32 슬롯 크기 부근의 공간)이 있습니다.
커널·포맷 버전에 따라 필드 정렬이 달라질 수 있으므로, 여기서는 의미 단위로 설명합니다.
- 호환 모드: 예전에는 이 영역이 직접 블록 12개 + 단일 간접 + 이중·삼중 간접으로 해석되었습니다.
- 익스텐트 모드: 동일한 바이트 공간이 익스텐트 헤더와 트리로 재해석됩니다.
INCOMPAT_EXTENTS플래그가 켜진 일반적인 ext4에서 작은 파일·디렉터리는 이 경로를 탑니다.
3.2 익스텐트 헤더와 트리
익스텐트 트리는 논리 블록 오프셋을 키로 하여 물리 블록 번호와 연속 길이를 찾는 작은 B-트리입니다.
- 헤더(
extent header): 이 블록이 리프인지 인덱스인지, 엔트리 개수, 깊이 등을 담습니다. 깊이가 0이면 아래가 리프, 0보다 크면 아래 블록은 인덱스 노드입니다. - 리프 엔트리(
extent): 논리 시작, 길이(블록 수), 물리 시작(블록 번호) 등의 조합으로 표현됩니다. 연속 할당이 되면 한 엔트리로 긴 구간을 표현할 수 있어, 레거시 삼중 간접보다 메타데이터와 CPU 비용이 유리합니다. - 인덱스 엔트리(
extent idx): 자식 블록 번호와, 그 아래 서브트리가 담당하는 논리 오프셋 하한을 묶어 탐색 경로를 만듭니다.
플래그 관점: inode가 익스텐트 모드일 때(대표적으로 EXT4_EXTENTS_FL 계열) i_block 바이트가 트리 루트로 해석됩니다. 포맷·마이그레이션 도구를 쓸 때 “legacy 블록 포인터 inode”와 “익스텐트 inode”를 구분하는 것이 첫 단계입니다.
운영 관점에서는 다음이 중요합니다.
- 메타데이터 쓰기량은 “몇 블록을 썼는가”뿐 아니라 트리 높이·분할·병합에도 좌우됩니다.
- 파일이 조각나면 리프·인덱스가 늘어나 inode·메타 블록 I/O가 증가할 수 있습니다.
3.3 인라인 데이터(inline data)와 fast commit
아주 작은 파일은 데이터 블록을 따로 잡지 않고 inode 여유 공간에 넣을 수 있습니다(인라인 데이터). 이는 inode 고갈과는 다른 축에서 블록 할당 압력을 줄입니다. 최신 커널·ext4 조합에서는 fast commit 경로가 저널 트랜잭션을 더 잘게 쪼개 특정 메타데이터 갱신 지연을 줄이는 등, 저널·fsync 패턴과 상호작용합니다(세부는 #09).
3.4 디렉터리와 해시 트리
ext4 디렉터리는 단순 리스트에서 해시 디렉터리(htree) 로 진화했습니다. 대량의 엔트리가 있는 디렉터리에서는 이름 조회가 디스크 상 트리 탐색이 되므로, “같은 ls라도” 콜드 캐시에서 I/O 패턴이 달라집니다. 이는 VFS dentry 캐시와 합쳐져 운영에서 관측되는 메타데이터 IOPS로 나타납니다.
4. XFS: dinode, 포크, BMBT
4.1 할당 그룹(AG)과 inode
XFS는 디스크를 할당 그룹(AG) 으로 나누고, 각 AG는 자유 공간·inode 정보를 B+트리로 관리하는 경우가 많습니다. inode는 고정 테이블 전체를 포맷 시 한 번에 깔아 두는 모델만이 아니라, 필요 시 inode 청크를 메타데이터 공간에서 잡아 B+트리에 매달는 쪽에 가깝습니다. 그래서 “inode 비트맵이 다 찼다”는 ext4 이미지와는 달리, 메타데이터 공간 부족과 inode 할당 실패가 동시에 논의되기도 합니다.
4.2 dinode와 데이터·속성 포크
온디스크 inode(dinode) 는 코어와 데이터 포크(data fork), 필요 시 속성 포크(attr fork) 로 나뉩니다.
- 데이터 포크: 파일 데이터의 익스텐트 목록을 담습니다. 짧으면 인라인, 길어지면 근처 익스텐트가 B+트리(BMBT, 블록 맵 B-트리) 로 확장됩니다.
- 속성 포크: 확장 속성(EA) 이 커지면 별도 포크가 자라 데이터와 독립적으로 공간을 씁니다.
백엔드·컨테이너 이미지에서 SELinux 라벨·xattr이 대량으로 붙는 환경에서는, 데이터 크기는 작은데 attr 포크가 비대해지는 패턴이 생길 수 있습니다. 이는 df -h만으로는 잘 안 보이고, getfattr·xfs_bmap·메타데이터 사용량 추적이 필요합니다.
4.3 익스텐트와 “스펙 프리올로케이션”
XFS는 지연 할당·스펙 프리온 등으로 연속 구간을 미리 잡아 두었다가 커밋 시점에 확정하는 경향이 있습니다. 순간적인 fallocate·대용량 순차 쓰기는 자유 공간 B+트리와 AG 간 밸런스에 영향을 주고, 다른 워크로드의 지연으로 번질 수 있습니다. 이는 블록 할당기·AG 선택 이슈로 #09에서 이어집니다.
5. ext4 vs XFS: inode 관점 요약
| 측면 | ext4 | XFS |
|---|---|---|
| inode 배치 | 블록 그룹의 inode 테이블·비트맵 | AG별 B+트리·inode 청크 |
| 데이터 위치 | i_block 내 익스텐트 트리 | 데이터 포크 → BMBT |
| 작은 파일 | 인라인 데이터 | 짧은 포크·인라인 성격 |
| 확장 속성 | inode 외부 블록으로 확장 가능 | attr 포크가 구조적으로 분리 |
| 운영 측면 | tune2fs, debugfs | xfs_info, xfs_bmap, xfs_db |
6. 관측 도구와 해석
6.1 ext4: debugfs로 익스텐트 확인
읽기 전용이라도 피크 시간대에는 부하가 있을 수 있습니다.
sudo debugfs -R 'stat /path/to/file' /dev/sdXN
출력의 Extents: 항목은 리프 익스텐트 개수를 가리키는 경우가 많습니다. 개수가 비정상적으로 많으면 조각화·작은 랜덤 쓰기를 의심합니다.
6.2 XFS: xfs_bmap
sudo xfs_bmap -v /path/to/file
논리 오프셋 → 물리 익스텐트가 한눈에 들어 옵니다. 스펙 프리올로케이션이 잡아 둔 예약 구간은 환경에 따라 unwritten 같은 표기로 보일 수 있습니다.
7. 프로덕션에서의 “패턴” (inode 층)
- 수백만 소량 파일: inode·dentry 캐시·디렉터리 블록 I/O가 병목. 배치 생성·샤딩·아카이브 포맷을 검토합니다.
- 대용량 순차 로그 한 파일: 익스텐트 수는 적을 수 있으나 단일 파일 성장이 저널·플러시와 맞물립니다.
- xattr·ACL 폭증: XFS attr 포크, ext4의 외부 EA 블록이 메타데이터 공간을 소모합니다.
- DB 테이블스페이스: 파일시스템 inode 자체보다 파일 내부 페이지 할당이 주 병목이지만, 동시 다발적 파일 생성(임시 테이블·sort 파일)에서는 inode·할당기가 드러납니다.
8. 마무리
이 글은 온디스크 inode가 익스텐트·트리·포크로 데이터 위치를 표현하는 방식을 ext4와 XFS로 압축해 설명했습니다. 저널 단계·복구·블록 buddy·스케줄러는 디스크 서브시스템 전체 이야기이므로 #09 Linux 디스크·블록 계층에서 이어집니다. 장애 직후의 실무 순서는 계속해서 디스크 vs inode 트러블슈팅을 기준으로 두면 됩니다.
내부 동작과 핵심 메커니즘
이 글의 주제는 「[2026] Linux 시리즈 #03 — 파일·inode 내부: ext4/XFS 온디스크 구조와 익스텐트」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.