2019/10/28

btrfs 파일시스템 이해 및 활용


btrfs 파일시스템을 본격적으로 쓰기로 했기 때문에 까먹기 전에 사용법에 대해 다시 정리해 두는게 좋겠다. 예전 글에서 btrfs local snapshot으로 시스템 복원하는 방법에 대해 다루었는데, 몇 년 사이에 btrfs도 많이 개선됐기에, 복습도 되고 새로 btrfs 파일시스템을 접하는 이들에게도 도움이 될 것이다. 특히나 btrfs는 기존의 ext4 파일시스템과 많이 다르기 때문에 개념을 이해하지 않으면 제대로 활용하기 어렵다. 사실, btrfs wiki를 보면 대부분의 btrfs 사용법을 익힐 수 있지만, btrfs를 잘 모르는 사용자들은 적응하기가 쉽지 않다.

Copy-on-Write(CoW) 개념

c++ 고급 사용자라면 한 번쯤 접해 봤을 것이다. Qt Framework에서는 container 들을 포함한 여러 class 객체 들이 copy-on-write(또는 implicit sharing) 방식으로 동작한다. 공유 객체가 만들어질 때 공유 data를 생성하고 reference counter 를 1로 설정했다가 참조하는 객체가 늘어날 때마다 counter를 증가시키고 줄어들면 감소시킨다. 0이 되면 공유 data를 삭제하고 공유 객체는 소멸된다. 참조 객체들은 처음에는 공유 객체 주소 정보만 복사해서(shallow copy) 사용하다가 변경 사항이 생길 때 reference counter가 1보다 크면(여러 객체가 data를 공유하고 있으면) 공유 data를 모두 복사하고(deep copy) 나서 변경사항을 적용한다.

변경이 필요할 때(on-write) 공유 data를 복사하면(copy) 된다는 개념인데 btrfs도 이런 식으로 동작한다. 즉, 어떤 파일이 변경되지 않는 한 공유 data 참조 정보만 저장하면 된다. 파일이 변경될 때는 원본은 그대로 두고 빈 공간에 새 data를 만들어서 meta 정보로 새 data를 참조한다. 원본 공유 data를 참조하는 놈들이 없으면 원본을 지운다.

여기서 복사(copy)와 복제(clone) 개념을 구분할 필요가 생긴다. 복사는 원본 데이터와 똑같은 데이터를 갖는 새로운 놈을 만드는 것이다. 복제는 새로운 놈을 만들 때 처음엔 원본에 대한 참조 정보만 복사하고 있다가 변경이 필요할 때 원본 데이터를 복사하고 나서 변경시킨다. 이후로는 복제본도 복사본처럼 더이상 원본을 참조할 필요가 없다.

$ cp /etc/fstab ~/test.copy
$ cp --reflink=always ~/test.copy ~/test.clone

위의 첫째 cp는 copy를 수행한 것이고 두번째 cp는 clone을 수행한 것이다. test.clone은 test.copy를 참조하고 있으므로 파일 데이터 공간을 차지하지 않는다. 파일시스템 내부적으로 처리하기 때문에 사용자가 차이를 알기 어려운데, VM 이미지같이 큰 파일을 사용해 보면 복제가 당연히 빠르다고 느낄 수 있다. 하지만, 복제된 VM으로 최초 부팅시엔 VM 파일에 변경이 필요하고 원본 데이터를 복사해야하므로 VM이 느려질 수 밖에 없다. 한번 복사가 이루어진 후에는 VM 복사본이나 복제본이나 별 속도 차이가 없다. VM 파일이나 DBMS도 내부적으로 CoW 방식을 사용하는 놈들도 있다.

Copy-on-Wrtie의 기본 발상은,  대부분의 경우엔 원본을 참조하는 복제본만 있으면 되고, 변경이 필요할 때만 복사하면 된다는 것이다. 즉, 변경이 일어나지 않는 한 매우 빠르고 효율적이지만 변경시에는 느려지고  비효율적이다.

