[MM 19840] Create a Cache Interface abstraction to support multiple cache backends (#13384)

* Refactor to use structured logging

* Properly formatted with gofmt

* created interface Cache, but construction of cache is still coupled.

* Implementing cache factories to build caches

* Simple redis implementation without error handling. Keys and values by default are string

* refactor NewLocalCacheLayer to inject cache factory

* Removed redis impl to focus on cache abstraction

* CacheFactory injected on sqlsupplier and saved in Store struct

* remove useless private method

* replace concrete declaration of lru cache to cache abstraction

* discard spaces

* Renamed factory to provider because concrete implementations of factories may hold an state (such as a redis client for example)

* refactor to include all caches in the same package cache and subpackages

* method close cache. This method will be used for concrete implementations that need to release resources (close a connection, etc)

* closing cacheprovider and releasing resources while closing sql store

* fixed merge conflict fail

* gofmt files

* remove unused property from post_store

* naming refactor to avoid stutter. Added godocs on interface

* Store doesnt know anything about the cache and provider. Cache provider will be built after loading config and injected in localCacheLayer

* fixed broken test

* cache provider initialized before RunOldAppInitialization which initializes the localcachelayer

* move statusCache to server to initialize it with the new cache provider

* update terms_service and channel_layer to have new cacheProvider

* gofmt

* Add Connect method to the cache provider

* mock cacheprovider in user_layer_test

Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Hector
2020-01-09 09:57:28 +01:00
коммит произвёл Jesús Espino
родитель 1909fd6607
Коммит 62b57143c8
31 изменённых файлов: 572 добавлений и 177 удалений

Просмотреть файл

@@ -1,207 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// This files was copied/modified from https://github.com/hashicorp/golang-lru
// which was (see below)
// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package utils
import (
"container/list"
"sync"
"time"
)
// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
size int
evictList *list.List
items map[interface{}]*list.Element
lock sync.RWMutex
name string
defaultExpiry int64
invalidateClusterEvent string
currentGeneration int64
len int
}
// entry is used to hold a value in the evictList.
type entry struct {
key interface{}
value interface{}
expires time.Time
generation int64
}
// New creates an LRU of the given size.
func NewLru(size int) *Cache {
return &Cache{
size: size,
evictList: list.New(),
items: make(map[interface{}]*list.Element, size),
}
}
// New creates an LRU with the given parameters.
func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
lru := NewLru(size)
lru.name = name
lru.defaultExpiry = defaultExpiry
lru.invalidateClusterEvent = invalidateClusterEvent
return lru
}
// Purge is used to completely clear the cache.
func (c *Cache) Purge() {
c.lock.Lock()
defer c.lock.Unlock()
c.len = 0
c.currentGeneration++
}
// Add adds the given key and value to the store without an expiry.
func (c *Cache) Add(key, value interface{}) {
c.AddWithExpiresInSecs(key, value, 0)
}
// Add adds the given key and value to the store with the default expiry.
func (c *Cache) AddWithDefaultExpires(key, value interface{}) {
c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
}
// AddWithExpiresInSecs adds the given key and value to the cache with the given expiry.
func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) {
c.lock.Lock()
defer c.lock.Unlock()
c.add(key, value, time.Duration(expireAtSecs)*time.Second)
}
func (c *Cache) add(key, value interface{}, ttl time.Duration) {
var expires time.Time
if ttl > 0 {
expires = time.Now().Add(ttl)
}
// Check for existing item, ignoring expiry since we'd update anyway.
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
e := ent.Value.(*entry)
e.value = value
e.expires = expires
if e.generation != c.currentGeneration {
e.generation = c.currentGeneration
c.len++
}
return
}
// Add new item
ent := &entry{key, value, expires, c.currentGeneration}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
c.len++
if c.evictList.Len() > c.size {
c.removeElement(c.evictList.Back())
}
}
// Get returns the value stored in the cache for a key, or nil if no value is present. The ok result indicates whether value was found in the cache.
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
c.lock.Lock()
defer c.lock.Unlock()
return c.getValue(key)
}
func (c *Cache) getValue(key interface{}) (value interface{}, ok bool) {
if ent, ok := c.items[key]; ok {
e := ent.Value.(*entry)
if e.generation != c.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
c.removeElement(ent)
return nil, false
}
c.evictList.MoveToFront(ent)
return ent.Value.(*entry).value, true
}
return nil, false
}
// GetOrAdd returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.
// This API intentionally deviates from the Add-only variants above for simplicity. We should simplify the entire API in the future.
func (c *Cache) GetOrAdd(key, value interface{}, ttl time.Duration) (actual interface{}, loaded bool) {
c.lock.Lock()
defer c.lock.Unlock()
// Check for existing item
if actualValue, ok := c.getValue(key); ok {
return actualValue, true
}
c.add(key, value, ttl)
return value, false
}
// Remove deletes the value for a key.
func (c *Cache) Remove(key interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
}
}
// Keys returns a slice of the keys in the cache, from oldest to newest.
func (c *Cache) Keys() []interface{} {
c.lock.RLock()
defer c.lock.RUnlock()
keys := make([]interface{}, c.len)
i := 0
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
e := ent.Value.(*entry)
if e.generation == c.currentGeneration {
keys[i] = e.key
i++
}
}
return keys
}
// Len returns the number of items in the cache.
func (c *Cache) Len() int {
c.lock.RLock()
defer c.lock.RUnlock()
return c.len
}
// Name identifies this cache instance among others in the system.
func (c *Cache) Name() string {
return c.name
}
// GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
func (c *Cache) GetInvalidateClusterEvent() string {
return c.invalidateClusterEvent
}
func (c *Cache) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*entry)
if kv.generation == c.currentGeneration {
c.len--
}
delete(c.items, kv.key)
}

