/proc/mounts polluted by nearly 50k zfs snapshot mounts

Hi,
I have no idea how it happened, but much stuff on my server was unusably slow. systemd-udevd was constantly timing out and restarting. strace revealed that /proc/self/mountinfo had 48199 entries which took too long to read. Same for /proc/mounts. Most entries are of the sort:
a/zfs/dataset@autosnap_2026-01-31_19:00:04_daily /mountpoint/to/a/zfs/dataset/.zfs/snapshot/autosnap_2026-01-31_19:00:04_daily zfs ro,relatime,xattr,posixacl,casesensitive 0 0

As much as I can tell snapshots shouldn’t end up in /proc/mounts at all. | sort | uniq -c shows that the same snapshots are mounted always 40/80/120 times, with 2 snapshots being mounted 20760 or 20720 times.

This happened for sanoid-snapshots as well as for syncoid-snapshots. All for datasets I created just a few days/weeks ago, not for older ones.

Any idea how this happened? Or is still happening I guess …

I am right now unmounting them in a loop, but the it seems that the kernel-time of the umount command takes ~15s each. I am hesitant of a reboot right now as I won’t be able to get physically to the server for the next few days in case the reboot doesn’t work as expected.

Thanks for any ideas - either on the root cause or on ways to mitigate the issue.
Best,
Thomas

I dont have the link, but we’ve encountered something similar at $work. Its a known bug to which the workaround isnnot to use snapdirs sadly… check the snapdir property on your datasets

2 Likes

Thanks @MagicRB ! I will do that!

In the meantime, some other info I found out. First I executed this command:

time umount /path/to/mountpoint/.zfs/snapshot/autosnap_2026-02-08_23:03:03_hourly

real    0m16,673s
user    0m0,146s
sys     0m15,869s

Since it was sysI thought the umount-syscall was the bottleneck.

But letting ChatGPT write me a quick umount rust-script which calls libc::umount directly lead to a 450x performence increase :slight_smile:

I let it just umount a mountpoint in a loop as long as the syscall doesn’t return an error. (requires libc-crate, so probably a new cargo project and cargo add libc)

use std::ffi::CString;
use std::os::raw::c_int;
use std::time::Instant;
use std::env;

fn main() {
    let path = env::args().nth(1).unwrap();

    // Prepare C string
    let c_path = CString::new(path.clone()).expect("CString::new failed");

    println!("loop-unmounting '{}'", path);
    while {
        // Time the syscall
        let start = Instant::now();
        // Syscall: int umount(const char *target);
        let ret = unsafe { libc::umount(c_path.as_ptr() as *const i8) == 0 };
        let elapsed = start.elapsed();

        if ret {
            println!("umount succeeded for '{}'", path);
        } else {
            // errno available via libc::errno location
            let err = std::io::Error::last_os_error();
            println!("umount failed for '{}': {}", path, err);
        }

        println!("syscall elapsed: {}.{:06} seconds",
                elapsed.as_secs(),
                elapsed.subsec_micros());
        
        ret //condition here - abuse as do-while
    }{}
}

Then I did a bash-for-loop over all datasets that need umounting (the list wasn’t very long) and called my umount-script instead.

Otherwise, in any language iterating over /proc/mounts, deciding for each entry if it needs unmounting (e.g. path contains .zfs/snapshot) and then unmounting once via libc would also have done the trick. Just don’t use the cli-umount-command.

As I can’t edit my previous post another note:
snapdir NEEDS to be set to disabled! hidden is NOT enough! (Own experiments)

IIUC it’s not really a bug; it’s just how snapdir works, isn’t it? When .zfs/snapshot/foo/ is accessed, the foo snapshot is mounted on that directory.

I seem to recall having issues with snapshots being mounted every time the snapdir was accessed. In other words it would just keep overmounting forever.

1 Like

Another Update: Even though I snapdir=disabled root (and the rest inherits that), I can still access .zfs/snapshot and mounts are still being created :man_facepalming: .

My inperfect workaroung using my own unmount-script which is orders of magnitudes faster than umount since it just directly calls the libc syscall:

  services.cron = {
    enable = true;
    systemCronJobs = [ #!!UTC!!
      ''*/5 * * * *      root    for el in $(cat /proc/mounts | grep snapshot | sort | uniq | cut -d" " -f 2); do /root/20260211_del_loop_unmount/target/release/loop_unmount "$el"; done'' #ZFS Hotfix!
    ];
  };

Update 2: I created an upstream Issue: ZFS snapshot automounts pollutes /proc/mounts, even with snapdir=disabled (~50k entries) · Issue #18231 · openzfs/zfs · GitHub