btrfs의 snapshot도 subvolume의 복제본이다. 특정 subvolume에 대한 snapshot을 만들면, 최초엔 snapshot이 subvolume을 참조만 하기 때문에 데이터 공간을 차지하지 않는다. 두 놈이 같은 참조 정보를 저장하고 있다가 subvolume 내의 파일이 변경되면 subvolume은 변경된 데이터를 빈 공간에 새로 저장하고, 그 파일에 대한 meta 정보를 갱신한다. snapshot은 계속 원본을 참조한다. snapshot에는 불필요한 정보가 남아 있는 것이므로 원본 subvolume이 많이 변경될수록 snapshot이 차지하는 공간이 점점 늘어나게 된다. subvolume 내의 어떤 파일을 삭제하면 snapshot으로 인해 원본 파일 데이터 전체를 보존해야 하므로 불필요한 공간이 크게 늘어난다. 뭐 snapshot의 존재 이유가 저장 공간을 희생해서 시스템 복구를 쉽게하자는 것이다.

만약, 이 snapshot을 다른 컴퓨터나 USB 외장 디스크에 backup 한다면 외부 파일시스템에는 참조 정보가 없기 때문에 최초 복사 시에는 모든 원본을 복사해야 한다. 최초의 backup subvolume이 만들어지면 이후로는 이를 참조하여 변경된 정보만으로 incremental backup을 할 수 있게 된다.

Subvolume vs. folder(directory)

btrfs wiki에 따르면, subvolume은 자신의 독립적인 파일/디렉터리 계층(hierarchy)을 갖는 파일시스템의 일부이고, 파일 extents를 공유할 수 있다. snapshot은 최초에 원본 subvolume의 내용을 갖고 있는 subvolume이다.  btrfs 파일시스템은 block-level의 LVM 논리 volume과는 달리 file-extent 기반이다......

SysadminGuide에서, btrfs subvolume은 독립적으로 mount 할 수 있는 POSIX filetree이지만 block device는 아니다.  대부분의 다른 POSIX 파일시스템은 마운트할 수 있는 root가 단 1개인데 비해, btrfs는 top level subvolume을 포함한 각 subvolume이 독립적으로 마운트 할 수 있는 root를 갖는다. btrfs subvolume은 POSIX file namespace라 생각할 수 있다. LVM 논리 volume과 ZFS subvolume과도 다르다......

폴더가 파일들을 담을 수 있는 컨테이너 정도로 이해하는 사람들에게 subvolume이 뭔지 설명하기란 힘들다. 사용자 관점에서 subvolume과 폴더는 사용법도 비슷하고, @를 subvolume 명 앞에 사용하지 않으면  ls 명령으로 아예 구분할 수도 없다. 폴더와 subvolume 모두 하위에 자식 폴더나 자식 subvolume을 만들 수 있고 다른 폴더에 마운트도 할 수 있다.

흠, subvolume은 CoW를 사용하는 특별한 폴더라고 이해하는 게 편할 듯하다. 하지만, CoW 개념도 사실 일반 사용자들이 이해하기 어렵다. 자신을 복제할 수 있는 특별한 폴더 ???

여기서, 특별한 폴더라고 하는 이유는 subvolume을 복사해야 하는 상황에서는 subvolume이 폴더로 바뀌기 때문이다. 가령, subvolume을 cp -R 로 복사하면 복사본은 폴더로 바뀐다. 또한, mv를 사용할 때도 특정 subvolume 내의 자식 subvolume들은 부모 subvolume 내에서는 이동이 되지만, 부모 subvolume 밖의 폴더나 subvolume으로 mv하면 폴더 복사본을 만들고 나서(파일 참조 불가) 자신을 삭제한다. 또한, 부모 subvolume의 snapshot 생성 시에도 자식 subvolume은 snapshot에 빈 폴더로 남게 된다(이 경우도 파일 참조 불가 문제인듯. 다만, snapshot에는 복사본을 사용하는게 의미 없기 때문에 아예 빈 폴더로 두는 듯하다).

