Skip to content
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 random replacement mapper cache #281

Merged
merged 1 commit into from
Mar 5, 2020

Conversation

bakins
Copy link
Contributor

@bakins bakins commented Jan 2, 2020

Add a simple random replacement cache based on a map.

Add command line flag to choose mapper cache type, defaulting to existing lru cache.

In the case that all entries fit into the cache, this new cache is faster as it uses a read lock for gets and also has less bookkeeping than the lru cache.

In the case that all entries do not fit into cache, the random replacement cache can avoid thrashing the cache. In some cases, the lru cache will have a very low hit ratio as it is constantly evicting entries. Our use case is a huge number of unique metric names in "old" statsd format that we map to relatively few Prometheus metrics.

Note: I realize this is probably a much larger change than we may want all at once, but wanted to get feedback on the approach, etc.

Benchmark results:

go test -bench='.*Cache.*' -benchtime=10s -benchmem ./pkg/mapper/

pkg: github.com/prometheus/statsd_exporter/pkg/mapper
BenchmarkGlob10RulesCached/lru-12                           	76762423	       155 ns/op	      48 B/op	       2 allocs/op
BenchmarkGlob10RulesCached/random-12                        	153020101	        78.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkRegex10RulesAverageCached/lru-12                   	78939664	       150 ns/op	      48 B/op	       2 allocs/op
BenchmarkRegex10RulesAverageCached/random-12                	153228613	        78.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkGlob100RulesCached/lru-12                          	78150949	       153 ns/op	      48 B/op	       2 allocs/op
BenchmarkGlob100RulesCached/random-12                       	151522714	        78.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkGlob100RulesMultipleCapturesCached/lru-12          	76434423	       158 ns/op	      64 B/op	       2 allocs/op
BenchmarkGlob100RulesMultipleCapturesCached/random-12       	100000000	       105 ns/op	      48 B/op	       1 allocs/op
BenchmarkRegex100RulesMultipleCapturesWorstCached/lru-12    	75379135	       159 ns/op	      64 B/op	       2 allocs/op
BenchmarkRegex100RulesMultipleCapturesWorstCached/random-12 	100000000	       109 ns/op	      48 B/op	       1 allocs/op
BenchmarkGlob100RulesCached100Metrics/lru-12                	  643710	     18510 ns/op	    6240 B/op	     200 allocs/op
BenchmarkGlob100RulesCached100Metrics/random-12             	 1000000	     10976 ns/op	    4320 B/op	      90 allocs/op
BenchmarkGlob100RulesCached100MetricsSmallCache/lru-12      	   76018	    167613 ns/op	   28502 B/op	    1000 allocs/op
BenchmarkGlob100RulesCached100MetricsSmallCache/random-12   	  128126	     96533 ns/op	   13483 B/op	     446 allocs/op
BenchmarkGlob100RulesCached100MetricsRandomSmallCache/lru-12         	   10000	   1154307 ns/op	  183750 B/op	    6361 allocs/op
BenchmarkGlob100RulesCached100MetricsRandomSmallCache/random-12      	   16196	    731579 ns/op	  108832 B/op	    3478 allocs/op
PASS
ok  	github.com/prometheus/statsd_exporter/pkg/mapper	224.196s

@bakins bakins force-pushed the random-replacement branch from 07b8584 to c6dc60b Compare January 2, 2020 17:53
Copy link
Contributor

@matthiasr matthiasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding of different caching strategies is weak – when would I use the random replacement cache? Would it be enough to sufficiently size the LRU cache instead, or are there work loads where an RR cache is always better?

The mapper package is accumulating a lot of responsibilities, and consequently a lot of mode switching. I would prefer to break out all the caches into a separate "mapping cache" package, to be composed by the user / in main.go. This could be either by injecting the dependency later:

c := cache.NewLRUCache(*cacheSize)
mapper.InitFromFile(*mappingConfig, c)

or (this feels cleaner to me) we define an interface for metric mappers, have the various caches implement it, and wrap the pure mapper:

metricMapper := mapper.InitFromFile(*mappingConfig)
cachedMetricMapper := cache.NewLRUCache(metricMapper, *cacheSize)

What do you think about breaking up the package in this way?

cacheType string
}

type CacheOption func(*cacheOptions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the benefit of this construction over, say, passing explicit separate parameters to the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By passing it in as optional arguments, then callers do not need to change their calls. Within statsd_exporter it's not a big deal to change all the call sites, but I know the mapper package is used by a few other projects, so they could continue to work when they updated their dependencies.

@glightfoot
Copy link
Contributor

If memory resources are limited for the statsd exporter, the LRU cache will provide a higher hit-rate as it's keeping the hottest mappings. However, with high throughput and lots of metrics, it can become a bottleneck due to the write lock needed for all operations.

If memory resources are not limited and throughput is very high, the random replacement cache will outperform the LRU cache if the size is big enough to hold all the mappings since a write lock is only needed when adding new mappings.

@matthiasr
Copy link
Contributor

thank you for the explanation! it makes sense to have both options then.

@bakins
Copy link
Contributor Author

bakins commented Jan 11, 2020

@matthiasr I'll fix the conflicts. I'm okay to break up the packages. Are we worried about compatibility for others who may have imported the mapper package? I could add a shim to ensure the old calls in mapper worked by calling into mapper/cache or something like that.

@matthiasr
Copy link
Contributor

matthiasr commented Jan 11, 2020 via email

@matthiasr
Copy link
Contributor

@bakins are you still willing to work on this? If the desire to break out caching from mapping is holding this up, I'm also willing to merge this as is (after resolving the conflicts) and defer the refactoring.

@bakins
Copy link
Contributor Author

bakins commented Mar 4, 2020

@matthiasr I'm okay either way. I'll fix the conflicts and then we can decide. Whichever way is easier for you.

@bakins bakins force-pushed the random-replacement branch from c6dc60b to 90e247b Compare March 4, 2020 17:28
@matthiasr
Copy link
Contributor

Let's get it in like this and do the refactoring separately. Thanks a lot!

@matthiasr matthiasr merged commit 60fbaf5 into prometheus:master Mar 5, 2020
matthiasr pushed a commit that referenced this pull request Mar 5, 2020
explain the tradeoffs for the cache strategies based on this comment:

#281 (comment)

Signed-off-by: Matthias Rampke <[email protected]>
@bakins bakins deleted the random-replacement branch March 18, 2020 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants