Btrfs(B-tree file system, “better FS” 또는 “butter FS”로도 읽는다)는 Oracle의 Chris Mason이 2007년 시작해 2009년 Linux 2.6.29부터 메인라인에 포함된 파일시스템이다. ext4의 뒤를 이을 차세대 범용 파일시스템을 목표로, ZFS에 견줄만한 copy-on-write(CoW) 기반 스냅샷, 체크섬을 통한 데이터 무결성 검증, 파일시스템 계층의 내장 RAID, 온라인 리사이즈/밸런스 같은 기능을 하나로 묶었다. 이 글에서는 Btrfs의 설계 철학과 핵심 기능, 그리고 실제로 파일시스템을 만들고 서브볼륨/스냅샷을 다루는 절차를 다룬다.
설계 철학: everything is a B-tree, copy-on-write
Btrfs는 이름 그대로 거의 모든 것을 B-tree로 표현한다. 파일 데이터의 실제 위치를 관리하는 extent tree, 여러 디바이스에 걸친 물리적 공간 배치를 관리하는 chunk tree/device tree, 파일시스템 전체 구조의 진입점인 root tree, 데이터·메타데이터 체크섬을 담는 checksum tree, 그리고 서브볼륨마다 독립적으로 존재하는 서브볼륨 트리까지 전부 B-tree 구조로 저장된다. ext4처럼 고정된 온디스크 레이아웃(슈퍼블록, inode 테이블, 블록 그룹 등)을 갖는 대신, Btrfs는 필요에 따라 늘어나고 줄어드는 트리 집합으로 파일시스템을 표현한다.
더 근본적인 특징은 copy-on-write다. 기존 블록을 제자리에서 덮어쓰지 않고, 수정된 데이터를 항상 새 위치에 쓴 뒤 그 상위 메타데이터(B-tree 노드)를 루트까지 연쇄적으로 새로 써서 원자적으로 교체한다. 이 방식 덕분에 두 가지가 거의 공짜로 따라온다. 하나는 크래시 안전성이다. 쓰는 도중 전원이 나가도 이전 트리 루트가 그대로 남아있으므로 파일시스템이 절반만 쓰인 상태로 깨지지 않는다. 다른 하나는 스냅샷이다. 특정 시점의 트리 루트에 대한 참조만 하나 더 만들면 그 시점 전체 상태를 즉시 복제할 수 있고, 이후 원본과 스냅샷이 갈라지는 부분만 새로 쓰기가 일어나므로 스냅샷 자체는 거의 비용이 들지 않는다.
다만 CoW는 공짜가 아니다. 파일 하나를 아주 조금씩 반복해서 덮어쓰는 워크로드(데이터베이스, VM 디스크 이미지)에서는 매번 새 위치에 쓰면서 원래 연속적이던 파일이 물리적으로 조각나기 쉽고, 오래된 익스텐트를 참조하는 스냅샷이 있으면 그 공간을 회수하지도 못한다. 이런 워크로드는 뒤에서 다룰 nodatacow 속성으로 CoW를 우회하는 것이 일반적인 대응이다.
서브볼륨과 스냅샷
서브볼륨(subvolume)은 하나의 Btrfs 풀 안에 존재하는 독립된 네임스페이스다. 별도 파티션을 만들지 않고도 같은 공간 풀을 공유하는 여러 개의 논리적 파일시스템 트리를 만들 수 있다는 점에서 LVM의 논리 볼륨과 비슷해 보이지만, 크기가 미리 고정되지 않고 풀 전체 여유 공간을 자유롭게 나눠 쓴다는 점이 다르다. 스냅샷은 특정 서브볼륨의 특정 시점 상태를 그대로 복제한 것으로, 내부적으로는 새로운 서브볼륨을 만들되 그 루트가 원본과 같은 B-tree 노드를 가리키게 하는 것뿐이라 생성이 즉시 끝난다.
스냅샷은 읽기 전용(-r)으로도, 쓰기 가능한 상태로도 만들 수 있다. 읽기 전용 스냅샷은 백업이나 btrfs send로 다른 위치에 전송할 때, 쓰기 가능한 스냅샷은 시스템 업그레이드나 패키지 설치 전에 찍어두고 문제가 생기면 부트로더에서 이전 스냅샷으로 되돌리는 용도(openSUSE의 Snapper가 대표적인 예)로 흔히 쓰인다.
멀티 디바이스와 내장 RAID
Btrfs는 mdadm이나 LVM 없이도 여러 블록 디바이스를 하나의 풀로 묶어 파일시스템 계층에서 직접 RAID를 구성한다. 데이터와 메타데이터 각각에 대해 서로 다른 중복 정책(profile)을 지정할 수 있다는 점이 특징이다.
- single — 중복 없이 하나의 사본만 저장한다.
- DUP — 같은 디바이스 안에 사본을 두 벌 저장한다. 디바이스가 하나뿐이어도 메타데이터 손상에 대비할 수 있어,
mkfs.btrfs는 단일 디바이스에서도 메타데이터 프로필을 기본값으로 DUP를 사용한다. - RAID0 / RAID1 / RAID10 — 여러 디바이스에 걸친 스트라이핑/미러링으로, mdadm RAID와 개념은 같지만 파일시스템이 직접 관리하므로 디바이스 추가/제거와 재조정(balance)이 온라인으로 가능하다.
- RAID5 / RAID6 — 패리티 기반 중복이다. 다만 Btrfs의 RAID5/6 구현은 오래된 “write hole” 문제(스트라이프 쓰기 도중 crash가 나면 데이터와 패리티가 불일치할 수 있는 결함)가 완전히 해결되지 않은 상태로, 공식 문서에서도 아직 프로덕션에 안전하다고 보장하지 않는다. 중요한 데이터에는 RAID1/RAID10을 쓰는 편이 안전하다.
체크섬과 scrub
Btrfs는 데이터와 메타데이터 모두에 체크섬을 붙인다. 기본 알고리즘은 crc32c이고, 커널 5.5부터는 mkfs 시점에 xxhash64, sha256, blake2b 중에서 선택할 수도 있다. 체크섬은 checksum tree에 별도로 저장되며, 읽을 때마다 검증해 조용한 데이터 손상(bit rot)을 감지한다.
감지만으로는 부족하니, RAID1/RAID10처럼 중복 사본이 있는 구성에서는 체크섬이 틀린 사본을 발견하면 다른 사본으로 자동 복구(self-healing)한다. btrfs scrub은 이 검증을 파일시스템 전체에 대해 온라인 상태로 수행하는 도구로, 정기적으로 돌려두면 실제로 읽지 않는 콜드 데이터의 손상도 미리 잡아낼 수 있다.
투명 압축
마운트 옵션이나 파일/디렉터리 단위 property로 zlib, lzo, zstd 압축을 투명하게 적용할 수 있다. zstd는 압축률과 속도의 균형이 좋아 최근 배포판 기본값으로 많이 쓰인다. compress=zstd는 압축했을 때 이득이 있는 파일만 골라서 압축하고, compress-force=zstd는 이미 압축된 파일(mp4, jpg 등)까지 무조건 압축을 시도해 CPU만 낭비하는 경우가 있으니 일반적으로는 compress 쪽을 권장한다.
온라인 리사이즈와 디바이스 관리
Btrfs 파일시스템은 마운트된 상태에서 용량을 늘리거나 줄일 수 있고(btrfs filesystem resize), 디바이스를 추가하거나 제거하는 것도 마운트 해제 없이 가능하다(btrfs device add/remove). 디바이스 구성을 바꾸거나 블록 그룹이 여기저기 흩어져 낭비되는 공간을 정리하고 싶을 때는 btrfs balance로 블록 그룹을 재작성한다. 전체 balance는 대용량 파일시스템에서 시간이 오래 걸리므로, 보통 -dusage=/-musage= 같은 필터로 사용률이 낮은 블록 그룹만 골라서 처리한다.
btrfs-progs로 만들어보기
Ubuntu에서는 btrfs-progs 패키지로 관련 유틸리티를 설치한다.
$ sudo apt install btrfs-progs
테스트용 loopback 이미지에 파일시스템을 만들어본다.
$ truncate -s 1G btrfs.img
$ mkfs.btrfs -f -L mytest btrfs.img
실행 결과에는 앞서 설명한 데이터/메타데이터 프로필이 그대로 나타난다. 디바이스가 하나뿐인데도 메타데이터가 DUP로 잡히는 것을 확인할 수 있다.
Label: mytest
Node size: 16384
Sector size: 4096
Filesystem size: 1.00GiB
Block group profiles:
Data: single 8.00MiB
Metadata: DUP 51.19MiB
System: DUP 8.00MiB
Checksum: crc32c
Number of devices: 1
마운트한 뒤 서브볼륨을 만들고 스냅샷을 떠본다.
$ sudo mkdir -p /mnt/btrfs
$ sudo mount -o loop,compress=zstd btrfs.img /mnt/btrfs
$ sudo btrfs subvolume create /mnt/btrfs/data
$ echo "hello" | sudo tee /mnt/btrfs/data/file1.txt
$ sudo btrfs subvolume snapshot /mnt/btrfs/data /mnt/btrfs/data_snap1
$ sudo btrfs subvolume list /mnt/btrfs
읽기 전용 스냅샷을 뜨고 다른 파일시스템으로 전송하려면 send/receive를 쓴다.
$ sudo btrfs subvolume snapshot -r /mnt/btrfs/data /mnt/btrfs/data_snap_ro
$ sudo btrfs send /mnt/btrfs/data_snap_ro -f /tmp/snap.send
$ sudo btrfs receive /mnt/other -f /tmp/snap.send
전체 파일시스템 상태와 데이터 무결성은 다음 명령으로 점검한다.
$ sudo btrfs filesystem show /mnt/btrfs
$ sudo btrfs filesystem df /mnt/btrfs
$ sudo btrfs scrub start /mnt/btrfs
알아두면 유용한 마운트 옵션
compress=zstd|lzo|zlib— 투명 압축 알고리즘을 지정한다.autodefrag— 소규모 랜덤 쓰기가 많은 워크로드에서 백그라운드로 조각화를 완화한다. 다만 큰 파일(VM 이미지 등)에는 오히려 불리할 수 있다.space_cache=v2— 여유 공간 추적을 B-tree 기반으로 관리해 마운트 시간과 성능을 개선한다. 최신 커널에서는 기본값이다.ssd— SSD에 최적화된 할당 정책을 켠다. 커널이 회전형 디스크 여부를 감지해 대부분 자동으로 적용한다.discard=async— TRIM을 동기적으로 즉시 실행하는 대신 백그라운드로 비동기 처리해 쓰기 지연을 줄인다.noatime— 파일 접근 시각 갱신을 생략해 불필요한 메타데이터 쓰기를 줄인다.
알려진 한계와 주의점
- RAID5/RAID6 write hole — 위에서 언급했듯 아직 완전히 해결되지 않았다. 패리티 기반 중복이 꼭 필요하다면 mdadm RAID 위에 Btrfs를 단일 디바이스처럼 올리는 구성을 대안으로 고려할 수 있다.
- CoW와 랜덤 쓰기 워크로드의 상성 — 데이터베이스나 VM 디스크 이미지처럼 같은 파일을 반복해서 덮어쓰는 경우, CoW 때문에 조각화와 쓰기 증폭이 심해질 수 있다. 해당 파일이나 서브볼륨에
chattr +C로 nodatacow 속성을 주면 그 부분만 CoW를 끄고 제자리 쓰기를 하게 만들 수 있다. 단 nodatacow가 걸린 파일은 체크섬 보호와 압축도 함께 비활성화된다는 점을 감안해야 한다. - 거의 꽉 찬 파일시스템에서의 ENOSPC — Btrfs는 메타데이터용 공간을 별도 블록 그룹으로 미리 할당해 관리하는데, 데이터 공간이 넉넉해 보여도 메타데이터 블록 그룹이 부족하면 새로 할당받지 못해 쓰기가 실패하는 경우가 있다. 여유 공간이 5~10% 미만으로 떨어지면
btrfs balance로 블록 그룹을 정리해주는 것이 안전하다.
정리
Btrfs는 B-tree 기반 구조와 copy-on-write를 뼈대로, 스냅샷·체크섬·내장 RAID·온라인 관리 기능을 한 파일시스템 안에 통합했다. 서브볼륨과 스냅샷은 시스템 롤백이나 백업 워크플로를 크게 단순화하고, 체크섬과 scrub은 조용한 데이터 손상을 잡아낼 수 있게 해준다. 다만 RAID5/6는 아직 프로덕션에 권하기 어렵고, CoW 특유의 조각화 문제는 데이터베이스나 VM 이미지처럼 쓰기 패턴이 무거운 워크로드에서 별도의 튜닝(nodatacow, autodefrag)을 요구한다. 범용 데스크톱/서버 용도로는 이미 여러 배포판(openSUSE, Fedora)의 기본 파일시스템으로 채택될 만큼 성숙했지만, 특수한 워크로드에 도입하기 전에는 이런 트레이드오프를 먼저 점검하는 편이 안전하다.