initial work on tracking cache by item size
This commit is contained in:
parent
44cdb043d1
commit
ff8727e847
7 changed files with 50 additions and 23 deletions
16
bucket.go
16
bucket.go
|
@ -2,6 +2,7 @@ package ccache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,18 +17,27 @@ func (b *bucket) get(key string) *Item {
|
||||||
return b.lookup[key]
|
return b.lookup[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bucket) set(key string, value interface{}, duration time.Duration) (*Item, bool) {
|
func (b *bucket) set(key string, value interface{}, duration time.Duration) (*Item, bool, int64) {
|
||||||
expires := time.Now().Add(duration).Unix()
|
expires := time.Now().Add(duration).Unix()
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
if existing, exists := b.lookup[key]; exists {
|
if existing, exists := b.lookup[key]; exists {
|
||||||
|
s := existing.size
|
||||||
existing.value = value
|
existing.value = value
|
||||||
existing.expires = expires
|
existing.expires = expires
|
||||||
return existing, false
|
d := int64(0)
|
||||||
|
if sized, ok := value.(Sized); ok {
|
||||||
|
newSize := sized.Size()
|
||||||
|
d = newSize - s
|
||||||
|
if d != 0 {
|
||||||
|
atomic.StoreInt64(&existing.size, newSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existing, false, int64(d)
|
||||||
}
|
}
|
||||||
item := newItem(key, value, expires)
|
item := newItem(key, value, expires)
|
||||||
b.lookup[key] = item
|
b.lookup[key] = item
|
||||||
return item, true
|
return item, true, int64(item.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bucket) replace(key string, value interface{}) bool {
|
func (b *bucket) replace(key string, value interface{}) bool {
|
||||||
|
|
|
@ -32,20 +32,22 @@ func (_ *BucketTests) DeleteItemFromBucket() {
|
||||||
|
|
||||||
func (_ *BucketTests) SetsANewBucketItem() {
|
func (_ *BucketTests) SetsANewBucketItem() {
|
||||||
bucket := testBucket()
|
bucket := testBucket()
|
||||||
item, new := bucket.set("spice", TestValue("flow"), time.Minute)
|
item, new, d := bucket.set("spice", TestValue("flow"), time.Minute)
|
||||||
assertValue(item, "flow")
|
assertValue(item, "flow")
|
||||||
item = bucket.get("spice")
|
item = bucket.get("spice")
|
||||||
assertValue(item, "flow")
|
assertValue(item, "flow")
|
||||||
Expect(new).To.Equal(true)
|
Expect(new).To.Equal(true)
|
||||||
|
Expect(d).To.Equal(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *BucketTests) SetsAnExistingItem() {
|
func (_ *BucketTests) SetsAnExistingItem() {
|
||||||
bucket := testBucket()
|
bucket := testBucket()
|
||||||
item, new := bucket.set("power", TestValue("9002"), time.Minute)
|
item, new, d := bucket.set("power", TestValue("9002"), time.Minute)
|
||||||
assertValue(item, "9002")
|
assertValue(item, "9002")
|
||||||
item = bucket.get("power")
|
item = bucket.get("power")
|
||||||
assertValue(item, "9002")
|
assertValue(item, "9002")
|
||||||
Expect(new).To.Equal(false)
|
Expect(new).To.Equal(false)
|
||||||
|
Expect(d).To.Equal(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *BucketTests) ReplaceDoesNothingIfKeyDoesNotExist() {
|
func (_ *BucketTests) ReplaceDoesNothingIfKeyDoesNotExist() {
|
||||||
|
@ -56,7 +58,7 @@ func (_ *BucketTests) ReplaceDoesNothingIfKeyDoesNotExist() {
|
||||||
|
|
||||||
func (_ *BucketTests) ReplaceReplacesThevalue() {
|
func (_ *BucketTests) ReplaceReplacesThevalue() {
|
||||||
bucket := testBucket()
|
bucket := testBucket()
|
||||||
item, _ := bucket.set("power", TestValue("9002"), time.Minute)
|
item, _, _ := bucket.set("power", TestValue("9002"), time.Minute)
|
||||||
Expect(bucket.replace("power", TestValue("9004"))).To.Equal(true)
|
Expect(bucket.replace("power", TestValue("9004"))).To.Equal(true)
|
||||||
Expect(item.Value().(string)).To.Equal("9004")
|
Expect(item.Value().(string)).To.Equal("9004")
|
||||||
Expect(bucket.get("power").Value().(string)).To.Equal("9004")
|
Expect(bucket.get("power").Value().(string)).To.Equal("9004")
|
||||||
|
|
14
cache.go
14
cache.go
|
@ -11,7 +11,7 @@ import (
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
*Configuration
|
*Configuration
|
||||||
list *list.List
|
list *list.List
|
||||||
size uint64
|
size int64
|
||||||
buckets []*bucket
|
buckets []*bucket
|
||||||
bucketMask uint32
|
bucketMask uint32
|
||||||
deletables chan *Item
|
deletables chan *Item
|
||||||
|
@ -67,12 +67,15 @@ func (c *Cache) TrackingGet(key string) TrackedItem {
|
||||||
|
|
||||||
// Set the value in the cache for the specified duration
|
// Set the value in the cache for the specified duration
|
||||||
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
|
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
|
||||||
item, new := c.bucket(key).set(key, value, duration)
|
item, new, d := c.bucket(key).set(key, value, duration)
|
||||||
if new {
|
if new {
|
||||||
c.promote(item)
|
c.promote(item)
|
||||||
} else {
|
} else {
|
||||||
c.conditionalPromote(item)
|
c.conditionalPromote(item)
|
||||||
}
|
}
|
||||||
|
if d != 0 {
|
||||||
|
atomic.AddInt64(&c.size, d)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the value if it exists, does not set if it doesn't.
|
// Replace the value if it exists, does not set if it doesn't.
|
||||||
|
@ -142,14 +145,14 @@ func (c *Cache) worker() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case item := <-c.promotables:
|
case item := <-c.promotables:
|
||||||
if c.doPromote(item) && c.size > c.maxItems {
|
if c.doPromote(item) && atomic.LoadInt64(&c.size) > c.maxItems {
|
||||||
c.gc()
|
c.gc()
|
||||||
}
|
}
|
||||||
case item := <-c.deletables:
|
case item := <-c.deletables:
|
||||||
if item.element == nil {
|
if item.element == nil {
|
||||||
item.promotions = -2
|
item.promotions = -2
|
||||||
} else {
|
} else {
|
||||||
c.size -= 1
|
atomic.AddInt64(&c.size, -item.size)
|
||||||
c.list.Remove(item.element)
|
c.list.Remove(item.element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +169,6 @@ func (c *Cache) doPromote(item *Item) bool {
|
||||||
c.list.MoveToFront(item.element)
|
c.list.MoveToFront(item.element)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c.size += 1
|
|
||||||
item.element = c.list.PushFront(item)
|
item.element = c.list.PushFront(item)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -181,7 +183,7 @@ func (c *Cache) gc() {
|
||||||
item := element.Value.(*Item)
|
item := element.Value.(*Item)
|
||||||
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
||||||
c.bucket(item.key).delete(item.key)
|
c.bucket(item.key).delete(item.key)
|
||||||
c.size -= 1
|
atomic.AddInt64(&c.size, -item.size)
|
||||||
c.list.Remove(element)
|
c.list.Remove(element)
|
||||||
}
|
}
|
||||||
element = prev
|
element = prev
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ccache
|
package ccache
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
maxItems uint64
|
maxItems int64
|
||||||
buckets int
|
buckets int
|
||||||
itemsToPrune int
|
itemsToPrune int
|
||||||
deleteBuffer int
|
deleteBuffer int
|
||||||
|
@ -27,7 +27,7 @@ func Configure() *Configuration {
|
||||||
|
|
||||||
// The max number of items to store in the cache
|
// The max number of items to store in the cache
|
||||||
// [5000]
|
// [5000]
|
||||||
func (c *Configuration) MaxItems(max uint64) *Configuration {
|
func (c *Configuration) MaxItems(max int64) *Configuration {
|
||||||
c.maxItems = max
|
c.maxItems = max
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
10
item.go
10
item.go
|
@ -6,6 +6,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Sized interface {
|
||||||
|
Size() int64
|
||||||
|
}
|
||||||
|
|
||||||
type TrackedItem interface {
|
type TrackedItem interface {
|
||||||
Value() interface{}
|
Value() interface{}
|
||||||
Release()
|
Release()
|
||||||
|
@ -43,15 +47,21 @@ type Item struct {
|
||||||
promotions int32
|
promotions int32
|
||||||
refCount int32
|
refCount int32
|
||||||
expires int64
|
expires int64
|
||||||
|
size int64
|
||||||
value interface{}
|
value interface{}
|
||||||
element *list.Element
|
element *list.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
func newItem(key string, value interface{}, expires int64) *Item {
|
func newItem(key string, value interface{}, expires int64) *Item {
|
||||||
|
size := int64(1)
|
||||||
|
if sized, ok := value.(Sized); ok {
|
||||||
|
size = sized.Size()
|
||||||
|
}
|
||||||
return &Item{
|
return &Item{
|
||||||
key: key,
|
key: key,
|
||||||
value: value,
|
value: value,
|
||||||
promotions: -1,
|
promotions: -1,
|
||||||
|
size: size,
|
||||||
expires: expires,
|
expires: expires,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func (b *layeredBucket) get(primary, secondary string) *Item {
|
||||||
return bucket.get(secondary)
|
return bucket.get(secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *layeredBucket) set(primary, secondary string, value interface{}, duration time.Duration) (*Item, bool) {
|
func (b *layeredBucket) set(primary, secondary string, value interface{}, duration time.Duration) (*Item, bool, int64) {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
bkt, exists := b.buckets[primary]
|
bkt, exists := b.buckets[primary]
|
||||||
if exists == false {
|
if exists == false {
|
||||||
|
@ -28,11 +28,11 @@ func (b *layeredBucket) set(primary, secondary string, value interface{}, durati
|
||||||
b.buckets[primary] = bkt
|
b.buckets[primary] = bkt
|
||||||
}
|
}
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
item, new := bkt.set(secondary, value, duration)
|
item, new, d := bkt.set(secondary, value, duration)
|
||||||
if new {
|
if new {
|
||||||
item.group = primary
|
item.group = primary
|
||||||
}
|
}
|
||||||
return item, new
|
return item, new, d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *layeredBucket) replace(primary, secondary string, value interface{}) bool {
|
func (b *layeredBucket) replace(primary, secondary string, value interface{}) bool {
|
||||||
|
|
|
@ -13,7 +13,7 @@ type LayeredCache struct {
|
||||||
list *list.List
|
list *list.List
|
||||||
buckets []*layeredBucket
|
buckets []*layeredBucket
|
||||||
bucketMask uint32
|
bucketMask uint32
|
||||||
size uint64
|
size int64
|
||||||
deletables chan *Item
|
deletables chan *Item
|
||||||
promotables chan *Item
|
promotables chan *Item
|
||||||
}
|
}
|
||||||
|
@ -78,12 +78,15 @@ func (c *LayeredCache) TrackingGet(primary, secondary string) TrackedItem {
|
||||||
|
|
||||||
// Set the value in the cache for the specified duration
|
// Set the value in the cache for the specified duration
|
||||||
func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) {
|
func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) {
|
||||||
item, new := c.bucket(primary).set(primary, secondary, value, duration)
|
item, new, d := c.bucket(primary).set(primary, secondary, value, duration)
|
||||||
if new {
|
if new {
|
||||||
c.promote(item)
|
c.promote(item)
|
||||||
} else {
|
} else {
|
||||||
c.conditionalPromote(item)
|
c.conditionalPromote(item)
|
||||||
}
|
}
|
||||||
|
if d != 0 {
|
||||||
|
atomic.AddInt64(&c.size, d)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the value if it exists, does not set if it doesn't.
|
// Replace the value if it exists, does not set if it doesn't.
|
||||||
|
@ -128,6 +131,7 @@ func (c *LayeredCache) Clear() {
|
||||||
for _, bucket := range c.buckets {
|
for _, bucket := range c.buckets {
|
||||||
bucket.clear()
|
bucket.clear()
|
||||||
}
|
}
|
||||||
|
c.size = 0
|
||||||
c.list = list.New()
|
c.list = list.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +156,14 @@ func (c *LayeredCache) worker() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case item := <-c.promotables:
|
case item := <-c.promotables:
|
||||||
if c.doPromote(item) && c.size > c.maxItems {
|
if c.doPromote(item) && atomic.LoadInt64(&c.size) > c.maxItems {
|
||||||
c.gc()
|
c.gc()
|
||||||
}
|
}
|
||||||
case item := <-c.deletables:
|
case item := <-c.deletables:
|
||||||
if item.element == nil {
|
if item.element == nil {
|
||||||
item.promotions = -2
|
item.promotions = -2
|
||||||
} else {
|
} else {
|
||||||
c.size -= 1
|
atomic.AddInt64(&c.size, -item.size)
|
||||||
c.list.Remove(item.element)
|
c.list.Remove(item.element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +181,6 @@ func (c *LayeredCache) doPromote(item *Item) bool {
|
||||||
c.list.MoveToFront(item.element)
|
c.list.MoveToFront(item.element)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c.size += 1
|
|
||||||
item.element = c.list.PushFront(item)
|
item.element = c.list.PushFront(item)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -191,7 +194,7 @@ func (c *LayeredCache) gc() {
|
||||||
prev := element.Prev()
|
prev := element.Prev()
|
||||||
item := element.Value.(*Item)
|
item := element.Value.(*Item)
|
||||||
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 {
|
||||||
c.size -= 1
|
atomic.AddInt64(&c.size, -item.size)
|
||||||
c.bucket(item.group).delete(item.group, item.key)
|
c.bucket(item.group).delete(item.group, item.key)
|
||||||
c.list.Remove(element)
|
c.list.Remove(element)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue