Skip to content

Commit

Permalink
Greatly increased stat performance on Linux by allowing kernel caches.
Browse files Browse the repository at this point in the history
Fixes #115.

% go install -v && gcsfuse --temp-dir /mnt/ssd0 jacobsa-standard-asia ~/mp
% go build ./benchmarks/stat_files && ./stat_files --dir ~/mp

Before:

    Statted 64116 times in 10.000127533s (6.41 KHz)

After:

    Statted 18057185 times in 10.000000361s (1.81 MHz)
  • Loading branch information
jacobsa committed Aug 13, 2015
2 parents 289c4eb + e621b72 commit 8ecfe19
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 27 deletions.
5 changes: 5 additions & 0 deletions docs/semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ behavior is controlled by the `--stat-cache-ttl` flag, which can be set to a
value like `10s` or `1.5h`. (The default is one minute.) Positive and negative
stat results will be cached for the specified amount of time.

`--stat-cache-ttl` also controls the duration for which gcsfuse allows the
kernel to cache inode attributes. Caching these can help with file system
performance, since otherwise the kernel must send a request for inode attributes
to gcsfuse for each call to `write(2)`, `stat(2)`, and others.

**Warning**: Using stat caching breaks the consistency guarantees discussed in
this document. It is safe only in the following situations:

