mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-17 12:46:59 +00:00
mount: let the kernel cache directory listings (#10634)
Every enumeration of a directory walked the whole FUSE machinery, so
reopening a folder cost what opening it did. The kernel has a cache for
exactly this: with FOPEN_CACHE_DIR the listing lives in the directory's
page cache and a repeat enumeration never reaches the mount at all.
Local mutations already drop that cache in the kernel. Remote ones
arrive through the metadata subscription, so the entry invalidation
worker now also tells the kernel which directory changed. The worker is
the one place this is safe from: notifying from a thread serving a
kernel request can deadlock against the page it holds, which is why the
file paths deliberately avoid InodeNotify.
Measured in a Linux container, 20k-entry directory, ls repeated:
warm listing before 199-355ms after 6-9ms
A file written from outside the mount appeared in the next listing
within a second, through the subscription notify, and the listing
re-cached after.
The memory is the kernel's page cache: reclaimed under pressure, owned
per-directory, and covering read-through directories the mount-side
caches never see.
This commit is contained in:
@@ -802,9 +802,33 @@ func (wfs *WFS) onEntryInvalidation(invalidation meta_cache.EntryInvalidation) {
|
||||
if listener != nil {
|
||||
listener(invalidation)
|
||||
}
|
||||
wfs.invalidateKernelDirListing(invalidation.Path)
|
||||
wfs.invalidateOpenFileHandle(invalidation)
|
||||
}
|
||||
|
||||
// invalidateKernelDirListing drops the kernel's cached listing of the directory
|
||||
// holding path. Safe here because invalidations run on their own worker, never
|
||||
// on a thread serving a kernel request; notifying from a handler can deadlock
|
||||
// against the page it holds, which is why the file paths avoid InodeNotify.
|
||||
// A directory the kernel has not looked up has no inode here and nothing
|
||||
// cached, so it is skipped.
|
||||
func (wfs *WFS) invalidateKernelDirListing(path util.FullPath) {
|
||||
server := wfs.fuseServer
|
||||
if server == nil {
|
||||
return
|
||||
}
|
||||
dir, _ := path.DirAndName()
|
||||
dirInode, found := wfs.inodeToPath.GetInode(util.FullPath(dir))
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
// ENOENT is the kernel not holding the inode, ENOSYS a kernel without the
|
||||
// notify; neither is worth a line.
|
||||
if status := server.InodeNotify(dirInode, 0, -1); status != fuse.OK && status != fuse.ENOENT && status != fuse.ENOSYS {
|
||||
glog.V(4).Infof("invalidate kernel listing of %s: %v", dir, status)
|
||||
}
|
||||
}
|
||||
|
||||
// MountRoot is the filer path this mount is rooted at. Event paths are absolute
|
||||
// on the filer; a front end that addresses files relative to the mount needs it
|
||||
// to translate them.
|
||||
|
||||
@@ -109,6 +109,12 @@ func (wfs *WFS) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.Op
|
||||
}
|
||||
dhid, _ := wfs.AcquireDirectoryHandle()
|
||||
out.Fh = uint64(dhid)
|
||||
// Let the kernel keep the listing in the directory's page cache, so
|
||||
// reopening the directory does not reach the mount at all. Local mutations
|
||||
// drop that cache in the kernel; remote ones arrive through the metadata
|
||||
// subscription, which notifies the kernel per changed directory. A kernel
|
||||
// too old for the flag ignores it.
|
||||
out.OpenFlags |= fuse.FOPEN_CACHE_DIR | fuse.FOPEN_KEEP_CACHE
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/go-fuse/v2/fuse"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
// TestOpenDirEnablesKernelListingCache pins the reply flags: without them the
|
||||
// kernel calls back for every enumeration, and losing them would silently
|
||||
// revert repeat listings to full walks of the mount.
|
||||
func TestOpenDirEnablesKernelListingCache(t *testing.T) {
|
||||
dir := util.FullPath("/images")
|
||||
wfs := newBenchWFS(t, dir, 4)
|
||||
dirInode, _ := wfs.inodeToPath.GetInode(dir)
|
||||
|
||||
var out fuse.OpenOut
|
||||
if status := wfs.OpenDir(nil, &fuse.OpenIn{InHeader: fuse.InHeader{NodeId: dirInode}}, &out); status != fuse.OK {
|
||||
t.Fatalf("OpenDir: %v", status)
|
||||
}
|
||||
defer wfs.ReleaseDir(&fuse.ReleaseIn{Fh: out.Fh})
|
||||
|
||||
for _, want := range []struct {
|
||||
name string
|
||||
flag uint32
|
||||
}{{"FOPEN_CACHE_DIR", fuse.FOPEN_CACHE_DIR}, {"FOPEN_KEEP_CACHE", fuse.FOPEN_KEEP_CACHE}} {
|
||||
if out.OpenFlags&want.flag == 0 {
|
||||
t.Errorf("OpenDir reply lacks %s", want.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user