-
Notifications
You must be signed in to change notification settings - Fork 454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add lru cache for query conversion #1398
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1398 +/- ##
=========================================
- Coverage 70.7% 39.1% -31.6%
=========================================
Files 827 5 -822
Lines 71238 342 -70896
=========================================
- Hits 50414 134 -50280
+ Misses 17519 194 -17325
+ Partials 3305 14 -3291
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #1398 +/- ##
========================================
- Coverage 70.8% 63.1% -7.7%
========================================
Files 832 831 -1
Lines 71370 71248 -122
========================================
- Hits 50542 45001 -5541
- Misses 17526 23103 +5577
+ Partials 3302 3144 -158
Continue to review full report at Codecov.
|
@@ -53,6 +53,8 @@ const ( | |||
errNoIDGenerationScheme = "error: a recent breaking change means that an ID " + | |||
"generation scheme is required in coordinator configuration settings. " + | |||
"More information is available here: %s" | |||
|
|||
defaultQueryConversionCacheSize = 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably can be a lot higher?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, make it at least 4096
@@ -120,6 +122,9 @@ type Configuration struct { | |||
|
|||
// LookbackDuration determines the lookback duration for queries | |||
LookbackDuration *time.Duration `yaml:"lookbackDuration"` | |||
|
|||
// Cache configurations. | |||
Cache CacheConfigurations `yaml:"cache"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this instead of directly adding query conversion cache in case we have more cache options to add in the future
@@ -55,3 +55,7 @@ carbon: | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either add this to all configs or remove since it defaults
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd err towards default
// AddWithLock adds a value to the cache. Returns true if an eviction occurred. | ||
func (c *QueryConversionLRU) AddWithLock(key string, value idx.Query) (evicted bool) { | ||
// Check for existing item | ||
c.Lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a fan of the pattern the hashicorp implementation uses for this--implement all the datastructure operations without a lock, and then wrap that class in a thin class with a lock. Saves you on defer overhead while still maintaining lock safety fairly easily. Thoughts?
} | ||
|
||
// GetWithLock looks up a key's value from the cache. | ||
func (c *QueryConversionLRU) GetWithLock(key string) (value idx.Query, ok bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If key
is always a string, why is items
map[interface]
?
|
||
// QueryConversionLRU implements a fixed size LRU cache | ||
type QueryConversionLRU struct { | ||
sync.Mutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: embedding is weird here imo (sort of implies that the LRU is a lock); I would just use an explicit field.
c := &QueryConversionLRU{ | ||
size: size, | ||
evictList: list.New(), | ||
items: make(map[interface{}]*list.Element), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Size is known here, so let's use it in make
} | ||
|
||
// AddWithLock adds a value to the cache. Returns true if an eviction occurred. | ||
func (c *QueryConversionLRU) AddWithLock(key string, value idx.Query) (evicted bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't think the WithLock
suffixes are really necessary; we can comment the class as thread safe or not instead.
5456b6e
to
9199bf5
Compare
@@ -53,6 +53,8 @@ const ( | |||
errNoIDGenerationScheme = "error: a recent breaking change means that an ID " + | |||
"generation scheme is required in coordinator configuration settings. " + | |||
"More information is available here: %s" | |||
|
|||
defaultQueryConversionCacheSize = 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, make it at least 4096
@@ -146,6 +151,37 @@ type FilterConfiguration struct { | |||
CompleteTags Filter `yaml:"completeTags"` | |||
} | |||
|
|||
// CacheConfigurations is the cache configurations. | |||
type CacheConfigurations struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: CacheConfiguration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better with the s
as it implies it can be used for multiple cache configs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All other configs are XyzConfiguration
even with multiple configs, should probably stick to the precedent here
src/query/storage/index.go
Outdated
|
||
"github.com/m3db/m3/src/dbnode/storage/index" | ||
"github.com/m3db/m3/src/m3ninx/idx" | ||
"github.com/m3db/m3/src/query/models" | ||
"github.com/m3db/m3x/ident" | ||
) | ||
|
||
// QueryConvserionCache represents the query conversion LRU cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QueryConvserionCache
-> QueryConversionCache
lru.Add("c", idx.NewTermQuery([]byte("bar"), []byte("foo"))) | ||
lru.Add("d", idx.NewTermQuery([]byte("baz"), []byte("biz"))) | ||
lru.Add("e", idx.NewTermQuery([]byte("qux"), []byte("quz"))) | ||
lru.Add("f", idx.NewTermQuery([]byte("quz"), []byte("qux"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check that the return types for the others are false
and the return type for this one is true
?
src/query/storage/m3/storage.go
Outdated
opts := m3db.NewOptions(). | ||
SetTagOptions(tagOptions). | ||
SetLookbackDuration(lookbackDuration). | ||
SetConsolidationFunc(consolidators.TakeLast) | ||
|
||
conversionLRU, err := storage.NewQueryConversionLRU(conversionCacheSize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would make a bit more sense to create the cache above and then pass the cache into the storage constructor?
src/query/storage/m3/storage.go
Outdated
return &m3storage{ | ||
clusters: clusters, | ||
readWorkerPool: readWorkerPool, | ||
writeWorkerPool: writeWorkerPool, | ||
opts: opts, | ||
nowFn: time.Now, | ||
} | ||
conversionCache: &storage.QueryConvserionCache{LRU: conversionLRU}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make a constructor for this, taking in the LRU as an argument?
src/query/storage/index.go
Outdated
// Optimization for single matcher case. | ||
if len(matchers) == 1 { | ||
q, err := matcherToQuery(matchers[0]) | ||
if err != nil { | ||
return index.Query{}, err | ||
} | ||
|
||
cache.LRU.Add(string(k), q) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rather than calling .LRU.Add()
, can you put a thin wrapper Add
function onto the query conversion cache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any specific reason to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reads better, more clarity; also if at some point we want to add more logic to the add function we can keep it in one place
src/query/storage/index.go
Outdated
|
||
"github.com/m3db/m3/src/dbnode/storage/index" | ||
"github.com/m3db/m3/src/m3ninx/idx" | ||
"github.com/m3db/m3/src/query/models" | ||
"github.com/m3db/m3x/ident" | ||
) | ||
|
||
// QueryConvserionCache represents the query conversion LRU cache | ||
type QueryConvserionCache struct { | ||
mu sync.RWMutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're never using the Read lock, might be better to just make this a simple sync.Mutex
src/query/storage/m3/storage.go
Outdated
return &m3storage{ | ||
clusters: clusters, | ||
readWorkerPool: readWorkerPool, | ||
writeWorkerPool: writeWorkerPool, | ||
opts: opts, | ||
nowFn: time.Now, | ||
} | ||
// conversionCache: &storage.QueryConversionCache{LRU: conversionLRU}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
src/query/storage/m3/storage.go
Outdated
opts := m3db.NewOptions(). | ||
SetTagOptions(tagOptions). | ||
SetLookbackDuration(lookbackDuration). | ||
SetConsolidationFunc(consolidators.TakeLast) | ||
|
||
// conversionLRU, err := storage.NewQueryConversionLRU(conversionCacheSize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
|
||
cache: | ||
queryConversion: | ||
size: 200 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is very low
@@ -1,4 +1,4 @@ | |||
// Copyright (c) 2017 Uber Technologies, Inc. | |||
// Copyright (c) 2019 Uber Technologies, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: revert
5963fda
to
642cbd2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of nits, and Validate()
method on the configs
src/query/storage/index.go
Outdated
type QueryConversionCache struct { | ||
mu sync.Mutex | ||
|
||
LRU *QueryConversionLRU |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can make this private now
src/query/storage/index_test.go
Outdated
} | ||
|
||
func TestQueryKey(t *testing.T) { | ||
matchers := models.Matchers{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a few matchers here, maybe with different Type
s
require.False(t, ok) | ||
|
||
// make sure "b" is still in the cache | ||
_, ok = lru.Get([]byte("b")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: might as well check value here
|
||
// Validate validates the QueryConversionCacheConfiguration settings. | ||
func (q *QueryConversionCacheConfiguration) Validate() error { | ||
if *q.Size <= 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is 0 valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No
2949dbc
to
9d932b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved with a couple of nits
|
||
// Validate validates the QueryConversionCacheConfiguration settings. | ||
func (q *QueryConversionCacheConfiguration) Validate() error { | ||
switch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be cleaner as
if q.Size != nil && q.Size <= 0 { // return error...
// rewrite "e" and make sure nothing gets evicted | ||
// since "e" is already in the cache. | ||
evicted = lru.Set([]byte("e"), idx.NewTermQuery([]byte("qux"), []byte("quz"))) | ||
require.False(t, evicted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: check that the value of e
changes?
@@ -162,3 +162,13 @@ func TestTagOptionsConfig(t *testing.T) { | |||
assert.Equal(t, []byte("foo"), opts.BucketName()) | |||
assert.Equal(t, models.TypePrependMeta, opts.IDSchemeType()) | |||
} | |||
|
|||
func TestNegativeQueryConversionSize(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add test to validate nil case maybe? Up to you
W/O cache
W/ cache