subvolume을 폴더 대신 사용 하는 이유는,
  • 특정 subvolume의 backup 및 복구: snapshot을 만들거나, snapshot을 send/receive로 incremental backup 후 문제 발생시 복구
  • 반대로, 특정 subvolume을 backup에서 제외:
    • 가령, ~/.cache 폴더를 subvolume으로 바꾸면 @home의 snapshot 생성시 제외됨
  • 독립적인 애플리케이션 관리: 예를 들어 /var/www 또는 /var/lib/postgresql 등
  • 특별한 속성의 파일들을 분리하거나 mount 시 특별한 속성 부여:
    • chattr +c (compress: 파일 압축) 또는 chattr +C(nodatacow: 파일 생성시 CoW 비적용) 
    • 가령, VM(가상 머신) 파일 저장소나 DB 파일 들은 CoW를 사용하지 않도록 설정
    • 단, mount로 특별한 속성을 부여하려면 반드시 subvolume이 별개의 btrfs device(또는 파티션)에 있어야 함(향후 btrfs 버전에서 개선 예정???)
    • 즉, 동일 btrfs 파일시스템 내의 subvolume은 mount 옵션을 개별적으로 설정해도 첫번째 mount한 subvolume의 옵션만 적용됨(/etc/fstab 사용시에도 동일함)
btrfs 기본 사용법

우분투에서 예전의 btrfs-tools 패키지는 btrfs-progs 패키지로 바뀌었다. btrfs 파일 시스템을 사용하면 자동으로 설치되지만, ext4 등 다른 파일시스템에서 btrfs 파일시스템의 파일들을 사용하려면 따로 설치해야 한다. 이 놈이 있어야 btrfs partition 생성, format, mount 등을 비롯한 btrfs 파일시스템에 대한 모든 것을 처리할 수 있다.

$ sudo apt install btrfs-progs

일부 btrfs 명령을 일반 사용자가 사용할 수는 있지만 매우 제한적이고 대부분은 root 권한으로 사용해야 한다. 또, btrfs 명령들은 중복되지 않는 한 줄여서 사용할 수 있다.

하나 이상의 전체 btrfs 파일시스템(또는 디바이스)을 보고 싶다면 아래 명령을 사용한다(fi = filesystem). 이를 제외한 나머지 명령들은 btrfs 파일시스템 1개에 대한 명령임에 유의해야 한다.

$ sudo btrfs fi show

특정 btrfs 파일시스템의 subvolume 목록을 조회하려면 다음 명령과 같이 맨 끝에 마운트 위치를 알려 주어야 한다.

$ sudo btrfs subvolume list /

만약, 여러 개의 btrfs 파일시스템을 사용하고 있다면(예를 들어, /dev/sda3가 별개의 btrfs 파티션이고 /opt에 마운트돼 있다면),

$ sudo btrfs sub l /opt

와 같이 해야만 /dev/sda3의 subvolume 목록을 알 수 있다. 즉, 한번에 여러 btrfs 파일시스템 전체의 subvolume 목록을 조회할 수는 없고 각각의 btrfs 파일시스템에 대해 1개씩 조회해야 한다.

디스크 공간 관리

$ sudo btrfs fi usage /

이 명령으로 확인한 Free 공간은 df -h 명령으로 확인한 available 공간과 일치한다(Unallocated 용량이 합산됨). 하지만 CoW 파일시스템 특성상 Free (estimated) 공간이 실제 사용할 수 있는 공간에 더 가까울 수 있다. Unallocated 용량은 btrfs 파일시스템으로 파티션을 format 하더라도 전체 파티션을 모두 할당해서 사용하지는 않고 있다가 저장 공간이 부족하면 자동으로 할당한다. 또한, btrfs 파티션은 online으로(시스템 운영 중에) resize할 수 있는데 Unallocated 용량이 10GiB라면 바람직하진 않지만 아래와 같이 파티션 크기를 최대한 줄일 수 있다. gparted 앱을 사용해도 online resize가 된다.

$ sudo btrfs fi resize -10G /
$ sudo btrfs fi usage /

다시 최대 크기로 파티션을 늘리려면,

$ sudo btrfs fi resize max /

이외에도 아래의 명령들을 사용할 수 있다.

$ sudo btrfs fi df -H /

$ sudo mount /dev/sda2 /mnt
$ sudo btrfs fi du -s --iec /mnt/@home

defragment -r 명령은 하위 폴더를 포함해서(recursively) defragment를 수행하는데 subvolume과 mount point는 건너 띄도록 바뀐듯 하다. snapshot이나 reflink 파일에 적용하면 복제본 파일들이 복사본으로 바뀌어서 디스크 용량이 늘어나는 문제가 생긴다. 성능 감소가 대단하지 않기 때문에 defragment를 굳이 사용할 필요없다는 이들도 있다.

