Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folder metadata in stat cache #2097

Merged
merged 19 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion internal/cache/metadata/stat_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"math"
"time"

"cloud.google.com/go/storage/control/apiv2/controlpb"
"github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru"
"github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs"
"github.com/googlecloudplatform/gcsfuse/v2/internal/util"
Expand Down Expand Up @@ -45,10 +46,24 @@ type StatCache interface {
// Erase the entry for the given object name, if any.
Erase(name string)

// Return the current entry for the given name, or nil if there is a negative
// Return the current object entry for the given name, or nil if there is a negative
// entry. Return hit == false when there is neither a positive nor a negative
// entry, or the entry has expired according to the supplied current time.
LookUp(name string, now time.Time) (hit bool, m *gcs.MinObject)

// Insert an entry for the given folder resource.
//
// In order to help cope with caching of arbitrarily out of date (i.e.
// inconsistent) object listings, entry will not replace any positive entry
// with a newer meta generation number.
//
// The entry will expire after the supplied time.
InsertFolder(f *controlpb.Folder, expiration time.Time)

// Return the current folder entry for the given name, or nil if there is a negative
// entry. Return hit == false when there is neither a positive nor a negative
// entry, or the entry has expired according to the supplied current time.
LookUpFolder(folderName string, now time.Time) (bool, *controlpb.Folder)
}

// Create a new bucket-view to the passed shared-cache object.
Expand Down Expand Up @@ -80,6 +95,7 @@ type statCacheBucketView struct {
// entry. Nil object means negative entry.
type entry struct {
m *gcs.MinObject
f *controlpb.Folder
expiration time.Time
key string
}
Expand Down Expand Up @@ -203,3 +219,46 @@ func (sc *statCacheBucketView) LookUp(

return
}

Tulsishah marked this conversation as resolved.
Show resolved Hide resolved
ankitaluthra1 marked this conversation as resolved.
Show resolved Hide resolved
func (sc *statCacheBucketView) LookUpFolder(
folderName string,
now time.Time) (bool, *controlpb.Folder) {
// Look up in the LRU cache.
value := sc.sharedCache.LookUp(sc.key(folderName))
ankitaluthra1 marked this conversation as resolved.
Show resolved Hide resolved
if value == nil {
return false, nil
}

e := value.(entry)

// Has this entry expired?
if e.expiration.Before(now) {
sc.Erase(folderName)
return false, nil
}

return true, e.f
}

func (sc *statCacheBucketView) InsertFolder(f *controlpb.Folder, expiration time.Time) {
name := sc.key(f.Name)

// Return if there is already a better entry?
existing := sc.sharedCache.LookUp(name)
if existing != nil && existing.(entry).f != nil {
existingFolder := existing.(entry).f
if f.Metageneration != existingFolder.Metageneration && f.Metageneration < existingFolder.Metageneration {
return
}
}

e := entry{
f: f,
expiration: expiration,
key: name,
}

if _, err := sc.sharedCache.Insert(name, e); err != nil {
panic(err)
}
}
74 changes: 73 additions & 1 deletion internal/cache/metadata/stat_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"testing"
"time"

"cloud.google.com/go/storage/control/apiv2/controlpb"
"github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru"
"github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata"
"github.com/googlecloudplatform/gcsfuse/v2/internal/mount"
Expand Down Expand Up @@ -100,7 +101,8 @@ var expiration = someTime.Add(time.Second)

type StatCacheTest struct {
suite.Suite
cache testHelperCache
cache testHelperCache
statCache metadata.StatCache
}

type MultiBucketStatCacheTest struct {
Expand All @@ -111,6 +113,7 @@ type MultiBucketStatCacheTest struct {
func (t *StatCacheTest) SetupTest() {
cache := lru.NewCache(uint64((mount.AverageSizeOfPositiveStatCacheEntry + mount.AverageSizeOfNegativeStatCacheEntry) * capacity))
t.cache.wrapped = metadata.NewStatCacheBucketView(cache, "") // this demonstrates
t.statCache = metadata.NewStatCacheBucketView(cache, "") // this demonstrates
Tulsishah marked this conversation as resolved.
Show resolved Hide resolved
// that if you are using a cache for a single bucket, then
// its prepending bucketName can be left empty("") without any problem.
}
Expand Down Expand Up @@ -408,3 +411,72 @@ func (t *MultiBucketStatCacheTest) Test_ExpiresLeastRecentlyUsed() {
assert.Equal(t.T(), cardamom, spices.LookUpOrNil("cardamom", someTime))
assert.Equal(t.T(), saffron, spices.LookUpOrNil("saffron", someTime))
}

func (t *StatCacheTest) Test_Create_Entry_When_No_Entry_Is_Present() {
ankitaluthra1 marked this conversation as resolved.
Show resolved Hide resolved
const name = "key1"
newEntry := &controlpb.Folder{
Name: name,
Metageneration: 1,
}

t.statCache.InsertFolder(newEntry, expiration)

hit, entry := t.statCache.LookUpFolder(name, someTime)
assert.True(t.T(), hit)
assert.Equal(t.T(), "key1", entry.Name)
assert.Equal(t.T(), int64(1), entry.Metageneration)
}

func (t *StatCacheTest) Test_Override_Entry_Old_Entry_Is_Already_Present() {
const name = "key1"
existingEntry := &controlpb.Folder{
Name: name,
Metageneration: 1,
}
t.statCache.InsertFolder(existingEntry, expiration)
vadlakondaswetha marked this conversation as resolved.
Show resolved Hide resolved
newEntry := &controlpb.Folder{
Name: name,
Metageneration: 2,
}

t.statCache.InsertFolder(newEntry, expiration)

hit, entry := t.statCache.LookUpFolder(name, someTime)
assert.True(t.T(), hit)
assert.Equal(t.T(), "key1", entry.Name)
assert.Equal(t.T(), int64(2), entry.Metageneration)
}

func (t *StatCacheTest) Test_Lookup_Return_False_If_Expiration_Is_Passed() {
ankitaluthra1 marked this conversation as resolved.
Show resolved Hide resolved
const name = "key1"
entry := &controlpb.Folder{
Name: name,
Metageneration: 1,
}
t.statCache.InsertFolder(entry, expiration)

hit, result := t.statCache.LookUpFolder(name, expiration.Add(time.Second))

assert.False(t.T(), hit)
assert.Nil(t.T(), result)
}

func (t *StatCacheTest) Test_Should_Not_Override_Entry_If_Metageneration_Is_Old() {
const name = "key1"
existingEntry := &controlpb.Folder{
Name: name,
Metageneration: 2,
}
t.statCache.InsertFolder(existingEntry, expiration)
newEntry := &controlpb.Folder{
Name: name,
Metageneration: 1,
}

t.statCache.InsertFolder(newEntry, expiration)

hit, entry := t.statCache.LookUpFolder(name, someTime)
assert.True(t.T(), hit)
assert.Equal(t.T(), "key1", entry.Name)
assert.Equal(t.T(), int64(2), entry.Metageneration)
}
Loading