XFS 파일시스템(Filesystem)
대용량·고병렬 환경에 최적화된 XFS를 대상으로 Allocation Group 분할이 락 경합(Contention)을 줄이는 방식, B+tree 기반 free space/inode 인덱싱, extent 지연(Latency) 할당과 writeback 경로, 로그(WAL) 재생 및 복구 절차, reflink/COW 동작, quota/프로젝트 ID 운용, xfs_db·xfs_repair·xfs_scrub 활용법까지 성능과 안정성 관점에서 상세히 설명합니다.
핵심 요약
- Allocation Group (AG) — XFS는 파일시스템을 독립적인 AG로 분할하여 병렬 할당과 확장성을 확보합니다.
- B+tree 메타데이터 — inode, free space, extent 등 모든 메타데이터를 B+tree로 관리하여 대규모 파일시스템에서도 O(log n) 검색을 보장합니다.
- Extent 기반 할당 — 파일 데이터를 연속된 블록 범위(extent)로 할당하여 단편화를 줄이고 대용량 파일 처리를 효율화합니다.
- Delayed Allocation — 실제 디스크 블록 할당을 쓰기 시점이 아닌 플러시(Flush) 시점까지 지연하여 최적의 연속 공간을 확보합니다.
- Journaling (Log) — 메타데이터 변경을 로그에 먼저 기록하여 비정상 종료 후 빠른 복구를 보장합니다.
- Inode 구조 — 64비트 inode 번호, 인라인 확장 속성(xattr), 유연한 inode 크기를 지원합니다.
- 디렉토리 구조 — 엔트리 수에 따라 shortform → leaf → node → btree로 자동 전환되는 다단계 디렉토리를 사용합니다.
- xfs_repair — 오프라인 파일시스템 검사·복구 도구로, AG 단위로 메타데이터 일관성을 검증합니다.
단계별 이해
- AG 기반 파티셔닝 이해 — 파일시스템이 여러 AG로 분할되는 구조를 파악합니다.
각 AG는 자체 슈퍼블록(Superblock), free space B+tree, inode B+tree를 보유하므로 다중 스레드가 동시에 할당할 수 있습니다.
- B+tree 메타데이터 구조 탐색 — XFS가 메타데이터를 B+tree로 관리하는 방식을 확인합니다.
inode 할당, free space 추적, extent 매핑 모두 B+tree에 기반하므로, 이 자료구조가 XFS 성능의 핵심입니다.
- 쓰기 경로와 Delayed Allocation 추적 —
write()호출부터 실제 디스크 기록까지의 흐름을 따라갑니다.Page Cache에 dirty 페이지를 기록한 뒤, writeback 시점에 extent를 할당하여 연속 블록 배치를 최적화합니다.
- 저널(Log) 동작 분석 — 메타데이터 트랜잭션이 로그에 기록되고 체크포인트되는 과정을 확인합니다.
로그 항목이 순환 버퍼에 기록되며, 체크포인트 이후에 해당 공간이 재사용 가능해집니다.
- 복구와 xfs_repair 실습 — 비정상 종료 후 로그 리플레이와 오프라인 복구 과정을 검증합니다.
마운트 시 자동 로그 리플레이로 메타데이터를 복구하고, 심각한 손상 시
xfs_repair로 AG별 일관성을 복원합니다.
개요 & 역사
XFS는 1993년 SGI(Silicon Graphics)가 IRIX 운영체제를 위해 개발한 64비트 고성능 저널링 파일시스템입니다. 2001년 Linux 커널 2.4에 이식되었으며, 2014년 RHEL 7에서 기본 파일시스템으로 채택된 이후 엔터프라이즈 Linux의 사실상 표준이 되었습니다.
XFS 역사
| 연도 | 이벤트 |
|---|---|
| 1993 | SGI IRIX 5.3에서 XFS 최초 공개 |
| 2001 | Linux 커널 2.4에 이식 (SGI 오픈소스 기여) |
| 2006 | Delayed Allocation 지원 추가 |
| 2012 | v5 on-disk format: CRC32C 셀프체크 메타데이터 |
| 2014 | RHEL 7 기본 파일시스템 채택 |
| 2016 | Reflink / COW 지원 (커널 4.9) |
| 2019 | Online repair 프레임워크 개발 시작 |
| 2023 | Online fsck (xfs_scrub) 안정화, Large extent counters |
XFS 주요 스펙
| 항목 | 값 |
|---|---|
| 최대 볼륨 크기 | 8 EiB (263 바이트) |
| 최대 파일 크기 | 8 EiB |
| 최대 파일 수 | 264 (동적 inode 할당) |
| 파일명 길이 | 255 바이트 |
| 블록 크기 | 512B / 1K / 2K / 4K (기본 4K, 최대 64K) |
| 타임스탬프 범위 | 1901 ~ 2486 (나노초 정밀도) |
| 저널 방식 | Metadata-only Write-Ahead Logging (WAL) |
| On-disk format | v5 (CRC32C self-describing metadata, 기본) |
ext4 / Btrfs / XFS 비교
| 특성 | ext4 | XFS | Btrfs |
|---|---|---|---|
| 최대 볼륨 | 1 EiB | 8 EiB | 16 EiB |
| 최대 파일 | 16 TiB | 8 EiB | 16 EiB |
| 온라인 축소 | 지원 | 미지원 | 지원 |
| 온라인 확장 | 지원 | 지원 | 지원 |
| COW / 스냅샷 | 미지원 | Reflink (4.9+) | 기본 COW |
| 저널링 | JBD2 (데이터+메타) | WAL (메타 전용) | COW (암묵적) |
| inode 할당 | 고정 (mkfs 시 결정) | 동적 | 동적 |
| 병렬 I/O | Block Group 단위 | AG 기반 고도 병렬 | Chunk 기반 |
| 대형 파일 성능 | 우수 | 최우수 | 우수 |
| RHEL 기본 FS | RHEL 6 | RHEL 7+ | 미채택 |
아키텍처 & 디스크 레이아웃
XFS의 핵심 설계 철학은 Allocation Group(AG) 기반 병렬 처리입니다. 전체 파일시스템을 독립적인 AG로 분할하여 각 AG가 자체 공간 관리 구조를 가지므로, 멀티코어 환경에서 메타데이터 경합 없이 병렬 할당이 가능합니다.
xfs_sb 주요 필드
/* fs/xfs/libxfs/xfs_format.h */
typedef struct xfs_sb {
__uint32_t sb_magicnum; /* 0x58465342 ('XFSB') */
__uint32_t sb_blocksize; /* 파일시스템 블록 크기 (바이트) */
xfs_rfsblock_t sb_dblocks; /* 데이터 영역 총 블록 수 */
xfs_rfsblock_t sb_rblocks; /* 리얼타임 영역 블록 수 */
xfs_rtblock_t sb_rextents; /* 리얼타임 extent 수 */
uuid_t sb_uuid; /* 파일시스템 UUID */
xfs_fsblock_t sb_logstart; /* 내부 로그 시작 블록 (0=외부) */
xfs_ino_t sb_rootino; /* 루트 디렉토리 inode 번호 */
xfs_agblock_t sb_agblocks; /* AG당 블록 수 */
xfs_agnumber_t sb_agcount; /* AG 개수 */
__uint32_t sb_sectsize; /* 디스크 섹터 크기 */
__uint16_t sb_inodesize; /* inode 크기 (기본 512) */
__uint16_t sb_inopblock; /* 블록당 inode 수 */
__uint32_t sb_versionnum; /* 기능 비트 마스크 */
__uint32_t sb_features2; /* 확장 기능 플래그 */
__uint32_t sb_features_compat; /* v5: 호환 기능 */
__uint32_t sb_features_incompat; /* v5: 비호환 기능 (reflink 등) */
__uint32_t sb_crc; /* v5: CRC32C 체크섬 */
...
} xfs_sb_t;
코드 설명
- sb_magicnum
xfs_sb는 XFS 파일시스템의 수퍼블록 온디스크 구조체로,fs/xfs/libxfs/xfs_sb.h에 정의됩니다.sb_magicnum은0x58465342('XFSB') 매직 넘버로 XFS 파일시스템을 식별합니다. - sb_blocksize / sb_dblocks
sb_blocksize는 파일시스템 블록 크기(기본 4096바이트)이며,sb_dblocks는 데이터 영역의 총 블록 수를 나타냅니다. 이 두 값의 곱이 파일시스템 전체 크기입니다. - sb_agblocks / sb_agcount
sb_agblocks와sb_agcount는 AG(Allocation Group) 구성을 정의합니다. 볼륨을sb_agcount개의 AG로 등분하며, 각 AG는sb_agblocks개의 블록을 포함합니다. - sb_logstart내부 저널(로그)의 시작 블록 번호입니다. 0이면 외부 로그 디바이스를 사용하는 것을 의미합니다.
- sb_features_incompat / sb_crcv5 포맷에서 추가된 필드로,
sb_features_incompat는 reflink 등 비호환 기능 플래그를 저장하고,sb_crc는 수퍼블록 자체의 CRC32C 체크섬으로 메타데이터 무결성을 검증합니다.
Allocation Groups 상세
XFS 볼륨은 여러 개의 Allocation Group(AG)으로 등분됩니다. 각 AG는 자체 수퍼블록 복사본, 프리 스페이스 B+tree, inode 관리 구조를 가집니다. 이 설계 덕분에 여러 스레드(Thread)가 서로 다른 AG에서 동시에 할당 작업을 수행할 수 있습니다.
AGF (AG Free Space)
AGF 헤더는 각 AG의 프리 블록 관리를 담당합니다. 두 개의 B+tree를 유지합니다:
| B+tree | 키 | 용도 |
|---|---|---|
| BNO tree | 시작 블록 번호 | 특정 위치 근처에서 할당 (공간적 인접성) |
| CNT tree | extent 크기 | 요청 크기에 가장 적합한 free extent 탐색 |
/* fs/xfs/libxfs/xfs_format.h */
typedef struct xfs_agf {
__be32 agf_magicnum; /* 'XAGF' */
__be32 agf_versionnum;
__be32 agf_seqno; /* AG 번호 */
__be32 agf_length; /* AG 블록 수 */
__be32 agf_roots[2]; /* BNO, CNT B+tree 루트 */
__be32 agf_levels[2]; /* BNO, CNT B+tree 높이 */
__be32 agf_flfirst; /* AGFL 첫 활성 항목 */
__be32 agf_fllast; /* AGFL 마지막 활성 항목 */
__be32 agf_flcount; /* AGFL 활성 항목 수 */
__be32 agf_freeblks; /* AG 내 총 free 블록 */
__be32 agf_longest; /* 가장 긴 free extent 크기 */
__be32 agf_rmap_root; /* v5: reverse mapping B+tree 루트 */
__be32 agf_refcount_root; /* v5: refcount B+tree 루트 */
...
} xfs_agf_t;
AGI (AG Inode Management)
AGI는 AG 내 inode 할당을 관리합니다. Inode B+tree로 사용 중인 inode chunk를 추적하고, Free Inode B+tree로 여유 inode가 있는 chunk를 빠르게 찾습니다.
/* fs/xfs/libxfs/xfs_format.h */
typedef struct xfs_agi {
__be32 agi_magicnum; /* 'XAGI' */
__be32 agi_versionnum;
__be32 agi_seqno; /* AG 번호 */
__be32 agi_length; /* AG 블록 수 */
__be32 agi_count; /* AG 내 할당된 inode 수 */
__be32 agi_root; /* Inode B+tree 루트 */
__be32 agi_level; /* Inode B+tree 높이 */
__be32 agi_freecount; /* AG 내 free inode 수 */
__be32 agi_newino; /* 가장 최근 할당 inode chunk */
__be32 agi_free_root; /* Free Inode B+tree 루트 */
__be32 agi_free_level; /* Free Inode B+tree 높이 */
...
} xfs_agi_t;
AGFL (AG Free List)
AGFL은 B+tree 분할/병합 시 필요한 메타데이터 블록을 예약하는 소규모 풀입니다. B+tree 조작 중에 추가 블록이 필요하면 AGFL에서 꺼내 쓰고, B+tree가 축소되면 반납합니다. 이는 B+tree 수정과 공간 할당 사이의 순환 의존성을 해결합니다.
병렬 I/O 스케일링
/* AG별 독립 잠금 — fs/xfs/xfs_mount.h */
typedef struct xfs_perag {
struct xfs_mount *pag_mount;
xfs_agnumber_t pag_agno; /* AG 번호 */
atomic_t pag_ref; /* 참조 카운트 */
struct rw_semaphore pag_ici_lock; /* inode cache lock */
struct xfs_buf *pag_agf_bp; /* AGF 버퍼 */
struct xfs_buf *pag_agi_bp; /* AGI 버퍼 */
...
} xfs_perag_t;
코드 설명
- xfs_perag
xfs_perag는 AG별 런타임 상태를 관리하는 인메모리 구조체입니다(fs/xfs/xfs_mount.h). 각 AG가 독립적인 잠금과 캐시를 가지므로 멀티코어 환경에서 AG 간 잠금 경합 없이 병렬 할당이 가능합니다. - pag_mount / pag_agno
pag_mount는 소속 파일시스템의xfs_mount를 가리키고,pag_agno는 이 AG의 고유 번호입니다. - pag_ref원자적 참조 카운트로, 이 AG에 접근 중인 스레드 수를 추적합니다. 참조 카운트가 0이 될 때까지 AG 자원이 해제되지 않습니다.
- pag_ici_lockAG 내 inode 캐시를 보호하는
rw_semaphore입니다. 읽기는 공유 잠금, 삽입/삭제는 배타 잠금으로 동시 접근을 제어합니다. - pag_agf_bp / pag_agi_bpAGF와 AGI 헤더의
xfs_buf캐시 포인터입니다. 자주 참조되는 AG 헤더를 메모리에 유지하여 반복 디스크 읽기를 방지합니다.
B+tree 구조
XFS는 모든 메타데이터를 B+tree로 관리합니다. 이는 O(log n) 탐색 성능과 대규모 데이터셋에서의 일관된 성능을 보장합니다.
XFS B+tree 종류
| B+tree | 위치 | 키 | 용도 |
|---|---|---|---|
| BNO B+tree | AGF | 시작 블록 번호 | Free space (위치별 검색) |
| CNT B+tree | AGF | extent 크기 | Free space (크기별 검색) |
| Inode B+tree | AGI | inode 번호 | Inode chunk 추적 |
| Free Inode B+tree | AGI | inode 번호 | Free inode chunk 추적 |
| Reverse Map B+tree | AGF (v5) | 물리 블록 | 역방향 매핑 (online repair) |
| Refcount B+tree | AGF (v5) | 시작 블록 | Reflink 참조 카운트(Reference Count) |
| Extent B+tree | Inode data fork | 파일 오프셋(Offset) | 파일 extent 매핑 |
| Directory B+tree | Inode data fork | 해시(Hash)값 | 대형 디렉토리 엔트리 |
| Attr B+tree | Inode attr fork | 이름 해시 | 확장 속성(Extended Attribute) |
Short-format vs Long-format
XFS B+tree는 두 가지 포인터 형식을 사용합니다:
| 형식 | 범위 | 사용처 |
|---|---|---|
| Short-format | AG 내부 (AG 상대 블록 번호) | AGF/AGI의 free space, inode, rmap, refcount B+tree |
| Long-format | 전체 파일시스템 (절대 블록 번호) | Inode data fork의 extent B+tree, 디렉토리/속성 B+tree |
/* B+tree 커서 — fs/xfs/libxfs/xfs_btree.h */
struct xfs_btree_cur {
struct xfs_mount *bc_mp; /* 파일시스템 마운트 */
const struct xfs_btree_ops *bc_ops; /* B+tree 연산 함수 */
uint bc_btnum; /* B+tree 종류 식별자 */
int bc_nlevels; /* 트리 높이 */
union {
struct {
struct xfs_buf *agbp; /* AG 헤더 버퍼 (short) */
xfs_agnumber_t agno; /* AG 번호 */
} s;
struct {
struct xfs_inode *ip; /* inode (long) */
int whichfork;
} l;
} bc_ino;
struct xfs_btree_level bc_levels[]; /* 레벨별 상태 */
};
코드 설명
- xfs_btree_cur
xfs_btree_cur는 B+tree 탐색/수정 시 사용하는 커서 구조체입니다(fs/xfs/libxfs/xfs_btree.h). 모든 B+tree 연산은 이 커서를 통해 현재 위치와 컨텍스트를 추적합니다. - bc_opsB+tree 유형별 연산 콜백(
xfs_btree_ops) 포인터입니다. 키 비교, 레코드 읽기/쓰기, 블록 할당 등을 추상화하여 BNO/CNT/Inode/Rmap 등 모든 B+tree가 공통 알고리즘을 공유합니다. - bc_ino (union)Short-format과 Long-format B+tree를 구분하는 union입니다.
.s는 AG 내부 B+tree용으로 AG 번호와 헤더 버퍼를 저장하고,.l은 inode 기반 B+tree용으로 대상 inode와 fork 종류를 저장합니다. - bc_levels[]가변 길이 배열로 B+tree의 각 레벨별 상태(현재 블록 버퍼, 레코드 인덱스 등)를 유지합니다. 루트에서 리프까지 탐색 경로를 기억하여 삽입/삭제 시 상위 노드로 역추적할 수 있습니다.
Extent 관리
XFS는 파일 데이터를 extent 단위로 관리합니다. 각 extent는 연속된 파일시스템 블록의 범위를 나타내며, 128비트(16바이트) packed 레코드로 저장됩니다.
Extent 레코드 형식
/* 128비트 Extent 레코드 구조:
* 비트 [0:8] — extent flag (1비트) + 논리 오프셋 상위 (8비트)
* 비트 [9:62] — 논리 오프셋(54비트): 파일 내 시작 블록
* 비트 [63:115] — 물리 블록 번호(52비트): AG번호 + AG 내 블록
* 비트 [116:127]— extent 길이(21비트): 최대 2M 블록
*/
typedef struct xfs_bmbt_rec {
__be64 l0; /* flag(1) + offset(54) + startblock 상위(9) */
__be64 l1; /* startblock 하위(43) + blockcount(21) */
} xfs_bmbt_rec_t;
/* 언패킹 후 논리적 표현 */
typedef struct xfs_bmbt_irec {
xfs_fileoff_t br_startoff; /* 파일 내 논리 오프셋 */
xfs_fsblock_t br_startblock; /* 물리 블록 번호 */
xfs_filblks_t br_blockcount; /* 블록 수 */
xfs_exntst_t br_state; /* written / unwritten */
} xfs_bmbt_irec_t;
코드 설명
- xfs_bmbt_rec
xfs_bmbt_rec는 디스크에 저장되는 128비트 packed extent 레코드입니다. 두 개의 64비트 빅엔디안 필드(l0,l1)에 플래그, 논리 오프셋, 물리 블록 번호, 블록 수를 비트 단위로 압축합니다. 이 압축 덕분에 inode 내부에 더 많은 extent를 저장할 수 있습니다. - xfs_bmbt_irec커널 내부에서 사용하는 언패킹된 논리적 extent 표현입니다.
br_startoff는 파일 내 논리 블록 오프셋,br_startblock은 대응하는 물리 블록 번호,br_blockcount는 연속 블록 수입니다. - br_stateextent의 상태를 나타냅니다.
written은 실제 데이터가 기록된 상태,unwritten은fallocate()로 사전 할당되었지만 아직 데이터가 쓰이지 않은 상태입니다. unwritten extent를 읽으면 커널이 0을 반환하여 이전 데이터 노출을 방지합니다.
Delayed Allocation (delalloc)
XFS의 Delayed Allocation은 write() 시점에서 실제 블록 할당을 지연시키고, writeback 시점에 한꺼번에 할당합니다. 이를 통해:
- 단편화(Fragmentation) 최소화: 최종 파일 크기를 알고 할당하므로 연속 extent 확보 가능
- 메타데이터 오버헤드(Overhead) 감소: 임시 파일은 블록 할당 없이 삭제 가능
- 대역폭(Bandwidth) 최적화: 인접 블록을 한 번에 할당하여 디스크 시크 최소화
/* delalloc extent는 br_startblock에 특수 값을 사용 */
#define DELAYSTARTBLOCK ((xfs_fsblock_t)-1LL)
#define HOLESTARTBLOCK ((xfs_fsblock_t)-2LL)
/* delalloc 예약: 실제 블록 없이 카운터만 증가 */
int xfs_bmapi_reserve_delalloc(
struct xfs_inode *ip,
int whichfork,
struct xfs_bmbt_irec *got,
struct xfs_bmbt_irec *prev,
xfs_filblks_t len,
int eof);
Preallocation & Extent Size Hints
fallocate() 또는 xfs_io -c 'extsize'를 통해 extent 할당 크기를 제어할 수 있습니다:
/* Extent size hint 설정 — 데이터베이스 워크로드 최적화 */
$ xfs_io -c 'extsize 16m' /data/tablespace
/* 16MB 단위로 extent 할당하여 단편화 방지 */
/* fallocate로 사전 할당 */
$ fallocate -l 10G /data/bigfile
/* unwritten extent로 10GB 연속 공간 확보 */
Unwritten Extents
Unwritten extent는 물리 블록이 할당되었지만 아직 데이터가 쓰여지지 않은 상태입니다. fallocate()로 사전 할당하면 이 상태가 됩니다. 읽기 시 0을 반환하고, 실제 쓰기 시 written 상태로 전환됩니다. 이를 통해 보안(이전 데이터 노출 방지)과 성능(연속 할당)을 모두 달성합니다.
Inode 구조
XFS inode는 고정 크기(기본 512바이트)로, 동적으로 할당됩니다. 64개의 inode가 하나의 inode chunk를 구성하며, 필요에 따라 AG 내에서 새 chunk를 할당합니다.
Inode 포맷
| 버전 | 크기 | 특징 |
|---|---|---|
| v1 | 256B | 초기 형식, 32비트 프로젝트 ID |
| v2 | 256B | 나노초 타임스탬프, 64비트 프로젝트 ID |
| v3 | 512B (기본) | CRC32C 체크섬(Checksum), change count, 생성 시간 |
/* fs/xfs/libxfs/xfs_format.h */
typedef struct xfs_dinode {
__be16 di_magic; /* 0x494e ('IN') */
__be16 di_mode; /* 파일 유형 + 퍼미션 */
__u8 di_version; /* inode 버전 (1/2/3) */
__u8 di_format; /* data fork 형식 */
__be32 di_uid; /* 소유자 UID */
__be32 di_gid; /* 소유자 GID */
__be32 di_nlink; /* 하드링크 수 */
__be64 di_size; /* 파일 크기 (바이트) */
__be64 di_nblocks; /* 할당된 블록 수 */
__be32 di_extsize; /* extent size hint */
__be32 di_nextents; /* data fork extent 수 */
__be16 di_anextents; /* attr fork extent 수 */
__u8 di_forkoff; /* attr fork 시작 오프셋 (8바이트 단위) */
__s8 di_aformat; /* attr fork 형식 */
/* v3 추가 필드 */
__be32 di_crc; /* CRC32C */
__be64 di_changecount; /* inode 변경 횟수 */
__be64 di_flags2; /* 확장 플래그 (reflink 등) */
...
} xfs_dinode_t;
코드 설명
- xfs_dinode
xfs_dinode는 디스크에 저장되는 XFS inode의 온디스크 구조체입니다(fs/xfs/libxfs/xfs_format.h). 기본 크기는 512바이트이며,di_magic(0x494e, 'IN')으로 유효성을 검증합니다. - di_format / di_aformat
di_format은 Data Fork의 저장 형식(Local/Extents/B+tree)을 나타내고,di_aformat은 Attr Fork의 형식입니다. 파일이 커지면 자동으로 Local에서 Extents, 다시 B+tree로 전환됩니다. - di_forkoffinode 내부에서 Data Fork와 Attr Fork의 경계를 정의합니다. 8바이트 단위로 표현되며, 이 값을 조정하여 두 fork 간 공간을 동적으로 재분배할 수 있습니다.
- di_crc / di_changecountv3 inode에서 추가된 자기 검증 필드입니다.
di_crc는 CRC32C 체크섬,di_changecount는 inode 수정 횟수로 NFS 등에서 변경 감지에 활용됩니다. - di_flags2v3 확장 플래그 필드로, reflink 활성화 여부, DAX(Direct Access) 모드, COW extent size hint 등 최신 기능 플래그를 저장합니다.
Data Fork 형식 전환
파일의 extent 수에 따라 data fork 저장 형식이 자동 전환됩니다:
| 형식 | di_format 값 | 조건 | 설명 |
|---|---|---|---|
| Local (Inline) | XFS_DINODE_FMT_LOCAL | 데이터가 inode 내 수용 가능 | 심볼릭 링크, 소형 디렉토리 |
| Extents List | XFS_DINODE_FMT_EXTENTS | extent 수가 fork 공간 내 | extent 레코드를 inode에 직접 저장 |
| B+tree | XFS_DINODE_FMT_BTREE | extent 수가 fork 초과 | B+tree 루트만 inode에, 나머지는 외부 블록 |
Attr Fork
inode 내부는 Data Fork와 Attr Fork로 분할됩니다. di_forkoff가 경계를 정의하며, 확장 속성(xattr)은 Attr Fork에 저장됩니다. Data Fork와 동일한 Local → Extents → B+tree 전환 메커니즘을 사용합니다.
디렉토리 구조
XFS 디렉토리는 엔트리 수에 따라 5단계로 구조가 확장됩니다:
| 형식 | 조건 | 구조 |
|---|---|---|
| Shortform | inode 내 수용 가능 | 이름/inode 쌍을 inode data fork에 인라인 저장 |
| Block | 1 블록에 수용 | 단일 디렉토리 데이터 블록 (해시 정렬) |
| Leaf | 다수 데이터 블록 | 데이터 블록 + 별도 리프 블록 (해시→데이터 매핑) |
| Node | 리프가 1블록 초과 | 데이터 + 리프 + 내부 노드 블록 (B+tree) |
| B+tree | extent 수 초과 | inode의 extent list가 B+tree로 전환 |
디렉토리 해시
XFS는 xfs_da_hashname()으로 파일명의 해시값을 계산합니다. 이 해시는 디렉토리 내 엔트리 검색을 O(log n)으로 만들어 대규모 디렉토리에서도 빠른 lookup을 보장합니다.
/* fs/xfs/libxfs/xfs_da_btree.h */
xfs_dahash_t xfs_da_hashname(
const __uint8_t *name,
int namelen);
/* Leaf 엔트리: 해시값 → 데이터 블록 오프셋 */
typedef struct xfs_dir2_leaf_entry {
__be32 hashval; /* 이름 해시 */
__be32 address; /* 데이터 블록 내 오프셋 */
} xfs_dir2_leaf_entry_t;
저널링 (Log)
XFS는 Write-Ahead Logging(WAL) 기반의 메타데이터 전용 저널링을 사용합니다. 모든 메타데이터 변경은 로그에 먼저 기록되고, 이후 실제 위치에 반영(checkpoint)됩니다.
Log 구조
AIL (Active Item List)
AIL은 디스크에 반영되지 않은 로그 항목을 LSN 순서로 추적하는 자료구조입니다. Checkpoint 쓰레드가 AIL의 가장 오래된 항목부터 디스크에 반영하고, 해당 로그 공간을 재사용합니다.
/* fs/xfs/xfs_trans_ail.c — AIL 핵심 동작 */
/* 트랜잭션 커밋 시 로그 아이템을 AIL에 삽입 */
void xfs_trans_ail_insert(
struct xfs_ail *ailp,
struct xfs_log_item *lip,
xfs_lsn_t lsn);
/* Checkpoint: AIL tail부터 디스크 반영 */
void xfs_ail_push_all(
struct xfs_ail *ailp);
/* 반영 완료 후 AIL에서 제거 → 로그 공간 해제 */
void xfs_trans_ail_delete(
struct xfs_log_item *lip,
int shutdown_type);
Intent Logging (EFI/EFD, RUI/RUD)
XFS는 Intent Logging으로 복잡한 다단계 연산의 원자성을 보장합니다. Intent 로그 아이템(예: EFI)이 먼저 기록되고, 완료 시 Done 아이템(예: EFD)이 기록됩니다. 복구 시 Done이 없는 Intent를 재실행합니다.
| Intent | Done | 용도 |
|---|---|---|
| EFI (Extent Free Intent) | EFD | extent 해제 (truncate, rm) |
| RUI (Rmap Update Intent) | RUD | reverse mapping 업데이트 |
| CUI (Refcount Update Intent) | CUD | refcount 업데이트 (reflink) |
| BUI (Bmap Update Intent) | BUD | extent 매핑 업데이트 |
외부 로그 디바이스
XFS는 저널을 별도 디바이스에 배치할 수 있어, 데이터 I/O와 저널 I/O를 물리적으로 분리하여 성능을 향상시킬 수 있습니다:
# 외부 로그 디바이스를 사용하여 XFS 생성
$ mkfs.xfs -l logdev=/dev/sdb1,size=512m /dev/sda1
# 마운트 시 외부 로그 지정
$ mount -o logdev=/dev/sdb1 /dev/sda1 /mnt/data
고급 기능
Reflink & COW (커널 4.9+)
Reflink은 두 파일이 동일한 물리 extent를 공유하고, 어느 한쪽이 수정되면 COW(Copy-on-Write)로 분기하는 기능입니다. cp --reflink은 메타데이터만 복사하므로 즉각 완료됩니다.
# instant copy — 실제 데이터 복사 없음
$ cp --reflink=always source.img dest.img
# Reflink 활성화 확인 (v5 포맷 기본 활성)
$ xfs_info /mnt/data | grep reflink
reflink=1
/* fs/xfs/xfs_reflink.c — COW fork 처리 */
int xfs_reflink_allocate_cow(
struct xfs_inode *ip,
struct xfs_bmbt_irec *imap,
bool *shared,
uint *lockmode,
bool convert_now);
/* COW extent writeback 완료 후 원본 매핑 교체 */
int xfs_reflink_end_cow(
struct xfs_inode *ip,
xfs_off_t offset,
xfs_off_t count);
Online 확장 (xfs_growfs)
XFS는 마운트(Mount) 상태에서 파일시스템을 확장할 수 있습니다. 새 AG를 추가하는 방식이므로 기존 데이터 재배치(Relocation)가 불필요합니다:
# LV 확장 후 XFS 온라인 확장
$ lvextend -L +100G /dev/vg0/data
$ xfs_growfs /mnt/data
# 특정 크기로 확장 (블록 단위)
$ xfs_growfs -D 524288000 /mnt/data
Project Quotas (디렉토리 기반)
XFS의 Project Quota는 디렉토리 트리 단위로 공간 제한을 적용합니다. uid/gid 기반 quota와 달리, 특정 디렉토리 계층에 용량 한도를 설정할 수 있어 컨테이너(Container)나 프로젝트별 공간 관리에 유용합니다:
# Project Quota 설정
$ echo "42:/data/project_a" >> /etc/projects
$ echo "project_a:42" >> /etc/projid
# 마운트 옵션에 pquota 추가
$ mount -o pquota /dev/sda1 /data
# 프로젝트 디렉토리 초기화 및 제한 설정
$ xfs_quota -x -c 'project -s project_a' /data
$ xfs_quota -x -c 'limit -p bhard=100g project_a' /data
Real-time Subvolume
XFS는 선택적으로 별도의 real-time subvolume을 구성할 수 있습니다. 이 영역은 extent 단위 할당으로 일반 AG 메커니즘을 우회하여, 예측 가능한 I/O 지연이 필요한 워크로드(멀티미디어, 실시간(Real-time) 데이터 수집)에 적합합니다:
# real-time subvolume 포함 mkfs
$ mkfs.xfs -r rtdev=/dev/sdb1,extsize=1m /dev/sda1
# 마운트 시 rtdev 지정
$ mount -o rtdev=/dev/sdb1 /dev/sda1 /mnt/rtdata
DAX (Direct Access)
Persistent Memory(pmem) 디바이스에서 XFS를 DAX 모드로 사용하면, 페이지 캐시(Page Cache)를 우회하여 CPU가 메모리 매핑을 통해 직접 스토리지에 접근합니다:
# DAX 모드로 마운트 (전체 FS)
$ mount -o dax=always /dev/pmem0 /mnt/pmem
# 파일별 DAX 속성 설정 (커널 5.8+)
$ xfs_io -c 'chattr +x' /mnt/pmem/datafile
Atomic Writes (커널 6.13+)
커널 6.13에서 XFS에 atomic writes 지원이 추가되었습니다. 데이터베이스 등의 애플리케이션이 저널링 없이도 블록 단위의 원자적(Atomic) 쓰기를 보장받을 수 있습니다. RWF_ATOMIC 플래그를 사용한 Direct I/O 쓰기는 전체 기록되거나 전혀 기록되지 않음을 보장합니다.
/* atomic write — 블록 단위 all-or-nothing 보장 */
/* statx로 지원 여부 확인 */
struct statx stx;
statx(AT_FDCWD, path, 0, STATX_WRITE_ATOMIC, &stx);
/* stx.stx_atomic_write_unit_min/max 확인 */
/* pwritev2()에 RWF_ATOMIC 플래그 사용 */
pwritev2(fd, iov, iovcnt, offset, RWF_ATOMIC);
CRC32C Self-describing Metadata (v5 포맷)
v5 on-disk 포맷(Linux 3.7+, 기본 활성)은 모든 메타데이터 블록에 다음을 추가합니다:
- CRC32C 체크섬: 무결성 검증 (silent corruption 감지)
- UUID: 파일시스템 식별 (잘못된 블록 참조 방지)
- Block number: 자기 참조 블록 번호 (misplaced write 감지)
- Log Sequence Number: 최종 수정 시점 추적
성능 튜닝
마운트 옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
logbufs=N | 8 | 인메모리 로그 버퍼(Buffer) 수 (2-8). 높을수록 쓰기 버스(Bus)트 흡수 |
logbsize=N | 32K/256K | 로그 버퍼 크기. 대형 트랜잭션(Transaction) 워크로드에서 증가 |
allocsize=N | 64K | 스트리밍 쓰기 시 사전 할당 단위 (최대 1G) |
inode64 | v5 기본 | 모든 AG에서 inode 할당 (32비트 앱 호환 주의) |
largeio | off | stat()의 st_blksize를 stripe width로 설정 |
nobarrier | barrier=1 | 쓰기 배리어 비활성 (배터리 캐시 RAID만) |
discard | off | SSD TRIM 자동 발행 |
lazytime | off | 타임스탬프 업데이트 지연 |
핵심 sysctl
# XFS 관련 sysctl 파라미터
fs.xfs.xfssyncd_centisecs = 3000 # 동기화 데몬 주기 (30초)
fs.xfs.filestream_centisecs = 3000 # filestream 할당 AG 유지 시간
fs.xfs.speculative_prealloc_lifetime = 300 # delalloc 사전 할당 유지 (초)
fs.xfs.error_level = 3 # 오류 보고 수준 (0-5)
xfs_fsr (단편화 해소)
# 전체 파일시스템 단편화 해소
$ xfs_fsr /mnt/data
# 특정 파일만 defrag
$ xfs_fsr /mnt/data/largefile.dat
# 단편화 상태 확인
$ xfs_db -r -c 'frag -f' /dev/sda1
I/O 패턴별 최적화
| 워크로드 | 권장 설정 |
|---|---|
| 대용량 순차 쓰기 (미디어, 백업) | allocsize=1g, logbsize=256k, extent size hint 설정 |
| 소형 랜덤 I/O (데이터베이스) | inode64, noatime, logbufs=8, AG 수 조정 |
| 메타데이터 집중 (메일 서버) | 외부 로그 디바이스, logbsize=256k, lazytime |
| 가상화(Virtualization) 이미지 (VM) | reflink=1, extent size hint, allocsize 증가 |
| NVMe/SSD | discard 또는 fstrim cron, inode64 |
mkfs.xfs -d su=256k,sw=4로 stripe unit/width를 디스크 배열에 맞추면 데이터 정렬이 최적화됩니다. AG 크기도 -d agcount=N으로 CPU 코어 수에 맞출 수 있습니다.
관리 도구 (xfsprogs)
주요 도구 요약
| 도구 | 용도 |
|---|---|
mkfs.xfs | XFS 파일시스템 생성 |
xfs_info | 마운트된 FS의 지오메트리 정보 출력 |
xfs_admin | UUID, 레이블 변경, lazy-count 전환 |
xfs_repair | 오프라인 파일시스템 복구 |
xfs_db | 디버그 모드 — 메타데이터 직접 검사/수정 |
xfs_logprint | 저널 로그 내용 덤프(Dump) |
xfs_metadump | 메타데이터 이미지 추출 (버그 리포트용) |
xfs_growfs | 온라인 파일시스템 확장 |
xfs_fsr | 파일 단편화 해소 (defrag) |
xfs_quota | 사용자/그룹/프로젝트 quota 관리 |
xfs_freeze / xfs_thaw | I/O 일시 중지/재개 (스냅샷용) |
xfs_scrub | 온라인 메타데이터 검증 (v5 포맷) |
xfs_io | 파일 I/O 디버깅 (extent 정보, fallocate, fiemap) |
mkfs.xfs 주요 옵션
# 기본 생성 (v5 포맷, reflink 활성, CRC 활성)
$ mkfs.xfs /dev/sda1
# RAID 최적화 (stripe unit 256K, 4 data disks)
$ mkfs.xfs -d su=256k,sw=4 -l su=256k /dev/md0
# 외부 로그, 큰 로그 크기
$ mkfs.xfs -l logdev=/dev/sdb1,size=1g /dev/sda1
# inode 크기 변경, AG 수 지정
$ mkfs.xfs -i size=1024 -d agcount=32 /dev/sda1
# 블록 크기 변경 (1K, 2K, 4K 중 선택)
$ mkfs.xfs -b size=4096 /dev/sda1
디버깅 & 트러블슈팅
xfs_db 실전 활용
# 수퍼블록 검사
$ xfs_db -r /dev/sda1
xfs_db> sb 0
xfs_db> print
magicnum = 0x58465342
blocksize = 4096
dblocks = 524288000
agcount = 4
agblocks = 131072000
...
# 특정 inode 검사
xfs_db> inode 131
xfs_db> print
core.magic = 0x494e
core.mode = 0100644
core.version = 3
core.format = 2 (extents)
core.size = 1048576
...
# AG free space 통계
xfs_db> agf 0
xfs_db> print freeblks longest
freeblks = 95000000
longest = 32000000
# 단편화 통계
xfs_db> frag -f
xfs_repair 복구 절차
xfs_repair는 반드시 언마운트 상태에서 실행해야 합니다. 마운트된 상태에서 실행하면 데이터 손상이 발생할 수 있습니다.
# 1단계: 드라이런 (변경 없이 검사만)
$ xfs_repair -n /dev/sda1
# 2단계: 실제 복구
$ umount /mnt/data
$ xfs_repair /dev/sda1
# 더티 로그가 있을 경우: 먼저 로그 클리어 후 복구
$ xfs_repair -L /dev/sda1
# -L은 로그를 제로화하므로 최근 트랜잭션 손실 가능
# 외부 로그 디바이스 사용 시
$ xfs_repair -l /dev/sdb1 /dev/sda1
커널 로그 메시지 해석
| 메시지 패턴 | 의미 | 대응 |
|---|---|---|
XFS: Corruption detected | 메타데이터 CRC 불일치 | xfs_repair 실행 |
XFS: Log force timed out | 로그 I/O 응답 없음 | 스토리지 상태 점검 |
XFS: xfs_do_force_shutdown | 치명적 오류로 FS 중단 | dmesg 확인 후 xfs_repair |
XFS: Filesystem has duplicate UUID | UUID 충돌 (클론 볼륨) | xfs_admin -U generate |
XFS: possible memory allocation deadlock | 메모리 압력 하 할당 실패 | 메모리 부족 원인 해결 |
Tracepoints (xfs:*)
# 사용 가능한 XFS tracepoints 목록
$ perf list 'xfs:*' 2>&1 | head -20
xfs:xfs_alloc_exact_done
xfs:xfs_alloc_near_first
xfs:xfs_buf_read
xfs:xfs_ilock
xfs:xfs_iomap_found
xfs:xfs_reflink_bounce_dio_write
...
# extent 할당 추적
$ perf record -e 'xfs:xfs_alloc_*' -a -- sleep 10
$ perf script
# trace-cmd로 delalloc 모니터링
$ trace-cmd record -e 'xfs:xfs_iomap*' -e 'xfs:xfs_alloc*'
$ trace-cmd report
# bpftrace로 실시간 분석
$ bpftrace -e 'tracepoint:xfs:xfs_file_buffered_write { printf("%s %d bytes\n", comm, args->count); }'
xfs_io -c 'fiemap -v' file로 파일의 extent 매핑을 확인하고, xfs_io -c 'stat' file로 inode 세부 정보를 조회할 수 있습니다. 성능 문제 진단 시 xfs_io -c 'freesp -s' mountpoint로 free space 단편화를 확인하세요.
AG(Allocation Group) 아키텍처
XFS의 Allocation Group은 단순한 파티셔닝이 아니라 완전히 독립적인 파일시스템 단위입니다. 각 AG는 자체 수퍼블록 복사본, 프리 스페이스 인덱스, inode 할당기, 역방향 매핑 트리를 보유하며, 고유한 잠금 세트로 보호됩니다. 이 설계는 NUMA 아키텍처에서 메모리 지역성과 결합하여 대규모 병렬 I/O 성능을 극대화합니다.
AG 헤더 섹터 배치
각 AG의 처음 4개 섹터는 고정된 헤더 구조체(Struct)를 담습니다. 이 배치는 모든 AG에서 동일하며, AG 0의 수퍼블록이 마스터 역할을 합니다:
| 섹터 오프셋 | 구조체 | 매직 넘버 | 역할 |
|---|---|---|---|
| 0 | xfs_sb | XFSB | 수퍼블록 (AG 0 = 마스터, 나머지 = 복사본) |
| 1 | xfs_agf | XAGF | 프리 스페이스 관리 (BNO/CNT/RMAP/Refcount B+tree 루트) |
| 2 | xfs_agi | XAGI | Inode 할당 관리 (Inode/Free Inode B+tree 루트) |
| 3 | xfs_agfl | XAFL | B+tree 메타데이터 블록 예약 풀 (순환 배열) |
AG 선택 전략
XFS는 새 파일이나 inode를 할당할 때 라운드 로빈(Round Robin)과 파일 스트림 두 가지 AG 선택 전략을 사용합니다:
/* AG 선택 정책 — fs/xfs/xfs_ialloc.c */
/* 기본: 라운드 로빈 — 부모 디렉토리의 AG부터 시작하여 순환 탐색 */
xfs_agnumber_t xfs_ialloc_ag_select(
struct xfs_trans *tp,
xfs_ino_t parent,
umode_t mode);
/* 파일 스트림: 관련 파일을 동일 AG에 배치하여 인접성 극대화 */
/* mount -o filestreams 로 활성화 */
int xfs_filestream_new_ag(
struct xfs_bmalloca *ap,
xfs_agnumber_t *agp);
/* filestream AG 점유 타이머 — 기본 30초 유지 */
/* fs.xfs.filestream_centisecs = 3000 */
mkfs.xfs는 기본적으로 볼륨 크기에 따라 AG 수를 자동 결정합니다. 일반적으로 AG 크기는 최소 16MB에서 최대 1TB 범위이며, CPU 코어 수보다 AG 수가 많아야 병렬 할당의 이점을 최대한 활용할 수 있습니다. -d agcount=64와 같이 명시적으로 지정할 수도 있습니다.
AG 잠금 순서 규약
데드락을 방지하기 위해 XFS는 엄격한 잠금 순서를 정의합니다. AG 내에서는 항상 AGF → AGI → AGFL 순서로 잠금을 획득해야 하며, 여러 AG에 동시 접근할 때는 낮은 AG 번호부터 잠금합니다:
/* 잠금 순서 규약 — fs/xfs/xfs_ag.h */
/*
* AG 잠금 순서 (데드락 방지):
* 1. AGF (free space 잠금)
* 2. AGI (inode 잠금)
* 3. AGFL (free list 잠금)
*
* 다중 AG 접근 시: agno 오름차순으로 잠금
* AG 0 → AG 1 → AG 2 (역순 금지)
*/
/* AGF 잠금 획득 */
int xfs_alloc_read_agf(
struct xfs_perag *pag,
struct xfs_trans *tp,
int flags,
struct xfs_buf **agfbpp);
/* AGI 잠금 획득 — AGF 이후에만 호출 */
int xfs_ialloc_read_agi(
struct xfs_perag *pag,
struct xfs_trans *tp,
int flags,
struct xfs_buf **agibpp);
B+tree 구조
XFS의 모든 메타데이터 인덱싱은 일관된 generic B+tree 프레임워크로 구현됩니다. 이 프레임워크는 공통 삽입/삭제/탐색 로직을 제공하고, 각 B+tree 유형별 차이점은 xfs_btree_ops 콜백(Callback)으로 추상화합니다. 이 섹션에서는 온디스크 노드 구조, 탐색 알고리즘, 분할/병합 메커니즘을 상세히 살펴봅니다.
B+tree 블록 헤더
모든 B+tree 블록은 공통 헤더를 가집니다. v5 포맷에서는 자기 검증 메타데이터가 추가됩니다:
/* fs/xfs/libxfs/xfs_btree.h — B+tree 블록 헤더 */
struct xfs_btree_block {
__be32 bb_magic; /* B+tree 유형별 매직 넘버 */
__be16 bb_level; /* 트리 레벨 (0=리프) */
__be16 bb_numrecs; /* 이 블록의 레코드/키 수 */
union {
struct { /* short-format (AG 내부) */
__be32 bb_leftsib; /* 왼쪽 형제 AG 블록 */
__be32 bb_rightsib; /* 오른쪽 형제 AG 블록 */
/* v5 추가 */
__be64 bb_blkno; /* 자기 참조 블록 번호 */
__be64 bb_lsn; /* 마지막 수정 LSN */
uuid_t bb_uuid; /* FS UUID */
__be32 bb_crc; /* CRC32C */
__be32 bb_owner; /* AG 번호 */
} s;
struct { /* long-format (전체 FS) */
__be64 bb_leftsib;
__be64 bb_rightsib;
__be64 bb_blkno;
__be64 bb_lsn;
uuid_t bb_uuid;
__be32 bb_crc;
__be64 bb_owner; /* inode 번호 */
} l;
} bb_u;
};
코드 설명
- xfs_btree_block
xfs_btree_block은 모든 XFS B+tree 블록이 공유하는 공통 헤더입니다(fs/xfs/libxfs/xfs_btree.h). 리프 노드와 내부 노드 모두 이 헤더로 시작하며, 헤더 뒤에 키/포인터 또는 레코드가 배치됩니다. - bb_magic / bb_level / bb_numrecs
bb_magic은 B+tree 유형별 고유 매직 넘버(예: BNO='AB3B', CNT='AB3C')이고,bb_level은 트리 내 레벨(0이면 리프),bb_numrecs는 이 블록에 저장된 레코드 또는 키/포인터 쌍의 수입니다. - bb_u.s (short-format)AG 내부 B+tree(BNO/CNT/Inode/Rmap/Refcount)가 사용하는 형식입니다. 형제 포인터(
bb_leftsib,bb_rightsib)가 32비트 AG 상대 블록 번호이며,bb_owner는 AG 번호를 저장합니다. - bb_u.l (long-format)inode data fork의 extent B+tree 등 AG 경계를 넘는 B+tree가 사용합니다. 형제 포인터가 64비트 절대 블록 번호이며,
bb_owner는 소유 inode 번호입니다. - bb_blkno / bb_lsn / bb_crcv5 자기 검증 메타데이터 필드입니다.
bb_blkno는 자신의 디스크 위치를 기록하여 잘못된 위치에 있는 블록을 감지하고,bb_lsn은 마지막 수정 로그 시점,bb_crc는 CRC32C 체크섬입니다.
xfs_btree_ops 콜백 프레임워크
각 B+tree 유형은 xfs_btree_ops 구조체로 유형별 동작을 정의합니다. 이 추상화 덕분에 삽입, 삭제, 탐색 등의 공통 알고리즘을 한 번만 구현하면 됩니다:
/* fs/xfs/libxfs/xfs_btree.h — B+tree 연산 콜백 */
struct xfs_btree_ops {
/* 키 비교 */
int (*key_diff)(struct xfs_btree_cur *cur,
const union xfs_btree_key *key);
/* 레코드 읽기/쓰기 */
int (*get_rec)(struct xfs_btree_cur *cur,
union xfs_btree_rec **recp);
/* 블록 할당/해제 (AGFL과 상호작용) */
int (*alloc_block)(struct xfs_btree_cur *cur,
const union xfs_btree_ptr *start,
union xfs_btree_ptr *new,
int *stat);
int (*free_block)(struct xfs_btree_cur *cur,
struct xfs_buf *bp);
/* 키 업데이트 (내부 노드) */
void (*init_key_from_rec)(union xfs_btree_key *key,
const union xfs_btree_rec *rec);
};
B+tree 분할/병합
B+tree 노드가 가득 차면 분할(split)이 발생합니다. 이때 AGFL에서 새 블록을 가져와 사용합니다. 반대로 노드의 점유율이 낮아지면 병합(merge)이 발생하고 빈 블록은 AGFL에 반납됩니다:
/* B+tree 분할 — fs/xfs/libxfs/xfs_btree.c */
int xfs_btree_split(
struct xfs_btree_cur *cur,
int level, /* 분할할 레벨 */
union xfs_btree_ptr *ptrp, /* 새 블록 포인터 반환 */
union xfs_btree_key *key, /* 분할 키 반환 */
struct xfs_btree_cur **curp, /* 새 커서 반환 */
int *stat);
/* 핵심 흐름:
* 1. bc_ops->alloc_block()으로 AGFL에서 새 블록 확보
* 2. 기존 블록의 상위 절반을 새 블록으로 이동
* 3. 형제 포인터(sibling) 업데이트
* 4. 상위 노드에 새 키/포인터 삽입 (재귀적 분할 가능)
*/
지연 할당(Delayed Allocation)
XFS의 지연 할당은 write() 시스템 콜(System Call) 시점에서 디스크 블록을 즉시 할당하지 않고, 페이지 캐시에 데이터를 축적한 뒤 writeback 시점에 한꺼번에 최적의 연속 extent를 할당하는 전략입니다. 이 메커니즘은 XFS가 대용량 순차 쓰기에서 탁월한 성능을 내는 핵심 기술입니다.
ENOSPC 방지와 Speculative Preallocation
지연 할당의 핵심 난제는 ENOSPC(디스크 부족) 상황 처리입니다. write() 시점에 블록을 할당하지 않으므로, writeback 시점에 공간이 부족할 수 있습니다. XFS는 이를 예약 카운터로 해결합니다:
/* Speculative preallocation — fs/xfs/xfs_iomap.c */
/* write 시 실제 크기보다 넉넉하게 예약 (EOF 너머 추가 공간) */
xfs_extlen_t xfs_iomap_prealloc_size(
struct xfs_inode *ip,
int whichfork,
loff_t offset,
loff_t count,
struct xfs_bmbt_irec *prev);
/* speculative prealloc 크기 결정:
* - 파일 크기의 2배 (최대 64MB까지 지수 성장)
* - extent size hint가 설정되면 해당 값 사용
* - 여유 공간 부족 시 축소 할당
*/
/* 유휴 파일의 speculative prealloc 해제 */
/* fs.xfs.speculative_prealloc_lifetime = 300 (초) */
void xfs_inode_free_eofblocks(
struct xfs_inode *ip);
xfs_blockgc_scan())하고, 그래도 부족하면 현재 쓰기를 가능한 범위까지만 할당합니다. 최악의 경우 쓰기가 부분적으로 실패할 수 있지만, 메타데이터 일관성은 항상 보장됩니다.
Delalloc 변환 경로
/* Writeback 시 delalloc → 실제 블록 변환 경로 */
/* 1. writeback 데몬이 dirty page flush 시작 */
xfs_vm_writepages()
→ iomap_writepages() /* iomap 프레임워크 */
→ xfs_map_blocks() /* XFS iomap 콜백 */
→ xfs_bmapi_convert_delalloc() /* 핵심: delalloc→실제 할당 */
/* 2. xfs_bmapi_convert_delalloc() 내부 */
int xfs_bmapi_convert_delalloc(
struct xfs_inode *ip,
int whichfork,
loff_t offset,
struct iomap *iomap,
unsigned int *seq);
/* a. DELAYSTARTBLOCK extent를 찾음
* b. AG를 선택하고 BNO/CNT tree에서 최적 free extent 탐색
* c. 실제 블록 번호로 extent 레코드 업데이트
* d. 예약 카운터 정산 (예약 해제 + 실제 할당 반영)
* e. 트랜잭션 로그에 변경 기록
*/
XFS 저널링
XFS의 저널(로그)은 단순한 순환 버퍼가 아니라, CIL(Committed Item List)과 AIL(Active Item List)이라는 두 단계 파이프라인(Pipeline)을 통해 높은 동시성과 빠른 커밋 지연시간을 동시에 달성하는 정교한 구조입니다. 이 섹션에서는 트랜잭션 생명주기, CIL 집계, AIL 체크포인팅, 복구 절차를 상세히 분석합니다.
CIL(Committed Item List) 동작
CIL은 XFS 로그 성능의 핵심입니다. 여러 트랜잭션의 로그 아이템을 메모리에 집계(aggregate)하여 디스크 로그에 일괄 기록합니다. 이를 통해 개별 트랜잭션마다 디스크 I/O를 발생시키지 않고 배치 효과를 극대화합니다:
/* CIL 구조 — fs/xfs/xfs_log_priv.h */
struct xlog_cil {
struct xlog *xc_log;
struct list_head xc_cil; /* 현재 CIL 아이템 리스트 */
struct xlog_cil_ctx *xc_ctx; /* 현재 CIL 컨텍스트 */
xfs_csn_t xc_current_sequence; /* CIL 시퀀스 번호 */
wait_queue_head_t xc_push_wait; /* push 완료 대기 큐 */
atomic_t xc_space_used; /* 현재 CIL 사용 공간 */
};
/* 트랜잭션 커밋 → CIL에 아이템 삽입 */
void xlog_cil_insert_items(
struct xlog *log,
struct xfs_trans *tp);
/* CIL push: 집계된 아이템을 로그 디스크에 기록 */
void xlog_cil_push_work(
struct work_struct *work);
코드 설명
- xlog_cil
xlog_cil은 CIL(Committed Item List) 구조체로, XFS 지연 로깅(delayed logging)의 핵심입니다(fs/xfs/xfs_log_priv.h). 여러 트랜잭션의 로그 아이템을 메모리에 집계하여 디스크 I/O를 배치 처리합니다. - xc_cil / xc_ctx
xc_cil은 현재 CIL에 삽입된 로그 아이템의 리스트이고,xc_ctx는 현재 CIL 컨텍스트를 가리킵니다. push 시 현재 컨텍스트를 통째로 디스크에 기록하고 새 컨텍스트로 교체합니다. - xc_space_used현재 CIL에 축적된 로그 데이터의 크기를 원자적으로 추적합니다. 이 값이 임계치(기본 로그 크기의 1/8)를 초과하면 백그라운드 push가 트리거됩니다.
- xlog_cil_insert_items트랜잭션 커밋 시 호출되어 변경된 로그 아이템을 CIL에 삽입합니다. 동일 아이템이 이미 CIL에 있으면 최신 버전으로 교체(relogging)하여 중복 기록을 방지합니다.
- xlog_cil_push_work워크큐 콜백으로 실행되며, 집계된 CIL 아이템들을 순환 로그 버퍼에 일괄 기록합니다. 기록 완료 후 아이템들이 AIL로 이동하여 체크포인트 대상이 됩니다.
LSN(Log Sequence Number) 체계
LSN은 64비트 값으로, 상위 32비트는 사이클 번호(순환 로그의 랩 횟수), 하위 32비트는 블록 오프셋(로그 내 위치)입니다. 모든 메타데이터 블록에 LSN이 기록되어, 해당 블록이 마지막으로 수정된 로그 시점을 정확히 추적합니다:
/* LSN 구조 — fs/xfs/xfs_log_format.h */
typedef __be64 xfs_lsn_t;
/* LSN = (cycle << 32) | block_offset */
#define CYCLE_LSN(lsn) ((uint)((lsn) >> 32))
#define BLOCK_LSN(lsn) ((uint)(lsn))
/* LSN 비교: a > b 인지 확인 (사이클 랩어라운드 고려) */
static inline bool xlog_lsn_is_later(
xfs_lsn_t a,
xfs_lsn_t b)
{
return CYCLE_LSN(a) > CYCLE_LSN(b) ||
(CYCLE_LSN(a) == CYCLE_LSN(b) &&
BLOCK_LSN(a) > BLOCK_LSN(b));
}
로그 복구 절차 상세
비정상 종료 후 마운트 시, XFS는 다음 단계를 거쳐 메타데이터를 복구합니다:
| 단계 | 함수 | 동작 |
|---|---|---|
| 1. 로그 스캔 | xlog_find_head() | 로그 head/tail 위치 파악 (사이클 넘버 비교) |
| 2. 레코드 수집 | xlog_do_recovery_pass() | tail→head 순서로 커밋된 트랜잭션 수집 |
| 3. 메타데이터 재생 | xlog_recover_items_pass2() | 수집된 아이템을 실제 디스크 위치에 기록 |
| 4. Intent 재실행 | xlog_recover_finish() | 미완료 EFI/RUI/CUI/BUI의 작업 재실행 |
| 5. 정리 | xfs_log_mount_finish() | 복구 완료, 정상 운영 전환 |
Reflink & COW(Copy-on-Write)
XFS의 reflink는 물리 extent를 여러 파일이 공유하면서 COW 의미론을 통해 독립적 수정을 보장하는 기능입니다. 가상 머신 이미지의 빠른 복제, 파일 레벨 스냅샷, 데이터 중복 제거(dedup) 등에 활용됩니다. 커널 4.9에서 도입되어 v5 포맷에서 기본 활성화됩니다.
COW Fork 메커니즘
XFS는 COW를 위해 inode에 COW fork라는 세 번째 fork를 도입했습니다. 공유 extent에 쓰기가 발생하면, 새 블록을 COW fork에 먼저 할당하고 데이터를 복사한 뒤, writeback 완료 시 data fork의 매핑을 교체합니다:
/* COW fork 처리 — fs/xfs/xfs_reflink.c */
/* 1. 공유 여부 확인 → COW fork에 extent 예약 */
int xfs_reflink_allocate_cow(
struct xfs_inode *ip,
struct xfs_bmbt_irec *imap, /* 대상 extent 정보 */
bool *shared, /* 공유 여부 반환 */
uint *lockmode,
bool convert_now);
/* - Refcount B+tree 조회로 공유 여부 판별
* - 공유 시 COW fork에 새 extent 할당 (unwritten 상태)
*/
/* 2. writeback 완료 → data fork 매핑 교체 */
int xfs_reflink_end_cow(
struct xfs_inode *ip,
xfs_off_t offset,
xfs_off_t count);
/* - COW fork의 extent를 data fork로 이동
* - 원본 extent의 refcount 감소 (CUI intent 로그)
* - refcount 0이면 원본 extent 해제 (EFI intent 로그)
*/
/* 3. Refcount B+tree 업데이트 */
int xfs_refcount_adjust(
struct xfs_btree_cur *cur,
xfs_agblock_t agbno,
xfs_extlen_t aglen,
int adj); /* +1 (공유) 또는 -1 (해제) */
Deduplication (중복 제거)
XFS의 reflink 인프라를 활용한 데이터 중복 제거가 가능합니다. xfs_io -c 'dedupe' 또는 FIDEDUPERANGE ioctl로 동일한 내용의 블록을 공유 extent로 병합합니다:
# 두 파일의 동일 범위를 deduplicate
$ xfs_io -c 'dedupe /path/to/src 0 0 4m' /path/to/dst
# duperemove: 파일시스템 전체 중복 블록 탐색 및 제거
$ duperemove -dhr /data/
# -d: dedupe 실행, -h: 해시 기반 비교, -r: 재귀
# 공유 extent 확인
$ xfs_io -c 'fiemap -v' /path/to/file
# flags 필드에 'shared' 표시 확인
xfs_scrub 온라인 검증
xfs_scrub은 마운트된 상태에서 XFS 메타데이터를 검증하고 경미한 손상을 자동 복구하는 도구입니다. 오프라인 xfs_repair와 달리 서비스 중단 없이 운영할 수 있어 엔터프라이즈 환경에서 특히 유용합니다. v5 포맷의 self-describing metadata(CRC, UUID, owner)를 활용하여 교차 검증을 수행합니다.
xfs_scrub 실전 활용
# 전체 파일시스템 온라인 검증 (읽기 전용)
$ xfs_scrub /mnt/data
# 백그라운드 주기적 검증 (systemd 타이머 권장)
$ xfs_scrub -b /mnt/data
# 수정 가능한 문제 자동 복구 (-n: 드라이런)
$ xfs_scrub -n /mnt/data # 먼저 확인
$ xfs_scrub -y /mnt/data # 자동 복구 승인
# 특정 AG만 검증
$ xfs_io -c 'scrub agf 3' /mnt/data # AG 3의 AGF 검증
$ xfs_io -c 'scrub bnobt 0' /mnt/data # AG 0의 BNO B+tree 검증
# systemd 서비스로 주기적 실행
$ systemctl enable --now xfs_scrub_all.timer
# /etc/systemd/system/xfs_scrub_all.timer로 주기 조정
xfs_scrub vs xfs_repair 비교
| 특성 | xfs_scrub | xfs_repair |
|---|---|---|
| 실행 조건 | 마운트 상태 (온라인) | 언마운트 상태 (오프라인) |
| 포맷 요구 | v5 포맷 필수 (CRC/RMAP) | 모든 포맷 |
| 검증 방식 | 교차 참조 (RMAP 기반) | 전체 재스캔 |
| 복구 범위 | 경미한 메타데이터 손상 | 심각한 손상 포함 전체 |
| 성능 영향 | 백그라운드, I/O 우선도 낮춤 | 서비스 중단 필요 |
| 사용 시나리오 | 예방적 정기 검증 | 크래시 후 복구, 심각한 손상 |
xfs_scrub의 핵심은 Reverse Mapping B+tree(RMAP)입니다. RMAP은 물리 블록에서 소유자(inode, AG 구조체 등)를 역추적할 수 있으므로, BNO tree의 free 블록이 실제로 어떤 inode에도 할당되지 않았는지, refcount tree의 값이 실제 참조 수와 일치하는지를 교차 검증할 수 있습니다.
프로젝트 쿼타
XFS는 사용자(uid), 그룹(gid), 프로젝트(projid) 세 가지 차원의 쿼타를 지원합니다. 특히 프로젝트 쿼타는 디렉토리 트리 단위로 공간 제한을 적용할 수 있어, 컨테이너, 가상 호스팅, 부서별 공간 관리에 매우 유용합니다.
프로젝트 쿼타 설정 절차
# 1. /etc/projects에 프로젝트 ID ↔ 경로 매핑
$ echo "42:/data/project_a" >> /etc/projects
$ echo "43:/data/project_b" >> /etc/projects
# 2. /etc/projid에 프로젝트 이름 ↔ ID 매핑
$ echo "project_a:42" >> /etc/projid
$ echo "project_b:43" >> /etc/projid
# 3. pquota 옵션으로 마운트
$ mount -o pquota /dev/sda1 /data
# 4. 프로젝트 디렉토리 초기화 (재귀적으로 projid 설정)
$ xfs_quota -x -c 'project -s project_a' /data
$ xfs_quota -x -c 'project -s project_b' /data
# 5. 제한 설정
$ xfs_quota -x -c 'limit -p bhard=100g bsoft=90g ihard=1000000 project_a' /data
$ xfs_quota -x -c 'limit -p bhard=200g bsoft=180g ihard=2000000 project_b' /data
# 6. 사용량 확인
$ xfs_quota -x -c 'report -p -h' /data
Project quota on /data (/dev/sda1)
Blocks Inodes
Project ID Used Soft Hard Used Soft Hard
---------- ------ ------ ------ ------ ------ ------
project_a 90G 90G 100G 450000 0 1000000
project_b 150G 180G 200G 800000 0 2000000
쿼타 커널 내부 구조
/* 쿼타 레코드 — fs/xfs/libxfs/xfs_format.h */
typedef struct xfs_disk_dquot {
__be16 d_magic; /* 0x4451 ('DQ') */
__u8 d_type; /* USER/GROUP/PROJ */
__be32 d_id; /* uid/gid/projid */
__be64 d_blk_hardlimit; /* 블록 하드 제한 */
__be64 d_blk_softlimit; /* 블록 소프트 제한 */
__be64 d_bcount; /* 현재 사용 블록 */
__be64 d_ino_hardlimit; /* inode 하드 제한 */
__be64 d_ino_softlimit; /* inode 소프트 제한 */
__be64 d_icount; /* 현재 사용 inode */
__be32 d_btimer; /* 블록 유예 만료 시각 */
__be32 d_itimer; /* inode 유예 만료 시각 */
...
} xfs_disk_dquot_t;
/* 쿼타 검사: 블록 할당 시 호출 */
int xfs_trans_dqresv(
struct xfs_trans *tp,
struct xfs_mount *mp,
struct xfs_dquot *dqp,
long nblks, /* 할당 요청 블록 수 */
long ninos, /* 할당 요청 inode 수 */
uint flags);
/* 제한 초과 시 -EDQUOT 반환 */
커널 내부 핵심 구조체
XFS 커널 드라이버의 핵심은 xfs_mount(파일시스템 전역 상태), xfs_inode(인메모리 inode 표현), xfs_buf(메타데이터 캐시 버퍼), xfs_trans(트랜잭션) 네 가지 구조체입니다. 이들의 상호작용을 이해하면 XFS의 내부 동작을 완전히 파악할 수 있습니다.
xfs_mount 상세
/* fs/xfs/xfs_mount.h — 파일시스템 마운트 구조체 */
typedef struct xfs_mount {
struct super_block *m_super; /* VFS 수퍼블록 */
struct xfs_sb m_sb; /* 인메모리 수퍼블록 복사 */
struct xlog *m_log; /* 로그 구조체 */
struct xfs_ail m_ail; /* AIL */
struct xfs_perag **m_perag; /* AG별 상태 배열 */
struct xfs_quotainfo *m_quotainfo; /* 쿼타 정보 */
percpu_counter_t m_fdblocks; /* free 데이터 블록 (per-CPU) */
percpu_counter_t m_ifree; /* free inode (per-CPU) */
percpu_counter_t m_icount; /* 총 inode (per-CPU) */
uint64_t m_resblks; /* 시스템 예약 블록 */
uint m_alloc_mxr[2]; /* BNO/CNT max recs/block */
uint m_bmap_dmxr[2]; /* BMAP max recs/block */
...
} xfs_mount_t;
/* per-CPU 카운터: 멀티코어 환경에서 락 없이 블록 카운팅
* - 각 CPU가 로컬 카운터를 유지
* - 정확한 값이 필요할 때만 모든 CPU 합산
* - statvfs() 등에서 빠른 응답 가능
*/
코드 설명
- xfs_mount
xfs_mount는 마운트된 XFS 파일시스템 전체의 런타임 상태를 관리하는 중심 구조체입니다(fs/xfs/xfs_mount.c). 커널이 파일시스템을 마운트하면 이 구조체가 생성되어 모든 XFS 연산의 컨텍스트로 사용됩니다. - m_super / m_sb
m_super는 VFS 계층의super_block을 가리키고,m_sb는 디스크 수퍼블록의 인메모리 복사본입니다. 수퍼블록 필드를 빈번하게 참조하므로 포인터가 아닌 내장 복사본으로 캐시 미스를 줄입니다. - m_log / m_ail
m_log는 저널(WAL) 구조체,m_ail은 AIL(Active Item List)입니다. 모든 메타데이터 변경이 이 두 구조체를 거쳐 디스크에 반영됩니다. - m_fdblocks / m_ifree / m_icountper-CPU 카운터로 free 블록 수, free inode 수, 총 inode 수를 추적합니다. 각 CPU가 로컬 카운터를 유지하므로
statfs()등 빈번한 조회에서 전역 잠금 없이 빠른 응답이 가능합니다. - m_resblks시스템 예약 블록 수로, 트랜잭션 로그 기록이나 B+tree 분할 등 메타데이터 연산에 필요한 최소 여유 공간을 보장합니다. 이 예약 덕분에 디스크가 거의 가득 찬 상황에서도 메타데이터 일관성을 유지할 수 있습니다.
xfs_inode 상세
/* fs/xfs/xfs_inode.h — 인메모리 inode */
typedef struct xfs_inode {
struct inode vfs_inode; /* VFS inode (내장) */
struct xfs_mount *i_mount; /* 마운트 포인터 */
xfs_ino_t i_ino; /* inode 번호 */
/* Data fork / Attr fork */
struct xfs_ifork i_df; /* 데이터 fork */
struct xfs_ifork *i_af; /* 속성 fork (없으면 NULL) */
struct xfs_ifork *i_cowfp; /* COW fork (reflink 용) */
/* 잠금 */
struct rw_semaphore i_lock; /* inode 잠금 */
mrlock_t i_mmaplock; /* mmap 잠금 */
/* 쿼타 */
struct xfs_dquot *i_udquot; /* 사용자 쿼타 */
struct xfs_dquot *i_gdquot; /* 그룹 쿼타 */
struct xfs_dquot *i_pdquot; /* 프로젝트 쿼타 */
uint64_t i_diflags2; /* 확장 플래그 */
...
} xfs_inode_t;
xfs_buf 버퍼 캐시
xfs_buf는 XFS의 독자적인 메타데이터 버퍼 캐시입니다. Linux의 기본 buffer_head/bio와 별도로, XFS는 자체 캐시를 유지하여 B+tree 노드, AGF/AGI 헤더 등의 메타데이터를 효율적으로 관리합니다:
/* fs/xfs/xfs_buf.h — 메타데이터 버퍼 */
struct xfs_buf {
struct list_head b_lru; /* LRU 리스트 */
xfs_daddr_t b_bn; /* 디스크 블록 번호 */
int b_length; /* 버퍼 크기 (섹터 단위) */
void *b_addr; /* 데이터 포인터 */
struct xfs_mount *b_mount; /* 마운트 포인터 */
atomic_t b_hold; /* 참조 카운트 */
xfs_buf_flags_t b_flags; /* 상태 플래그 */
const struct xfs_buf_ops *b_ops; /* 검증 콜백 */
struct completion b_iowait; /* I/O 완료 대기 */
struct xfs_log_item *b_log_item; /* 로그 아이템 */
};
/* b_ops: 읽기 시 CRC/UUID/magic 검증, 쓰기 시 CRC 재계산 */
struct xfs_buf_ops {
const char *name;
void (*verify_read)(struct xfs_buf *bp);
void (*verify_write)(struct xfs_buf *bp);
};
ftrace/bpftrace XFS 성능 분석
XFS는 커널에 400개 이상의 tracepoint를 제공하여, 할당, 잠금, 로그, I/O 경로를 상세히 추적할 수 있습니다. ftrace, perf, bpftrace를 사용하여 실시간 성능 분석과 병목(Bottleneck) 진단이 가능합니다.
ftrace/perf 실전 레시피
# === 할당 경로 추적 ===
# AG별 할당 요청 분포 확인
$ perf record -e 'xfs:xfs_alloc_exact_done' \
-e 'xfs:xfs_alloc_near_first' \
-e 'xfs:xfs_alloc_near_greater' -a -- sleep 30
$ perf script | awk '/agno=/{print $0}' | sort | uniq -c
# === 로그 I/O 병목 진단 ===
# log force 빈도 — 높으면 로그 크기 부족 또는 sync 과다
$ trace-cmd record -e 'xfs:xfs_log_force' -e 'xfs:xfs_log_grant_sleep' -- sleep 60
$ trace-cmd report | grep 'log_grant_sleep'
# log_grant_sleep 빈번 → logbsize/logbufs 증가 검토
# === 버퍼 I/O 대기 시간 ===
# xfs_buf read/write 지연시간 측정
$ trace-cmd record -e 'xfs:xfs_buf_read' \
-e 'xfs:xfs_buf_iowait_done' -- sleep 30
$ trace-cmd report
# === inode 잠금 경합 ===
# 어떤 파일에서 ilock 경합이 발생하는지 확인
$ perf record -e 'xfs:xfs_ilock' -e 'xfs:xfs_ilock_demote' \
-e 'xfs:xfs_iunlock' -a -- sleep 30
$ perf script | grep 'ilock' | awk '{print $NF}' | sort | uniq -c | sort -rn
bpftrace 실전 레시피
# === 파일별 쓰기 크기 히스토그램 ===
$ bpftrace -e '
tracepoint:xfs:xfs_file_buffered_write {
@write_size = hist(args->count);
@by_comm[comm] = sum(args->count);
}
END { print(@write_size); print(@by_comm); }
'
# === delalloc 변환 지연시간 측정 ===
$ bpftrace -e '
kprobe:xfs_bmapi_convert_delalloc {
@start[tid] = nsecs;
}
kretprobe:xfs_bmapi_convert_delalloc /@start[tid]/ {
@convert_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
'
# === AG별 할당 횟수 실시간 모니터링 ===
$ bpftrace -e '
tracepoint:xfs:xfs_alloc_exact_done,
tracepoint:xfs:xfs_alloc_near_first {
@ag_allocs[args->agno] = count();
}
interval:s:5 { print(@ag_allocs); clear(@ag_allocs); }
'
# === COW extent 할당 추적 ===
$ bpftrace -e '
tracepoint:xfs:xfs_reflink_allocate_cow {
printf("%s pid=%d ino=%lu off=%lu len=%lu\n",
comm, pid, args->ino, args->offset, args->len);
}
'
# === xfs_buf I/O 지연시간 분포 ===
$ bpftrace -e '
kprobe:xfs_buf_read_map { @start[arg0] = nsecs; }
kretprobe:xfs_buf_read_map /@start[arg0]/ {
@buf_read_us = hist((nsecs - @start[arg0]) / 1000);
delete(@start[arg0]);
}
'
sleep 30)하세요. bpftrace는 커널 5.0 이상에서 가장 안정적으로 동작합니다.
성능 튜닝
XFS 성능 최적화는 mkfs 시점(볼륨 생성), 마운트 시점(런타임 옵션), sysctl(커널 파라미터), 워크로드 프로파일(응용별 튜닝)의 네 단계로 접근합니다. 각 단계에서 잘못된 설정은 성능을 오히려 저하시킬 수 있으므로, 워크로드 특성을 먼저 파악하는 것이 중요합니다.
mkfs.xfs 워크로드별 최적 설정
# === 대용량 순차 쓰기 (미디어/백업 서버) ===
$ mkfs.xfs \
-d agcount=32 \ # 병렬 할당
-l size=512m,logdev=/dev/sdb1 \ # 외부 로그 + 큰 로그
-b size=4096 \
/dev/sda1
$ mount -o allocsize=1g,logbsize=256k,logbufs=8,noatime \
-o logdev=/dev/sdb1 /dev/sda1 /data
# extent size hint 설정 (디렉토리 단위)
$ xfs_io -c 'extsize 16m' /data/media/
# === 데이터베이스 (PostgreSQL/MySQL) ===
$ mkfs.xfs \
-d agcount=64,su=256k,sw=4 \ # RAID 최적화 + 많은 AG
-l size=1g,logdev=/dev/nvme1n1p1 \ # NVMe 외부 로그
-i size=512 \ # 작은 inode (DB는 소수 대형 파일)
/dev/md0
$ mount -o inode64,noatime,logbufs=8,logbsize=256k,allocsize=64k \
-o logdev=/dev/nvme1n1p1 /dev/md0 /data/db
# === 컨테이너/가상화 (VM 이미지) ===
$ mkfs.xfs \
-d agcount=16 \
-m reflink=1 \ # reflink 활성 (기본값이지만 명시)
/dev/sda1
$ mount -o inode64,lazytime /dev/sda1 /var/lib/containers
sysctl 튜닝 가이드
| 파라미터 | 기본값 | 대용량 순차 | DB 랜덤 I/O | 설명 |
|---|---|---|---|---|
fs.xfs.xfssyncd_centisecs | 3000 | 6000 | 3000 | 동기화 데몬 주기 (센티초). 높이면 배치 효과 증가 |
fs.xfs.speculative_prealloc_lifetime | 300 | 600 | 60 | Speculative prealloc 유지 시간 (초) |
fs.xfs.filestream_centisecs | 3000 | 6000 | 1500 | filestream AG 점유 유지 시간 |
vm.dirty_ratio | 20 | 40 | 10 | 쓰기 캐시 최대 비율 (%) |
vm.dirty_background_ratio | 10 | 20 | 5 | 백그라운드 flush 시작 비율 |
vm.dirty_expire_centisecs | 3000 | 6000 | 1500 | dirty 페이지 만료 시간 |
튜닝 효과 측정
# XFS 통계 확인 (누적/리셋 가능)
$ cat /sys/fs/xfs/sda1/stats/stats
extent_alloc 1523456 2345678 ...
abt 234567 345678 ...
blk_map 4567890 5678901 ...
log 678901 789012 ...
# 실시간 통계 변화 모니터링 (1초 간격)
$ watch -n 1 'cat /sys/fs/xfs/sda1/stats/stats'
# 튜닝 전후 비교: fio 벤치마크
$ fio --name=seq-write --rw=write --bs=1m --size=10g \
--directory=/data --numjobs=4 --ioengine=libaio --direct=1
$ fio --name=rand-rw --rw=randrw --bs=4k --size=1g \
--directory=/data --numjobs=16 --ioengine=libaio --direct=1
# extent 단편화 비율 확인 (낮을수록 좋음)
$ xfs_db -r -c 'frag -f' /dev/sda1
actual 1523456, ideal 234567, fragmentation factor 84.60%
# AG별 free space 분포 — 불균형 시 rebalance 필요
$ xfs_info /data
$ for ag in $(seq 0 31); do
xfs_db -r -c "agf $ag" -c 'print freeblks longest' /dev/sda1
done
nobarrier는 배터리 백업 RAID 컨트롤러에서만 사용하고, 그 외 환경에서는 데이터 손실 위험이 있습니다. (4) mkfs 옵션은 재생성 전에는 변경할 수 없으므로 초기 설계가 중요합니다.
참고자료
공식 문서
- XFS — Linux Kernel Documentation — 커널 공식 XFS 문서입니다
- XFS Online Fsck Design — Kernel Docs — 온라인 파일시스템 검사 설계 문서입니다
- XFS Self Describing Metadata — Kernel Docs — v5 자기 기술 메타데이터 구조를 설명합니다
- XFS Wiki (kernel.org) — XFS 공식 위키 페이지입니다
- xfs.org — XFS 커뮤니티 공식 사이트입니다
- xfs(5) — man page — XFS 마운트 옵션과 파일시스템 속성을 설명합니다
주요 참고 글
- A brief history of XFS (LWN, 2018) — SGI에서 리눅스까지 XFS의 역사를 다룹니다
- The ongoing evolution of XFS (LWN, 2022) — 온라인 복구, 역방향 매핑 등 최신 발전을 소개합니다
- Reflink and deduplication in XFS (LWN, 2015) — reflink와 COW 기능 설계를 설명합니다
- Improving the XFS log (LWN, 2020) — XFS 로그(저널) 개선 작업을 다룹니다
- XFS reverse-mapping (LWN, 2019) — 역방향 매핑 B+tree 구조와 활용을 설명합니다
- Online filesystem checking and repair for XFS (LWN, 2018) — 온라인 fsck 설계 개요입니다
커널 소스 (Elixir/Bootlin)
- fs/xfs/ — XFS 전체 소스 디렉터리입니다
- fs/xfs/xfs_super.c — 마운트/언마운트와 슈퍼블록 처리 코드입니다
- fs/xfs/xfs_inode.c — inode 연산 구현 코드입니다
- fs/xfs/xfs_alloc.c — Allocation Group 기반 블록 할당기입니다
- fs/xfs/xfs_log.c — WAL(Write-Ahead Logging) 구현 코드입니다
- fs/xfs/xfs_bmap.c — extent B+tree 매핑 구현 코드입니다
- fs/xfs/xfs_reflink.c — reflink/COW 구현 코드입니다
- fs/xfs/libxfs/xfs_ag.h — Allocation Group 자료구조 정의입니다
도구 및 프로젝트
- xfsprogs (git.kernel.org) — mkfs.xfs, xfs_repair, xfs_db 등 사용자 공간 도구 소스입니다
- xfstests (git.kernel.org) — 리눅스 파일시스템 통합 테스트 프레임워크입니다
- fio (GitHub) — XFS 벤치마크에 널리 사용되는 I/O 부하 생성 도구입니다
컨퍼런스 발표
- XFS: The Big Storage File System (linux.conf.au 2018) — Dave Chinner의 XFS 대용량 스토리지 활용 발표입니다
- XFS Online Repair (Vault 2022) — Darrick Wong의 온라인 복구 기능 발표입니다
관련 문서
XFS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.