Convert a ZFS mirror to raidz1 using a virtual device
It is not possible to convert a zfs vdev’s type (e.g. raidz1 to raidz2). However, with a bit of trickery, we can convert a pool with one mirrored vdev into a pool with one raidz1 vdev, without having an extra hard drive on hand.
Summary
We’re going to create a zfs raidz1 vdev with a virtual device. This will allow us to create the raidz1 vdev with 1 fewer physical hard drive than we really have.
In a concrete example, let’s say I have a current pool with one mirrored vdev:
zpool old-pool
vdev mirror-1
/dev/old_hdd_a
/dev/old_hdd_b
We’ll remove a device from that mirror:
zpool old-pool
vdev device-1
/dev/old_hdd_a
We’ll use the removed old_hdd_b
, along with my newly purchased new_hdd_a
and a virtual device /dev/loop1
to create a new pool:
zpool old-pool
vdev device-1
/dev/old_hdd_a
zpool new-pool
vdev raidz-1
/dev/old_hdd_b
/dev/new_hdd_a
/dev/loop1
We can then copy all our data over to the new-pool
, destroy the old-pool
, and use the old_hdd_a
to replace the virtual device /dev/loop1
.
How-to
WARNING: During this process, you will have no redundancy. ANY DISK FAILURE WILL RESULT IN LOSING ALL OF YOUR DATA FROM YOUR ENTIRE ZFS POOL. Additionally, messing up a command might result in losing ALL YOUR DATA. You should always have backups. Do not use this process for a business-critical pool. I’m just a home user with a bunch of linux isos and home backups, so I can afford to risk data loss in exchange for buying one less hard drive.
Ok, with that out of the way, let’s get on to it. This info is cobbled together from a few forum posts around the web, and this blog post on oracle.com (no longer available, except on the Wayback Machine).
Baseline zfs setup
This guide will use device names assuming your initial pool looks like this in zpool status -v
:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
old_hdd_a ONLINE 0 0 0
old_hdd_b ONLINE 0 0 0
Detach one of your mirrored devices
> sudo zpool offline tank old_hdd_b
> sudo zpool detach tank old_hdd_b
Now, zpool status -v
shows:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
old_hdd_a ONLINE 0 0 0
Create a sparse file and mount it
Create the sparse file. This file will look like it is the size you specify, but will take up 0 space on your disk:
> dd if=/dev/zero of=disk1.img bs=1 count=0 seek=10T # Use the size of your largest disk for the `seek=` argument
Mount it as a loopback device:
> sudo losetup -f ./disk1.img
Check the device was mounted, and get its path:
> losetup -a # Check in the output of this command to verify the disk was mounted
/dev/loop6: []: (/home/marckhouri/disk1.img)
Create the new pool and offline the virtual device
Use the loopback device path from the last step to create a new pool.
> sudo zpool create tankier raidz /dev/loop6 /dev/disk/by-id/old_hdd_b /dev/disk/by-id/new_hdd_a
Immediately take the virtual device offline so we don’t write to it
> sudo zpool offline tankier /dev/loop6
Now, zpool status -v
shows:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
old_hdd_a ONLINE 0 0 0
NAME STATE READ WRITE CKSUM
tankier DEGRADED 0 0 0
raidz1-0 DEGRADED 0 0 0
loop6 OFFLINE 0 0 0
old_hdd_b ONLINE 0 0 0
new_hdd_a ONLINE 0 0 0
Transfer your data
> sudo zfs snapshot tank@20200710
> sudo zfs send -Rv tank@20200710 | sudo zfs recv -vsF tankier
I saw ~350MBPS transfer speeds for this process. Use -s
on the recv
command to allow for session resumption in case the transfer is interrupted
Destroy the old pool and replace the virtual device with the old device
TODO
Conclusion
That’s it! Using a sparse file as a loopback device in your zfs pool is definitely a bad idea if you care about your data, but it can be helpful if you’re a home user who cares enough about saving $150 (or physically can’t fit another drive in your chassis 😬).
Addendum: full output of all commands as I went through this process
My setup wasn’t exactly as described above, these are my details:
- I had 3x8TB in raidz1 and 2x10TB in mirror (~26TB usable).
- I bought 2 more 10TB drives.
- I setup a second pool with a 4x10TB raidz1 vdev made up of one of the 10TB drives from my mirror, the 2 new 10TB drives, and a virtual drive (~30TB usable).
- My original pool new had 3x8TB in raidz1 and 1x10TB with no redundancy (still ~26TB usable).
- I transferred all my data from the old pool to the new pool
- I moved all the devices from my old pool to the new pool, so I ended with 3x8TB in raidz1 and 4x10TB in raidz1 (~46TB usable)
Here’s a step by step log showing lots of output:
marckhouri@mars:~$ zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 19.5T 3.32T 19.3T /tank
tank@20200426 141G - 15.6T -
tank@20200609 26.5G - 18.0T -
marckhouri@mars:~$ zpool status -v
pool: tank
state: ONLINE
scan: resilvered 80K in 0h0m with 0 errors on Fri Jul 10 03:28:20 2020
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HK2UN3N ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HJX81NF ONLINE 0 0 0
ata-WDC_WD80EZAZ-11TDBA0_2YJMUYUD ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_2YJGKGGD ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN ONLINE 0 0 0
cache
nvme-HP_SSD_EX900_120GB_HBSE18433200255 ONLINE 0 0 0
marckhouri@mars:~$ sudo zpool offline tank ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN
marckhouri@mars:~$ zpool status
pool: tank
state: DEGRADED
status: One or more devices has been taken offline by the administrator.
Sufficient replicas exist for the pool to continue functioning in a
degraded state.
action: Online the device using 'zpool online' or replace the device with
'zpool replace'.
scan: resilvered 80K in 0h0m with 0 errors on Fri Jul 10 03:28:20 2020
config:
NAME STATE READ WRITE CKSUM
tank DEGRADED 0 0 0
raidz1-0 ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HK2UN3N ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HJX81NF ONLINE 0 0 0
ata-WDC_WD80EZAZ-11TDBA0_2YJMUYUD ONLINE 0 0 0
mirror-1 DEGRADED 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_2YJGKGGD ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN OFFLINE 0 0 0
cache
nvme-HP_SSD_EX900_120GB_HBSE18433200255 ONLINE 0 0 0
errors: No known data errors
marckhouri@mars:~$ sudo zpool detach tank ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN
marckhouri@mars:~$ zpool status
pool: tank
state: ONLINE
scan: resilvered 80K in 0h0m with 0 errors on Fri Jul 10 03:28:20 2020
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HK2UN3N ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HJX81NF ONLINE 0 0 0
ata-WDC_WD80EZAZ-11TDBA0_2YJMUYUD ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_2YJGKGGD ONLINE 0 0 0
cache
nvme-HP_SSD_EX900_120GB_HBSE18433200255 ONLINE 0 0 0
errors: No known data errors
marckhouri@mars:~$ dd if=/dev/zero of=disk1.img bs=1 count=0 seek=10T
0+0 records in
0+0 records out
0 bytes copied, 0.000302862 s, 0.0 kB/s
marckhouri@mars:~$ losetup -a
/dev/loop1: []: (/var/lib/snapd/snaps/core18_1754.snap)
/dev/loop4: []: (/var/lib/snapd/snaps/core_9289.snap)
/dev/loop2: []: (/var/lib/snapd/snaps/core18_1705.snap)
/dev/loop0: []: (/var/lib/snapd/snaps/canonical-livepatch_94.snap)
/dev/loop5: []: (/var/lib/snapd/snaps/canonical-livepatch_95.snap)
/dev/loop3: []: (/var/lib/snapd/snaps/core_9436.snap)
marckhouri@mars:~$ sudo losetup -f ./disk1.img
marckhouri@mars:~$ losetup -a
/dev/loop1: []: (/var/lib/snapd/snaps/core18_1754.snap)
/dev/loop6: []: (/home/marckhouri/disk1.img)
/dev/loop4: []: (/var/lib/snapd/snaps/core_9289.snap)
/dev/loop2: []: (/var/lib/snapd/snaps/core18_1705.snap)
/dev/loop0: []: (/var/lib/snapd/snaps/canonical-livepatch_94.snap)
/dev/loop5: []: (/var/lib/snapd/snaps/canonical-livepatch_95.snap)
/dev/loop3: []: (/var/lib/snapd/snaps/core_9436.snap)
marckhouri@mars:~$ sudo zpool create tankier raidz /dev/loop6 /dev/disk/by-id/ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN /dev/disk/by-id/ata-WDC_WD100EMAZ-00WJTA0_JEK4EW0N /dev/disk/by-id/ata-WDC_WD100EMAZ-00WJTA0_JEK5W8XN
marckhouri@mars:~$ zpool status -v
pool: tank
state: ONLINE
scan: resilvered 80K in 0h0m with 0 errors on Fri Jul 10 03:28:20 2020
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HK2UN3N ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HJX81NF ONLINE 0 0 0
ata-WDC_WD80EZAZ-11TDBA0_2YJMUYUD ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_2YJGKGGD ONLINE 0 0 0
cache
nvme-HP_SSD_EX900_120GB_HBSE18433200255 ONLINE 0 0 0
errors: No known data errors
pool: tankier
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
tankier ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
loop6 ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEK4EW0N ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEK5W8XN ONLINE 0 0 0
errors: No known data errors
marckhouri@mars:~$ sudo zpool offline tankier /dev/loop6
marckhouri@mars:~$ zpool status -v
pool: tank
state: ONLINE
scan: resilvered 80K in 0h0m with 0 errors on Fri Jul 10 03:28:20 2020
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HK2UN3N ONLINE 0 0 0
ata-WDC_WD80EMAZ-00WJTA0_7HJX81NF ONLINE 0 0 0
ata-WDC_WD80EZAZ-11TDBA0_2YJMUYUD ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_2YJGKGGD ONLINE 0 0 0
cache
nvme-HP_SSD_EX900_120GB_HBSE18433200255 ONLINE 0 0 0
errors: No known data errors
pool: tankier
state: DEGRADED
status: One or more devices has been taken offline by the administrator.
Sufficient replicas exist for the pool to continue functioning in a
degraded state.
action: Online the device using 'zpool online' or replace the device with
'zpool replace'.
scan: none requested
config:
NAME STATE READ WRITE CKSUM
tankier DEGRADED 0 0 0
raidz1-0 DEGRADED 0 0 0
loop6 OFFLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEGVWWZN ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEK4EW0N ONLINE 0 0 0
ata-WDC_WD100EMAZ-00WJTA0_JEK5W8XN ONLINE 0 0 0
marckhouri@mars:~$ zfs list
NAME USED AVAIL REFER MOUNTPOINT
tank 19.5T 3.32T 19.3T /tank
tank@20200426 141G - 15.6T -
tank@20200609 26.5G - 18.0T -
tankier 453K 25.5T 140K /tankier
marckhouri@mars:~$ sudo zfs send -Rv tank@20200710 | sudo zfs recv -vsF tankier
full send of tank@20200426 estimated size is 15.6T
send from @20200426 to tank@20200609 estimated size is 2.52T
send from @20200609 to tank@20200710 estimated size is 1.38T
total estimated size is 19.5T
TIME SENT SNAPSHOT
receiving full stream of tank@20200426 into tankier@20200426
06:35:04 205M tank@20200426
06:35:05 544M tank@20200426
06:35:06 881M tank@20200426
06:35:07 1.19G tank@20200426
06:35:08 1.50G tank@20200426
06:35:09 1.77G tank@20200426
[...] trimmed
# TODO: add the destroy and migration