Skip to content

Commit

Permalink
Merge pull request #3987 from aduffeck/cache-space-indexes
Browse files Browse the repository at this point in the history
Cache space indexes
  • Loading branch information
aduffeck authored Jun 19, 2023
2 parents 50976ed + 1991fec commit 99d47da
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 24 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/cache-space-indexes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: cache space indexes

decomposedfs now caches the different space indexes in memory which greatly improves the performance of ListStorageSpaces on slow storages.

https://github.com/cs3org/reva/pull/3987
4 changes: 3 additions & 1 deletion pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/migrator"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree"
Expand Down Expand Up @@ -99,7 +100,8 @@ type Decomposedfs struct {
stream events.Stream
cache cache.StatCache

UserCache *ttlcache.Cache
UserCache *ttlcache.Cache
spaceIDCache mtimesyncedcache.Cache[string, map[string]string]
}

// NewDefault returns an instance with default components
Expand Down
36 changes: 36 additions & 0 deletions pkg/storage/utils/decomposedfs/mtimesyncedcache/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package mtimesyncedcache

import "sync"

type Map[K comparable, V any] struct {
m sync.Map
}

func (m *Map[K, V]) Delete(key K) { m.m.Delete(key) }

func (m *Map[K, V]) Load(key K) (value V, ok bool) {
v, ok := m.m.Load(key)
if !ok {
return value, ok
}
return v.(V), ok
}

func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
v, loaded := m.m.LoadAndDelete(key)
if !loaded {
return value, loaded
}
return v.(V), loaded
}

func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
a, loaded := m.m.LoadOrStore(key, value)
return a.(V), loaded
}

func (m *Map[K, V]) Range(f func(key K, value V) bool) {
m.m.Range(func(key, value any) bool { return f(key.(K), value.(V)) })
}

func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package mtimesyncedcache

import (
"sync"
"time"
)

type Cache[K comparable, T any] struct {
entries Map[K, *entry[T]]
}

type entry[T any] struct {
mtime time.Time
value T

mu sync.Mutex
}

func New[K comparable, T any]() Cache[K, T] {
return Cache[K, T]{
entries: Map[K, *entry[T]]{},
}
}

func (c *Cache[K, T]) Store(key K, mtime time.Time, value T) error {
c.entries.Store(key, &entry[T]{
mtime: mtime,
value: value,
})
return nil
}

func (c *Cache[K, T]) Load(key K) (T, bool) {
entry, ok := c.entries.Load(key)
if !ok {
var t T
return t, false
}
return entry.value, true
}

func (c *Cache[K, T]) LoadOrStore(key K, mtime time.Time, f func() (T, error)) (T, error) {
e, _ := c.entries.LoadOrStore(key, &entry[T]{})

e.mu.Lock()
defer e.mu.Unlock()
if mtime.After(e.mtime) {
e.mtime = mtime
v, err := f()
if err != nil {
var t T
return t, err
}
e.value = v
c.entries.Store(key, e)
}

return e.value, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mtimesyncedcache_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestMtimesyncedcache(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Mtimesyncedcache Suite")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package mtimesyncedcache_test

import (
"errors"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/mtimesyncedcache"
)

var _ = Describe("Mtimesyncedcache", func() {
var (
cache mtimesyncedcache.Cache[string, string]

key = "key"
value = "value"
)

BeforeEach(func() {
cache = mtimesyncedcache.New[string, string]()
})

Describe("Store", func() {
It("stores a value", func() {
time := time.Now()

err := cache.Store(key, time, value)
Expect(err).ToNot(HaveOccurred())
})
})

Describe("Load", func() {
It("loads the stored value", func() {
err := cache.Store(key, time.Now(), value)
Expect(err).ToNot(HaveOccurred())

v, ok := cache.Load(key)
Expect(ok).To(BeTrue())
Expect(v).To(Equal(value))
})

It("reports when the key doesn't exist", func() {
_, ok := cache.Load("doesnotexist")
Expect(ok).To(BeFalse())
})
})

Describe("LoadOrStore", func() {
It("does not update the cache if the cache is up to date", func() {
cachedTime := time.Now().Add(-1 * time.Hour)
err := cache.Store(key, cachedTime, value)
Expect(err).ToNot(HaveOccurred())

newvalue := "yaaay"
v, err := cache.LoadOrStore(key, cachedTime, func() (string, error) {
return newvalue, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(value))

v, err = cache.LoadOrStore(key, time.Now().Add(-2*time.Hour), func() (string, error) {
return newvalue, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(value))
})

It("updates the cache if the cache is outdated", func() {
outdatedTime := time.Now().Add(-1 * time.Hour)
err := cache.Store(key, outdatedTime, value)
Expect(err).ToNot(HaveOccurred())

newvalue := "yaaay"
v, err := cache.LoadOrStore(key, time.Now(), func() (string, error) {
return newvalue, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(newvalue))
})

It("stores the value if the key doesn't exist yet", func() {
newvalue := "yaaay"
v, err := cache.LoadOrStore(key, time.Now(), func() (string, error) {
return newvalue, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(newvalue))
})

It("sets the mtime when storing the value", func() {
newTime := time.Now()

newvalue := "yaaay"
v, err := cache.LoadOrStore(key, newTime, func() (string, error) {
return newvalue, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(newvalue))

newvalue2 := "asdfasdf"
v, err = cache.LoadOrStore(key, newTime, func() (string, error) {
return newvalue2, nil
})
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal(newvalue))
})

It("passes on error from the store func", func() {
v, err := cache.LoadOrStore(key, time.Now(), func() (string, error) {
return "", errors.New("baa")
})
Expect(v).To(Equal(""))
Expect(err.Error()).To(Equal("baa"))

})
})
})
Loading

0 comments on commit 99d47da

Please sign in to comment.