diff --git a/physical/cache.go b/physical/cache.go new file mode 100644 index 000000000..c13a52c98 --- /dev/null +++ b/physical/cache.go @@ -0,0 +1,75 @@ +package physical + +import "github.com/hashicorp/golang-lru" + +const ( + // DefaultCacheSize is used if no cache size is specified + // for NewPhysicalCache + DefaultCacheSize = 32 * 1024 +) + +// PhysicalCache is used to wrap an underlying physical backend +// and provide an LRU cache layer on top. Most of the reads done by +// Vault are for policy objects so there is a large read reduction +// by using a simple write-through cache. +type PhysicalCache struct { + backend Backend + lru *lru.Cache +} + +// NewPhysicalCache returns a physical cache of the given size. +// If no size is provided, the default size is used. +func NewPhysicalCache(b Backend, size int) *PhysicalCache { + if size <= 0 { + size = DefaultCacheSize + } + cache, _ := lru.New(size) + c := &PhysicalCache{ + backend: b, + lru: cache, + } + return c +} + +// Purge is used to clear the cache +func (c *PhysicalCache) Purge() { + c.lru.Purge() +} + +func (c *PhysicalCache) Put(entry *Entry) error { + err := c.backend.Put(entry) + c.lru.Add(entry.Key, entry) + return err +} + +func (c *PhysicalCache) Get(key string) (*Entry, error) { + // Check the LRU first + if raw, ok := c.lru.Get(key); ok { + if raw == nil { + return nil, nil + } else { + return raw.(*Entry), nil + } + } + + // Read from the underlying backend + ent, err := c.backend.Get(key) + if err != nil { + return nil, err + } + + // Cache the result + c.lru.Add(key, ent) + return ent, err +} + +func (c *PhysicalCache) Delete(key string) error { + err := c.backend.Delete(key) + c.lru.Remove(key) + return err +} + +func (c *PhysicalCache) List(prefix string) ([]string, error) { + // Always pass-through as this would be difficult to cache. + return c.backend.List(prefix) +} diff --git a/physical/cache_test.go b/physical/cache_test.go new file mode 100644 index 000000000..39d953cfe --- /dev/null +++ b/physical/cache_test.go @@ -0,0 +1,48 @@ +package physical + +import "testing" + +func TestCache(t *testing.T) { + inm := NewInmem() + cache := NewPhysicalCache(inm, 0) + testBackend(t, cache) + testBackend_ListPrefix(t, cache) +} + +func TestCache_Purge(t *testing.T) { + inm := NewInmem() + cache := NewPhysicalCache(inm, 0) + + ent := &Entry{ + Key: "foo", + Value: []byte("bar"), + } + err := cache.Put(ent) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Delete from under + inm.Delete("foo") + + // Read should work + out, err := cache.Get("foo") + if err != nil { + t.Fatalf("err: %v", err) + } + if out == nil { + t.Fatalf("should have key") + } + + // Clear the cache + cache.Purge() + + // Read should fail + out, err = cache.Get("foo") + if err != nil { + t.Fatalf("err: %v", err) + } + if out != nil { + t.Fatalf("should not have key") + } +}