Add sizecache to ValueStore (#1909)

Add sizecache to ValueStore.
This commit is contained in:
Rafael Weinstein
2016-06-25 07:09:08 -07:00
committed by GitHub
parent 9825d9802e
commit 2a04a0abaf
5 changed files with 186 additions and 7 deletions

View File

@@ -883,9 +883,9 @@ func TestListDiffLargeWithSameMiddle(t *testing.T) {
// should only read/write a "small & reasonably sized portion of the total"
assert.Equal(95, cs1.Writes)
assert.Equal(18, cs1.Reads)
assert.Equal(16, cs1.Reads)
assert.Equal(85, cs2.Writes)
assert.Equal(8, cs2.Reads)
assert.Equal(6, cs2.Reads)
}
func TestListTypeAfterMutations(t *testing.T) {

View File

@@ -10,6 +10,7 @@ import (
"github.com/attic-labs/noms/go/chunks"
"github.com/attic-labs/noms/go/d"
"github.com/attic-labs/noms/go/hash"
"github.com/attic-labs/noms/go/util/sizecache"
)
// ValueReader is an interface that knows how to read Noms Values, e.g. datas/Database. Required to avoid import cycle between this package and the package that implements Value reading.
@@ -34,11 +35,14 @@ type ValueReadWriter interface {
// - all Refs in v point to a Value that can be read from this ValueStore
// - all Refs in v point to a Value of the correct Type
type ValueStore struct {
bs BatchStore
cache map[hash.Hash]chunkCacheEntry
mu *sync.Mutex
bs BatchStore
cache map[hash.Hash]chunkCacheEntry
mu *sync.Mutex
valueCache *sizecache.SizeCache
}
const defaultValueCacheSize = 1 << 25 // 32MB
type chunkCacheEntry interface {
Present() bool
Hint() hash.Hash
@@ -56,7 +60,11 @@ func newLocalValueStore(cs chunks.ChunkStore) *ValueStore {
// NewValueStore returns a ValueStore instance that owns the provided BatchStore and manages its lifetime. Calling Close on the returned ValueStore will Close bs.
func NewValueStore(bs BatchStore) *ValueStore {
return &ValueStore{bs, map[hash.Hash]chunkCacheEntry{}, &sync.Mutex{}}
return NewValueStoreWithCache(bs, defaultValueCacheSize)
}
func NewValueStoreWithCache(bs BatchStore, cacheSize uint64) *ValueStore {
return &ValueStore{bs, map[hash.Hash]chunkCacheEntry{}, &sync.Mutex{}, sizecache.New(cacheSize)}
}
func (lvs *ValueStore) BatchStore() BatchStore {
@@ -65,11 +73,16 @@ func (lvs *ValueStore) BatchStore() BatchStore {
// ReadValue reads and decodes a value from lvs. It is not considered an error for the requested chunk to be empty; in this case, the function simply returns nil.
func (lvs *ValueStore) ReadValue(r hash.Hash) Value {
if v, ok := lvs.valueCache.Get(r); ok {
return v.(Value)
}
chunk := lvs.bs.Get(r)
if chunk.IsEmpty() {
lvs.valueCache.Add(r, 0, nil)
return nil
}
v := DecodeValue(chunk, lvs)
lvs.valueCache.Add(r, uint64(len(chunk.Data())), v)
var entry chunkCacheEntry = absentChunk{}
if v != nil {

View File

@@ -0,0 +1,78 @@
// Copyright 2016 The Noms Authors. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package sizecache
// SizeCache implements a simple LRU cache of interface{}-typed key-value pairs. When items are added, the "size" of the item must be provided. LRU items will be expired until the total of all items is below the specified size for the SizeCache
import (
"container/list"
"sync"
"github.com/attic-labs/noms/go/d"
)
type lruList struct {
list.List
}
type sizeCacheEntry struct {
size uint64
lruEntry *list.Element
value interface{}
}
type SizeCache struct {
totalSize uint64
maxSize uint64
mu sync.Mutex
lru lruList
cache map[interface{}]sizeCacheEntry
}
func New(maxSize uint64) *SizeCache {
return &SizeCache{maxSize: maxSize, cache: map[interface{}]sizeCacheEntry{}}
}
func (c *SizeCache) entry(key interface{}) (sizeCacheEntry, bool) {
c.mu.Lock()
defer c.mu.Unlock()
entry, ok := c.cache[key]
if !ok {
return sizeCacheEntry{}, false
}
c.lru.MoveToBack(entry.lruEntry)
return entry, true
}
func (c *SizeCache) Get(key interface{}) (interface{}, bool) {
if entry, ok := c.entry(key); ok {
return entry.value, true
}
return nil, false
}
func (c *SizeCache) Add(key interface{}, size uint64, value interface{}) {
if size <= c.maxSize {
if _, ok := c.entry(key); ok {
return
}
c.mu.Lock()
defer c.mu.Unlock()
newEl := c.lru.PushBack(key)
ce := sizeCacheEntry{size: size, lruEntry: newEl, value: value}
c.cache[key] = ce
c.totalSize += ce.size
for el := c.lru.Front(); el != nil && c.totalSize > c.maxSize; {
key1 := el.Value
ce, ok := c.cache[key1]
d.Chk.True(ok, "SizeCache is missing expected value")
next := el.Next()
delete(c.cache, key1)
c.totalSize -= ce.size
c.lru.Remove(el)
el = next
}
}
}

View File

@@ -0,0 +1,88 @@
package sizecache
import (
"testing"
"github.com/attic-labs/noms/go/hash"
"github.com/attic-labs/testify/assert"
)
var (
valueCounter = 0
)
func hashFromString(s string) hash.Hash {
return hash.FromData([]byte(s))
}
func TestSizeCache(t *testing.T) {
assert := assert.New(t)
defSize := uint64(200)
c := New(1024)
for i, v := range []string{"data-1", "data-2", "data-3", "data-4", "data-5", "data-6", "data-7", "data-8", "data-9"} {
c.Add(hashFromString(v), defSize, v)
maxElements := uint64(i + 1)
if maxElements >= uint64(5) {
maxElements = uint64(5)
}
assert.Equal(maxElements*defSize, c.totalSize)
}
_, ok := c.Get(hashFromString("data-1"))
assert.False(ok)
assert.Equal(hashFromString("data-5"), c.lru.Front().Value)
v, ok := c.Get(hashFromString("data-5"))
assert.True(ok)
assert.Equal("data-5", v.(string))
assert.Equal(hashFromString("data-5"), c.lru.Back().Value)
assert.Equal(hashFromString("data-6"), c.lru.Front().Value)
c.Add(hashFromString("data-7"), defSize, "data-7")
assert.Equal(hashFromString("data-7"), c.lru.Back().Value)
assert.Equal(uint64(1000), c.totalSize)
c.Add(hashFromString("no-data"), 0, nil)
v, ok = c.Get(hashFromString("no-data"))
assert.True(ok)
assert.Nil(v)
assert.Equal(hashFromString("no-data"), c.lru.Back().Value)
assert.Equal(uint64(1000), c.totalSize)
assert.Equal(6, c.lru.Len())
assert.Equal(6, len(c.cache))
for _, v := range []string{"data-5", "data-6", "data-7", "data-8", "data-9"} {
c.Get(hashFromString(v))
assert.Equal(hashFromString(v), c.lru.Back().Value)
}
assert.Equal(hashFromString("no-data"), c.lru.Front().Value)
c.Add(hashFromString("data-10"), 200, "data-10")
assert.Equal(uint64(1000), c.totalSize)
assert.Equal(5, c.lru.Len())
assert.Equal(5, len(c.cache))
_, ok = c.Get(hashFromString("no-data"))
assert.False(ok)
_, ok = c.Get(hashFromString("data-5"))
assert.False(ok)
}
func TestTooLargeValue(t *testing.T) {
assert := assert.New(t)
c := New(1024)
c.Add(hashFromString("big-data"), 2048, "big-data")
_, ok := c.Get(hashFromString("big-data"))
assert.False(ok)
}
func TestZeroSizeCache(t *testing.T) {
assert := assert.New(t)
c := New(0)
c.Add(hashFromString("data1"), 200, "data1")
_, ok := c.Get(hashFromString("data1"))
assert.False(ok)
}

View File

@@ -5,6 +5,7 @@ import (
"log"
"net"
"net/http"
"os"
"path"
"regexp"
"runtime/debug"
@@ -15,7 +16,6 @@ import (
"github.com/attic-labs/noms/go/d"
"github.com/attic-labs/noms/go/datas"
"github.com/julienschmidt/httprouter"
"os"
)
const (