Btrfs 파일시스템 심화

Btrfs의 핵심인 COW B-tree 설계를 바탕으로 트랜잭션 커밋, 루트 트리/청크 트리/extent 트리 관계, 서브볼륨과 스냅샷의 메타데이터 공유 모델, send/receive 증분 복제, 데이터·메타데이터 체크섬, 압축과 RAID 프로파일 선택, scrub/balance/defrag 운영 전략, ENOSPC와 조각화 대응까지 실제 운영 이슈 중심으로 상세히 정리합니다.

전제 조건: VFSBlock I/O 서브시스템 문서를 먼저 읽으세요. 디스크 기반 파일시스템은 저널링·할당기·복구 정책 차이가 핵심이므로, I/O 경로와 on-disk 구조를 함께 봐야 합니다.
일상 비유: 이 주제는 창고 배치와 재고 장부 운영과 비슷합니다. 공간 배치 규칙과 기록 정책이 달라지면 성능·복구·무결성 특성이 크게 달라집니다.

핵심 요약

  • 계층 이해 — VFS, 캐시, 하위 FS 경계를 구분합니다.
  • 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
  • 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
  • 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
  • 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.

단계별 이해

  1. 경계 계층 파악
    요청이 VFS에서 어디로 내려가는지 확인합니다.
  2. 메타/데이터 분리
    어느 경로에서 무엇이 갱신되는지 나눠 봅니다.
  3. 동기화/플러시 확인
    쓰기 반영 시점과 순서를 검증합니다.
  4. 복구 시나리오 점검
    비정상 종료 후 일관성 회복을 확인합니다.
관련 표준: Btrfs On-Disk Format Documentation (kernel.org) — Btrfs 디스크 레이아웃 공식 문서입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

개요 & 역사

Btrfs(B-tree File System, "버터 FS"로 발음)는 Copy-on-Write(COW) 기반의 차세대 Linux 파일시스템입니다. Oracle의 Chris Mason이 2007년 개발을 시작하여, Linux 2.6.29(2009)에서 메인라인에 병합되었습니다.

개발 역사

시기커널 버전주요 이정표
2007-Chris Mason(Oracle)이 개발 시작, ZFS 대안 목표
20092.6.29메인라인 병합, 기본 COW + B-tree 구조
20123.4send/receive, qgroup, device replace 추가
20133.9RAID 5/6 실험적 지원, skinny metadata
20184.14+zstd 압축 지원 추가
20205.5space_cache v2 (free space tree) 기본 활성화
20225.15+Fedora 33+ 기본 FS 채택, SUSE 엔터프라이즈 지원
20246.7+RAID1 성능 개선, extent tree v2 개발 진행

주요 스펙

항목
최대 볼륨 크기16 EiB
최대 파일 크기16 EiB (논리적), 실질적으로 볼륨 크기에 제한
최대 파일 수264
파일명 길이255 바이트
블록(섹터) 크기4K (기본), metadata nodesize 16K (기본)
체크섬crc32c (기본), xxhash, sha256, blake2b
내장 RAID0, 1, 10, 5/6 (실험적), DUP
COW전체 메타데이터 + 데이터 (기본)
압축zlib, lzo, zstd
타임스탬프 범위1970-01-01 ~ 2486 (나노초 정밀도)

설계 철학

Btrfs 핵심 원칙: 모든 메타데이터와 데이터를 B-tree로 관리하면서 COW를 통해 일관성을 보장합니다. 이로 인해 별도의 저널링 없이도 파일시스템 무결성을 유지하며, 스냅샷과 클론을 O(1)으로 생성할 수 있습니다.

Btrfs의 설계는 세 가지 핵심 원칙에 기반합니다:

아키텍처 & 디스크 레이아웃

Btrfs는 ext4처럼 고정 크기 Block Group을 사용하지 않습니다. 대신 Chunk 기반 동적 할당으로 스토리지를 관리하며, 모든 메타데이터를 B-tree에 저장합니다.

Btrfs 디스크 레이아웃 0 Superblock Sys Chunk Array Chunk (Metadata) Chunk (Data) Chunk (Data) Unallocated B-tree 계층 구조 Root Tree (tree of trees) FS Tree (subvol) Extent Tree Checksum Tree Chunk Tree Dev Tree Free Space Tree UUID Tree FS Tree 내부: INODE_ITEM DIR_ITEM / DIR_INDEX EXTENT_DATA

Superblock

Btrfs superblock은 디바이스의 고정 오프셋(64K, 64M, 256G)에 위치하며, 파일시스템 부팅에 필요한 핵심 정보를 담고 있습니다:

/* fs/btrfs/ctree.h */
struct btrfs_super_block {
    /* 체크섬: 첫 32바이트는 나머지 블록의 체크섬 */
    u8  csum[BTRFS_CSUM_SIZE];       /* 32B */
    u8  fsid[BTRFS_FSID_SIZE];       /* 16B - 파일시스템 UUID */
    __le64 bytenr;                    /* superblock 자신의 위치 */
    __le64 flags;
    __le64 magic;                     /* "_BHRfS_M" */
    __le64 generation;                /* 트랜잭션 세대 번호 */
    __le64 root;                      /* Root Tree의 루트 블록 위치 */
    __le64 chunk_root;                /* Chunk Tree 루트 */
    __le64 log_root;                  /* Tree-log 루트 */
    __le64 total_bytes;               /* 전체 크기 */
    __le64 bytes_used;                /* 사용된 바이트 */
    __le64 num_devices;               /* 디바이스 수 */
    __le32 sectorsize;                /* 섹터 크기 (보통 4096) */
    __le32 nodesize;                  /* B-tree 노드 크기 (보통 16384) */
    __le16 csum_type;                 /* 체크섬 알고리즘 */
    /* ... 추가 필드 생략 ... */
    u8  sys_chunk_array[BTRFS_SYSTEM_CHUNK_ARRAY_SIZE]; /* 부트스트랩 chunk 매핑 */
} __attribute__((packed));

핵심 B-tree 종류

TreeObject ID역할
Root Tree1"tree of trees" — 모든 서브 트리의 루트 포인터 저장
Extent Tree2데이터/메타데이터 extent의 할당 상태, 역참조(back-reference)
Chunk Tree3논리 → 물리 주소 매핑 (Chunk 할당)
Dev Tree4물리 디바이스별 할당 상태
FS Tree5+ (서브볼륨별)파일/디렉토리 메타데이터, extent 참조
Checksum Tree7데이터 extent의 체크섬
Free Space Tree10블록 그룹별 미사용 공간 (space_cache v2)
UUID Tree9서브볼륨 UUID → ID 매핑 (send/receive용)
Quota Tree8서브볼륨 공간 할당량(qgroup) 추적
Tree-Log-7~-8fsync 최적화용 임시 트리 (재부팅 시 replay)

B-tree 노드 구조

Btrfs의 모든 B-tree 노드는 nodesize(기본 16K) 크기이며, 헤더 + 아이템 배열로 구성됩니다:

/* B-tree 노드 헤더 */
struct btrfs_header {
    u8     csum[BTRFS_CSUM_SIZE];  /* 노드 전체의 체크섬 */
    u8     fsid[BTRFS_FSID_SIZE];  /* 파일시스템 UUID */
    __le64 bytenr;                /* 이 노드의 논리 주소 */
    __le64 flags;
    u8     chunk_tree_uuid[BTRFS_UUID_SIZE];
    __le64 generation;             /* COW 시점의 트랜잭션 번호 */
    __le64 owner;                  /* 이 노드가 속한 tree ID */
    __le32 nritems;                /* 아이템 수 */
    u8     level;                  /* 0=리프, 1+=내부 노드 */
};

/* 리프 노드 아이템 */
struct btrfs_item {
    struct btrfs_disk_key key;   /* (objectid, type, offset) */
    __le32 offset;                /* 리프 내 데이터 시작 오프셋 */
    __le32 size;                  /* 데이터 크기 */
};

Chunk 매핑

Btrfs는 논리 주소(logical address)와 물리 주소(physical address)를 분리합니다. Chunk Tree가 논리 → 물리 매핑을 담당하며, 이를 통해 여러 디바이스에 걸친 RAID 배치가 가능합니다:

/* Chunk 매핑 정보 */
struct btrfs_chunk {
    __le64 length;           /* chunk 크기 */
    __le64 owner;            /* Extent Tree objectid */
    __le64 stripe_len;       /* stripe 길이 (보통 64K) */
    __le64 type;             /* DATA | METADATA | SYSTEM + RAID 프로파일 */
    __le16 num_stripes;      /* stripe 수 */
    __le16 sub_stripes;      /* RAID10용 sub-stripe 수 */
    struct btrfs_stripe stripe[];  /* 가변 길이 stripe 배열 */
};

Copy-on-Write (COW)

COW는 Btrfs의 가장 근본적인 메커니즘입니다. 기존 블록을 직접 수정하지 않고 새 위치에 수정된 데이터를 기록한 후 포인터를 갱신합니다.

COW (Copy-on-Write) 동작 흐름 Before (기존 상태) Root Node Node A Node B Leaf 1 Leaf 2 ★ After (COW 후) Root' (new) Node A' (new) Node B Leaf 1 Leaf 2' (new) COW Leaf 2 수정 → Leaf 2' 새로 할당 → Node A' 새로 할당 → Root' 새로 할당 Node B는 변경 없으므로 기존 블록 공유 (O(log N) 복사) ★ 수정된 리프 / 음영 = 새로 할당된 노드 / 기존 노드는 가비지 컬렉션 대상

COW 동작 원리

파일의 한 블록을 수정하면 다음 과정이 일어납니다:

  1. 새로운 데이터 블록에 수정된 내용 기록
  2. 해당 리프 노드를 새 위치에 COW (새 데이터 블록 포인터 포함)
  3. 부모 노드들을 루트까지 재귀적으로 COW
  4. Superblock에 새 루트 위치 기록 (atomic commit point)
  5. 이전 블록들은 해제 대상이 됨 (스냅샷이 참조하지 않는 경우)

트랜잭션 모델

Btrfs는 COW를 기반으로 세대(generation) 기반 트랜잭션을 구현합니다:

/* fs/btrfs/transaction.h */
struct btrfs_transaction {
    u64 transid;                /* 트랜잭션 ID (generation) */
    atomic_t num_writers;         /* 현재 writer 수 */
    atomic_t use_count;           /* 참조 카운트 */
    enum btrfs_trans_state state; /* RUNNING → COMMIT_START → COMMIT_DOING → COMMITTED */
    struct list_head list;       /* 트랜잭션 리스트 */
    struct extent_io_tree dirty_pages; /* 더티 페이지 추적 */
};

