Btrfs 파일시스템(Filesystem)
Btrfs의 핵심인 COW B-tree 설계를 바탕으로 트랜잭션(Transaction) 커밋, 루트 트리/청크 트리/extent 트리 관계, 서브볼륨과 스냅샷의 메타데이터 공유 모델, send/receive 증분 복제, 데이터·메타데이터 체크섬(Checksum), 압축과 RAID 프로파일 선택, scrub/balance/defrag 운영 전략, ENOSPC와 조각화 대응까지 실제 운영 이슈 중심으로 상세히 정리합니다.
핵심 요약
- 계층 이해 — VFS, 캐시(Cache), 하위 FS 경계를 구분합니다.
- 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
- 저장 정책 — 저널링/압축/할당 정책 차이를 비교합니다.
- 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
- 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.
단계별 이해
- 경계 계층 파악
요청이 VFS에서 어디로 내려가는지 확인합니다. - 메타/데이터 분리
어느 경로에서 무엇이 갱신되는지 나눠 봅니다. - 동기화/플러시(Flush) 확인
쓰기 반영 시점과 순서를 검증합니다. - 복구 시나리오 점검
비정상 종료 후 일관성 회복을 확인합니다.
개요 & 역사
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 | RAID1C3/RAID1C4 지원, xxhash·SHA256 체크섬 추가 |
| 2020 | 5.8 | Fedora 33 기본 FS 채택 (2020-10-27 출시), 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은 디바이스의 고정 오프셋(Offset)(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의 할당 상태, 역참조(Dereference)(back-reference) |
| Chunk Tree | 3 | 논리 → 물리 주소(Physical Address) 매핑(Mapping) (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) 크기이며, 헤더 + 아이템 배열로 구성됩니다:
/* fs/btrfs/ctree.h — 간략화 */
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; /* 데이터 크기 */
};
코드 설명
fs/btrfs/ctree.h에 정의된 B-tree 노드의 온디스크 레이아웃입니다.
- btrfs_header모든 B-tree 노드(내부 노드와 리프)의 공통 헤더입니다.
csum은 노드 전체에 대한 체크섬으로, 읽기 시 무결성을 검증합니다.bytenr은 논리 주소이며 Chunk Tree를 통해 물리 주소로 변환됩니다. - generation이 노드가 COW로 복사된 시점의 트랜잭션 번호입니다. 스냅샷과 트랜잭션 커밋 시 어떤 노드가 현재 트랜잭션에 속하는지 판단하는 핵심 기준입니다.
- owner이 노드가 속한 트리의 ID입니다. Extent Tree가 역참조(back-reference)를 관리할 때 어떤 트리가 이 블록을 소유하는지 식별하는 데 사용됩니다.
- level0이면 리프 노드(실제 데이터 아이템 포함), 1 이상이면 내부 노드(자식 노드의 키와 포인터 포함)입니다.
- btrfs_item리프 노드 내부의 아이템 디스크립터입니다.
key는 3-tuple(objectid, type, offset)정렬 키이며,offset과size는 리프 노드의 데이터 영역에서 실제 페이로드(Payload)의 위치와 크기를 가리킵니다.
Chunk 매핑
Btrfs는 논리 주소(logical address)와 물리 주소를 분리합니다. Chunk Tree가 논리 → 물리 매핑을 담당하며, 이를 통해 여러 디바이스에 걸친 RAID 배치가 가능합니다:
/* include/uapi/linux/btrfs_tree.h — 간략화 */
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);
코드 설명
fs/btrfs/transaction.h에 정의된 트랜잭션 구조체(Struct)와 API입니다. Btrfs의 모든 메타데이터 수정은 트랜잭션 컨텍스트 안에서 수행됩니다.
- transid트랜잭션의 고유 ID이자 세대(generation) 번호입니다. 단조 증가하며, COW로 생성된 모든 블록의
btrfs_header.generation에 기록됩니다. - num_writers현재 이 트랜잭션에 참여 중인 writer 수를 원자적(Atomic)으로 추적합니다. 커밋 시 이 값이 0이 될 때까지 대기하여 모든 진행 중인 수정이 완료되도록 보장합니다.
- state
RUNNING→COMMIT_START→COMMIT_DOING→COMMITTED순으로 전이됩니다. 각 상태에서 새 writer의 참여 가능 여부와 I/O 동작이 달라집니다. - btrfs_start_transaction()현재 실행 중인 트랜잭션에 합류하거나 새 트랜잭션을 시작합니다.
num_items는 예상 수정 아이템 수로, 메타데이터 공간을 미리 예약하여 트랜잭션 중간에 ENOSPC가 발생하지 않도록 합니다. - btrfs_commit_transaction()트랜잭션을 디스크에 영속화합니다. delayed reference 처리, 더티 페이지(Dirty Page) flush, superblock 기록을 포함하며, superblock 기록이 commit point(원자적 전환점)입니다.
커밋 과정은 두 단계로 진행됩니다:
- 트랜잭션 실행: 모든 수정 사항이 메모리에 COW로 반영되고, 새 블록들이 디스크에 기록됨
- 슈퍼블록(Superblock) 기록: 모든 데이터가 디스크에 안착한 후 슈퍼블록의 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 | 암호화(Encryption) | 5.5+ | 높은 보안 수준, 성능 오버헤드(Overhead) 큼 |
| blake2b | 32B | 암호화 | 5.5+ | sha256 대비 빠른 암호화 해시(Hash), 보안 + 성능 균형 |
# 파일시스템 생성 시 체크섬 알고리즘 지정 (포맷 후 변경 불가)
$ 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 또는 미러에서 복구 시도
*/
코드 설명
fs/btrfs/file-item.c의 체크섬 조회 함수와 검증 흐름입니다. Btrfs는 모든 데이터 블록에 대해 체크섬을 유지하여 silent data corruption을 감지합니다.
- btrfs_lookup_csums_range()지정된 바이트 범위(
start~end)에 해당하는 체크섬을 Checksum Tree(tree ID=7)에서 조회합니다. 결과는list에btrfs_ordered_sum구조체로 반환됩니다. - search_commit
1이면 커밋된 루트(commit_root)에서 검색합니다. scrub이나 읽기 검증처럼 안정적인 스냅샷이 필요한 경우에 사용되며, 현재 트랜잭션의 수정 사항은 포함되지 않습니다. - 검증 흐름 1~4데이터를 읽은 후 저장된 체크섬과 새로 계산한 체크섬을 비교합니다. 불일치 시 RAID 구성이면 미러 디바이스에서 복구를 시도하고, 복구 불가능하면
-EIO를 반환합니다. 체크섬 알고리즘은 superblock의csum_type에 따라 CRC32C, xxHash, SHA-256, BLAKE2b 중 하나입니다.
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 반환
압축
Btrfs는 파일 데이터의 투명 압축(transparent compression)을 지원하여 디스크 공간을 절약하고, 특정 워크로드에서는 I/O 대역폭(Bandwidth)도 개선합니다.
압축 알고리즘
| 알고리즘 | 커널 버전 | 압축률 | 속도 | 레벨 | 특징 |
|---|---|---|---|---|---|
| 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 키로 정렬됩니다:
/* include/uapi/linux/btrfs_tree.h — 간략화 */
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 노드 크기 */
/* ... 수백 개의 필드 생략 ... */
};
코드 설명
fs/btrfs/fs.h에 정의된 파일시스템 전역 상태 구조체입니다. 마운트(Mount) 시 한 개만 생성되며, 모든 하위 구조체의 진입점(Entry Point) 역할을 합니다.
- tree_root ~ free_space_rootBtrfs의 핵심 트리들에 대한 포인터입니다. 각각 Root Tree(모든 서브 트리의 루트), Chunk Tree(논리-물리 주소 매핑), Extent Tree(할당 관리), Checksum Tree, UUID Tree, Free Space Tree를 가리킵니다.
- generation / last_trans_committed
generation은 현재 실행 중인 트랜잭션의 세대 번호이며,last_trans_committed는 디스크에 완전히 커밋된 마지막 세대입니다. 이 차이가 장애 복구 시 롤백(Rollback) 범위를 결정합니다. - running_transaction현재 활성 트랜잭션 객체를 가리킵니다. 새로운 쓰기 작업은 이 트랜잭션에 합류(join)하여 배치 커밋의 이점을 얻습니다.
- data_sinfo / meta_sinfo데이터와 메타데이터 각각의 공간 예약(reservation) 상태를 추적하는
btrfs_space_info구조체입니다. ENOSPC 처리와 chunk 자동 할당의 기준이 됩니다.
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; /* 마지막 수정 트랜잭션 */
};
코드 설명
fs/btrfs/ctree.h에 정의된 개별 B-tree의 런타임 표현입니다. 서브볼륨(FS Tree)마다 하나씩 존재합니다.
- node / commit_root
node는 현재 트랜잭션에서 수정 가능한 루트 노드이고,commit_root는 마지막으로 커밋된 안정적인 루트입니다. 읽기 전용 경로(예:btrfs send)는commit_root를 사용하여 잠금 경합(Lock Contention)을 피합니다. - root_itemRoot Tree에 저장되는 온디스크 아이템으로, 이 트리의 루트 노드 위치, 생성 세대, 사용 바이트 수 등의 영속적 메타데이터를 포함합니다.
- fs_info상위
btrfs_fs_info로의 역참조 포인터입니다. 트리 작업 중 전역 상태(트랜잭션, 공간 정보 등)에 접근할 때 사용됩니다.
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 (임베딩) */
};
코드 설명
fs/btrfs/btrfs_inode.h에 정의된 Btrfs 고유의 inode 구조체입니다. VFS의 struct inode를 임베딩하여 확장합니다.
- root이 inode가 속한 FS Tree(서브볼륨)를 가리킵니다. 같은 마운트 내에서도 서브볼륨마다 별도의 inode 번호 공간을 가지므로,
(root, ino)조합이 전역 고유 식별자가 됩니다. - locationB-tree에서 이 inode의
INODE_ITEM을 찾기 위한 키(ino, BTRFS_INODE_ITEM_KEY, 0)입니다. 디렉토리 항목이 이 키를 참조하여 inode를 가리킵니다. - flags
BTRFS_INODE_NODATACOW(데이터 COW 비활성),BTRFS_INODE_COMPRESS(압축 활성) 등 inode별 동작을 제어하는 플래그입니다.chattr +C명령으로 NODATACOW를 설정할 수 있습니다. - io_tree이 inode의 페이지(Page) 범위별 I/O 상태(읽기 중, 쓰기 중, 잠금(Lock) 등)를 추적하는 구간 트리(Extent I/O Tree)입니다. 비동기 I/O 완료 처리와 ordered extent 관리에 핵심적입니다.
- vfs_inodeVFS
struct inode가btrfs_inode안에 임베딩됩니다.container_of()매크로(Macro)로 VFS inode에서btrfs_inode를 역참조할 수 있으며, 이는BTRFS_I(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는 블록 그룹 간 데이터를 재배치(Relocation)하는 작업입니다. 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 사용자 공간(User Space) 도구 모음입니다. 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 | 없음 | 자동 조각 모음(Defragmentation) (랜덤 쓰기 워크로드) |
commit=N | 30 | 트랜잭션 커밋 주기(초) |
thread_pool=N | 코어 수 + 2 | 워커 스레드(Thread) 수 |
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+에서 크게 개선되어 대부분 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
다른 파일시스템 비교
기능 비교표
| 기능 | 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 |
| 컨테이너(Container) 호스트 | 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 계보) |
CoW B-tree
Btrfs의 B-tree는 일반적인 B+ tree와 달리 모든 수정이 Copy-on-Write로 처리됩니다. 이 섹션에서는 COW B-tree의 노드 분할/병합 메커니즘, 쓰기 증폭(write amplification), 그리고 트리 높이에 따른 성능 영향을 심층적으로 분석합니다.
노드 분할 (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;
}
코드 설명
fs/btrfs/ctree.c의 리프 분할 함수입니다. B-tree 삽입 시 리프가 가득 차면 호출되며, COW 의미론을 유지하면서 분할을 수행합니다.
- l = path->nodes[0]현재 가득 찬 리프 노드를 가져옵니다.
path는btrfs_search_slot()이 채운 루트→리프 경로입니다. - mid = (nritems + 1) / 2분할 지점을 계산합니다. 아이템의 대략 절반을 새 리프로 이동시킵니다.
- btrfs_alloc_tree_block()Extent Tree에서 새 블록을 할당합니다. 이 블록은 오른쪽 리프(
right)가 됩니다. COW 방식이므로 기존 리프는 스냅샷이 계속 참조할 수 있습니다. - copy_for_split()원본 리프의 후반부 아이템(
mid~nritems)을 새 리프로 복사하고, 원본 리프의 아이템 수를 줄입니다. - insert_ptr()부모 내부 노드에 새 리프의 첫 번째 키와 블록 포인터를 삽입합니다. 부모도 가득 차면 재귀적으로 분할이 전파됩니다.
노드 병합 (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 GiB | 2 | 2 (리프+루트) | 32 KiB |
| 100 GiB | 3 | 3 | 48 KiB |
| 10 TiB | 4 | 4 | 64 KiB |
| 1 PiB | 5 | 5 | 80 KiB |
스냅샷 트리
스냅샷은 서브볼륨의 FS Tree 루트를 공유하는 새로운 트리입니다. 이 섹션에서는 스냅샷의 내부 트리 구조, 메타데이터 공유 모델, 스냅샷 생성/삭제 비용, 그리고 qgroup 기반 공간 추적을 분석합니다.
스냅샷 생성 내부
/* 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;
}
코드 설명
fs/btrfs/ioctl.c의 스냅샷/서브볼륨 생성 핵심 함수입니다. btrfs subvolume snapshot 명령이 이 경로를 통해 실행됩니다.
- snap_src스냅샷의 소스가 될 기존 서브볼륨의
btrfs_root입니다.NULL이면 빈 서브볼륨을 새로 생성합니다. - btrfs_start_transaction()트랜잭션을 시작하여 스냅샷 생성이 원자적으로 수행되도록 합니다. 스냅샷 생성 자체가 하나의 트랜잭션 내에서 완결됩니다.
- btrfs_create_snapshot()소스 FS Tree의 루트 노드 포인터를 COW로 복사하고, 새 트리 ID를 할당한 뒤 Root Tree에
ROOT_ITEM을 삽입합니다. 실제 데이터 블록은 복사하지 않으므로 O(1) 시간에 완료됩니다. - extent 참조 카운트(Reference Count)스냅샷 생성 시점에는 extent의 참조 카운트를 갱신하지 않습니다. 이후 원본이나 스냅샷에서 데이터를 수정할 때 COW가 발생하면서 비로소 back-reference가 분리됩니다.
- readonly
true이면 읽기 전용 스냅샷을 생성합니다.btrfs send는 읽기 전용 스냅샷만 소스로 사용할 수 있습니다.
스냅샷 삭제 비용
스냅샷 삭제는 독점(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
squota(simple quota) 모드가 도입되어 오버헤드가 크게 줄었습니다.
Send/Receive
Btrfs send/receive는 두 읽기 전용 스냅샷 사이의 차이를 바이너리 스트림으로 직렬화(Serialization)하여 전송합니다. 이 섹션에서는 send 스트림 형식, 증분 전송 알고리즘, 그리고 원격 복제 파이프라인(Pipeline)을 상세히 분석합니다.
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/"
-c(clone source) 옵션으로 여러 스냅샷을 지정하면 CLONE 명령이 더 많이 생성되어 실제 데이터 전송량이 줄어듭니다. v2 프로토콜의 --compressed-data는 압축 해제/재압축 오버헤드를 제거합니다.
Scrub 메커니즘
Scrub은 Btrfs의 온라인 데이터 무결성 검증 시스템입니다. 모든 extent를 순차적으로 읽어 체크섬을 검증하고, RAID 미러가 있는 경우 손상된 데이터를 자동으로 복구합니다. 이 섹션에서는 scrub의 내부 구조, I/O 스케줄링, 복구 전략을 심층 분석합니다.
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
RAID 프로파일
Btrfs의 RAID는 전통적인 mdadm RAID와 달리 Chunk 레벨에서 동작합니다. 각 Chunk(Block Group)마다 독립적인 RAID 프로파일이 적용되며, 데이터와 메타데이터에 서로 다른 프로파일을 혼합할 수 있습니다. 이 섹션에서는 Chunk 기반 RAID의 내부 동작, 리밸런싱, 그리고 장애 복구를 분석합니다.
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(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:3 | 3~5:1 | 높은 압축률, 적당한 속도 |
| 로그 파일 | compress=zstd:3 | 5~10:1 | 반복 패턴 많아 매우 효과적 |
| 데이터베이스 | nodatacow (압축 비활성) | - | 랜덤 I/O, COW 오버헤드 제거 |
| 가상머신 이미지 | nodatacow | - | 내부 FS가 별도 관리 |
| 미디어 (사진/동영상) | compress=zstd:1 또는 none | 1:1~1.1:1 | 이미 압축된 데이터, 효과 미미 |
| 범용 데스크톱 | compress=zstd:1 | 1.5~2.5:1 | 최소 CPU로 적당한 절약 |
| NAS/아카이브 | compress=zstd:9 | 2.5~4:1 | 공간 절약 우선, I/O 부담 수용 |
중복 제거 (Deduplication)
Btrfs는 ZFS와 달리 인라인(실시간(Real-time)) 중복 제거를 지원하지 않지만, reflink 기반의 오프라인/out-of-band 중복 제거를 효과적으로 수행할 수 있습니다. 이 섹션에서는 reflink, 중복 제거 도구, 그리고 extent 공유 메커니즘을 분석합니다.
Reflink 메커니즘
/* 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)
커널 내부
이 섹션에서는 Btrfs의 핵심 커널 내부 구조인 btrfs_fs_info, btrfs_root, 트랜잭션 관리자, 그리고 delayed ref 메커니즘을 심층 분석합니다. fs/btrfs/ 디렉토리의 주요 소스 파일 간 관계를 이해하는 것이 목표입니다.
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.h | btrfs_fs_info 정의 | btrfs_fs_info, 마운트 옵션 상수 |
ctree.h / ctree.c | B-tree 핵심 | btrfs_search_slot, split_leaf, btrfs_key |
transaction.c | 트랜잭션 관리 | btrfs_start_transaction, btrfs_commit_transaction |
extent-tree.c | extent 할당/해제 | btrfs_alloc_tree_block, btrfs_free_tree_block |
delayed-ref.c | 지연(Latency) 참조 처리 | btrfs_run_delayed_refs |
volumes.c | RAID/Chunk/디바이스 | btrfs_map_block, decide_stripe_size |
disk-io.c | 디스크 I/O | btrfs_read_buffer, write_all_supers |
inode.c | VFS inode 연산 | btrfs_lookup_dentry, btrfs_new_inode |
file.c | 파일 연산 | btrfs_file_write_iter, btrfs_sync_file |
send.c | send/receive | btrfs_ioctl_send, changed_cb |
scrub.c | scrub | scrub_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 스케줄러(Scheduler) 연동, 그리고 벤치마크 방법론을 분석합니다.
벤치마크 방법론
# ── 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+) 사용 또는 비활성화 |
| 파일 단편화(Fragmentation) | COW + 랜덤 쓰기 | autodefrag 또는 주기적 defragment |
| send/receive 느림 | 대량 변경 + 비효율적 비교 | -c clone source 지정, v2 프로토콜 사용 |
autodefrag은 소형 랜덤 쓰기 워크로드에서 유용하지만, 대용량 순차 쓰기 환경에서는 불필요한 I/O를 생성할 수 있습니다. 또한 스냅샷이 많은 환경에서 defrag은 extent 공유를 해제하여 디스크 사용량을 급증시킬 수 있으므로, 항상 사전에 btrfs fi usage로 여유 공간을 확인하세요.
참고자료
공식 문서
- Kernel Documentation: Btrfs — 커널 공식 Btrfs 문서입니다
- Btrfs Documentation (readthedocs) — Btrfs 프로젝트 공식 문서 사이트입니다
- Btrfs Wiki (kernel.org) — Btrfs 커뮤니티 위키로 사용법, FAQ, 디자인 문서를 포괄합니다
- Btrfs FAQ — 자주 묻는 질문과 알려진 이슈를 정리한 페이지입니다
- Btrfs Multi-Device 가이드 — 다중 디바이스 및 RAID 프로파일 사용 가이드입니다
- btrfs(5) Man Page — 마운트 옵션 및 파일시스템 속성 매뉴얼입니다
- Btrfs Design Documentation — 커널 소스 내 Btrfs 설계 문서입니다
커널 소스
- fs/btrfs/ — Btrfs 전체 소스 디렉터리입니다
- fs/btrfs/super.c — 파일시스템 등록, 마운트 옵션 처리 핵심 코드입니다
- fs/btrfs/ctree.c — COW B-tree 핵심 구현입니다
- fs/btrfs/ctree.h — 온디스크 자료구조 정의 헤더입니다
- fs/btrfs/disk-io.c — 슈퍼블록 읽기/쓰기, 트리 루트 관리 코드입니다
- fs/btrfs/extent-tree.c — extent 할당 및 참조 카운트 관리 코드입니다
- fs/btrfs/inode.c — inode 연산 및 VFS 인터페이스 구현입니다
- fs/btrfs/volumes.c — 다중 디바이스, RAID, 청크(Chunk) 할당 관리입니다
- fs/btrfs/send.c — send/receive 스트림 생성 코드입니다
- fs/btrfs/scrub.c — 데이터 무결성 검사(scrub) 구현입니다
- fs/btrfs/compression.c — 투명 압축(zlib/lzo/zstd) 프레임워크입니다
- fs/btrfs/qgroup.c — 쿼터 그룹(qgroup) 구현입니다
- fs/btrfs/space-info.c — 공간 예약 및 ENOSPC 처리 코드입니다
- fs/btrfs/transaction.c — 트랜잭션 커밋 및 로그 트리 관리입니다
- fs/btrfs/reflink.c — reflink(CoW 파일 복사) 구현입니다
- fs/btrfs/zoned.c — Zoned Storage 장치 지원 코드입니다
LWN.net 주요 기사
- The Btrfs filesystem: An introduction (2013) — Btrfs 기본 개념과 설계를 소개하는 기사입니다
- Btrfs: Working with multiple devices (2013) — 다중 디바이스와 RAID 프로파일 활용을 다룹니다
- Btrfs: Subvolumes and snapshots (2013) — 서브볼륨과 스냅샷의 동작 원리를 설명합니다
- Btrfs: Send/receive and ioctl() (2013) — send/receive 메커니즘과 ioctl 인터페이스를 다룹니다
- A short history of btrfs (2009) — Btrfs 개발 초기 역사와 설계 동기를 정리합니다
- An update on Btrfs (2020) — Btrfs 최신 개발 현황과 안정성 개선 사항을 다룹니다
- Btrfs at Facebook (2021) — Meta(Facebook)에서의 대규모 Btrfs 운영 경험을 공유합니다
- Btrfs send protocol v2 (2022) — send 프로토콜 v2의 설계와 개선점을 설명합니다
- Btrfs: Pair up and repair (2018) — RAID 복구 및 데이터 무결성 메커니즘을 다룹니다
관련 발표 및 컨퍼런스
- Btrfs – the Swiss Army Knife of Storage (FOSDEM) — Btrfs 주요 기능을 종합 소개하는 발표입니다
- Deploying Btrfs at Facebook Scale (Vault 2019) — Meta에서의 대규모 운영 사례를 발표한 영상입니다
- Btrfs Project Ideas — Btrfs 향후 개발 방향과 프로젝트 아이디어를 정리합니다
- Btrfs Changelog — 커널 버전별 Btrfs 변경 이력을 기록합니다
사용자 도구
- btrfs-progs (GitHub) — Btrfs 사용자 공간 유틸리티(mkfs.btrfs, btrfs 명령 등) 소스입니다
- btrbk — 스냅샷 기반 증분 백업 관리 도구입니다
- WinBtrfs — Windows용 Btrfs 드라이버 프로젝트입니다
- BEES (Best-Effort Extent-Same) — Btrfs 중복 제거 데몬입니다
- Snapper — openSUSE의 Btrfs 스냅샷 관리 도구입니다
Btrfs 최신 변화 (v6.8~v6.15)
Btrfs는 v6.8부터 v6.15까지 zoned 디바이스 지원 강화, RAID 정합성 개선, folio 기반 I/O 경로 재작성, 사용자 공간 확장 인터페이스(Encoded I/O) 확충에 초점을 맞추어 꾸준히 진화하고 있습니다. 아래에서는 실무에 영향이 큰 주요 변화를 정리합니다.
RAID Stripe Tree (RST, v6.12~v6.13)
RAID Stripe Tree는 블록 그룹별 실제 물리 스트라이프 위치를 별도 B-tree에 기록하는 신규 메타데이터 구조입니다. v6.12에서 CONFIG_BTRFS_EXPERIMENTAL=y 플래그 하에 도입되었고, v6.13에서 zoned 모드 RAID1/10 지원이 확대되었습니다. 기존 chunk item 기반 매핑으로는 해결이 어려웠던 zoned 프로파일 및 RAID56 write hole 문제를 근본적으로 재설계한 접근입니다.
- 기존 chunk item 매핑의 한계(zoned RAID1/10, RAID56 write hole)를 극복하는 목적입니다
- v6.13에서 zoned 모드의
raid1*,raid0,raid10프로파일을 RST 위에서 지원합니다 - 사용하려면
mkfs.btrfs -O raid-stripe-tree옵션이 필요하며 btrfs-progs 6.7 이상이어야 합니다 - 아직 실험적(Experimental) 단계이므로 프로덕션 도입 전 LTS 안정화 여부를 확인해야 합니다
btrfs inspect-internal dump-tree에서 새 트리 ID(RAID_STRIPE_TREE_OBJECTID)를 확인할 수 있습니다.
Temporary FSID (v6.7~v6.12)
동일 FSID를 가진 디스크 이미지를 복제한 경우, 과거에는 두 장치를 동시에 마운트하는 것이 불가능했습니다. v6.7에서 도입되고 v6.12에 안정화된 Temporary FSID 기능은 마운트 시 커널이 런타임 임시 UUID를 할당하여 이 제약을 완화합니다. 컨테이너/VM 이미지 클론 워크플로우에 특히 유용합니다.
- 기본적으로 읽기 전용(
ro)이 안전한 사용 패턴입니다 - 쓰기 마운트 시에는 먼저 보조 이미지를 분리 후
btrfstune -M으로 새 UUID를 발급하는 관용구가 권장됩니다 - LXC, systemd-nspawn, QEMU qcow2 스냅샷 등의 이미지 복제 시나리오에서 즉시 마운트가 가능해집니다
Encoded I/O ioctl (v6.6 read, v6.13 write)
BTRFS_IOC_ENCODED_READ와 BTRFS_IOC_ENCODED_WRITE는 사용자 공간이 압축된 데이터를 해제 없이 그대로 읽고 쓸 수 있게 하는 ioctl입니다. btrbk와 같은 백업 도구가 증분 송수신 시 압축 해제를 생략해 전송 효율을 크게 높일 수 있습니다.
struct btrfs_ioctl_encoded_io_args args = {
.iov = iov, /* scatter-gather 버퍼 */
.iovcnt = 1,
.offset = 0,
.flags = 0,
.len = logical_len, /* 논리 길이 */
.unencoded_len = logical_len,
.compression = BTRFS_ENCODED_IO_COMPRESSION_ZSTD,
.encryption = BTRFS_ENCODED_IO_ENCRYPTION_NONE,
};
ioctl(fd, BTRFS_IOC_ENCODED_READ, &args);
- send/receive 스트림 내부에서도 encoded extent를 그대로 전달하여 CPU/대역폭을 동시에 절약합니다
- v6.13에서 WRITE 측이 추가되어 사용자 공간에서 직접 압축 extent를 구성해 삽입 가능합니다
Simple Zoned 모드 & 기타 (v6.11+)
Zoned 디바이스 지원은 v6.11 이후 실사용 수준으로 강화되었습니다. btrfs send가 zoned 파일시스템에서 동작하게 되었고, 복구 옵션(rescue= 계열)도 확장되었습니다.
- Zoned 모드에서
btrfs send지원(v6.11) rescue=ignoredatacsums,rescue=ignoremetacsums옵션 확장으로 손상 이미지 포렌식 용이- v6.12
defrag시 파일 범위(offset/length) 지정이 강화되어 핫스폿만 선택 조각모음 가능 - v6.12 qgroup cleanup 경로 성능 개선으로 대규모 스냅샷 삭제 지연 감소
- v6.13~v6.14 전반적 워크큐 재구성으로 write 경로 지연(latency) 감소
Folio 전환 및 Large Folio (v6.12+)
Btrfs의 read/write 경로가 page 단위에서 folio 우선 구조로 재작성되면서, 압축 읽기(compressed read)에도 large folio가 부분적으로 지원되기 시작했습니다. 페이지 캐시 동작이 전체적으로 변경되어 mmap 집약적 워크로드에 영향을 줍니다.
- compressed read 경로의 디코드 버퍼(Buffer)가 folio 기반으로 교체되어 메모리 단편화 감소
- mmap 집약적 워크로드(DB, 이미지 뷰어 등)의 페이지 캐시(Page Cache) 히트 패턴 개선
- large folio 경로는 점진 적용 중이므로 커널 버전별 프로파일링(Profiling) 권장
-stable 백포트 상태를 확인하세요.
참고 링크
- 공식 문서(버전별 변경사항): btrfs.readthedocs.io — Kernel by version
- LWN RAID Stripe Tree 심층 해설: LWN 944631
Btrfs 최신 변화 (v6.15~v7.0)
v6.15부터 v7.0까지 Btrfs는 성능 최적화, 새 메타데이터 구조(Remap Tree), 압축 개선, 그리고 fscrypt 준비 작업 등 실용적인 개선에 집중하였습니다.
v6.15 주요 변화
v6.15에서는 btrfs send 내부 경로 재사용을 줄이는 리팩터링(refactoring)으로 샘플 부하 기준 런타임이 크게 단축되었습니다(정도는 하드웨어·워크로드에 따라 달라짐). 또한 Zstd 음수(Negative) 압축 레벨을 마운트 옵션으로 설정할 수 있게 되었고, defrag ioctl에서도 압축 레벨 지정이 가능해졌습니다.
- send 최적화: 반복 경로 계산·할당을 제거하여 런타임이 크게 개선(정도는 하드웨어·워크로드에 따라 달라짐)
- Zstd 음수 레벨:
compress=zstd:-1처럼 속도 우선 압축 설정 가능 - Direct I/O 폴백: 체크섬이 필요한 파일에 Direct I/O 쓰기 시 버퍼드(Buffered) I/O로 자동 폴백
- Extent I/O folio 준비: Large Folio 지원을 위한 extent I/O 레이어 정비
v6.16 성능 개선
v6.16에서는 extent buffer writeback 단순화로 메타데이터 집중 워크로드의 처리량이 크게 향상되고 런타임이 크게 단축되었습니다. extent 고정 해제(unpinning) 경로 최적화로 트랜잭션(Transaction) 커밋 시 추가 개선을 달성하였습니다.
- Extent Buffer Writeback 단순화: 메타데이터 집중 워크로드에서 처리량 향상, 런타임 단축
- Extent 고정 해제 최적화: 트랜잭션 커밋 시 런타임 개선
- scrub 메모리 절감:
struct scrub_sector_verification의 메모리 사용량 감소 - Block-perfect 압축 정식화: 실험적(Experimental) 상태에서 정식 기능으로 승격
# Zstd 음수 레벨로 마운트 (v6.15+)
mount -o compress=zstd:-3 /dev/sda1 /mnt/btrfs
v6.17 성능 및 모니터링 개선
v6.17에서는 free space 비트맵(Bitmap) 조회 결과 캐싱(Caching)이 추가되어 빈 파일 생성 벤치마크(Benchmark) 기준으로 런타임이 크게 개선되었습니다(정도는 하드웨어·워크로드에 따라 달라짐). extent buffer를 XArray 기반 인덱싱(Indexing)으로 재구성하여 노드 패킹(Packing) 밀도를 높였습니다.
- free space bitmap 캐싱: 조회 결과 캐싱으로 빈 파일 생성 속도가 크게 향상(정도는 하드웨어·워크로드에 따라 달라짐)
- XArray 기반 extent buffer 인덱싱: 더 촘촘한 키(Key) 배치로 노드 탐색 효율 개선
- commit_stats 통계:
/sys/fs/btrfs/<uuid>/commit_stats에 현재 커밋(Commit) 소요 시간 추적 항목 추가 - send v2 fallocate hole 펀칭: 이전의 제로 데이터 채우기 대신
fallocate로 hole punching하여 전송 효율 개선 - Large data folio 실험 지원:
CONFIG_BTRFS_EXPERIMENTAL=y환경에서 large data folio 활성화 가능
# commit_stats 확인 (v6.17+)
cat /sys/fs/btrfs/<uuid>/commit_stats
v6.18 병렬성 및 참조 추적 개선
v6.18에서는 읽기 집중 워크로드(Workload)에서 체크섬 검색이 이전 트랜잭션의 커밋 루트(commit root)를 사용하도록 변경되어 잠금 경합(Lock Contention)이 줄고 트랜잭션 커밋 시간이 수 분에서 수십 초로 단축되었습니다.
- 체크섬 조회 커밋 루트 분리: 읽기 경로에서 이전 커밋 루트를 사용하여 잠금 경합 감소
- 압축 워크스페이스(Workspace) 관리자: 파일시스템별 압축 워크스페이스 관리자 도입으로 자원 할당 효율 개선
- delayed_node 참조 추적:
ref_tracker를delayed_node에 적용하여 메모리 누수 탐지 강화
v6.19 전원 관리(Power Management) 통합 및 RAID56 개선
v6.19에서는 스크럽(Scrub)과 장치 교체(Device Replace) 중 시스템 일시 중지(Suspend)가 가능해졌습니다. 스크럽은 마지막 상태를 기록한 뒤 재개할 수 있고, 장치 교체는 재시작(Restart)이 필요합니다.
- 스크럽 일시 중지/재개: 스크럽 진행 중 시스템 Suspend 시 마지막 체크포인트(Checkpoint)에서 재개 가능
- 장치 교체 Suspend 대응: Suspend 시 교체 작업을 중단하고 재시작(Reboot); 이전에는 Suspend 자체를 차단
- 공간 예약 잠금 최적화: 공간 예약 티켓(Ticket) 처리 시 임계 구역(Critical Section) 단축으로 lockstat 지표 기준 개선
- 비동기 체크섬: 데이터 쓰기의 체크섬 계산을 병렬화하여 Direct I/O 기준 처리량(Throughput) 향상
- RAID56 블록 크기 확장: 메모리 페이지(Page) 크기보다 큰 블록 크기를 RAID56 구성에서 실험적으로 지원
- fscrypt 준비: 암호화(Encryption) 지원을 위한 초기 기반 작업 시작
커널 6.19부터: 셧다운(Shutdown) ioctl (BTRFS_IOC_SHUTDOWN)이 실험적 기능으로 추가되었습니다. 파일시스템 상태를 강제 종료로 설정하여 모든 진행 중인 작업이 EIO를 반환하도록 합니다. 언마운트 전까지 되돌릴 수 없습니다 (CONFIG_BTRFS_EXPERIMENTAL=y 필요).
v7.0 Remap Tree 및 직접 I/O 확장
v7.0에서는 새로운 Remap Tree(재배치 트리) 메타데이터 구조가 도입되었습니다. 블록 재배치(Relocation) 시 모든 트리를 즉시 수정하는 대신, 기존 주소와 새 주소의 매핑(Mapping)을 remap tree에 기록합니다. 이를 통해 재배치 신뢰성이 높아지고 향후 더 큰 데이터 extent와 쓰기 증폭(Write Amplification) 감소를 위한 기반이 마련됩니다.
- Remap Tree: 블록 재배치 시 간접(Indirection) 레이어로 동작하며 재배치 중 충돌(Crash) 복구 신뢰성 향상
- 블록 크기 > 페이지 크기 Direct I/O: 블록 크기가 메모리 페이지 크기보다 큰 구성에서 Direct I/O 지원
- 셧다운 ioctl 실험 단계 유지: v7.0 시점에도
CONFIG_BTRFS_EXPERIMENTAL이 필요한 기능이며, 실험 플래그 제거 시점은 이후 mainline 변경 이력을 다시 확인해야 합니다
-stable 브랜치 적용 여부를 확인한 뒤 업그레이드하세요.
참고 링크
- Btrfs 공식 버전별 변경사항: btrfs.readthedocs.io — Kernel by version
- Phoronix Linux 6.17 Btrfs 기사: phoronix.com — Linux 6.17 Btrfs
- Phoronix Linux 6.19 Btrfs 기사: phoronix.com — Linux 6.19 Btrfs
관련 문서
Btrfs와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.