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:
parent
b60c6ee2c8
commit
bf414c412e
92
cache.go
92
cache.go
|
@ -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
|
c.Lock()
|
||||||
itemCount := c.itemCount()
|
|
||||||
|
|
||||||
if itemCount >= c.maxItems {
|
var lastTime int64 = 0
|
||||||
|
lastItems := make([]string, numItems) // ringbuffer for the last numItems
|
||||||
|
liCount := 0
|
||||||
|
full := false
|
||||||
|
|
||||||
// Cache the current time.
|
for k, v := range c.items {
|
||||||
// We do this "early" so that when we set an Expiration,
|
if v.Expired() == false {
|
||||||
// it is definitely "do" when the janitor ticks
|
// unexpired item
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
var lastTime int64 = 0
|
atime := v.LastAccessed().UnixNano()
|
||||||
var lastName string = ""
|
if full == false || atime < lastTime {
|
||||||
|
// We found a least-recently-used item,
|
||||||
|
// or our purge buffer isn't full yet
|
||||||
|
lastTime = atime
|
||||||
|
|
||||||
for k, v := range c.items {
|
// Append it to the buffer,
|
||||||
if v.Expired() == false {
|
// or start overwriting it
|
||||||
// unexpired item
|
if liCount < numItems {
|
||||||
|
lastItems[liCount] = k
|
||||||
atime := v.LastAccessed().UnixNano()
|
liCount ++
|
||||||
if lastTime == 0 {
|
} else {
|
||||||
// We need to expire something, so let's start with the first item
|
lastItems[0] = k
|
||||||
lastTime = atime
|
liCount = 1
|
||||||
lastName = k
|
full = true
|
||||||
} else if atime < lastTime {
|
|
||||||
// We found a least-recently-used item
|
|
||||||
lastTime = atime
|
|
||||||
lastName = k
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if lastTime > 0 {
|
if lastTime > 0 {
|
||||||
// We expire the item, but making it look .Expired(),
|
// We expire the items, but making it look .Expired(),
|
||||||
// so the janitor will clean it up for us
|
// so the janitor will clean it up for us
|
||||||
c.items[lastName].Expiration = &now
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue