Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: use groupedCache to optimize cache access (#944)
* refactor: use groupedCache to optimize cache access * refactor: fix review findings
- Loading branch information
Showing
11 changed files
with
584 additions
and
288 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package stringcache | ||
|
||
import ( | ||
"sort" | ||
|
||
"golang.org/x/exp/maps" | ||
) | ||
|
||
type ChainedGroupedCache struct { | ||
caches []GroupedStringCache | ||
} | ||
|
||
func NewChainedGroupedCache(caches ...GroupedStringCache) *ChainedGroupedCache { | ||
return &ChainedGroupedCache{ | ||
caches: caches, | ||
} | ||
} | ||
|
||
func (c *ChainedGroupedCache) ElementCount(group string) int { | ||
sum := 0 | ||
for _, cache := range c.caches { | ||
sum += cache.ElementCount(group) | ||
} | ||
|
||
return sum | ||
} | ||
|
||
func (c *ChainedGroupedCache) Contains(searchString string, groups []string) []string { | ||
groupMatchedMap := make(map[string]struct{}, len(groups)) | ||
|
||
for _, cache := range c.caches { | ||
for _, group := range cache.Contains(searchString, groups) { | ||
groupMatchedMap[group] = struct{}{} | ||
} | ||
} | ||
|
||
matchedGroups := maps.Keys(groupMatchedMap) | ||
|
||
sort.Strings(matchedGroups) | ||
|
||
return matchedGroups | ||
} | ||
|
||
func (c *ChainedGroupedCache) Refresh(group string) GroupFactory { | ||
cacheFactories := make([]GroupFactory, len(c.caches)) | ||
for i, cache := range c.caches { | ||
cacheFactories[i] = cache.Refresh(group) | ||
} | ||
|
||
return &chainedGroupFactory{ | ||
cacheFactories: cacheFactories, | ||
} | ||
} | ||
|
||
type chainedGroupFactory struct { | ||
cacheFactories []GroupFactory | ||
} | ||
|
||
func (c *chainedGroupFactory) AddEntry(entry string) { | ||
for _, factory := range c.cacheFactories { | ||
factory.AddEntry(entry) | ||
} | ||
} | ||
|
||
func (c *chainedGroupFactory) Count() int { | ||
var cnt int | ||
for _, factory := range c.cacheFactories { | ||
cnt += factory.Count() | ||
} | ||
|
||
return cnt | ||
} | ||
|
||
func (c *chainedGroupFactory) Finish() { | ||
for _, factory := range c.cacheFactories { | ||
factory.Finish() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package stringcache_test | ||
|
||
import ( | ||
"github.com/0xERR0R/blocky/cache/stringcache" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
var _ = Describe("Chained grouped cache", func() { | ||
Describe("Empty cache", func() { | ||
When("empty cache was created", func() { | ||
cache := stringcache.NewChainedGroupedCache() | ||
|
||
It("should have element count of 0", func() { | ||
Expect(cache.ElementCount("someGroup")).Should(BeNumerically("==", 0)) | ||
}) | ||
|
||
It("should not find any string", func() { | ||
Expect(cache.Contains("searchString", []string{"someGroup"})).Should(BeEmpty()) | ||
}) | ||
}) | ||
}) | ||
Describe("Delegation", func() { | ||
When("Chained cache contains delegates", func() { | ||
inMemoryCache1 := stringcache.NewInMemoryGroupedStringCache() | ||
inMemoryCache2 := stringcache.NewInMemoryGroupedStringCache() | ||
cache := stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2) | ||
|
||
factory := cache.Refresh("group1") | ||
|
||
factory.AddEntry("string1") | ||
factory.AddEntry("string2") | ||
|
||
It("cache should still have 0 element, since finish was not executed", func() { | ||
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0)) | ||
}) | ||
|
||
It("factory has 4 elements (both caches)", func() { | ||
Expect(factory.Count()).Should(BeNumerically("==", 4)) | ||
}) | ||
|
||
It("should have element count of 4", func() { | ||
factory.Finish() | ||
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4)) | ||
}) | ||
|
||
It("should find strings", func() { | ||
Expect(cache.Contains("string1", []string{"group1"})).Should(ConsistOf("group1")) | ||
Expect(cache.Contains("string2", []string{"group1", "someOtherGroup"})).Should(ConsistOf("group1")) | ||
}) | ||
}) | ||
}) | ||
|
||
Describe("Cache refresh", func() { | ||
When("cache with 2 groups was created", func() { | ||
inMemoryCache1 := stringcache.NewInMemoryGroupedStringCache() | ||
inMemoryCache2 := stringcache.NewInMemoryGroupedStringCache() | ||
cache := stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2) | ||
|
||
factory := cache.Refresh("group1") | ||
|
||
factory.AddEntry("g1") | ||
factory.AddEntry("both") | ||
factory.Finish() | ||
|
||
factory = cache.Refresh("group2") | ||
factory.AddEntry("g2") | ||
factory.AddEntry("both") | ||
factory.Finish() | ||
|
||
It("should contain 4 elements in 2 groups", func() { | ||
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4)) | ||
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4)) | ||
Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(ConsistOf("group1")) | ||
Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2")) | ||
Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group1", "group2")) | ||
}) | ||
|
||
It("Should replace group content on refresh", func() { | ||
factory := cache.Refresh("group1") | ||
factory.AddEntry("newString") | ||
factory.Finish() | ||
|
||
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2)) | ||
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4)) | ||
Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(BeEmpty()) | ||
Expect(cache.Contains("newString", []string{"group1", "group2"})).Should(ConsistOf("group1")) | ||
Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2")) | ||
Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group2")) | ||
}) | ||
}) | ||
|
||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package stringcache | ||
|
||
type GroupedStringCache interface { | ||
// Contains checks if one or more groups in the cache contains the search string. | ||
// Returns group(s) containing the string or empty slice if string was not found | ||
Contains(searchString string, groups []string) []string | ||
|
||
// Refresh creates new factory for the group to be refreshed. | ||
// Calling Finish on the factory will perform the group refresh. | ||
Refresh(group string) GroupFactory | ||
|
||
// ElementCount returns the amount of elements in the group | ||
ElementCount(group string) int | ||
} | ||
|
||
type GroupFactory interface { | ||
// AddEntry adds a new string to the factory to be added later to the cache groups. | ||
AddEntry(entry string) | ||
|
||
// Count returns amount of processed string in the factory | ||
Count() int | ||
|
||
// Finish replaces the group in cache with factory's content | ||
Finish() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package stringcache | ||
|
||
import "sync" | ||
|
||
type stringCacheFactoryFn func() cacheFactory | ||
|
||
type InMemoryGroupedCache struct { | ||
caches map[string]stringCache | ||
lock sync.RWMutex | ||
factoryFn stringCacheFactoryFn | ||
} | ||
|
||
func NewInMemoryGroupedStringCache() *InMemoryGroupedCache { | ||
return &InMemoryGroupedCache{ | ||
caches: make(map[string]stringCache), | ||
factoryFn: newStringCacheFactory, | ||
} | ||
} | ||
|
||
func NewInMemoryGroupedRegexCache() *InMemoryGroupedCache { | ||
return &InMemoryGroupedCache{ | ||
caches: make(map[string]stringCache), | ||
factoryFn: newRegexCacheFactory, | ||
} | ||
} | ||
|
||
func (c *InMemoryGroupedCache) ElementCount(group string) int { | ||
c.lock.RLock() | ||
cache, found := c.caches[group] | ||
c.lock.RUnlock() | ||
|
||
if !found { | ||
return 0 | ||
} | ||
|
||
return cache.elementCount() | ||
} | ||
|
||
func (c *InMemoryGroupedCache) Contains(searchString string, groups []string) []string { | ||
var result []string | ||
|
||
for _, group := range groups { | ||
c.lock.RLock() | ||
cache, found := c.caches[group] | ||
c.lock.RUnlock() | ||
|
||
if found && cache.contains(searchString) { | ||
result = append(result, group) | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
func (c *InMemoryGroupedCache) Refresh(group string) GroupFactory { | ||
return &inMemoryGroupFactory{ | ||
factory: c.factoryFn(), | ||
finishFn: func(sc stringCache) { | ||
c.lock.Lock() | ||
c.caches[group] = sc | ||
c.lock.Unlock() | ||
}, | ||
} | ||
} | ||
|
||
type inMemoryGroupFactory struct { | ||
factory cacheFactory | ||
finishFn func(stringCache) | ||
} | ||
|
||
func (c *inMemoryGroupFactory) AddEntry(entry string) { | ||
c.factory.addEntry(entry) | ||
} | ||
|
||
func (c *inMemoryGroupFactory) Count() int { | ||
return c.factory.count() | ||
} | ||
|
||
func (c *inMemoryGroupFactory) Finish() { | ||
sc := c.factory.create() | ||
c.finishFn(sc) | ||
} |
Oops, something went wrong.