XFS 파일시스템 심화
대용량·고병렬 환경에 최적화된 XFS를 대상으로 Allocation Group 분할이 락 경합을 줄이는 방식, B+tree 기반 free space/inode 인덱싱, extent 지연 할당과 writeback 경로, 로그(WAL) 재생 및 복구 절차, reflink/COW 동작, quota/프로젝트 ID 운용, xfs_db·xfs_repair·xfs_scrub 활용법까지 성능과 안정성 관점에서 상세히 설명합니다.
핵심 요약
- 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
- 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
- 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
- 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
- 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.
단계별 이해
- 경계 계층 파악
요청이 VFS에서 어디로 내려가는지 확인합니다. - 메타/데이터 분리
어느 경로에서 무엇이 갱신되는지 나눠 봅니다. - 동기화/플러시 확인
쓰기 반영 시점과 순서를 검증합니다. - 복구 시나리오 점검
비정상 종료 후 일관성 회복을 확인합니다.
개요 & 역사
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;
Allocation Groups 상세
XFS 볼륨은 여러 개의 Allocation Group(AG)으로 등분됩니다. 각 AG는 자체 수퍼블록 복사본, 프리 스페이스 B+tree, inode 관리 구조를 가집니다. 이 설계 덕분에 여러 스레드가 서로 다른 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;
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 참조 카운트 |
| Extent B+tree | Inode data fork | 파일 오프셋 | 파일 extent 매핑 |
| Directory B+tree | Inode data fork | 해시값 | 대형 디렉토리 엔트리 |
| Attr B+tree | Inode attr fork | 이름 해시 | 확장 속성 |
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[]; /* 레벨별 상태 */
};
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;
Delayed Allocation (delalloc)
XFS의 Delayed Allocation은 write() 시점에서 실제 블록 할당을 지연시키고, writeback 시점에 한꺼번에 할당합니다. 이를 통해:
- 단편화 최소화: 최종 파일 크기를 알고 할당하므로 연속 extent 확보 가능
- 메타데이터 오버헤드 감소: 임시 파일은 블록 할당 없이 삭제 가능
- 대역폭 최적화: 인접 블록을 한 번에 할당하여 디스크 시크 최소화
/* 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 체크섬, 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;
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는 마운트 상태에서 파일시스템을 확장할 수 있습니다. 새 AG를 추가하는 방식이므로 기존 데이터 재배치가 불필요합니다:
# 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와 달리, 특정 디렉토리 계층에 용량 한도를 설정할 수 있어 컨테이너나 프로젝트별 공간 관리에 유용합니다:
# 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 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 모드로 사용하면, 페이지 캐시를 우회하여 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 지원이 추가되었습니다. 데이터베이스 등의 애플리케이션이 저널링 없이도 블록 단위의 원자적 쓰기를 보장받을 수 있습니다. 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 | 인메모리 로그 버퍼 수 (2-8). 높을수록 쓰기 버스트 흡수 |
logbsize=N | 32K/256K | 로그 버퍼 크기. 대형 트랜잭션 워크로드에서 증가 |
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 |
| 가상화 이미지 (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 | 저널 로그 내용 덤프 |
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개 섹터는 고정된 헤더 구조체를 담습니다. 이 배치는 모든 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를 할당할 때 라운드 로빈과 파일 스트림 두 가지 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 콜백으로 추상화합니다. 이 섹션에서는 온디스크 노드 구조, 탐색 알고리즘, 분할/병합 메커니즘을 상세히 살펴봅니다.
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_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() 시스템 콜 시점에서 디스크 블록을 즉시 할당하지 않고, 페이지 캐시에 데이터를 축적한 뒤 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)이라는 두 단계 파이프라인을 통해 높은 동시성과 빠른 커밋 지연시간을 동시에 달성하는 정교한 구조입니다. 이 섹션에서는 트랜잭션 생명주기, 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);
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_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를 사용하여 실시간 성능 분석과 병목 진단이 가능합니다.
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와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.