Expand Down
2 changes: 1 addition & 1 deletion flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func newApp() (app *cli.App) {
cli.DurationFlag{
Name: "stat-cache-ttl",
Value: time.Minute,
Usage: "How long to cache StatObject results from GCS.",
Usage: "How long to cache StatObject results and inode attributes.",
},

cli.DurationFlag{
Expand Down
78 changes: 61 additions & 17 deletions internal/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ type ServerConfig struct {
// See docs/semantics.md for more info.
ImplicitDirectories bool

// How long to allow the kernel to cache inode attributes.
//
// Any given object generation in GCS is immutable, and a new generation
// results in a new inode number. So every update from a remote system results
// in a new inode number, and it's therefore safe to allow the kernel to cache
// inode attributes.
//
// The one exception to the above logic is that objects can be _deleted_, in
// which case stat::st_nlink changes. So choosing this value comes down to
// whether you care about that field being up to date.
InodeAttributeCacheTTL time.Duration

// If non-zero, each directory will maintain a cache from child name to
// information about whether that name exists as a file and/or directory.
// This may speed up calls to look up and stat inodes, especially when
Expand Down Expand Up @@ -126,6 +138,7 @@ func NewServer(cfg *ServerConfig) (server fuse.Server, err error) {
syncer: syncer,
tempDir: cfg.TempDir,
implicitDirs: cfg.ImplicitDirectories,
inodeAttributeCacheTTL: cfg.InodeAttributeCacheTTL,
dirTypeCacheTTL: cfg.DirTypeCacheTTL,
uid: cfg.Uid,
gid: cfg.Gid,
Expand Down Expand Up @@ -211,9 +224,10 @@ type fileSystem struct {
// Constant data
/////////////////////////

tempDir string
implicitDirs bool
dirTypeCacheTTL time.Duration
tempDir string
implicitDirs bool
inodeAttributeCacheTTL time.Duration
dirTypeCacheTTL time.Duration

// The user and group owning everything in the file system.
uid uint32
Expand Down Expand Up @@ -819,6 +833,30 @@ func (fs *fileSystem) unlockAndMaybeDisposeOfInode(
fs.unlockAndDecrementLookupCount(in, 1)
}

// Fetch attributes for the supplied inode and fill in an appropriate
// expiration time for them.
//
// LOCKS_REQUIRED(in)
func (fs *fileSystem) getAttributes(
ctx context.Context,
in inode.Inode) (
attr fuseops.InodeAttributes,
expiration time.Time,
err error) {
// Call through.
attr, err = in.Attributes(ctx)
if err != nil {
return
}

// Set up the expiration time.
if fs.inodeAttributeCacheTTL > 0 {
expiration = time.Now().Add(fs.inodeAttributeCacheTTL)
}

return
}

////////////////////////////////////////////////////////////////////////
// fuse.FileSystem methods
////////////////////////////////////////////////////////////////////////
Expand All @@ -845,8 +883,11 @@ func (fs *fileSystem) LookUpInode(
defer fs.unlockAndMaybeDisposeOfInode(child, &err)

// Fill out the response.
op.Entry.Child = child.ID()
if op.Entry.Attributes, err = child.Attributes(ctx); err != nil {
e := &op.Entry
e.Child = child.ID()
e.Attributes, e.AttributesExpiration, err = fs.getAttributes(ctx, child)

if err != nil {
return
}

Expand All @@ -866,7 +907,7 @@ func (fs *fileSystem) GetInodeAttributes(
defer in.Unlock()

// Grab its attributes.
op.Attributes, err = in.Attributes(ctx)
op.Attributes, op.AttributesExpiration, err = fs.getAttributes(ctx, in)
if err != nil {
return
}
Expand Down Expand Up @@ -919,9 +960,9 @@ func (fs *fileSystem) SetInodeAttributes(
}

// Fill in the response.
op.Attributes, err = in.Attributes(ctx)
op.Attributes, op.AttributesExpiration, err = fs.getAttributes(ctx, in)
if err != nil {
err = fmt.Errorf("Attributes: %v", err)
err = fmt.Errorf("getAttributes: %v", err)
return
}

Expand Down Expand Up @@ -987,11 +1028,12 @@ func (fs *fileSystem) MkDir(
defer fs.unlockAndMaybeDisposeOfInode(child, &err)

// Fill out the response.
op.Entry.Child = child.ID()
op.Entry.Attributes, err = child.Attributes(ctx)
e := &op.Entry
e.Child = child.ID()
e.Attributes, e.AttributesExpiration, err = fs.getAttributes(ctx, child)

if err != nil {
err = fmt.Errorf("Attributes: %v", err)
err = fmt.Errorf("getAttributes: %v", err)
return
}

Expand Down Expand Up @@ -1051,11 +1093,12 @@ func (fs *fileSystem) CreateFile(
fs.mu.Unlock()

// Fill out the response.
op.Entry.Child = child.ID()
op.Entry.Attributes, err = child.Attributes(ctx)
e := &op.Entry
e.Child = child.ID()
e.Attributes, e.AttributesExpiration, err = fs.getAttributes(ctx, child)

if err != nil {
err = fmt.Errorf("Attributes: %v", err)
err = fmt.Errorf("getAttributes: %v", err)
return
}

Expand Down Expand Up @@ -1101,11 +1144,12 @@ func (fs *fileSystem) CreateSymlink(
defer fs.unlockAndMaybeDisposeOfInode(child, &err)

// Fill out the response.
op.Entry.Child = child.ID()
op.Entry.Attributes, err = child.Attributes(ctx)
e := &op.Entry
e.Child = child.ID()
e.Attributes, e.AttributesExpiration, err = fs.getAttributes(ctx, child)

if err != nil {
err = fmt.Errorf("Attributes: %v", err)
err = fmt.Errorf("getAttributes: %v", err)
return
}

Expand Down
19 changes: 10 additions & 9 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,16 @@ func mount(

// Create a file system server.
serverCfg := &fs.ServerConfig{
Clock: timeutil.RealClock(),
Bucket: bucket,
TempDir: flags.TempDir,
ImplicitDirectories: flags.ImplicitDirs,
DirTypeCacheTTL: flags.TypeCacheTTL,
Uid: uid,
Gid: gid,
FilePerms: os.FileMode(flags.FileMode),
DirPerms: os.FileMode(flags.DirMode),
Clock: timeutil.RealClock(),
Bucket: bucket,
TempDir: flags.TempDir,
ImplicitDirectories: flags.ImplicitDirs,
InodeAttributeCacheTTL: flags.StatCacheTTL,
DirTypeCacheTTL: flags.TypeCacheTTL,
Uid: uid,
Gid: gid,
FilePerms: os.FileMode(flags.FileMode),
DirPerms: os.FileMode(flags.DirMode),

AppendThreshold: 1 << 21, // 2 MiB, a total guess.
TmpObjectPrefix: ".gcsfuse_tmp/",
Expand Down

0 comments on commit 8ecfe19

Please sign in to comment.