/* 트랜잭션 참여 */
struct btrfs_trans_handle *btrfs_start_transaction(
    struct btrfs_root *root, unsigned int num_items);
int btrfs_commit_transaction(struct btrfs_trans_handle *trans);

커밋 과정은 두 단계로 진행됩니다:

  1. 트랜잭션 실행: 모든 수정 사항이 메모리에 COW로 반영되고, 새 블록들이 디스크에 기록됨
  2. 슈퍼블록 기록: 모든 데이터가 디스크에 안착한 후 슈퍼블록의 root 포인터를 원자적으로 갱신 (commit point)

NODATACOW

주의: NODATACOW 설정 시 해당 파일의 데이터는 COW가 비활성화되어 체크섬도 비활성화됩니다. 데이터베이스 파일이나 VM 이미지처럼 랜덤 쓰기가 빈번한 경우에 유용하지만, 데이터 무결성 검증이 불가능해집니다.
# 특정 파일/디렉토리에 NODATACOW 속성 설정
$ chattr +C /path/to/file

# 마운트 옵션으로 전체 적용
$ mount -o nodatacow /dev/sda1 /mnt

# 상태 확인
$ lsattr /path/to/file
---------------C---- /path/to/file

서브볼륨 & 스냅샷

서브볼륨(subvolume)은 Btrfs의 핵심 기능으로, 하나의 파일시스템 내에서 독립적인 POSIX 파일 트리를 형성합니다. 각 서브볼륨은 고유한 FS Tree(tree ID)를 가지며, 스냅샷은 서브볼륨의 특수한 형태입니다.

서브볼륨과 스냅샷 구조 Top-level (ID=5) @rootfs (ID=256) @home (ID=257) @snap-home (ID=258) [RO] 공유 extent (COW) 스냅샷(@snap-home)은 @home과 동일한 extent를 COW로 공유 수정이 발생할 때만 새 블록이 할당됨 (공간 효율적)

서브볼륨 개념

# 서브볼륨 생성
$ btrfs subvolume create /mnt/@rootfs
$ btrfs subvolume create /mnt/@home

# 서브볼륨 목록 확인
$ btrfs subvolume list /mnt
ID 256 gen 100 top level 5 path @rootfs
ID 257 gen 100 top level 5 path @home

# 특정 서브볼륨으로 마운트
$ mount -o subvol=@rootfs /dev/sda1 /
$ mount -o subvol=@home   /dev/sda1 /home

# 서브볼륨별 마운트 (subvolid 사용)
$ mount -o subvolid=256 /dev/sda1 /

스냅샷

스냅샷은 COW를 활용하여 서브볼륨의 시점 복사본을 O(1) 시간에 생성합니다:

# 읽기 전용 스냅샷 (백업/복구에 권장)
$ btrfs subvolume snapshot -r /home /mnt/@snap-home-$(date +%Y%m%d)

# 쓰기 가능 스냅샷
$ btrfs subvolume snapshot /home /mnt/@home-work

# 스냅샷 삭제
$ btrfs subvolume delete /mnt/@snap-home-20240101

# 스냅샷에서 롤백 (서브볼륨 교체 방식)
$ btrfs subvolume delete /mnt/@rootfs
$ btrfs subvolume snapshot /mnt/@snap-rootfs-good /mnt/@rootfs

Send/Receive

Btrfs send/receive는 스냅샷 간 차이(delta)를 스트림으로 전송하여 증분 백업이나 원격 복제를 구현합니다:

# 전체 전송 (최초 백업)
$ btrfs send /mnt/@snap-day1 | btrfs receive /backup/

# 증분 전송 (이전 스냅샷 대비 변경분만)
$ btrfs send -p /mnt/@snap-day1 /mnt/@snap-day2 | btrfs receive /backup/

# SSH를 통한 원격 전송
$ btrfs send -p /mnt/@snap-day1 /mnt/@snap-day2 | \
    ssh remote_host btrfs receive /backup/

# 파일로 저장
$ btrfs send /mnt/@snap-day1 -f /tmp/snap-day1.btrfs
팁: send/receive는 읽기 전용 스냅샷에서만 동작합니다. 증분 전송의 경우 소스와 대상 모두에 기준 스냅샷(-p)이 존재해야 합니다. btrfs send--compressed-data 옵션(커널 5.18+)은 압축된 데이터를 그대로 전송하여 CPU 사용을 줄입니다.

기본 서브볼륨

# 기본 서브볼륨 변경 (부팅 시 자동 마운트될 서브볼륨)
$ btrfs subvolume set-default 256 /mnt

# 현재 기본 서브볼륨 확인
$ btrfs subvolume get-default /mnt
ID 256 gen 100 top level 5 path @rootfs

데이터 무결성

Btrfs는 모든 데이터와 메타데이터에 체크섬을 적용하여 bit rot, 불량 섹터, 기타 무음 데이터 손상(silent corruption)을 탐지합니다.

체크섬 알고리즘

알고리즘크기유형커널 버전특징
crc32c4B비암호화기본CPU 가속(SSE4.2), 높은 성능, 대부분의 워크로드에 적합
xxhash8B비암호화5.5+crc32c 대비 더 강한 충돌 저항, ARM에서 빠름
sha25632B암호화5.5+높은 보안 수준, 성능 오버헤드 큼
blake2b32B암호화5.5+sha256 대비 빠른 암호화 해시, 보안 + 성능 균형
# 파일시스템 생성 시 체크섬 알고리즘 지정 (포맷 후 변경 불가)
$ mkfs.btrfs --csum xxhash /dev/sda1
$ mkfs.btrfs --csum blake2b /dev/sda1

# 현재 사용 중인 체크섬 확인
$ btrfs inspect-internal dump-super /dev/sda1 | grep csum_type
csum_type               xxhash64 (2)

Checksum Tree

데이터 블록의 체크섬은 Checksum Tree(tree ID=7)에 저장됩니다. 각 항목은 연속된 데이터 블록 범위의 체크섬 배열입니다:

/* fs/btrfs/file-item.c - 체크섬 조회 */
int btrfs_lookup_csums_range(
    struct btrfs_root *root,
    u64 start,             /* 시작 바이트 오프셋 */
    u64 end,               /* 끝 바이트 오프셋 */
    struct list_head *list, /* 결과 체크섬 리스트 */
    int search_commit       /* 커밋된 루트에서 검색 여부 */
);

/* 체크섬 검증 흐름:
 * 1. 데이터 블록 읽기
 * 2. Checksum Tree에서 저장된 체크섬 조회
 * 3. 읽은 데이터로 체크섬 계산
 * 4. 비교 → 불일치 시 -EIO 또는 미러에서 복구 시도
 */

Scrub

Scrub은 온라인 상태에서 전체 파일시스템의 데이터 무결성을 검증하는 백그라운드 작업입니다:

# scrub 시작
$ btrfs scrub start /mnt

# 진행 상태 확인
$ btrfs scrub status /mnt
UUID:             12345678-...
Scrub started:    Mon Jan  1 00:00:00 2024
Status:           running
Duration:         0:05:32
Total to scrub:   100.00GiB
Bytes scrubbed:   45.20GiB (45.20%)
Rate:             140.00MiB/s
Error summary:    csum=0

# scrub 일시 중지 / 재개
$ btrfs scrub cancel /mnt
$ btrfs scrub resume /mnt

자가 복구

RAID1/10/DUP 구성에서 체크섬 불일치가 감지되면, Btrfs는 정상 미러에서 데이터를 읽어 손상된 복사본을 자동으로 복구합니다:

설명 요약:
  • fs/btrfs/raid56.c, volumes.c - 자가 복구 흐름 */
  • 읽기 I/O 완료 → 체크섬 검증 실패
  • bio에서 미러 번호 확인
  • 다른 미러(mirror_num)에서 동일 블록 재읽기
  • 체크섬 검증 성공 시:
  • 정상 데이터를 요청자에게 반환
  • 손상된 미러에 정상 데이터를 기록 (repair)
  • 모든 미러 실패 시 → -EIO 반환
참고: 단일 디바이스 + DUP 메타데이터 구성에서도 메타데이터의 자가 복구가 가능합니다. 데이터의 자가 복구는 RAID1/10 또는 DUP 데이터 프로파일이 필요합니다.

압축

Btrfs는 파일 데이터의 투명 압축(transparent compression)을 지원하여 디스크 공간을 절약하고, 특정 워크로드에서는 I/O 대역폭도 개선합니다.

압축 알고리즘

알고리즘커널 버전압축률속도레벨특징
zlib초기높음느림1~9 (기본 3)전통적, 최고 압축률이 필요할 때
lzo2.6.38낮음빠름없음CPU 오버헤드 최소, 임베디드/SBC에 적합
zstd4.14높음빠름1~15 (기본 3)최신, 최적의 압축률/속도 균형 (권장)

압축 설정

# 마운트 옵션으로 전체 압축 활성화
$ mount -o compress=zstd /dev/sda1 /mnt
$ mount -o compress=zstd:3 /dev/sda1 /mnt          # 레벨 지정
$ mount -o compress-force=zstd /dev/sda1 /mnt       # 강제 압축 (비압축성 데이터 포함)

# 파일/디렉토리별 압축 설정 (btrfs property)
$ btrfs property set /mnt/logs compression zstd
$ btrfs property get /mnt/logs compression
compression=zstd

# 기존 파일 재압축
$ btrfs filesystem defragment -r -czstd /mnt/data/

내부 구조

Btrfs 압축은 최대 128K 단위로 동작합니다. 파일의 각 extent는 독립적으로 압축되며, 압축 유형은 extent 메타데이터에 기록됩니다:

/* fs/btrfs/ctree.h - extent 데이터 */
struct btrfs_file_extent_item {
    __le64 generation;
    __le64 ram_bytes;       /* 압축 전 크기 (원본) */
    u8     compression;      /* 0=없음, 1=zlib, 2=lzo, 3=zstd */
    u8     encryption;       /* 미사용 (예약) */
    __le16 other_encoding;   /* 미사용 */
    u8     type;             /* INLINE / REG / PREALLOC */
    __le64 disk_bytenr;     /* 디스크상의 시작 위치 */
    __le64 disk_num_bytes;  /* 압축 후 디스크 크기 */
    __le64 offset;          /* extent 내 오프셋 */
    __le64 num_bytes;       /* 논리 크기 */
};

/* 압축 비율 확인 */
$ compsize /mnt
Processed 12345 files, 6789 regular extents (7000 refs)
Type       Perc     Disk Usage   Uncompressed
TOTAL       65%       6.5G          10G
zstd        62%       5.8G          9.3G
none       100%       700M          700M

RAID & 멀티 디바이스

Btrfs는 볼륨 매니저(LVM) 없이 자체적으로 여러 디바이스를 관리하고 RAID를 구성합니다. 데이터와 메타데이터에 서로 다른 RAID 프로파일을 적용할 수 있습니다.

RAID 프로파일

프로파일최소 디바이스중복도가용 용량읽기 성능상태
single1없음100%1x안정
DUP12 복사 (같은 디바이스)~50%1x안정 (메타데이터 기본)
RAID02없음N × 최소Nx안정
RAID122 복사 (다른 디바이스)~50%2x안정
RAID1C333 복사~33%3x안정 (5.5+)
RAID1C444 복사~25%4x안정 (5.5+)
RAID1042 복사 + 스트라이핑~50%Nx안정
RAID531 패리티(N-1) × 최소(N-1)x불안정
RAID642 패리티(N-2) × 최소(N-2)x불안정
경고: RAID5/6은 프로덕션에서 사용하지 마십시오. Btrfs RAID5/6은 write hole 문제와 불완전한 복구 코드로 인해 여전히 불안정합니다. 데이터 손실 위험이 있으며, Btrfs 위키에서도 프로덕션 사용을 권장하지 않습니다. 패리티 기반 중복이 필요하면 mdadm RAID5/6 위에 Btrfs single 프로파일을 사용하십시오.
Btrfs RAID1 데이터/메타데이터 분리 배치 Device 1 (sda - 500G) Meta (RAID1) Data (RAID1) Device 2 (sdb - 500G) Meta (RAID1) Data (RAID1) 메타데이터: RAID1 (2 복사) | 데이터: RAID1 (2 복사) 각 chunk는 독립적으로 프로파일이 적용됨 일반적 권장 구성: 메타데이터 = RAID1 / 데이터 = RAID1 or RAID0 mkfs.btrfs -d raid1 -m raid1 /dev/sda /dev/sdb

디바이스 관리

# 멀티 디바이스 파일시스템 생성
$ mkfs.btrfs -d raid1 -m raid1 /dev/sda /dev/sdb

# 디바이스 추가
$ btrfs device add /dev/sdc /mnt
$ btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt

# 디바이스 제거
$ btrfs device remove /dev/sdb /mnt

# 결함 디바이스 교체 (온라인)
$ btrfs replace start /dev/sdb /dev/sdd /mnt
$ btrfs replace status /mnt

# 디바이스 사용량 확인
$ btrfs device usage /mnt
/dev/sda, ID: 1
   Device size:           500.00GiB
   Data,RAID1:            200.00GiB
   Metadata,RAID1:         10.00GiB
   System,RAID1:           32.00MiB
   Unallocated:           289.97GiB

프로파일 변환

# single → RAID1 변환 (온라인)
$ btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt

# RAID1 → RAID10 변환 (4+ 디바이스 필요)
$ btrfs balance start -dconvert=raid10 /mnt

# 필터를 사용한 부분 변환
$ btrfs balance start -dconvert=raid1,soft /mnt  # 이미 raid1인 chunk는 건너뜀

핵심 커널 자료구조

Btrfs 커널 코드(fs/btrfs/)의 핵심 자료구조를 살펴봅니다.

btrfs_key

B-tree의 모든 아이템은 (objectid, type, offset) 3-tuple 키로 정렬됩니다:

struct btrfs_key {
    __le64 objectid;   /* 대상 객체 ID (inode 번호, tree ID 등) */
    u8     type;       /* 아이템 타입 */
    __le64 offset;     /* 타입별 의미 다름 (offset, size 등) */
};

주요 아이템 타입:

상수objectid 의미offset 의미
BTRFS_INODE_ITEM_KEY1inode 번호0
BTRFS_DIR_ITEM_KEY84부모 inode이름의 crc32c 해시
BTRFS_DIR_INDEX_KEY96부모 inode시퀀스 번호
BTRFS_EXTENT_DATA_KEY108inode 번호파일 내 오프셋
BTRFS_EXTENT_ITEM_KEY168바이트 오프셋extent 크기
BTRFS_CHUNK_ITEM_KEY228tree ID (보통 256)논리 오프셋
BTRFS_ROOT_ITEM_KEY132tree ID0 또는 transid

btrfs_fs_info

파일시스템 전체의 런타임 상태를 관리하는 최상위 구조체:

/* fs/btrfs/fs.h */
struct btrfs_fs_info {
    struct btrfs_root  *tree_root;       /* Root Tree */
    struct btrfs_root  *chunk_root;      /* Chunk Tree */
    struct btrfs_root  *extent_root;     /* Extent Tree */
    struct btrfs_root  *csum_root;       /* Checksum Tree */
    struct btrfs_root  *uuid_root;       /* UUID Tree */
    struct btrfs_root  *free_space_root; /* Free Space Tree */
    struct super_block *sb;              /* VFS super_block */

    u64 generation;                       /* 현재 트랜잭션 세대 */
    u64 last_trans_committed;              /* 마지막 커밋된 세대 */

    struct btrfs_transaction *running_transaction;
    struct btrfs_space_info *data_sinfo;   /* 데이터 공간 정보 */
    struct btrfs_space_info *meta_sinfo;   /* 메타데이터 공간 정보 */

    unsigned long mount_opt;               /* 마운트 옵션 비트필드 */
    u32 sectorsize;                       /* 섹터 크기 */
    u32 nodesize;                         /* B-tree 노드 크기 */
    /* ... 수백 개의 필드 생략 ... */
};

btrfs_root

/* fs/btrfs/ctree.h */
struct btrfs_root {
    struct rb_node        rb_node;        /* fs_info의 rbtree에 연결 */
    struct extent_buffer  *node;           /* 루트 노드 (메모리) */
    struct extent_buffer  *commit_root;    /* 커밋된 루트 (읽기용) */
    struct btrfs_root_item root_item;      /* 디스크 아이템 */
    struct btrfs_key       root_key;       /* Root Tree 내의 키 */
    struct btrfs_fs_info  *fs_info;        /* 역참조 */
    u64 root_key_objectid;                  /* tree ID */
    u64 last_trans;                          /* 마지막 수정 트랜잭션 */
};

btrfs_inode

/* fs/btrfs/btrfs_inode.h */
struct btrfs_inode {
    struct btrfs_root     *root;           /* 소속 FS Tree */
    struct btrfs_key       location;       /* (ino, INODE_ITEM, 0) */
    u64                    disk_i_size;    /* 디스크 상 파일 크기 */
    u64                    generation;     /* 생성 트랜잭션 */
    u64                    flags;          /* NODATACOW, COMPRESS 등 */
    struct extent_io_tree  io_tree;        /* I/O 상태 추적 */
    struct inode           vfs_inode;      /* VFS inode (임베딩) */
};

btrfs_path

B-tree 검색/순회에 사용되는 경로 구조체:

/* fs/btrfs/ctree.h */
struct btrfs_path {
    struct extent_buffer *nodes[BTRFS_MAX_LEVEL]; /* 루트→리프 노드 배열 */
    int                   slots[BTRFS_MAX_LEVEL]; /* 각 레벨의 슬롯 인덱스 */
    u8                    locks[BTRFS_MAX_LEVEL]; /* 잠금 상태 */
    int                   keep_locks;              /* 순회 시 잠금 유지 */
    int                   lowest_level;             /* 검색 중단 레벨 */
};

/* B-tree 검색 예시 */
struct btrfs_path *path = btrfs_alloc_path();
struct btrfs_key key = { .objectid = ino, .type = BTRFS_INODE_ITEM_KEY, .offset = 0 };
int ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
/* ret == 0: 정확한 키 발견
 * ret > 0: 키 없음, path는 삽입 위치
 * ret < 0: 에러 */
btrfs_free_path(path);

공간 관리

Btrfs의 공간 관리는 블록 그룹(Chunk), 미사용 공간 추적, balance, defrag 등 여러 메커니즘으로 구성됩니다.

Btrfs 공간 할당 계층 Block Group Allocator Space Info (data / meta / system) Chunk Allocator Block Group (1~수 GiB 단위) Free Space Cache/Tree Used Extents Free Space Used Extents Free Space (unallocated)

블록 그룹

Btrfs는 스토리지를 블록 그룹(Block Group) 단위로 관리합니다. 각 블록 그룹은 하나의 Chunk에 대응하며, DATA/METADATA/SYSTEM 타입 중 하나를 가집니다:

# 블록 그룹 할당 상태 확인
$ btrfs filesystem usage /mnt
Overall:
    Device size:                   1.00TiB
    Device allocated:            500.03GiB
    Device unallocated:          523.97GiB
    Used:                        400.00GiB

Data,RAID1: Size:240.00GiB, Used:195.50GiB (81.46%)
Metadata,RAID1: Size:10.00GiB, Used:5.20GiB (52.00%)
System,RAID1: Size:32.00MiB, Used:48.00KiB (0.15%)

미사용 공간 추적

Btrfs는 두 가지 방식으로 블록 그룹 내 미사용 공간을 추적합니다:

방식저장 위치특징
space_cache v1숨겨진 inode 파일기존 방식, 마운트 시 전체 로드
space_cache v2 (Free Space Tree)전용 B-tree (tree ID=10)5.15+ 기본값, COW 보호, 빠른 마운트
# space_cache v2 강제 활성화
$ mount -o space_cache=v2 /dev/sda1 /mnt

# space_cache 상태 확인
$ btrfs inspect-internal dump-super /dev/sda1 | grep cache
compat_ro_flags         0x1
                        ( FREE_SPACE_TREE | FREE_SPACE_TREE_VALID )

Balance

Balance는 블록 그룹 간 데이터를 재배치하는 작업입니다. RAID 프로파일 변환, 디바이스 추가/제거 후 균등 분배, 공간 회수에 사용됩니다:

# 전체 balance (모든 블록 그룹 재배치 - 시간 많이 소요)
$ btrfs balance start /mnt

# 사용률이 낮은 블록 그룹만 balance (공간 회수)
$ btrfs balance start -dusage=50 /mnt    # 사용률 50% 미만 데이터 그룹
$ btrfs balance start -musage=50 /mnt    # 사용률 50% 미만 메타데이터 그룹

# balance 상태 / 취소
$ btrfs balance status /mnt
$ btrfs balance cancel /mnt

Defrag

# 파일 단편화 해소
$ btrfs filesystem defragment /mnt/large_file

# 디렉토리 재귀 defrag + 압축 적용
$ btrfs filesystem defragment -r -czstd /mnt/data/

# defrag 범위 지정 (오프셋 + 길이)
$ btrfs filesystem defragment -s 0 -l 1G /mnt/large_file
주의: defrag은 COW 특성상 스냅샷과 공유하던 extent의 공유를 해제합니다. 스냅샷이 많은 환경에서 defrag을 수행하면 디스크 사용량이 급증할 수 있습니다.

Resize

# 파일시스템 확장 (온라인)
$ btrfs filesystem resize +100G /mnt
$ btrfs filesystem resize max /mnt      # 디바이스 전체로 확장

# 파일시스템 축소 (온라인, 주의 필요)
$ btrfs filesystem resize -50G /mnt

# 특정 디바이스만 resize (devid 지정)
$ btrfs filesystem resize 1:+100G /mnt

ENOSPC 문제

Btrfs는 Chunk 단위 할당 방식 때문에 디스크에 빈 공간이 있어도 ENOSPC가 발생할 수 있습니다:

# ENOSPC 진단
$ btrfs filesystem usage /mnt           # allocated vs unallocated 확인
$ btrfs filesystem df /mnt              # 타입별 사용량

# ENOSPC 대처: 사용률 낮은 블록 그룹 재배치
$ btrfs balance start -dusage=0 /mnt    # 빈 데이터 그룹 해제
$ btrfs balance start -dusage=10 /mnt   # 10% 미만 그룹 통합
$ btrfs balance start -musage=10 /mnt   # 메타데이터도 동일

관리 도구 (btrfs-progs)

btrfs-progs는 Btrfs 사용자 공간 도구 모음입니다. btrfs 명령어를 통해 파일시스템 관리의 모든 측면을 제어합니다.

주요 명령어

명령어설명예시
mkfs.btrfs파일시스템 생성mkfs.btrfs -L myfs -d raid1 /dev/sd{a,b}
btrfs filesystemFS 관리 (usage, df, resize, defrag, show)btrfs fi usage /mnt
btrfs subvolume서브볼륨 관리 (create, delete, list, snapshot)btrfs sub list /mnt
btrfs device디바이스 관리 (add, remove, usage)btrfs dev usage /mnt
btrfs balance블록 그룹 재배치btrfs bal start -dusage=50 /mnt
btrfs scrub데이터 무결성 검증btrfs scrub start /mnt
btrfs replace디바이스 교체btrfs replace start /dev/sda /dev/sdb /mnt
btrfs send/receive스냅샷 전송/수신btrfs send /snap | btrfs receive /backup
btrfs check오프라인 검사 (fsck)btrfs check /dev/sda1
btrfs rescue복구 도구btrfs rescue super-recover /dev/sda1
btrfs property속성 관리btrfs prop set /dir compression zstd
btrfs quota할당량 관리btrfs quota enable /mnt
btrfs qgroup쿼터 그룹 관리btrfs qgroup show /mnt

filesystem usage

# 가장 유용한 공간 확인 명령어
$ btrfs filesystem usage /mnt
Overall:
    Device size:                   1.00TiB
    Device allocated:            600.03GiB
    Device unallocated:          423.97GiB
    Device missing:                  0.00B
    Device slack:                    0.00B
    Used:                        450.00GiB
    Free (estimated):            350.00GiB      (min: 250.00GiB)
    Free (statfs, df):           350.00GiB
    Data ratio:                       2.00
    Metadata ratio:                   2.00
    Global reserve:              512.00MiB      (used: 0.00B)
    Multiple profiles:                  no

Data,RAID1: Size:280.00GiB, Used:220.00GiB (78.57%)
   /dev/sda      280.00GiB
   /dev/sdb      280.00GiB

Metadata,RAID1: Size:20.00GiB, Used:5.00GiB (25.00%)
   /dev/sda       20.00GiB
   /dev/sdb       20.00GiB

System,RAID1: Size:32.00MiB, Used:48.00KiB (0.15%)
   /dev/sda       32.00MiB
   /dev/sdb       32.00MiB

Unallocated:
   /dev/sda      211.97GiB
   /dev/sdb      211.97GiB

inspect-internal

# superblock 정보 덤프
$ btrfs inspect-internal dump-super /dev/sda1

# B-tree 덤프 (디버깅용)
$ btrfs inspect-internal dump-tree /dev/sda1

# 특정 tree만 덤프
$ btrfs inspect-internal dump-tree -t 2 /dev/sda1  # Extent Tree

# inode → 경로 역참조
$ btrfs inspect-internal inode-resolve 256 /mnt
/mnt/some/file.txt

# 논리 주소 → 물리 주소 변환
$ btrfs inspect-internal logical-resolve 12345678 /mnt

성능 튜닝

Mount 옵션

옵션기본값설명
compress=zstd:N없음투명 압축 (zstd 권장, 레벨 1~15)
compress-force=zstd없음비압축성 파일도 강제 압축 시도
ssd자동 감지SSD 최적화 활성화
discard=async없음비동기 TRIM (SSD 필수)
noatimerelatimeatime 갱신 비활성화 (성능 향상)
space_cache=v2v2 (5.15+)Free Space Tree 사용
autodefrag없음자동 조각 모음 (랜덤 쓰기 워크로드)
commit=N30트랜잭션 커밋 주기(초)
thread_pool=N코어 수 + 2워커 스레드 수
max_inline=N2048인라인 extent 최대 크기
nodatacow없음COW 비활성화 (DB, VM 이미지용)
flushoncommit없음커밋 시 강제 flush (안정성 ↑, 성능 ↓)

SSD 최적화

# SSD에 최적화된 마운트 예시
$ mount -o compress=zstd:1,ssd,discard=async,noatime,space_cache=v2 \
    /dev/nvme0n1p2 /

# fstab 예시
UUID=xxx  /  btrfs  defaults,compress=zstd:1,ssd,discard=async,noatime,space_cache=v2,subvol=@  0 0
팁: discard=async는 커널 6.2+에서 크게 개선되어 대부분 SSD 환경에서 권장됩니다. 다만 장치/워크로드에 따라 주기적 fstrim이 더 적합할 수 있으므로 실측 비교 후 선택하세요. ssd 옵션은 대개 자동 감지되므로 명시하지 않아도 됩니다.

워크로드별 튜닝

# 데스크톱 / 범용 서버
$ mount -o compress=zstd:1,noatime,discard=async,space_cache=v2 ...

# 데이터베이스 (MySQL, PostgreSQL)
$ mount -o nodatacow,noatime,discard=async ...
# + DB 데이터 디렉토리에 chattr +C 설정
$ chattr +C /var/lib/mysql/
$ chattr +C /var/lib/postgresql/

# 가상머신 이미지 저장소
$ mount -o nodatacow,noatime ...
$ chattr +C /var/lib/libvirt/images/

# 로그 서버 (대량 순차 쓰기)
$ mount -o compress=zstd:3,noatime,commit=120,autodefrag ...

# NAS / 미디어 스토리지
$ mount -o compress=zstd:3,noatime,space_cache=v2 ...

모니터링

# 실시간 할당 상태
$ watch -n 5 btrfs fi usage /mnt

# 디바이스 I/O 통계
$ btrfs device stats /mnt
[/dev/sda].write_io_errs    0
[/dev/sda].read_io_errs     0
[/dev/sda].flush_io_errs    0
[/dev/sda].corruption_errs  0
[/dev/sda].generation_errs  0

# 에러 카운터 리셋
$ btrfs device stats -z /mnt

# 커널 메시지에서 btrfs 관련 로그 확인
$ dmesg | grep -i btrfs

다른 파일시스템 비교

기능 비교표

기능Btrfsext4XFSZFS
COWOXX (reflink O)O
스냅샷O (서브볼륨 단위)X (LVM 의존)XO (데이터셋 단위)
데이터 체크섬OX (메타만)XO
투명 압축O (zlib/lzo/zstd)XXO (lz4/gzip/zstd)
내장 RAIDO (0/1/10/5/6)XXO (Z1/Z2/Z3)
인라인 dedupeX (오프라인만)XXO
Send/ReceiveOXXO
온라인 축소OOXX
최대 볼륨 크기16 EiB1 EiB8 EiB256 ZiB
라이선스GPLGPLGPLCDDL
안정성양호 (RAID5/6 제외)매우 높음매우 높음매우 높음

선택 가이드

사용 사례추천이유
엔터프라이즈 서버 (보수적)ext4 / XFS검증된 안정성, 성숙한 fsck 도구
데스크톱 / 워크스테이션Btrfs스냅샷 롤백, 압축, 유연한 관리
NAS / 스토리지 서버Btrfs / ZFS데이터 무결성, 스냅샷, RAID
컨테이너 호스트Btrfs서브볼륨, 스냅샷, overlay 성능
데이터베이스 전용ext4 / XFSCOW 오버헤드 없음, 예측 가능한 I/O
대용량 데이터 레이크XFS대규모 병렬 I/O 최적화
최고 수준 데이터 보호ZFS검증된 RAIDZ, 인라인 dedupe, 성숙도

Btrfs vs ZFS

두 파일시스템 모두 COW 기반이며 유사한 기능 세트를 제공하지만, 근본적인 차이가 있습니다:

측면BtrfsZFS
라이선스GPL v2 (커널 내장)CDDL (외부 모듈, OpenZFS)
RAID 안정성RAID1/10 안정, RAID5/6 불안정RAIDZ1/Z2/Z3 매우 안정
메모리 사용상대적으로 적음ARC 캐시로 대량 메모리 사용 (RAM 절반 이상)
커널 통합메인라인 포함별도 DKMS/kmod 설치 필요
인라인 Dedupe미지원 (오프라인만)지원
Send/Receive지원지원
축소온라인 축소 가능불가
성숙도발전 중매우 성숙 (Solaris 계보)
팁: 순수 Linux 환경에서 커널 통합과 간편한 관리를 원하면 Btrfs, 대규모 스토리지의 최고 수준 데이터 보호와 검증된 RAID를 원하면 ZFS를 선택하십시오.

CoW B-tree 심화

Btrfs의 B-tree는 일반적인 B+ tree와 달리 모든 수정이 Copy-on-Write로 처리됩니다. 이 섹션에서는 COW B-tree의 노드 분할/병합 메커니즘, 쓰기 증폭(write amplification), 그리고 트리 높이에 따른 성능 영향을 심층적으로 분석합니다.