$ sudo btrfs fi defagment -r /no-cow/VM

subvolume 관리

subvolume의 생성(create), 복제(snapshot), 조회(list/show), 삭제(delete)를 위해 아래와 같이 btrfs subvolume 명령을 사용한다. 참고로, subvolume을 cp -R 명령으로 복사하면 앞서 설명했듯이 폴더로 바뀐다. 즉, btrfs 파일시스템이 아닌 다른 파일시스템을 어떤 폴더에 mount 해서 subvolume을 복사하려면 일반 폴더처럼 cp -R 명령을 사용하면 된다.

$ cd ~/
$ btrfs subvolume create @test
$ cp /etc/fstab @test/
$ ls @test/
$ btrfs sub snap @test @test-snapshot
$ sudo btrfs sub l /
$ sudo btrfs sub show @test
$ sudo btrfs sub sh @test-snapshot
$ sudo btrfs sub delete -c @test
$ mv @test-snapshot @test
$ sudo btrfs sub l /
$ sudo btrfs sub del -c @test

위에서 del -c 옵션은 subvolume을 지우고 즉시 commit을 수행하라는 것이다.

다른 btrfs 파일시스템으로 subvolume을 복제하려면 나중에 설명하겠지만 btrfs send/receive를 사용해야 한다. 가령, /dev/sda2가 root btrfs 파티션이고, /dev/sda3가 별개의 btrfs 파티션으로 /opt에 마운트 되어 있다면 아래와 같이 snapshot을 생성할 수는 없다.

$ cd /opt
$ sudo btrfs sub snap /mnt/@home ./@home-snapshot

USB 외장 디스크의 btrfs 파티션을 마운트해서 사용하는 경우를 생각해 볼 수 있다.

btrfs Incremental 백업 및 복구

하나의 btrfs 파일시스템 내에서는 snapshot 만으로도 local backup이 되고, snapshot은 원본 subvolume이 많이 변경되지 않는 한 디스크 용량도 많이 차지하지는 않는다. 백업해야 할 사용자 데이터가 많다면 외장 디스크나 다른 컴퓨터의 디스크에 백업해야 하는데 btrfs incremental 백업은 btrfs 파일시스템 간에만 가능하다. 즉, 백업 받는 쪽의 디스크도 btrfs 파일시스템이어야 한다.

여기서는 btrfs root 시스템이 /dev/sda2에 설치되어 있을 때 외장 디스크의 btrfs 파일시스템이 /dev/sdb3 파티션이라고 가정하고, @home subvolume의 snapshot을 백업했다가 복구하는 예를 들었다. 외장 디스크가 아니고 원격 컴퓨터의 btrfs 파일시스템을 사용하려면 ssh를 사용하면 된다.

1. 원본 subvolume 마운트

$ sudo mount /dev/sda2 /mnt
$ ls -CF /mnt
@/  @home/

2. 외장 디스크 btrfs 파티션 마운트

$ sudo mkdir /back
$ sudo mount /dev/sdb3 /back
$ sudo mkdir /back/backups

3. 최초 snapshot 생성 및 백업

$ sudo btrfs sub snap -r @home @home20190101
$ sudo sync

여기서 주의할 점은, btrfs send/recive 명령을 사용하기 위해 snapshot 생성시 read-only(-r) 옵션을 사용해야 한다. 그리고, 이 -r 옵션이 recursively라고 착각하는 이들도 더러 있는데, ZFS와는 달리 btrfs snapshot은 원본 subvolume에 자식 subvolume이 있으면 빈 폴더로 바꿔서 snapshot이 생성된다. 앞서 설명했듯이 btrfs에서 자식 subvolume을 만드는 이유는 ~/.cache 폴더와 같이 불필요한 특정 폴더를 subvolume으로 대체함으로써 snapshot이나 백업에서 제외하기 위해서이다.

아래와 같이 writable snapshot을 생성했을 경우 btrfs property 명령으로 read-only snapshot으로 만들 수도 있다.

$ sudo btrfs sub snap @home @home20190101
$ sudo btrfs property set -ts /mnt/@home20190101 ro true
$ sudo sync

위에서 true대신 false 옵션을 사용하면 read-only snapshot을 다시 writable snapshot으로 설정한다.

$ sudo btrfs send /mnt/@home20190101 | sudo btrfs receive /back/backups

위의 명령과 같이 최초 백업시에는 외장 디스크에 참조할 데이터가 없기 때문에 btrfs send시 아무 옵션이 없다. 외장디스크의 /backups 폴더에 @home20190101 subvolume이 생성된다.

4. Increametal 백업

이제 다음날 @home 파일들이 변경돼서 백업을 해야 한다면, 아래와 같이 새로운 snapshot을 만들어서 incremental backup을 할 수 있게 된다.

$ sudo btrfs sub snap -r @home @home20190102
$ sudo sync
$ ls -CF /mnt
@/  @home/  @home20190101/  @home20190102/

$ sudo btrfs send -p /mnt/@home20190101 /mnt/@home20190102 | sudo btrfs receive /back/backups

위의 -p <parent subvolume> 옵션은 incremental 백업시 참조할 부모 subvolume이다. -p 옵션을 사용하면  receive(백업) 쪽에서 부모 subvolume의 snapshot을 만들고 나서 변경된 부분을 백업 @home20190102 subvolume에 반영한다. 대부분의 경우에 -p 옵션만으로 충분하다.

참고로 -c <clone source subvolume>을 여러 개 사용할 수도 있는데 원본 subvolume을 여러개 참조해서 백업 subvolume을 생성하라는 의미이다. -c 옵션은 snapshot을 생성하지 않고 빈 백업 subvolume을 먼저 만들고 나서 백업 쪽에 clone source subvolume에 참조할 데이터가 있을 경우 그 들을 참조해서 백업 subvolume이 만들어지도록 한다.

$ ls -CF /back/backups
@home20190101/  @home20190102/

5. 외장 디스크의 백업 snapshot에서 root btrfs 파일시스템의 @home 복구

최신 데이터 백업이 됐기 때문에 아래와 같이 불필요한 snapshot들을 제거해도 된다.

$ sudo btrfs sub del -c /mnt/@home20190101
$ sudo btrfs sub del -c /mnt/@home20190102
$ sudo btrfs sub del -c /back/backups/@home20190101

$ ls -CF /mnt
@/  @home/

$ ls -CF /back/backups
@home20190102/

이제는 복구할 것이므로 백업 쪽의 snapshot을 send 한다. @home을 대체할 것이므로 writable snapshot으로 만들어야 한다.

$ sudo btrfs send /back/backups/@home20190102 | sudo btrfs receive /mnt
$ sudo btrfs property set -ts /mnt/@home20190102 ro false

$ ls -CF /mnt
@/  @home/  @home20190102/

이제는 local snapshot을 복구하는 것과 똑같이 하면 된다.

$ sudo mv @home @home-problem
$ sudo mv @home20190102 @home
$ sudo reboot

안전한 Checksum 검사 및 복구

btrfs scrub은 디스크의 모든 data를 읽어서 checksum을 다시 계산해서 기존 값과 비교함으로써 meta data를 포함한 data(file 단위는 아님)가 깨졌는지 알려주고 가능한 경우 복구한다. Background 프로세스로 돌기 때문에 상태를 확인하려면 아래와 같이 하면 된다. RAID를 구성한 경우에는 최소한 한달에 한번 주기적으로 scrub을 돌리는 게 좋단다.

$ sudo btrfs scrub start /
$ sudo btrfs scrub status

아예 root btrfs 파일시스템을 마운트 할 수 없을 경우에는 우분투 설치 iso로 부팅해서,

$ sudo btrfs scrub /dev/sda2

scrub으로 복구가 안될 경우엔 아래 명령으로 B-tree를 복구해 본다. 

$ sudo mount -o usebackuproot /dev/sda2 /mnt

위의 과정은 모두 안전한 복구 방법인데, 디스크 문제로 파일이 깨졌을 때 사용할 수 있는 방법은 아니다. 이것으로 복구가 안되면 안전하지 않은 btrfs check/restore/rescue 등의 명령을 사용해야 한단다.

댓글 없음:

댓글 쓰기