Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit fa43a86

Browse files
committed
add findCache for memoryIdx
Cache patterns and their results in a LRU. When new defs are added to the index, only cached patterns that match the new series name are invalidated. The cache is purged after every prune task runs and after any call to delete items from the index.
1 parent 20f6fde commit fa43a86

File tree

9 files changed

+186
-22
lines changed

9 files changed

+186
-22
lines changed

docker/docker-chaos/metrictank.ini

+2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ rules-file = /etc/metrictank/index-rules.conf
396396
max-prune-lock-time = 100ms
397397
# use separate indexes per partition
398398
partitioned = false
399+
# number of find expressions to cache
400+
find-cache-size = 1000
399401

400402
### Bigtable index
401403
[bigtable-idx]

docker/docker-cluster/metrictank.ini

+2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ rules-file = /etc/metrictank/index-rules.conf
396396
max-prune-lock-time = 100ms
397397
# use separate indexes per partition
398398
partitioned = false
399+
# number of find expressions to cache
400+
find-cache-size = 1000
399401

400402
### Bigtable index
401403
[bigtable-idx]

docker/docker-dev-custom-cfg-kafka/metrictank.ini

+2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ rules-file = /etc/metrictank/index-rules.conf
396396
max-prune-lock-time = 100ms
397397
# use separate indexes per partition
398398
partitioned = false
399+
# number of find expressions to cache
400+
find-cache-size = 1000
399401

400402
### Bigtable index
401403
[bigtable-idx]

docs/config.md

