Przenosiny z ZFS na bcache+(mdadm+btrfs)
Tym razem dokonuję przenosin z macierzy mirror ZFS na stack składający się z macierzy RAID1 zbudowanej za pomocą mdadm, z jedną partycją sformatowaną systemem plików btrfs. Będzie hardkorowo, bez obrazków – tylko polecenia w bashu 🙂
Kluczowe punkty związane z tą decyzją:
- Łatwa możliwość migracji z obecnego RAID1 na RAID5 w przyszłości
- Lepsza integracja w środowisku Linux (normalne mount/umount i wpis w /etc/fstab. Po roku używania wciąż nie wiem kiedy używa się polecenia zfs a kiedy zpool)
- btrfs oferuje wszystkie cechy które były dla mnie atrakcyjne w ZFS: CoW, subvols i kompresja. Snapshoty też są, ale prawie ich nie używam.
- Ostatnio w formie eksperymentu „dokleiłem” dysk NVMe jako special device do mojej puli nie zdając sobie sprawy z konsekwencji i nieodwracalności tego procesu, więc i tak musiałem zaorać obecną pulę.
Moja macierz składa się z dwóch dysków 18TB w konfiguracji mirror, na której znajduje się 10,2TB danych. Aby dokonać manewru o którym piszę i przenieść się z ZFS na BTRFS, potrzebowałem gdzieś te dane tymczasowo przetrzymać. Do tego celu posłużyły mi dwa dyski 8TB na których skonfigurowałem macierz RAID0 oraz sformatowałem je w systemie plików EXT4. Poniżej będę dokumentował krok po kroku proces przenosin.
Plan
- Stworzenie macierzy RAID0 (tymczasowy stripe) na dwóch dyskach 8TB po to aby tymczasowo przetrzymać dane.
- Istotne aby dyski których użyjemy byly w dobrym stanie.
- Przełączenie ZFS (stary mirror) w tryb readonly.
- Kopiowanie danych ze starego mirrora na tymczasowy stripe.
- Odmontowanie / export puli ZFS i zamontowanie tymczasowego stripe w miejscu dotychczasowej puli.
- Wyczyszczenie dysków stanowiących dotychczas pulę ZFS oraz postawienie RAID1 (nowy mirror).
- Przełączenie tymczasowego stripe w tryb readonly.
- Kopiowanie danych z tymczasowego stripe na nowy mirror.
- Zamontowanie nowego mirrora w miejscu tymczasowego stripe.
Stworzenie macierzy tymczasowej na przetrzymanie danych
wipefs -a /dev/sdc /dev/sdd
mdadm --create /dev/md0 --level=0 --raid-devices=2 /dev/sdc /dev/sdd
mkfs.ext4 -F /dev/md0
Raczej nie wymaga wyjaśnień: wyczyszczenie systemów plików, stworzenie programowej macierzy RAID0, sformatowanie macierzy w systemie plików EXT4.
Przygotowanie do migracji danych
zfs set readonly=on old_pool
(reboot)
mkdir /mnt/temp_pool
mount /dev/md0 /mnt/temp_pool
Przestawiam macierz w tryb readonly po to, aby po przeniesieniu danych na nową macierz mieć kopię 1:1 danych. Gdybym tego nie zrobił, dane zmieniałyby się w trakcie. Restart wykonuję po to, aby wszystkie usługi „pogodziły się z faktem”, że są montowane na zasobie działającym w trybie readonly. Nastepnie tworzę mountpoint i montuję zasób.
Przenoszenie danych
tmux
sudo rsync -aHAX --progress --info=progress2 /old_pool/ /mnt/temp_pool/
Odpalam tmux aby proces cały czas działał w tle. Opis poszczególnych parametrów rsync:
- -a – tryb archiwizacji (zachowuje uprawnienia, symlinki, daty modyfikacji itp.).
- -H – zachowuje twarde linki.
- -A – zachowuje listy ACL.
- -X – zachowuje rozszerzone atrybuty.
- –progress – wyświetla postęp kopiowania.
- –info=progress2 – pokazuje bardziej szczegółowe informacje o postępie kopiowania całkowitego.
- …ale nie działa prawidłowo przy dużej liczbie plików i pokazuje głównie głupoty 🙂
Po wykonaniu powyższego polecenia trzeba się uzbroić w cierpliwość. U mnie 10,2TB kopiowało sie 20,5 godziny, co daje średni transfer na poziomie 145MB/s.
Weryfikacja czy wszystkie dane są na miejscu
Tutaj warto spędzić chwilę i porównać zawartość danych w /old_pool/ oraz w /mnt/temp_pool/
Użyłem do tego celu programu ncdu który potrafi ładnie wizualizować zajętość miejsca w poszczególnych katalogach. Tutaj jako ciekawostkę podam, że mój zestaw danych zajmował 200GB miejsca więcej na ext4 niż na ZFS z aktywną kompresją. Jednak coś ta kompresja daje 🙂
Kilka kluczowych aspektów do sprawdzenia:
- Czy skopiowały się ukryte pliki?
- Czy linki symbolicznie przeniosły sie poprawnie?
- Czy timestampy są prawidłowe?
- Czy poszczególne katalogi zajmują tyle miejsca co powinny?
Wyłączam montowanie puli
sudo systemctl mask zfs-mount
sudo zfs set canmount=off old_pool
(reboot)
sudo zfs export old_pool
Powyższymi poleceniami upewniam się, że pula zfs przestaje być widoczna w systemie i mogę spokojnie zamontować tymczasowego stripe’a w miejsce dotychczasowej puli.
Podmiana puli ZFS na tymczasowy stripe
Dodaję wpis do /etc/fstab:
/dev/md0 /old_pool ext4 defaults,noatime 0 1
Nastepnie restartuję ponownie serwer aby wszystkie usługi mogły załadować się poprawnie z danymi na innej macierzy.
Sprawdzenie czy wszystkie usługi działają poprawnie na tymczasowym stripe
Sprawdziłem nastepujące rzeczy:
- Montowanie 'bind’ w dockerze
- smb
- nfs
- KVM/QEMU i czy maszyny wirtualne, które są na tych dyskach poprawnie się włączają
Point of no return
W tym momencie powinniśmy mieć kopię danych 1:1 na obu macierzach: starej i tymczasowej. Jest to punkt z którego można jeszcze zawrócić i przywrócić konfigurację w której dane będą dostepne na starym mirrorze. Po przejściu dalej ta możliwośc zostanie utracona.
Czyszczenie dysków ze starego mirrora
sudo wipefs -a /dev/sda
sudo wipefs -a /dev/sdb
sudo wipefs -a /dev/nvme0n1
sudo dd if=/dev/zero of=/dev/sda bs=1M count=100
sudo dd if=/dev/zero of=/dev/sdb bs=1M count=100
sudo dd if=/dev/zero of=/dev/nvme0n1 bs=1M count=100
sda i sdb były mirrorem, nvme0n1 to był special device.
Aktualizacja 28.02.2025
Z moich testów wychodzi, że bcache ma duże problemy z wydajnością, gdy caching device jest ustawiony na całe urządzenie blokowe, dlatego też tam gdzie używałem wcześniej w tym wpisie „nvme0n1”, teraz widnieje „nvme0n1p1”. Używam jednej partycji na cały dysk.
Tworzenie macierzy i systemu plików
sudo mdadm --create --verbose /dev/md1 --level=1 --raid-devices=2 /dev/sda /dev/sdb
sudo make-bcache -B /dev/md1 -C /dev/nvme0n1p1
sudo mkfs.btrfs /dev/bcache0
Skonfigurowane w ten sposób, nvme0n1p1 będzie stanowiło „cache device”, a sda i sdb stworzą md0 które będzie stanowiło „backing device”. Od tego momentu, wszystkie trzy dyski będą dostepne jako block-device pod ścieżką /dev/bcache0 (dlatego system plików tworzymy na /dev/bcache0)
Tuning bcache
W pliku /etc/tmpfiles.d/bcache.conf wpisujemy następujące parametry:
w /sys/block/bcache0/bcache/cache_mode - - - - writeback
w /sys/block/bcache0/bcache/writeback_percent - - - - 40
w /sys/block/bcache0/bcache/sequential_cutoff - - - - 0
w /sys/block/bcache0/bcache/cache/congested_write_threshold_us - - - - 0
w /sys/block/bcache0/bcache/cache/congested_read_threshold_us - - - - 0
Poszczególne parametry oznaczają nastepujące rzeczy:
- cache_mode: domyślnie aktywny jest tryb writethrough. Mi zależy na przyspieszeniu zapisów oraz mam UPS, dlatego włączam writeback
- writeback_percent: domyślnie 10. Oznacza ile procent miejsca przeznaczone jest na zapisy. Niestety 40% to sztywny limit bcache, który żeby podnieść, trzeba przebudować kernel. Na moim dysku SSD 512GB będzie to około 190GB. Chciałbym móc wykorzystać cały dysk jako cache, ale na poczatek 190GB wystarczy.
- writeback_delay: 1 oznacza opóźnienie zapisu do HDD o 1s. Powinno zoptymalizować zapisy.
- sequential_cutoff: domyślnie cache’owane są pliki o maksymalnej wielkości 4MB. Ja chcę cache’ować zapisy po 100GB, więc wyłączam ten limit.
- congested_write_threshold_us: ustawienie na 0 powoduje wyłączenie throttlingu odczytów z cache device. W efekcie prefereowane są odczyty z NVMe.
- congested_read_threshold_us: ustawienie na 0 powoduje wyłączenie throttlingu zapisów na cache device. W efekcie prefereowane są zapisy na NVMe. Bez tego write cache praktycznie nie działał.
Aby natychmiast zaaplikować zmiany wprowadzone w /etc/tmpfiles.d/bcache.conf należy wykonać polecenie:
sudo systemd-tmpfiles --create
Przełączenie tymczasowego stripe w tryb readonly
Zaczynamy powtarzać procedurę z przerzucaniem danych. Modyfikujemy wcześniejszy wpis w /etc/fstab:
/dev/md0 /old_pool ext4 ro,noatime 0 1
Restart komputera i sprawdzenie czy wszystko poprawnie przeszło w tryb readonly.
Zamontowanie w tymczasowym miejscu
tmux
sudo mount -o compress=zstd:3 /dev/bcache0 /mnt/temp_pool
sudo rsync -aHAX --progress --info=progress2 /old_pool/ /mnt/temp_pool/
Montuję nowy mirror w tymczasowym miejscu i uruchamiam kopiowanie z tymczasowego stripe’a na nowego mirrora. Znów doba czekania…
Podmiana mountpoint
Wcześniejszy wpis w fstab podmieniamy na nastepujący:
/dev/bcache0 /old_pool btrfs defaults,noatime,compress=zstd:3,discard=async 0 1
Restartuję komputer i cała operacja zostaje zakończona.
Profit
Wydajność momentami kuleje, ale testowałem również z niekompresowalnymi danymi i byłem w stanie zapisać 122GB na NASie bez żadnego spowolnienia (czas trwania zapisu: 1m52s), caly czas saturując łącze 10Gbit (czas trwania zapisu: 1m52s).