CoW B-tree 노드 분할 과정 1단계: 리프 노드 포화 Root (gen=100) Leaf A [FULL] (gen=99) Leaf B (gen=98) COW 2단계: COW 분할 완료 Root' (gen=101) Leaf A' (gen=101) Leaf C (gen=101) Leaf B COW B-tree 분할 상세 1. 포화 리프(Leaf A)에 새 아이템 삽입 요청 2. Leaf A를 COW → Leaf A' 생성 (새 위치에 기록) 3. Leaf A'의 아이템을 절반으로 분할 → Leaf C 생성 (새 키 범위) 4. Root를 COW → Root' 생성 (3개 자식 포인터 갱신) 쓰기 증폭 (Write Amplification) 높이 2 (2노드) 높이 3 (3노드) 높이 4 (4노드) 높이 5+ (5+노드) 1개 리프 수정 → 루트까지 O(h) 노드 COW 필요 (h = 트리 높이) nodesize=16K 기준: 1TB 데이터 → 높이 약 3~4, 100TB → 높이 약 4~5

노드 분할 (Split)

B-tree 리프 노드가 가득 차면 COW와 함께 분할이 일어납니다. 일반 B-tree와 달리 원본 노드를 수정하지 않고 새 노드를 할당하므로, 스냅샷이 원본을 계속 참조할 수 있습니다:

/* fs/btrfs/ctree.c - 리프 분할 */
static noinline int split_leaf(
    struct btrfs_trans_handle *trans,
    struct btrfs_root *root,
    const struct btrfs_key *ins_key,
    struct btrfs_path *path,
    int data_size,
    int extend)
{
    struct extent_buffer *l = path->nodes[0];
    struct extent_buffer *right;
    int mid, nritems;

    nritems = btrfs_header_nritems(l);
    mid = (nritems + 1) / 2;

    /* 새 리프 노드 할당 (COW 방식) */
    right = btrfs_alloc_tree_block(trans, root, 0,
                                   root->root_key.objectid,
                                   &disk_key, 0, l->start, 0);

    /* 아이템의 후반부를 새 리프로 복사 */
    copy_for_split(trans, path, l, right, mid, nritems);

    /* 부모 노드에 새 키 삽입 */
    insert_ptr(trans, path, &disk_key,
              right->start, path->slots[1] + 1, 1);
    return 0;
}

노드 병합 (Merge)

삭제 연산으로 리프 사용률이 낮아지면 인접 노드와 병합(merge)을 시도합니다. 병합도 COW 방식으로 진행되므로 스냅샷 참조에 영향을 주지 않습니다:

/* fs/btrfs/ctree.c - 삭제 후 리프 병합 판단 */
static void try_merge_after_delete(
    struct btrfs_trans_handle *trans,
    struct btrfs_root *root,
    struct btrfs_path *path)
{
    struct extent_buffer *leaf = path->nodes[0];
    u32 used = btrfs_leaf_data_size(leaf);
    u32 threshold = BTRFS_LEAF_DATA_SIZE(root->fs_info) / 3;

    /* 사용률이 1/3 이하면 인접 리프와 병합 시도 */
    if (used < threshold) {
        /* 왼쪽 또는 오른쪽 형제 노드에 아이템 이동 */
        push_leaf_left(trans, root, path, 1, 1);
        push_leaf_right(trans, root, path, 1, 1);
    }
}

쓰기 증폭 분석

파일시스템 크기예상 트리 높이단일 쓰기 시 COW 노드 수쓰기 증폭량 (nodesize=16K)
1 GiB22 (리프+루트)32 KiB
100 GiB3348 KiB
10 TiB4464 KiB
1 PiB5580 KiB
최적화: Btrfs는 동일 트랜잭션 내에서 같은 노드에 대한 중복 COW를 방지합니다. 노드가 이미 현재 트랜잭션의 세대(generation)로 COW되었다면, 추가 수정은 해당 노드에 직접(in-place) 적용됩니다. 이를 세대 기반 COW 최적화라고 합니다.

스냅샷 트리 심화

스냅샷은 서브볼륨의 FS Tree 루트를 공유하는 새로운 트리입니다. 이 섹션에서는 스냅샷의 내부 트리 구조, 메타데이터 공유 모델, 스냅샷 생성/삭제 비용, 그리고 qgroup 기반 공간 추적을 분석합니다.

스냅샷 트리 공유 구조 Root Tree (tree of trees) FS Tree @home (ID=257) Snap @snap1 (ID=258) Snap @snap2 (ID=259) Node A' Node B Node A Node B Node A Node B 공유 데이터 extent (refcount=3) 전용 extent (refcount=1) 스냅샷 비용 분석 생성 비용: O(1) — Root Tree에 새 ROOT_ITEM 추가 + FS Tree 루트 포인터 복사 삭제 비용: O(N) — 참조 카운트 감소 + exclusive extent 해제 (대량 스냅샷 시 느림) 공간 비용: 수정된 노드만 추가 공간 사용 (공유 extent는 refcount로 추적) @home에서 파일 수정 → Node A만 COW → Node A' 생성 (snap1/snap2는 원본 Node A 유지) qgroup으로 서브볼륨별 exclusive/shared 공간을 정확히 추적 가능

스냅샷 생성 내부

/* fs/btrfs/ioctl.c - 스냅샷 생성 핵심 */
static noinline int btrfs_mksubvol(
    const struct path *parent,
    const char *name, int namelen,
    struct btrfs_root *snap_src,
    bool readonly)
{
    /* 1. 현재 트랜잭션 시작 */
    trans = btrfs_start_transaction(root, 0);

    /* 2. 소스 FS Tree의 루트 노드를 COW로 복사
     *    → 새 트리 ID 할당, Root Tree에 ROOT_ITEM 삽입 */
    btrfs_create_snapshot(trans, snap_src,
                          parent_root, dentry, readonly);

    /* 3. extent 참조 카운트는 암묵적으로 공유
     *    (삭제/수정 시에만 back-reference 갱신) */

    btrfs_commit_transaction(trans);
    return 0;
}

스냅샷 삭제 비용

스냅샷 삭제는 독점(exclusive) extent의 해제와 공유 extent의 참조 카운트 감소를 포함하므로 생성보다 훨씬 비용이 큽니다:

/* fs/btrfs/extent-tree.c - 스냅샷 삭제 시 extent 해제 */
static int do_walk_down(
    struct btrfs_trans_handle *trans,
    struct btrfs_root *root,
    struct btrfs_path *path,
    struct walk_control *wc)
{
    /* 각 노드를 순회하며:
     * - 공유 노드(다른 스냅샷이 참조): refcount-- 만 수행
     * - 독점 노드(이 스냅샷만 참조): 실제 extent 해제
     * → 스냅샷 수가 많을수록 삭제 시간 증가 */
    if (wc->refs[level] > 1) {
        /* 공유 노드: refcount 감소만 */
        btrfs_free_tree_block(trans, root, eb, 0, 1);
    } else {
        /* 독점 노드: 하위 트리 전체 순회 필요 */
        walk_down_proc(trans, root, path, wc);
    }
}

Qgroup 공간 추적

# qgroup 활성화 (서브볼륨별 공간 추적)
$ btrfs quota enable /mnt

# 서브볼륨별 사용량 확인 (exclusive/shared)
$ btrfs qgroup show -reF /mnt
qgroupid         rfer         excl
--------         ----         ----
0/256         10.00GiB      5.00GiB    # @rootfs: 10G 참조, 5G 독점
0/257          8.00GiB      3.00GiB    # @home: 8G 참조, 3G 독점
0/258          8.00GiB     200.00MiB   # @snap-home: 8G 참조, 200M 독점

# 서브볼륨에 공간 할당 한도 설정
$ btrfs qgroup limit 50G /mnt/@home
주의: qgroup은 메타데이터 오버헤드가 상당합니다. 특히 스냅샷이 빈번한 환경에서 qgroup 활성화 시 쓰기 성능이 30~50% 저하될 수 있습니다. 커널 6.1 이후 squota(simple quota) 모드가 도입되어 오버헤드가 크게 줄었습니다.

Send/Receive 심화

Btrfs send/receive는 두 읽기 전용 스냅샷 사이의 차이를 바이너리 스트림으로 직렬화하여 전송합니다. 이 섹션에서는 send 스트림 형식, 증분 전송 알고리즘, 그리고 원격 복제 파이프라인을 상세히 분석합니다.

Send/Receive 증분 전송 파이프라인 소스 (btrfs send) @snap-day1 (parent) @snap-day2 (send_root) diff changed_cb() B-tree 순회: 두 트리 비교 변경된 inode/extent 감지 send stream 대상 (btrfs receive) @snap-day1 (기존) @snap-day2 (생성) process_cmd() 스트림 명령 해석 → ioctl 호출 파일 생성/수정/삭제/권한 설정 Send 스트림 명령 형식 SUBVOL MKFILE WRITE RENAME CHMOD CLONE UPDATE_EXTENT 각 명령: | cmd_type(2B) | cmd_len(4B) | TLV 속성들... | crc32(4B) | v2 (커널 5.18+): ENCODED_WRITE — 압축 데이터 그대로 전송 (CPU 절약) CLONE 명령: reflink 기반으로 동일 extent를 참조 → 데이터 복사 없이 공유

Send 알고리즘

/* fs/btrfs/send.c - 증분 send 핵심 */
static int full_send_tree(struct send_ctx *sctx)
{
    struct btrfs_root *send_root = sctx->send_root;
    struct btrfs_root *parent_root = sctx->parent_root;

    /* 두 트리를 동시에 순회하며 차이점 감지 */
    ret = btrfs_compare_trees(send_root, parent_root,
                              changed_cb, sctx);

    /* changed_cb가 감지하는 변경 유형:
     * BTRFS_COMPARE_TREE_NEW    → 새 아이템 (파일 생성)
     * BTRFS_COMPARE_TREE_DELETED → 삭제된 아이템
     * BTRFS_COMPARE_TREE_CHANGED → 수정된 아이템
     */
    return ret;
}

/* 변경 콜백: 각 변경에 대해 send 명령 생성 */
static int changed_cb(struct send_ctx *sctx,
                      enum btrfs_compare_tree_result result,
                      struct btrfs_key *key)
{
    switch (result) {
    case BTRFS_COMPARE_TREE_NEW:
        send_create_inode(sctx, key);
        break;
    case BTRFS_COMPARE_TREE_DELETED:
        send_unlink(sctx, key);
        break;
    case BTRFS_COMPARE_TREE_CHANGED:
        send_write_or_clone(sctx, key);
        break;
    }
}

고급 사용법

# 다중 클론 소스를 사용한 증분 전송 (공유 데이터 최대 활용)
$ btrfs send -p /snap/@base -c /snap/@other1 -c /snap/@other2 \
    /snap/@latest | btrfs receive /backup/

# v2 프로토콜: 압축 데이터 그대로 전송 (커널 5.18+)
$ btrfs send --compressed-data /snap/@latest | btrfs receive /backup/

# 파이프로 진행률 확인
$ btrfs send -p /snap/@day1 /snap/@day2 | \
    pv -pterb | btrfs receive /backup/

# 스트림을 파일로 저장 후 나중에 적용
$ btrfs send -p /snap/@base /snap/@incr -f /tmp/incr.btrfs
$ btrfs receive -f /tmp/incr.btrfs /backup/

# SSH 원격 전송 (대역폭 제한 + 압축)
$ btrfs send -p /snap/@day1 /snap/@day2 | \
    zstd -1 | ssh -l 10M user@remote "zstd -d | btrfs receive /backup/"
성능 팁: send/receive의 속도는 주로 소스측 B-tree 순회에 의해 결정됩니다. -c(clone source) 옵션으로 여러 스냅샷을 지정하면 CLONE 명령이 더 많이 생성되어 실제 데이터 전송량이 줄어듭니다. v2 프로토콜의 --compressed-data는 압축 해제/재압축 오버헤드를 제거합니다.

Scrub 메커니즘 심화

Scrub은 Btrfs의 온라인 데이터 무결성 검증 시스템입니다. 모든 extent를 순차적으로 읽어 체크섬을 검증하고, RAID 미러가 있는 경우 손상된 데이터를 자동으로 복구합니다. 이 섹션에서는 scrub의 내부 구조, I/O 스케줄링, 복구 전략을 심층 분석합니다.

Scrub 메커니즘 및 자동 복구 흐름 btrfs scrub start Scrub Worker 스레드 (디바이스당 1개) Extent Tree 순회 블록 순차 읽기 체크섬 계산 체크섬 비교 일치? 정상 (PASS) Y 불일치 (ERROR) N 미러? 다른 미러에서 읽기 Y 정상? 자동 복구 (repair write) Y 복구 불가 (EIO) N 모든 미러 실패 → EIO N

Scrub 내부 구조

/* fs/btrfs/scrub.c - scrub 핵심 구조체 */
struct scrub_ctx {
    struct btrfs_fs_info *fs_info;
    int                   readonly;     /* 복구 쓰기 허용 여부 */
    struct scrub_bio     *bios[SCRUB_BIOS_PER_SCTX];
    int                   curr;         /* 현재 bio 인덱스 */
    atomic_t              cancel_req;   /* 취소 요청 */
    struct scrub_stat     stat;         /* 통계 */
};

struct scrub_stat {
    u64 data_extents_scrubbed;   /* 검사한 데이터 extent 수 */
    u64 tree_extents_scrubbed;   /* 검사한 메타데이터 extent 수 */
    u64 data_bytes_scrubbed;     /* 검사한 데이터 바이트 */
    u64 tree_bytes_scrubbed;     /* 검사한 메타데이터 바이트 */
    u64 read_errors;              /* 읽기 에러 수 */
    u64 csum_errors;              /* 체크섬 불일치 수 */
    u64 verify_errors;            /* 메타데이터 검증 에러 */
    u64 corrected_errors;         /* 자동 복구된 에러 수 */
    u64 uncorrectable_errors;     /* 복구 불가 에러 수 */
};

Scrub 스케줄링

# I/O 우선순위 제어 (ionice로 scrub 영향 최소화)
$ ionice -c 3 btrfs scrub start /mnt

# systemd timer로 주기적 scrub 설정
$ systemctl enable btrfs-scrub@-.timer        # 루트 파일시스템
$ systemctl enable btrfs-scrub@mnt-data.timer # /mnt/data

# 사용자 정의 scrub 타이머 (매주 일요일 02:00)
$ cat /etc/systemd/system/btrfs-scrub-weekly.timer
[Timer]
OnCalendar=Sun *-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target

# scrub 상세 결과 확인
$ btrfs scrub status -d /mnt
UUID:             a1b2c3d4-...
Scrub started:    Sun Mar  1 02:00:00 2026
Status:           finished
Duration:         2:15:32
Total to scrub:   500.00GiB
Rate:             63.00MiB/s
Error summary:    csum=2 read=0 super=0 verify=0 nocsum=0
Corrected:        2
Uncorrectable:    0
운영 권장: 프로덕션 환경에서는 월 1회 이상 scrub을 실행하여 silent data corruption을 조기에 발견하세요. RAID1/10 구성에서는 자동 복구가 가능하므로 scrub이 특히 효과적입니다.

RAID 프로파일 심화

Btrfs의 RAID는 전통적인 mdadm RAID와 달리 Chunk 레벨에서 동작합니다. 각 Chunk(Block Group)마다 독립적인 RAID 프로파일이 적용되며, 데이터와 메타데이터에 서로 다른 프로파일을 혼합할 수 있습니다. 이 섹션에서는 Chunk 기반 RAID의 내부 동작, 리밸런싱, 그리고 장애 복구를 분석합니다.

Btrfs Chunk 기반 RAID 프로파일 Device 1 (/dev/sda - 1TB) Meta RAID1 Data R1 Data R1 Unalloc Sys R1 Data R1 Device 2 (/dev/sdb - 1TB) Meta RAID1 Data R1 Data R1 Unalloc Sys R1 Data R1 RAID 프로파일별 Chunk 배치 전략 RAID0 (stripe) Chunk를 64K stripe로 디바이스에 분산 배치 RAID1 (mirror) 각 Chunk를 2개 디바이스에 복제 RAID10 (stripe+mirror) 2개 디바이스 미러쌍 + 쌍 사이 stripe DUP (same device) 같은 디바이스 내 2개 복사본 유지 핵심 차이: Btrfs RAID는 Chunk 단위로 동작 (mdadm은 섹터 단위) 장점: 데이터/메타데이터 별도 프로파일, 온라인 프로파일 변환 단점: 비균등 디바이스 크기 시 공간 낭비 가능 Chunk 할당 정책 Data: 기본 1GiB (RAID0/1) Metadata: 기본 256MiB RAID5/6 주의사항 Write hole 문제: 부분 stripe 쓰기 시 패리티 불일치 복구 코드 불완전 → 프로덕션 사용 금지

Chunk 할당 메커니즘

/* fs/btrfs/volumes.c - RAID 프로파일별 stripe 할당 */
static int decide_stripe_size_regular(
    struct alloc_chunk_ctl *ctl,
    struct btrfs_device_info *devices_info)
{
    /* RAID 프로파일에 따른 stripe 계산 */
    switch (ctl->type & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
    case BTRFS_BLOCK_GROUP_RAID0:
        ctl->num_stripes = ctl->devs_max;
        ctl->sub_stripes = 0;
        break;
    case BTRFS_BLOCK_GROUP_RAID1:
        ctl->num_stripes = 2;
        ctl->ncopies = 2;
        break;
    case BTRFS_BLOCK_GROUP_RAID1C3:
        ctl->num_stripes = 3;
        ctl->ncopies = 3;
        break;
    case BTRFS_BLOCK_GROUP_RAID10:
        ctl->num_stripes = ctl->devs_max;
        ctl->sub_stripes = 2;
        ctl->ncopies = 2;
        break;
    case BTRFS_BLOCK_GROUP_DUP:
        ctl->num_stripes = 2;
        ctl->ncopies = 2;
        /* 같은 디바이스에 2개 stripe */
        break;
    }
}

리밸런싱과 프로파일 변환

# 현재 Chunk 배치 상태 확인
$ btrfs filesystem usage -T /mnt
                  Data      Metadata  System
Id  Path          RAID1     RAID1     RAID1
--  ----          -----     -----     -----
 1  /dev/sda      240.00G    8.00G   32.00M
 2  /dev/sdb      240.00G    8.00G   32.00M

# 디바이스 추가 후 리밸런싱
$ btrfs device add /dev/sdc /dev/sdd /mnt
$ btrfs balance start -dconvert=raid10 -mconvert=raid1c3 /mnt

# 리밸런싱 진행률 모니터링
$ btrfs balance status /mnt
Balance on '/mnt' is running
14 out of 56 chunks balanced (8 considered), 75% left

# 특정 사용률 이하 Chunk만 리밸런싱 (빠른 공간 회수)
$ btrfs balance start -dusage=30,soft -musage=30,soft /mnt

# 비균등 디바이스에서의 데이터 균등 분배
$ btrfs balance start -ddevid=1,limit=10 /mnt  # dev1에서 10개 chunk만

Degraded 모드 운영

# 디바이스 장애 시 degraded 마운트
$ mount -o degraded /dev/sda /mnt

# 장애 디바이스 교체
$ btrfs replace start 2 /dev/sdd /mnt  # devid 2를 /dev/sdd로 교체
$ btrfs replace status /mnt

# 디바이스 에러 통계 확인
$ btrfs device stats /mnt
[/dev/sda].write_io_errs    0
[/dev/sda].read_io_errs     0
[/dev/sda].flush_io_errs    0
[/dev/sda].corruption_errs  0
[/dev/sda].generation_errs  0
[/dev/sdb].write_io_errs    145     # 장애 징후!
[/dev/sdb].read_io_errs     23
[/dev/sdb].corruption_errs  5

압축 심화

Btrfs의 투명 압축은 extent 단위(최대 128K)로 동작하며, 각 extent에 독립적인 압축 유형이 기록됩니다. 이 섹션에서는 압축 알고리즘별 성능 특성, 인라인 extent, 휴리스틱 기반 자동 판단, 그리고 워크로드별 최적 설정을 분석합니다.

Btrfs 압축 파이프라인 쓰기 경로 (compress 활성화 시) Page Cache 휴리스틱 판단 압축 (128K 단위) 체크섬 계산 Extent 할당 compress: 비압축성 → 스킵 compress-force: 항상 시도 인라인 extent vs 일반 extent 인라인: 데이터가 B-tree 리프 내부에 저장 (max_inline 이하) 일반 extent: 데이터 Chunk에 별도 할당, 128K 단위 압축 압축 알고리즘 성능 비교 (128K 블록 기준) zstd (권장) 레벨 1: 500MB/s 압축 레벨 3: 300MB/s / 비율 2.5:1 레벨 15: 20MB/s / 비율 3.5:1 lzo (빠른 처리) 800MB/s 압축 속도 비율 약 2:1 CPU 오버헤드 최소 zlib (최고 압축률) 레벨 1: 100MB/s / 레벨 9: 15MB/s 비율 3~4:1 (레벨 9) 아카이브에 적합, 실시간 워크로드에 부적합

압축 휴리스틱

Btrfs는 compress(non-force) 모드에서 파일의 첫 부분을 샘플링하여 압축 가능성을 판단합니다:

/* fs/btrfs/compression.c - 휴리스틱 판단 */
static int btrfs_compress_heuristic(
    struct inode *inode,
    u64 start, u64 end)
{
    /* 1. 파일의 처음 일부를 샘플링 */
    /* 2. 바이트 분포(Shannon entropy) 계산 */
    /* 3. 엔트로피가 임계값 이상이면 비압축성으로 판단
     *    → 해당 범위를 NOCOMPRESS로 마킹
     *    → 이후 같은 파일의 쓰기는 압축 시도하지 않음 */

    sample_end = min(end, start + BTRFS_MAX_UNCOMPRESSED);
    heuristic_result = sample_repeated_patterns(input);

    if (heuristic_result > ENTROPY_THRESHOLD)
        return 0; /* 압축하지 않음 */
    return 1; /* 압축 진행 */
}

압축률 측정과 분석

# compsize 도구로 실제 압축률 확인
$ compsize /mnt/data
Processed 25000 files, 18000 regular extents (20000 refs), 5000 inline.
Type       Perc     Disk Usage   Uncompressed Referenced
TOTAL       58%       23.2G         40.0G       42.0G
zstd        52%       18.5G         35.5G       37.0G
none       100%        4.7G          4.5G        5.0G

# 파일별 압축 정보 확인
$ compsize -x /mnt/data/logfile.txt
Processed 1 file, 120 regular extents (120 refs), 0 inline.
Type       Perc     Disk Usage   Uncompressed
zstd        25%       250.0M          1.0G

# 압축 레벨별 테스트 (defrag으로 재압축)
$ btrfs filesystem defragment -czstd:1 /mnt/testfile   # 빠른 압축
$ btrfs filesystem defragment -czstd:9 /mnt/testfile   # 높은 압축률
$ btrfs filesystem defragment -czlib:9 /mnt/testfile   # 최대 압축률

워크로드별 압축 가이드

워크로드권장 설정예상 압축률이유
소스 코드/텍스트compress=zstd:33~5:1높은 압축률, 적당한 속도
로그 파일compress=zstd:35~10:1반복 패턴 많아 매우 효과적
데이터베이스nodatacow (압축 비활성)-랜덤 I/O, COW 오버헤드 제거
가상머신 이미지nodatacow-내부 FS가 별도 관리
미디어 (사진/동영상)compress=zstd:1 또는 none1:1~1.1:1이미 압축된 데이터, 효과 미미
범용 데스크톱compress=zstd:11.5~2.5:1최소 CPU로 적당한 절약
NAS/아카이브compress=zstd:92.5~4:1공간 절약 우선, I/O 부담 수용

중복 제거 (Deduplication)

Btrfs는 ZFS와 달리 인라인(실시간) 중복 제거를 지원하지 않지만, reflink 기반의 오프라인/out-of-band 중복 제거를 효과적으로 수행할 수 있습니다. 이 섹션에서는 reflink, 중복 제거 도구, 그리고 extent 공유 메커니즘을 분석합니다.

Btrfs 중복 제거 (Reflink) 구조 Before: 중복 데이터 존재 File A (inode 100) File B (inode 200) Extent X (1GB) Extent Y (1GB) X와 Y의 내용이 동일 → 2GB 낭비 dedup After: Reflink으로 공유 File A (inode 100) File B (inode 200) Extent X (ref=2) refcount=2, 실제 1GB만 사용 → 1GB 절약 중복 제거 방식 비교 Out-of-band (오프라인) duperemove / bees 백그라운드 스캔 + reflink I/O 영향 제어 가능 Btrfs 지원: 안정적 cp --reflink (수동) 파일 복사 시 COW 공유 사용자가 직접 수행 O(1) 복사, 즉시 완료 가장 안전한 방법 인라인 (실시간) 쓰기 시점에 즉시 중복 판단 대용량 해시 테이블 필요 쓰기 성능 저하 심각 Btrfs 미지원 (ZFS만 지원)
/* fs/btrfs/reflink.c - reflink(파일 클론) 구현 */
loff_t btrfs_remap_file_range(
    struct file *src_file, loff_t src_off,
    struct file *dst_file, loff_t dst_off,
    loff_t len, unsigned int remap_flags)
{
    /* FICLONE / FICLONERANGE ioctl 핸들러
     *
     * 동작:
     * 1. src의 EXTENT_DATA 아이템을 dst의 FS Tree에 복사
     * 2. Extent Tree에서 해당 extent의 refcount 증가
     * 3. 실제 데이터 복사 없음 → O(수정된 메타데이터) 시간
     *
     * 이후 src 또는 dst에서 수정 발생 시:
     * → COW로 새 extent 할당, refcount 감소
     */
    return btrfs_clone_file_range(src_file, src_off,
                                  dst_file, dst_off, len);
}

중복 제거 도구

# ── duperemove: 블록 레벨 중복 제거 ──
$ duperemove -rdh /mnt/data/
# -r: 재귀, -d: 실행(dry-run 아님), -h: human-readable

# 해시 DB를 저장하여 증분 스캔
$ duperemove -rdh --hashfile=/tmp/dedup.hash /mnt/data/

# ── bees (Best-Effort Extent-Same): 온라인 중복 제거 데몬 ──
$ bees /mnt                    # 백그라운드 데몬으로 실행
$ bees --loadavg-target=2.0 /mnt  # 시스템 부하 제한

# ── cp --reflink: 수동 COW 복사 ──
$ cp --reflink=always src.img dst.img  # 즉시 완료, 공간 0 추가

# ── FIDEDUPERANGE ioctl: 프로그래밍 방식 ──
# 두 파일의 특정 범위가 동일한지 확인 후 reflink 처리
# ioctl(fd, FIDEDUPERANGE, &args)
duperemove vs bees: duperemove는 일회성 스캔 + 중복 제거에 적합하고, bees는 상시 데몬으로 새로 작성되는 데이터도 지속적으로 중복 제거합니다. bees는 메모리 사용이 더 많지만 (해시 테이블 상주), 파일시스템이 지속적으로 변경되는 환경에서 더 효과적입니다.

커널 내부 심화

이 섹션에서는 Btrfs의 핵심 커널 내부 구조인 btrfs_fs_info, btrfs_root, 트랜잭션 관리자, 그리고 delayed ref 메커니즘을 심층 분석합니다. fs/btrfs/ 디렉토리의 주요 소스 파일 간 관계를 이해하는 것이 목표입니다.

Btrfs 커널 내부 자료구조 관계 VFS (super_block, inode, dentry) btrfs_fs_info tree_root / chunk_root / extent_root running_transaction / data_sinfo / meta_sinfo fs_devices / delayed_ref_root / workers mount_opt / generation / nodesize / sectorsize btrfs_root node (extent_buffer) commit_root / root_item root_key / last_trans btrfs_transaction transid / state / num_writers delayed_refs / dirty_bgs RUNNING→COMMIT→DONE btrfs_space_info total_bytes / bytes_used bytes_pinned / bytes_reserved flush_state / reclaim_size Delayed References extent 참조 변경을 배치 처리 커밋 시점에 Extent Tree 갱신 extent_buffer B-tree 노드의 메모리 표현 pages[] + locks + refs Worker 스레드 submit_workers / endio_workers caching / delalloc / flush 핵심 소스: fs.h(fs_info) | ctree.h/c(B-tree) | transaction.c | extent-tree.c | volumes.c(RAID) | disk-io.c(I/O) 총 약 120개 소스 파일, 20만+ 라인 (커널 6.x 기준)

Delayed References

Btrfs의 extent 참조 카운트 갱신은 즉시 수행되지 않고 delayed reference 큐에 쌓였다가 트랜잭션 커밋 시 배치 처리됩니다. 이는 COW 기반 시스템에서 Extent Tree의 반복적인 수정을 줄여 성능을 크게 향상시킵니다:

/* fs/btrfs/delayed-ref.h */
struct btrfs_delayed_ref_root {
    struct rb_root_cached href_root;    /* head ref RB-tree */
    spinlock_t            lock;
    atomic_t              num_entries;  /* 대기 중인 ref 수 */
    unsigned long         num_heads;    /* 고유 extent 수 */
    int                   flushing;     /* 플러시 진행 중 */
    u64                   run_delayed_start; /* 처리 시작 오프셋 */
};

struct btrfs_delayed_ref_head {
    struct rb_node    href_node;
    struct list_head ref_add_list;    /* 추가 참조 리스트 */
    struct list_head ref_del_list;    /* 삭제 참조 리스트 */
    u64              bytenr;          /* extent 논리 주소 */
    u64              num_bytes;       /* extent 크기 */
    int              total_ref_mod;   /* 순 참조 변경량 (+/-) */
    int              must_insert_reserved;
};

트랜잭션 생명주기

/* fs/btrfs/transaction.c - 트랜잭션 상태 전이 */
enum btrfs_trans_state {
    TRANS_STATE_RUNNING,        /* writer 참여 가능 */
    TRANS_STATE_COMMIT_START,   /* 커밋 시작, 새 writer 차단 */
    TRANS_STATE_COMMIT_DOING,   /* delayed ref 처리 + 데이터 flush */
    TRANS_STATE_UNBLOCKED,      /* 새 트랜잭션 시작 가능 */
    TRANS_STATE_SUPER_COMMITTED,/* superblock 기록 완료 */
    TRANS_STATE_COMPLETED,      /* 완전히 종료 */
};

/* 커밋 과정 요약:
 * 1. RUNNING → COMMIT_START: 새 writer 차단
 * 2. 기존 writer 완료 대기 (num_writers == 0)
 * 3. COMMIT_DOING: delayed refs 실행, 더티 페이지 flush
 * 4. UNBLOCKED: 새 트랜잭션 시작 허용 (파이프라이닝)
 * 5. 모든 I/O 완료 대기
 * 6. SUPER_COMMITTED: superblock 기록 (commit point)
 * 7. COMPLETED: 이전 트랜잭션 정리
 */

소스 코드 맵

파일역할주요 함수/구조체
fs.hbtrfs_fs_info 정의btrfs_fs_info, 마운트 옵션 상수
ctree.h / ctree.cB-tree 핵심btrfs_search_slot, split_leaf, btrfs_key
transaction.c트랜잭션 관리btrfs_start_transaction, btrfs_commit_transaction
extent-tree.cextent 할당/해제btrfs_alloc_tree_block, btrfs_free_tree_block
delayed-ref.c지연 참조 처리btrfs_run_delayed_refs
volumes.cRAID/Chunk/디바이스btrfs_map_block, decide_stripe_size
disk-io.c디스크 I/Obtrfs_read_buffer, write_all_supers
inode.cVFS inode 연산btrfs_lookup_dentry, btrfs_new_inode
file.c파일 연산btrfs_file_write_iter, btrfs_sync_file
send.csend/receivebtrfs_ioctl_send, changed_cb
scrub.cscrubscrub_stripe, scrub_repair
compression.c투명 압축btrfs_submit_compressed_read/write

