Btrfs 파일시스템 심화
COW B-tree 아키텍처, 서브볼륨/스냅샷, 데이터 체크섬, 압축, RAID, 핵심 자료구조, 공간 관리, btrfs-progs, 성능 튜닝 종합 가이드.
개요 & 역사
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 대안 목표 |
| 2009 | 2.6.29 | 메인라인 병합, 기본 COW + B-tree 구조 |
| 2012 | 3.4 | send/receive, qgroup, device replace 추가 |
| 2013 | 3.9 | RAID 5/6 실험적 지원, skinny metadata |
| 2018 | 4.14+ | zstd 압축 지원 추가 |
| 2020 | 5.5 | space_cache v2 (free space tree) 기본 활성화 |
| 2022 | 5.15+ | Fedora 33+ 기본 FS 채택, SUSE 엔터프라이즈 지원 |
| 2024 | 6.7+ | RAID1 성능 개선, extent tree v2 개발 진행 |
주요 스펙
| 항목 | 값 |
|---|---|
| 최대 볼륨 크기 | 16 EiB |
| 최대 파일 크기 | 16 EiB (논리적), 실질적으로 볼륨 크기에 제한 |
| 최대 파일 수 | 264 |
| 파일명 길이 | 255 바이트 |
| 블록(섹터) 크기 | 4K (기본), metadata nodesize 16K (기본) |
| 체크섬 | crc32c (기본), xxhash, sha256, blake2b |
| 내장 RAID | 0, 1, 10, 5/6 (실험적), DUP |
| COW | 전체 메타데이터 + 데이터 (기본) |
| 압축 | zlib, lzo, zstd |
| 타임스탬프 범위 | 1970-01-01 ~ 2486 (나노초 정밀도) |
설계 철학
Btrfs의 설계는 세 가지 핵심 원칙에 기반합니다:
- COW 기반 일관성 — 기존 데이터를 덮어쓰지 않으므로 저널링이 불필요하며, 전원 장애 시에도 항상 일관된 상태 보장
- B-tree 통합 관리 — 파일 데이터, 메타데이터, 체크섬, 디렉토리 등 모든 요소를 B-tree로 통합 관리
- 스토리지 풀 — 여러 물리 장치를 하나의 볼륨으로 통합하고, 데이터/메타데이터에 서로 다른 RAID 프로파일 적용 가능
아키텍처 & 디스크 레이아웃
Btrfs는 ext4처럼 고정 크기 Block Group을 사용하지 않습니다. 대신 Chunk 기반 동적 할당으로 스토리지를 관리하며, 모든 메타데이터를 B-tree에 저장합니다.
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 종류
| Tree | Object ID | 역할 |
|---|---|---|
| Root Tree | 1 | "tree of trees" — 모든 서브 트리의 루트 포인터 저장 |
| Extent Tree | 2 | 데이터/메타데이터 extent의 할당 상태, 역참조(back-reference) |
| Chunk Tree | 3 | 논리 → 물리 주소 매핑 (Chunk 할당) |
| Dev Tree | 4 | 물리 디바이스별 할당 상태 |
| FS Tree | 5+ (서브볼륨별) | 파일/디렉토리 메타데이터, extent 참조 |
| Checksum Tree | 7 | 데이터 extent의 체크섬 |
| Free Space Tree | 10 | 블록 그룹별 미사용 공간 (space_cache v2) |
| UUID Tree | 9 | 서브볼륨 UUID → ID 매핑 (send/receive용) |
| Quota Tree | 8 | 서브볼륨 공간 할당량(qgroup) 추적 |
| Tree-Log | -7~-8 | fsync 최적화용 임시 트리 (재부팅 시 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 동작 원리
파일의 한 블록을 수정하면 다음 과정이 일어납니다:
- 새로운 데이터 블록에 수정된 내용 기록
- 해당 리프 노드를 새 위치에 COW (새 데이터 블록 포인터 포함)
- 부모 노드들을 루트까지 재귀적으로 COW
- Superblock에 새 루트 위치 기록 (atomic commit point)
- 이전 블록들은 해제 대상이 됨 (스냅샷이 참조하지 않는 경우)
트랜잭션 모델
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);
커밋 과정은 두 단계로 진행됩니다:
- 트랜잭션 실행: 모든 수정 사항이 메모리에 COW로 반영되고, 새 블록들이 디스크에 기록됨
- 슈퍼블록 기록: 모든 데이터가 디스크에 안착한 후 슈퍼블록의 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)를 가지며, 스냅샷은 서브볼륨의 특수한 형태입니다.
서브볼륨 개념
# 서브볼륨 생성
$ 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
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)을 탐지합니다.
체크섬 알고리즘
| 알고리즘 | 크기 | 유형 | 커널 버전 | 특징 |
|---|---|---|---|---|
| crc32c | 4B | 비암호화 | 기본 | CPU 가속(SSE4.2), 높은 성능, 대부분의 워크로드에 적합 |
| xxhash | 8B | 비암호화 | 5.5+ | crc32c 대비 더 강한 충돌 저항, ARM에서 빠름 |
| sha256 | 32B | 암호화 | 5.5+ | 높은 보안 수준, 성능 오버헤드 큼 |
| blake2b | 32B | 암호화 | 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 - 자가 복구 흐름 */
/*
* 1. 읽기 I/O 완료 → 체크섬 검증 실패
* 2. bio에서 미러 번호 확인
* 3. 다른 미러(mirror_num)에서 동일 블록 재읽기
* 4. 체크섬 검증 성공 시:
* - 정상 데이터를 요청자에게 반환
* - 손상된 미러에 정상 데이터를 기록 (repair)
* 5. 모든 미러 실패 시 → -EIO 반환
*/
압축
Btrfs는 파일 데이터의 투명 압축(transparent compression)을 지원하여 디스크 공간을 절약하고, 특정 워크로드에서는 I/O 대역폭도 개선합니다.
압축 알고리즘
| 알고리즘 | 커널 버전 | 압축률 | 속도 | 레벨 | 특징 |
|---|---|---|---|---|---|
| zlib | 초기 | 높음 | 느림 | 1~9 (기본 3) | 전통적, 최고 압축률이 필요할 때 |
| lzo | 2.6.38 | 낮음 | 빠름 | 없음 | CPU 오버헤드 최소, 임베디드/SBC에 적합 |
| zstd | 4.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 프로파일
| 프로파일 | 최소 디바이스 | 중복도 | 가용 용량 | 읽기 성능 | 상태 |
|---|---|---|---|---|---|
| single | 1 | 없음 | 100% | 1x | 안정 |
| DUP | 1 | 2 복사 (같은 디바이스) | ~50% | 1x | 안정 (메타데이터 기본) |
| RAID0 | 2 | 없음 | N × 최소 | Nx | 안정 |
| RAID1 | 2 | 2 복사 (다른 디바이스) | ~50% | 2x | 안정 |
| RAID1C3 | 3 | 3 복사 | ~33% | 3x | 안정 (5.5+) |
| RAID1C4 | 4 | 4 복사 | ~25% | 4x | 안정 (5.5+) |
| RAID10 | 4 | 2 복사 + 스트라이핑 | ~50% | Nx | 안정 |
| RAID5 | 3 | 1 패리티 | (N-1) × 최소 | (N-1)x | 불안정 |
| RAID6 | 4 | 2 패리티 | (N-2) × 최소 | (N-2)x | 불안정 |
디바이스 관리
# 멀티 디바이스 파일시스템 생성
$ 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_KEY | 1 | inode 번호 | 0 |
BTRFS_DIR_ITEM_KEY | 84 | 부모 inode | 이름의 crc32c 해시 |
BTRFS_DIR_INDEX_KEY | 96 | 부모 inode | 시퀀스 번호 |
BTRFS_EXTENT_DATA_KEY | 108 | inode 번호 | 파일 내 오프셋 |
BTRFS_EXTENT_ITEM_KEY | 168 | 바이트 오프셋 | extent 크기 |
BTRFS_CHUNK_ITEM_KEY | 228 | tree ID (보통 256) | 논리 오프셋 |
BTRFS_ROOT_ITEM_KEY | 132 | tree ID | 0 또는 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) 단위로 관리합니다. 각 블록 그룹은 하나의 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
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 filesystem | FS 관리 (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 필수) |
noatime | relatime | atime 갱신 비활성화 (성능 향상) |
space_cache=v2 | v2 (5.15+) | Free Space Tree 사용 |
autodefrag | 없음 | 자동 조각 모음 (랜덤 쓰기 워크로드) |
commit=N | 30 | 트랜잭션 커밋 주기(초) |
thread_pool=N | 코어 수 + 2 | 워커 스레드 수 |
max_inline=N | 2048 | 인라인 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+에서 크게 개선되었습니다. 동기 discard 대신 항상 discard=async를 사용하십시오. SSD에서 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
다른 파일시스템 비교
기능 비교표
| 기능 | Btrfs | ext4 | XFS | ZFS |
|---|---|---|---|---|
| COW | O | X | X (reflink O) | O |
| 스냅샷 | O (서브볼륨 단위) | X (LVM 의존) | X | O (데이터셋 단위) |
| 데이터 체크섬 | O | X (메타만) | X | O |
| 투명 압축 | O (zlib/lzo/zstd) | X | X | O (lz4/gzip/zstd) |
| 내장 RAID | O (0/1/10/5/6) | X | X | O (Z1/Z2/Z3) |
| 인라인 dedupe | X (오프라인만) | X | X | O |
| Send/Receive | O | X | X | O |
| 온라인 축소 | O | O | X | X |
| 최대 볼륨 크기 | 16 EiB | 1 EiB | 8 EiB | 256 ZiB |
| 라이선스 | GPL | GPL | GPL | CDDL |
| 안정성 | 양호 (RAID5/6 제외) | 매우 높음 | 매우 높음 | 매우 높음 |
선택 가이드
| 사용 사례 | 추천 | 이유 |
|---|---|---|
| 엔터프라이즈 서버 (보수적) | ext4 / XFS | 검증된 안정성, 성숙한 fsck 도구 |
| 데스크톱 / 워크스테이션 | Btrfs | 스냅샷 롤백, 압축, 유연한 관리 |
| NAS / 스토리지 서버 | Btrfs / ZFS | 데이터 무결성, 스냅샷, RAID |
| 컨테이너 호스트 | Btrfs | 서브볼륨, 스냅샷, overlay 성능 |
| 데이터베이스 전용 | ext4 / XFS | COW 오버헤드 없음, 예측 가능한 I/O |
| 대용량 데이터 레이크 | XFS | 대규모 병렬 I/O 최적화 |
| 최고 수준 데이터 보호 | ZFS | 검증된 RAIDZ, 인라인 dedupe, 성숙도 |
Btrfs vs ZFS
두 파일시스템 모두 COW 기반이며 유사한 기능 세트를 제공하지만, 근본적인 차이가 있습니다:
| 측면 | Btrfs | ZFS |
|---|---|---|
| 라이선스 | GPL v2 (커널 내장) | CDDL (외부 모듈, OpenZFS) |
| RAID 안정성 | RAID1/10 안정, RAID5/6 불안정 | RAIDZ1/Z2/Z3 매우 안정 |
| 메모리 사용 | 상대적으로 적음 | ARC 캐시로 대량 메모리 사용 (RAM 절반 이상) |
| 커널 통합 | 메인라인 포함 | 별도 DKMS/kmod 설치 필요 |
| 인라인 Dedupe | 미지원 (오프라인만) | 지원 |
| Send/Receive | 지원 | 지원 |
| 축소 | 온라인 축소 가능 | 불가 |
| 성숙도 | 발전 중 | 매우 성숙 (Solaris 계보) |