+2
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ rules-file = /etc/metrictank/index-rules.conf
463463
max-prune-lock-time = 100ms
464464
# use separate indexes per partition
465465
partitioned = false
466+
# number of find expressions to cache
467+
find-cache-size = 1000
466468
```
467469

468470
### Bigtable index

idx/memory/find_cache.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package memory
2+
3+
import (
4+
"strings"
5+
"sync"
6+
7+
"github.com/grafana/metrictank/stats"
8+
lru "github.com/hashicorp/golang-lru"
9+
"github.com/raintank/schema"
10+
log "github.com/sirupsen/logrus"
11+
)
12+
13+
var (
14+
// metric idx.memory.find-cache.hit is a counter findCache hits
15+
findCacheHit = stats.NewCounterRate32("idx.memory.find-cache.hit")
16+
// metric idx.memory.find-cache.hit is a counter findCache misses
17+
findCacheMiss = stats.NewCounterRate32("idx.memory.find-cache.miss")
18+
)
19+
20+
type FindCache struct {
21+
cache map[uint32]*lru.Cache
22+
size int
23+
sync.RWMutex
24+
}
25+
26+
func NewFindCache(size int) *FindCache {
27+
return &FindCache{
28+
cache: make(map[uint32]*lru.Cache),
29+
size: size,
30+
}
31+
}
32+
33+
func (c *FindCache) Get(orgId uint32, pattern string) ([]*Node, bool) {
34+
c.RLock()
35+
cache, ok := c.cache[orgId]
36+
c.RUnlock()
37+
if !ok {
38+
findCacheMiss.Inc()
39+
return nil, false
40+
}
41+
nodes, ok := cache.Get(pattern)
42+
if !ok {
43+
return nil, ok
44+
}
45+
findCacheHit.Inc()
46+
return nodes.([]*Node), ok
47+
}
48+
49+
func (c *FindCache) Add(orgId uint32, pattern string, nodes []*Node) {
50+
c.RLock()
51+
cache, ok := c.cache[orgId]
52+
c.RUnlock()
53+
var err error
54+
if !ok {
55+
cache, err = lru.New(c.size)
56+
if err != nil {
57+
log.Errorf("memory-idx: findCache failed to create lru. err=%s", err)
58+
return
59+
}
60+
c.Lock()
61+
c.cache[orgId] = cache
62+
c.Unlock()
63+
}
64+
cache.Add(pattern, nodes)
65+
}
66+
67+
func (c *FindCache) Purge(orgId uint32) {
68+
c.RLock()
69+
cache, ok := c.cache[orgId]
70+
c.RUnlock()
71+
if !ok {
72+
return
73+
}
74+
cache.Purge()
75+
}
76+
77+
func (c *FindCache) InvalidateFor(orgId uint32, path string) {
78+
c.RLock()
79+
cache, ok := c.cache[orgId]
80+
c.RUnlock()
81+
if !ok || cache.Len() < 1 {
82+
return
83+
}
84+
tree := &Tree{
85+
Items: map[string]*Node{
86+
"": &Node{
87+
Path: "",
88+
Children: make([]string, 0),
89+
Defs: make([]schema.MKey, 0),
90+
},
91+
},
92+
}
93+
pos := strings.Index(path, ".")
94+
prevPos := 0
95+
for {
96+
branch := path[:pos]
97+
// add as child of parent branch
98+
tree.Items[path[:prevPos]].Children = []string{branch}
99+
100+
// create this branch/leaf
101+
tree.Items[branch] = &Node{
102+
Path: branch,
103+
}
104+
if branch == path {
105+
tree.Items[branch].Defs = []schema.MKey{{}}
106+
break
107+
}
108+
prevPos = pos
109+
pos = pos + strings.Index(path[pos+1:], ".")
110+
}
111+
112+
for _, k := range cache.Keys() {
113+
matches, err := find(tree, k.(string))
114+
if err != nil {
115+
log.Errorf("memory-idx: checking if new series matches expressions in findCache. series=%s expr=%s", path, k)
116+
continue
117+
}
118+
if len(matches) > 0 {
119+
cache.Remove(k)
120+
}
121+
}
122+
}

idx/memory/memory.go

+50-22
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ var (
5656
indexRulesFile string
5757
IndexRules conf.IndexRules
5858
Partitioned bool
59+
findCacheSize = 1000
5960
)
6061

6162
func ConfigSetup() {
@@ -65,6 +66,7 @@ func ConfigSetup() {
6566
memoryIdx.BoolVar(&Partitioned, "partitioned", false, "use separate indexes per partition")
6667
memoryIdx.IntVar(&TagQueryWorkers, "tag-query-workers", 50, "number of workers to spin up to evaluate tag queries")
6768
memoryIdx.IntVar(&matchCacheSize, "match-cache-size", 1000, "size of regular expression cache in tag query evaluation")
69+
memoryIdx.IntVar(&findCacheSize, "find-cache-size", 1000, "number of find expressions to cache")
6870
memoryIdx.StringVar(&indexRulesFile, "rules-file", "/etc/metrictank/index-rules.conf", "path to index-rules.conf file")
6971
memoryIdx.StringVar(&maxPruneLockTimeStr, "max-prune-lock-time", "100ms", "Maximum duration each second a prune job can lock the index.")
7072
globalconf.Register("memory-idx", memoryIdx, flag.ExitOnError)
@@ -233,6 +235,8 @@ type UnpartitionedMemoryIdx struct {
233235
// used by tag index
234236
defByTagSet defByTagSet
235237
tags map[uint32]TagIndex // by orgId
238+
239+
findCache *FindCache
236240
}
237241

238242
func NewUnpartitionedMemoryIdx() *UnpartitionedMemoryIdx {
@@ -241,6 +245,7 @@ func NewUnpartitionedMemoryIdx() *UnpartitionedMemoryIdx {
241245
defByTagSet: make(defByTagSet),
242246
tree: make(map[uint32]*Tree),
243247
tags: make(map[uint32]TagIndex),
248+
findCache: NewFindCache(findCacheSize),
244249
}
245250
}
246251

@@ -453,6 +458,11 @@ func (m *UnpartitionedMemoryIdx) add(def *schema.MetricDefinition) idx.Archive {
453458
return *archive
454459
}
455460

461+
// invalidate any findCache items that match this series name
462+
defer func() {
463+
go m.findCache.InvalidateFor(def.OrgId, path)
464+
}()
465+
456466
//first check to see if a tree has been created for this OrgId
457467
tree, ok := m.tree[def.OrgId]
458468
if !ok || len(tree.Items) == 0 {
@@ -943,18 +953,36 @@ func (m *UnpartitionedMemoryIdx) idsByTagQuery(orgId uint32, query TagQuery) IdS
943953

944954
func (m *UnpartitionedMemoryIdx) Find(orgId uint32, pattern string, from int64) ([]idx.Node, error) {
945955
pre := time.Now()
956+
var matchedNodes []*Node
957+
var err error
946958
m.RLock()
947959
defer m.RUnlock()
948-
matchedNodes, err := m.find(orgId, pattern)
949-
if err != nil {
950-
return nil, err
960+
tree, ok := m.tree[orgId]
961+
if !ok {
962+
log.Debugf("memory-idx: orgId %d has no metrics indexed.", orgId)
963+
} else {
964+
matchedNodes, ok = m.findCache.Get(orgId, pattern)
965+
if !ok {
966+
matchedNodes, err = find(tree, pattern)
967+
if err != nil {
968+
return nil, err
969+
}
970+
m.findCache.Add(orgId, pattern, matchedNodes)
971+
}
951972
}
952973
if orgId != idx.OrgIdPublic && idx.OrgIdPublic > 0 {
953-
publicNodes, err := m.find(idx.OrgIdPublic, pattern)
954-
if err != nil {
955-
return nil, err
974+
tree, ok = m.tree[idx.OrgIdPublic]
975+
if ok {
976+
publicNodes, ok := m.findCache.Get(idx.OrgIdPublic, pattern)
977+
if !ok {
978+
publicNodes, err = find(tree, pattern)
979+
if err != nil {
980+
return nil, err
981+
}
982+
m.findCache.Add(idx.OrgIdPublic, pattern, publicNodes)
983+
}
984+
matchedNodes = append(matchedNodes, publicNodes...)
956985
}
957-
matchedNodes = append(matchedNodes, publicNodes...)
958986
}
959987
log.Debugf("memory-idx: %d nodes matching pattern %s found", len(matchedNodes), pattern)
960988
results := make([]idx.Node, 0)
@@ -997,14 +1025,8 @@ func (m *UnpartitionedMemoryIdx) Find(orgId uint32, pattern string, from int64)
9971025
return results, nil
9981026
}
9991027

1000-
// find returns all Nodes matching the pattern for the given orgId
1001-
func (m *UnpartitionedMemoryIdx) find(orgId uint32, pattern string) ([]*Node, error) {
1002-
tree, ok := m.tree[orgId]
1003-
if !ok {
1004-
log.Debugf("memory-idx: orgId %d has no metrics indexed.", orgId)
1005-
return nil, nil
1006-
}
1007-
1028+
// find returns all Nodes matching the pattern for the given tree
1029+
func find(tree *Tree, pattern string) ([]*Node, error) {
10081030
var nodes []string
10091031
if strings.Index(pattern, ";") == -1 {
10101032
nodes = strings.Split(pattern, ".")
@@ -1031,17 +1053,17 @@ func (m *UnpartitionedMemoryIdx) find(orgId uint32, pattern string) ([]*Node, er
10311053
if pos != 0 {
10321054
branch = strings.Join(nodes[:pos], ".")
10331055
}
1034-
log.Debugf("memory-idx: starting search at orgId %d, node %q", orgId, branch)
1056+
log.Debugf("memory-idx: starting search at node %q", branch)
10351057
startNode, ok := tree.Items[branch]
10361058

10371059
if !ok {
1038-
log.Debugf("memory-idx: branch %q does not exist in the index for orgId %d", branch, orgId)
1060+
log.Debugf("memory-idx: branch %q does not exist in the index", branch)
10391061
return nil, nil
10401062
}
10411063

10421064
if startNode == nil {
10431065
corruptIndex.Inc()
1044-
log.Errorf("memory-idx: startNode is nil. org=%d,patt=%q,pos=%d,branch=%q", orgId, pattern, pos, branch)
1066+
log.Errorf("memory-idx: startNode is nil. patt=%q,pos=%d,branch=%q", pattern, pos, branch)
10451067
return nil, errors.NewInternal("hit an empty path in the index")
10461068
}
10471069

@@ -1072,7 +1094,7 @@ func (m *UnpartitionedMemoryIdx) find(orgId uint32, pattern string) ([]*Node, er
10721094
grandChild := tree.Items[newBranch]
10731095
if grandChild == nil {
10741096
corruptIndex.Inc()
1075-
log.Errorf("memory-idx: grandChild is nil. org=%d,patt=%q,i=%d,pos=%d,p=%q,path=%q", orgId, pattern, i, pos, p, newBranch)
1097+
log.Errorf("memory-idx: grandChild is nil. patt=%q,i=%d,pos=%d,p=%q,path=%q", pattern, i, pos, p, newBranch)
10761098
return nil, errors.NewInternal("hit an empty path in the index")
10771099
}
10781100

@@ -1180,7 +1202,11 @@ func (m *UnpartitionedMemoryIdx) Delete(orgId uint32, pattern string) ([]idx.Arc
11801202
pre := time.Now()
11811203
m.Lock()
11821204
defer m.Unlock()
1183-
found, err := m.find(orgId, pattern)
1205+
tree, ok := m.tree[orgId]
1206+
if !ok {
1207+
return nil, nil
1208+
}
1209+
found, err := find(tree, pattern)
11841210
if err != nil {
11851211
return nil, err
11861212
}
@@ -1192,7 +1218,9 @@ func (m *UnpartitionedMemoryIdx) Delete(orgId uint32, pattern string) ([]idx.Arc
11921218

11931219
statMetricsActive.Set(len(m.defById))
11941220
statDeleteDuration.Value(time.Since(pre))
1195-
1221+
if len(deletedDefs) > 0 {
1222+
m.findCache.Purge(orgId)
1223+
}
11961224
return deletedDefs, nil
11971225
}
11981226

@@ -1411,8 +1439,8 @@ ORGS:
14111439
m.Unlock()
14121440
tl.Add(time.Since(lockStart))
14131441
pruned = append(pruned, defs...)
1414-
14151442
}
1443+
m.findCache.Purge(org)
14161444
}
14171445

14181446
statMetricsActive.Set(len(m.defById))

metrictank-sample.ini

+2
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ rules-file = /etc/metrictank/index-rules.conf
399399
max-prune-lock-time = 100ms
400400
# use separate indexes per partition
401401
partitioned = false
402+
# number of find expressions to cache
403+
find-cache-size = 1000
402404

403405
### Bigtable index
404406
[bigtable-idx]

scripts/config/metrictank-docker.ini

+2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ rules-file = /etc/metrictank/index-rules.conf
396396
max-prune-lock-time = 100ms
397397
# use separate indexes per partition
398398
partitioned = false
399+
# number of find expressions to cache
400+
find-cache-size = 1000
399401

400402
### Bigtable index
401403
[bigtable-idx]

scripts/config/metrictank-package.ini

+2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ rules-file = /etc/metrictank/index-rules.conf
396396
max-prune-lock-time = 100ms
397397
# use separate indexes per partition
398398
partitioned = false
399+
# number of find expressions to cache
400+
find-cache-size = 1000
399401

400402
### Bigtable index
401403
[bigtable-idx]

0 commit comments

Comments
 (0)