ftrace/bpftrace 성능 분석

Btrfs는 커널의 tracepoint 인프라를 활용하여 내부 동작을 추적할 수 있습니다. 이 섹션에서는 ftrace, perf, bpftrace를 사용한 Btrfs 성능 분석과 문제 진단 방법을 다룹니다.

Btrfs Tracepoints

# 사용 가능한 btrfs tracepoint 목록 확인
$ ls /sys/kernel/debug/tracing/events/btrfs/
btrfs_chunk_alloc           btrfs_cow_block
btrfs_delayed_data_ref      btrfs_delayed_ref_head
btrfs_delayed_tree_ref      btrfs_flush_space
btrfs_ordered_extent_add    btrfs_ordered_extent_put
btrfs_qgroup_account_extent btrfs_reserved_extent_alloc
btrfs_space_reservation     btrfs_transaction_commit
btrfs_trigger_flush         btrfs_writepage_end_io_hook
...

# ftrace로 트랜잭션 커밋 추적
$ echo 1 > /sys/kernel/debug/tracing/events/btrfs/btrfs_transaction_commit/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
  btrfs-transacti-1234  [002] .... 12345.678: btrfs_transaction_commit:
    generation=1000 root=5

# COW 블록 추적
$ echo 1 > /sys/kernel/debug/tracing/events/btrfs/btrfs_cow_block/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
  kworker/2:0-567 [002] .... 12345.789: btrfs_cow_block:
    root=5 refs=1 orig_buf=0x1a2b3c new_buf=0x4d5e6f

bpftrace 스크립트

# 트랜잭션 커밋 지연 시간 히스토그램
$ bpftrace -e '
kprobe:btrfs_commit_transaction {
    @start[tid] = nsecs;
}
kretprobe:btrfs_commit_transaction /@start[tid]/ {
    @commit_latency_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}'

# extent 할당 크기 분포
$ bpftrace -e '
tracepoint:btrfs:btrfs_reserved_extent_alloc {
    @alloc_size = hist(args->len);
    @alloc_by_root[args->root_objectid] = count();
}'

# COW 빈도 모니터링 (초당)
$ bpftrace -e '
tracepoint:btrfs:btrfs_cow_block {
    @cow_per_sec = count();
}
interval:s:1 {
    print(@cow_per_sec);
    clear(@cow_per_sec);
}'

# 압축 성능 분석
$ bpftrace -e '
kprobe:btrfs_compress_pages {
    @start[tid] = nsecs;
}
kretprobe:btrfs_compress_pages /@start[tid]/ {
    @compress_us = hist((nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}'

# ENOSPC flush 이벤트 추적
$ bpftrace -e '
tracepoint:btrfs:btrfs_trigger_flush {
    printf("%s: flush_action=%d flags=0x%lx\n",
           comm, args->flush_action, args->flags);
}'

perf 기반 분석

# btrfs 관련 함수의 CPU 사용률 프로파일링
$ perf top -g --call-graph dwarf -p $(pidof btrfs-transacti)

# btrfs commit 과정의 flame graph 생성
$ perf record -g -p $(pidof btrfs-transacti) -- sleep 30
$ perf script | stackcollapse-perf.pl | flamegraph.pl > btrfs-commit.svg

# btrfs tracepoint 기반 I/O 지연 분석
$ perf stat -e 'btrfs:btrfs_cow_block' \
            -e 'btrfs:btrfs_transaction_commit' \
            -e 'btrfs:btrfs_ordered_extent_add' \
            -- sleep 60

# scrub I/O 패턴 분석
$ perf trace -e btrfs:btrfs_scrub_* -p $(pgrep -f "btrfs scrub")
디버깅 팁: btrfs_flush_space tracepoint가 빈번히 발생하면 ENOSPC 임박을 의미합니다. btrfs_cow_block의 빈도가 비정상적으로 높다면 과도한 메타데이터 수정을 의심하세요. commit=N 마운트 옵션을 늘려 트랜잭션 커밋 빈도를 줄이면 COW 오버헤드를 완화할 수 있습니다.

성능 튜닝 심화

Btrfs의 성능은 마운트 옵션, 파일시스템 구성, 워크로드 패턴, 그리고 커널 파라미터의 조합에 크게 영향을 받습니다. 이 섹션에서는 실측 데이터 기반의 튜닝 전략, I/O 스케줄러 연동, 그리고 벤치마크 방법론을 분석합니다.

Btrfs 성능 튜닝 체크리스트 마운트 옵션 튜닝 1. noatime — 불필요한 메타데이터 쓰기 제거 2. compress=zstd:1 — 최소 CPU로 I/O 절약 3. discard=async — SSD TRIM 비동기 처리 4. space_cache=v2 — 빠른 마운트 + COW 보호 5. commit=30~120 — 워크로드에 따라 조정 6. max_inline=2048 — 소형 파일 인라인 저장 7. thread_pool=N — 코어 수에 맞게 조정 8. ssd — 자동 감지 (수동 지정 가능) 워크로드별 최적화 DB: nodatacow + chattr +C + noatime VM 이미지: nodatacow + chattr +C 로그 서버: compress=zstd:3 + autodefrag 빌드 서버: compress=zstd:1 + ssd NAS: compress=zstd:3 + RAID1 컨테이너: subvolume + snapshot rollback 데스크톱: compress=zstd:1 + discard=async 아카이브: compress=zstd:9 + dedup 시스템 레벨 튜닝 I/O 스케줄러: mq-deadline (HDD) / none (NVMe) vm.dirty_ratio: 10~20 (기본) / 5 (DB) vm.dirty_background_ratio: 5~10 readahead: blockdev --setra 4096 (HDD) NUMA: 메모리 로컬리티 고려한 배치 hugepages: B-tree 노드 캐시 효율 향상 swappiness: 10 이하 (메타데이터 캐시 유지) noop/none 스케줄러: NVMe + RAID 환경 모니터링 및 유지보수 btrfs device stats — 에러 카운터 추적 btrfs fi usage — 공간/할당 모니터링 scrub 월 1회 이상 — 데이터 무결성 검증 balance -dusage=50 — 주기적 공간 회수 dmesg | grep btrfs — 커널 경고 확인 compsize — 압축률 추적 filefrag — 파일 단편화 수준 확인 iostat/iotop — I/O 병목 식별

벤치마크 방법론

# ── fio: 랜덤 읽기/쓰기 벤치마크 ──
# 4K 랜덤 읽기 (IOPS 측정)
$ fio --name=randread --directory=/mnt/test \
      --ioengine=libaio --direct=1 --bs=4k \
      --iodepth=32 --numjobs=4 --size=1G \
      --rw=randread --group_reporting

# 4K 랜덤 쓰기 (COW 오버헤드 측정)
$ fio --name=randwrite --directory=/mnt/test \
      --ioengine=libaio --direct=1 --bs=4k \
      --iodepth=32 --numjobs=4 --size=1G \
      --rw=randwrite --group_reporting

# 순차 쓰기 + 압축 효과 측정
$ fio --name=seqwrite --directory=/mnt/test \
      --ioengine=libaio --direct=0 --bs=128k \
      --iodepth=16 --numjobs=1 --size=10G \
      --rw=write --group_reporting

# ── bonnie++: 종합 파일시스템 벤치마크 ──
$ bonnie++ -d /mnt/test -r 4096 -s 8192

# ── 스냅샷 오버헤드 측정 ──
$ # 기준선 측정
$ fio --name=base --directory=/mnt/test --rw=randrw --size=1G --runtime=60
$ # 스냅샷 100개 생성 후 재측정
$ for i in $(seq 1 100); do
    btrfs subvolume snapshot /mnt/@data /mnt/@snap-$i
  done
$ fio --name=with_snaps --directory=/mnt/test --rw=randrw --size=1G --runtime=60

실전 튜닝 예제

# ── 고성능 NVMe 서버 ──
$ mkfs.btrfs -m dup -d single -L fast-storage /dev/nvme0n1p1
$ mount -o noatime,compress=zstd:1,discard=async,space_cache=v2,\
    ssd,commit=60,max_inline=2048 /dev/nvme0n1p1 /data

# ── RAID1 NAS 서버 ──
$ mkfs.btrfs -m raid1 -d raid1 -L nas-pool /dev/sd{a,b,c,d}
$ mount -o noatime,compress=zstd:3,space_cache=v2,\
    autodefrag,commit=120 /dev/sda /nas

# ── 컨테이너/CI 호스트 ──
$ mkfs.btrfs -m dup -d single -L containers /dev/nvme0n1p2
$ mount -o noatime,compress=zstd:1,discard=async,space_cache=v2,\
    ssd /dev/nvme0n1p2 /var/lib/containers

# ── 주기적 유지보수 크론잡 ──
$ cat /etc/cron.weekly/btrfs-maintenance
#!/bin/bash
# 빈 블록 그룹 회수
btrfs balance start -dusage=0 -musage=0 /data
# 사용률 낮은 블록 그룹 통합
btrfs balance start -dusage=30,soft -musage=30,soft /data
# scrub 실행
btrfs scrub start -B /data
# 에러 통계 확인
btrfs device stats /data

흔한 성능 문제와 해결

증상원인해결 방법
쓰기 성능 급락메타데이터 공간 부족btrfs balance start -musage=50
ENOSPC (df에 공간 있음)Chunk 할당 불균형btrfs balance start -dusage=0
마운트 지연space_cache v1 로드mount -o space_cache=v2
DB 성능 저하COW + 체크섬 오버헤드nodatacow + chattr +C
스냅샷 삭제 느림대량 extent 참조 해제한번에 적은 수의 스냅샷 삭제
qgroup 활성화 후 느림참조 카운트 추적 오버헤드squota(커널 6.1+) 사용 또는 비활성화
파일 단편화COW + 랜덤 쓰기autodefrag 또는 주기적 defragment
send/receive 느림대량 변경 + 비효율적 비교-c clone source 지정, v2 프로토콜 사용
주의: autodefrag은 소형 랜덤 쓰기 워크로드에서 유용하지만, 대용량 순차 쓰기 환경에서는 불필요한 I/O를 생성할 수 있습니다. 또한 스냅샷이 많은 환경에서 defrag은 extent 공유를 해제하여 디스크 사용량을 급증시킬 수 있으므로, 항상 사전에 btrfs fi usage로 여유 공간을 확인하세요.

Btrfs와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.