Performance was suffering with very large caches, when the cache was

under maxItems pressure. Reverted the LRU handler back to the first
iteration: lazy deletion via the janitor, with bulk removal.
This commit is contained in:
Matt Keller 2015-03-08 11:50:57 -04:00
parent b60c6ee2c8
commit bf414c412e
1 changed files with 49 additions and 47 deletions

View File

@ -90,11 +90,8 @@ func (c *cache) set(k string, x interface{}, d time.Duration) {
if c.maxItems > 0 { if c.maxItems > 0 {
// We don't want to hit the expiration if we're updating. // We don't want to hit the expiration if we're updating.
_, found := c.get(k) _, found := c.get(k)
if ! found { if found {
// Create, means maybe expire // Update means change the Ctime
c.ExpireLRU()
} else {
// Update, means don't expire, or change the Ctime
ct = c.items[k].Created() ct = c.items[k].Created()
} }
c.items[k] = &Item{ c.items[k] = &Item{
@ -1062,16 +1059,11 @@ func (c *cache) ItemCount() int {
return n return n
} }
// Returns the number of items in the cache *that have not expired* // Returns the number of items in the cache without locking. This may include
// items that have expired, but have not yet been cleaned up. Equivalent to
// len(c.Items()).
func (c *cache) itemCount() int { func (c *cache) itemCount() int {
n := 0 n := len(c.items)
now := time.Now()
for _, v := range c.items {
if v.Expiration == nil || ! v.Expiration.Before(now) {
n++
}
}
return n return n
} }
@ -1082,48 +1074,50 @@ func (c *cache) Flush() {
c.Unlock() c.Unlock()
} }
// Find oldest N items, and Expire them. // Find oldest N items, and delete them.
// Returns the number "expired". func (c *cache) DeleteLRU(numItems int) {
// BIG HAIRY ALERT: This function assumes it is called
// from inside a c.Lock() .. c.Unlock() block.
func (c *cache) ExpireLRU() {
// We need the number of UNexpired Items
itemCount := c.itemCount()
if itemCount >= c.maxItems {
// Cache the current time. c.Lock()
// We do this "early" so that when we set an Expiration,
// it is definitely "do" when the janitor ticks
now := time.Now()
var lastTime int64 = 0 var lastTime int64 = 0
var lastName string = "" lastItems := make([]string, numItems) // ringbuffer for the last numItems
liCount := 0
full := false
for k, v := range c.items { for k, v := range c.items {
if v.Expired() == false { if v.Expired() == false {
// unexpired item // unexpired item
atime := v.LastAccessed().UnixNano()
if full == false || atime < lastTime {
// We found a least-recently-used item,
// or our purge buffer isn't full yet
lastTime = atime
atime := v.LastAccessed().UnixNano() // Append it to the buffer,
if lastTime == 0 { // or start overwriting it
// We need to expire something, so let's start with the first item if liCount < numItems {
lastTime = atime lastItems[liCount] = k
lastName = k liCount ++
} else if atime < lastTime { } else {
// We found a least-recently-used item lastItems[0] = k
lastTime = atime liCount = 1
lastName = k full = true
} }
} }
} }
}
if lastTime > 0 {
// We expire the item, but making it look .Expired(), if lastTime > 0 {
// so the janitor will clean it up for us // We expire the items, but making it look .Expired(),
c.items[lastName].Expiration = &now // so the janitor will clean it up for us
for i := 0; i < len(lastItems) && lastItems[i] != ""; i++ {
lastName := lastItems[i]
c.delete(lastName)
} }
} }
c.Unlock()
} }
type janitor struct { type janitor struct {
@ -1138,6 +1132,14 @@ func (j *janitor) Run(c *cache) {
select { select {
case <-tick: case <-tick:
c.DeleteExpired() c.DeleteExpired()
if c.maxItems > 0 {
// Purge any LRU overages
overCount := c.itemCount() - c.maxItems
if overCount > 0 {
c.DeleteLRU(overCount)
}
}
case <-j.stop: case <-j.stop:
return return
} }