Просмотреть файл

@@ -1,124 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// This files was copied/modified from https://github.com/hashicorp/golang-lru
// which was (see below)
// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package utils
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLRU(t *testing.T) {
l := NewLru(128)
for i := 0; i < 256; i++ {
l.Add(i, i)
}
require.Equalf(t, l.Len(), 128, "bad len: %v", l.Len())
for i, k := range l.Keys() {
v, ok := l.Get(k)
require.True(t, ok, "bad key: %v", k)
require.Equalf(t, v, k, "bad key: %v", k)
require.Equalf(t, i+128, v, "bad key: %v", k)
}
for i := 0; i < 128; i++ {
_, ok := l.Get(i)
require.False(t, ok, "should be evicted")
}
for i := 128; i < 256; i++ {
_, ok := l.Get(i)
require.True(t, ok, "should not be evicted")
}
for i := 128; i < 192; i++ {
l.Remove(i)
_, ok := l.Get(i)
require.False(t, ok, "should be deleted")
}
l.Get(192) // expect 192 to be last key in l.Keys()
for i, k := range l.Keys() {
require.Falsef(t, (i < 63 && k != i+193), "out of order key: %v", k)
require.Falsef(t, (i == 63 && k != 192), "out of order key: %v", k)
}
l.Purge()
require.Equalf(t, l.Len(), 0, "bad len: %v", l.Len())
_, ok := l.Get(200)
require.False(t, ok, "should contain nothing")
}
func TestLRUExpire(t *testing.T) {
l := NewLru(128)
l.AddWithExpiresInSecs(1, 1, 1)
l.AddWithExpiresInSecs(2, 2, 1)
l.AddWithExpiresInSecs(3, 3, 0)
time.Sleep(time.Millisecond * 2100)
r1, ok := l.Get(1)
require.False(t, ok, r1)
_, ok2 := l.Get(3)
require.True(t, ok2, "should exist")
}
func TestLRUGetOrAdd(t *testing.T) {
l := NewLru(128)
// First GetOrAdd should save
value, loaded := l.GetOrAdd(1, 1, 0)
assert.Equal(t, 1, value)
assert.False(t, loaded)
// Second GetOrAdd should load original value, ignoring new value
value, loaded = l.GetOrAdd(1, 10, 0)
assert.Equal(t, 1, value)
assert.True(t, loaded)
// Third GetOrAdd should still load original value
value, loaded = l.GetOrAdd(1, 1, 0)
assert.Equal(t, 1, value)
assert.True(t, loaded)
// First GetOrAdd on a new key should save
value, loaded = l.GetOrAdd(2, 2, 0)
assert.Equal(t, 2, value)
assert.False(t, loaded)
l.Remove(1)
// GetOrAdd after a remove should save
value, loaded = l.GetOrAdd(1, 10, 0)
assert.Equal(t, 10, value)
assert.False(t, loaded)
// GetOrAdd after another key was removed should load original value for key
value, loaded = l.GetOrAdd(2, 2, 0)
assert.Equal(t, 2, value)
assert.True(t, loaded)
// GetOrAdd should expire
value, loaded = l.GetOrAdd(3, 3, 500*time.Millisecond)
assert.Equal(t, 3, value)
assert.False(t, loaded)
value, loaded = l.GetOrAdd(3, 4, 500*time.Millisecond)
assert.Equal(t, 3, value)
assert.True(t, loaded)
time.Sleep(1 * time.Second)
value, loaded = l.GetOrAdd(3, 5, 500*time.Millisecond)
assert.Equal(t, 5, value)
assert.False(t, loaded)
}