Feep! » Blog » Post

In-place conversion to BTRFS

Because Feep! Search runs on a server with a fairly small hard disk (and also to making huge copies all the time), I need a way to share files between my production and development environments, ideally without causing dev changes to accidentally mangle production data. I’m currently using hardlinks to read-only files, which is a strategy that can sort of emulate copy-on-write data but has some annoying limitations: in particular, it only works correctly for files that are deleted and replaced, so any files that are updated in place (like SQLite databases) had to be treated specially. To simplify this, I decided to switch out the underlying filesystem for one that has better support for the kind of things I want to do.

Choosing a new filesystem

I decided I needed a filesystem that would:

This got the list down to BTRFS or XFS; in the end, I went with BTRFS for two reasons:

Another option I considered briefly was to take snapshots at the LVM (Linux Volume Manager) layer. I mostly decided not to do this because it would require managing mountpoints; BTRFS subvolumes use regular UNIX file permissions and don’t need root permissions to modify. I would also likely end up overprovisioning disk space, and I don’t know how it works to run out of physical space on the drive when the filesystem still thinks it has room. Finally, I already have tooling for post-hoc deduplication when two versions of a file turn out to be identical, and dealing with block-level deduplication using LVM-VDO seemed awkward. I didn’t dig into this too deeply though, and it might be a perfectly reasonable solution to my problem.

Converting to BTRFS

Now that I had a filesystem selected to migrate to, it was time to start running commands and hoping that none of them corrupted my data.

I poked around and determined that the existing filesystem was mounted from /dev/delta-vg/root. Consulting the LVM howto I gather that delta-vg is the name of the volume group (which in my case only has one physical volume—a 1TB hard disk), and root is the name of a logical volume in that group. This drive started its life as the root filesystem for a host named delta, so this all makes sense. Poking around with sudo lvs delta-vg reveals I also have another logical volume named swap_1 which looks like it was the swapfile for that system; I’ll clean that up later so I can use the entire drive.

While poking around my LVM setup, I also discovered my current root partition is on a 500GB SSD of which only ~250GB is allocated. Apparently this is by design: the Ubuntu installer’s “use full disk” setting only uses half the disk, even if you’re only using LVM for encryption, not snapshots. This is particularly annoying because I regularly run out of /tmp space, but thought I’d just misremembered what size drive I’d purchased.

Preparing

Rewriting filesystems in-place is always a slightly nerve-wracking process. Fortunately for me all of the data on this drive is recoverable (though it’d be annoying) if it gets lost somehow. Still, I prepared beforehand by taking checksums of all of my files (so I could at least know if anything had gotten corrupted) and also making a snapshot of the filesystem so if it went disastrously wrong I could probably roll back:

sudo find /media/fe2o3 -type f -print0 | sudo xargs -0 sha512sum > fe2o3.sha512sum
sudo umount /media/fe2o3
sudo lvcreate -L 900G -s -n snap_20250714 delta-vg/root

I have to admit I just copied this from a tutorial so I’m not entirely sure what the -L is for; in hindsight I think it ought to have been -L 100%ORIGIN.

This snapshot shares all of the underlying data, and btrfs-convert says it keeps the original filesystem’s metadata as well (in a subvolume called ext2_saved), so this should be very cheap to keep around while testing that the new filesystem is working OK. I could also have mounted the snapshot readonly to keep the data available, but I didn’t need that and so didn’t bother. (I bet if you were really clever you could do something with overlayfs and bind mounts and make this a zero-downtime migration, but that seems messy.)

Conversion

Now that I had something resembling a rollback plan, I consulted the btrfs-convert manpage and kicked off the conversion:

sudo e2fsck -fvy /dev/delta-vg/root
sudo btrfs-convert  /dev/delta-vg/root

This took a couple of hours (I didn't monitor it closely, but I gather it computes checksums for every file and so has to read the entire disk) and reported success:

wolf@delta:~$ sudo btrfs-convert  /dev/delta-vg/root
[sudo] password for wolf:
create btrfs filesystem:
        blocksize: 4096
        nodesize:  16384
        features:  extref, skinny-metadata (default)
        checksum:  crc32c
creating ext2 image file
creating btrfs metadata
copy inodes [o] [     28982/     12198]
conversion complete

After that I remounted the filesystem and ran sudo sha512sum -c --quiet fe2o3.sha512sum to make sure all my data was still where I expected it to be; that took a bit over three hours and reported no problems.

As mentioned above, I had also noticed an old swap partition that I didn’t need any more, so I also cleared that up, removed the snapshot now that it looked like my data was OK, and gave my root partition the rest of the disk:

sudo lvremove delta-vg/snap_20250714
sudo lvremove delta-vg/swap_1
sudo lvextend -l +100%FREE /dev/delta-vg/root
sudo btrfs filesystem resize max /media/fe2o3

Subvolumes and snapshots

Now that I’m switched over to btrfs, I can set up a subvolume and copy the files into it (this is very quick because it just has to populate the metadata; the data is still shared by reflink):

mv /media/fe2o3/feepsearch/prod{,.old}
btrfs subvolume create /media/fe2o3/feepsearch/prod
cp --recursive --preserve=all --reflink=always /media/fe2o3/feepsearch/{prod.old,prod}

Finally, I switched from cp -r --link to btrfs subvolume snapshot and moved some SQLite databases into the partition, and now I can easily copy the production data into my development environment.