2019-05-22 23:16:55 +02:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2019-08-22 15:35:55 +02:00
|
|
|
"regexp"
|
2019-05-22 23:16:55 +02:00
|
|
|
"strconv"
|
2019-06-25 19:09:57 +02:00
|
|
|
"sync/atomic"
|
2019-05-22 23:16:55 +02:00
|
|
|
"testing"
|
2019-08-13 20:35:19 +02:00
|
|
|
"time"
|
2019-05-22 23:16:55 +02:00
|
|
|
|
2019-08-13 20:35:19 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
2019-05-22 23:16:55 +02:00
|
|
|
)
|
|
|
|
|
2019-08-22 15:35:55 +02:00
|
|
|
func BenchmarkRegexpFilterMatch(b *testing.B) {
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
re := regexp.MustCompile(`.*foo-bar-baz.*`)
|
|
|
|
b := []byte("fdsffd foo-bar-baz assd fdsfad dasf dsa")
|
|
|
|
for pb.Next() {
|
|
|
|
if !re.Match(b) {
|
|
|
|
panic("BUG: regexp must match!")
|
|
|
|
}
|
|
|
|
b[0]++
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkRegexpFilterMismatch(b *testing.B) {
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
re := regexp.MustCompile(`.*foo-bar-baz.*`)
|
|
|
|
b := []byte("fdsffd foo-bar sfddsf assd nmn,mfdsdsakj")
|
|
|
|
for pb.Next() {
|
|
|
|
if re.Match(b) {
|
|
|
|
panic("BUG: regexp mustn't match!")
|
|
|
|
}
|
|
|
|
b[0]++
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
func BenchmarkIndexDBAddTSIDs(b *testing.B) {
|
|
|
|
const recordsPerLoop = 1e3
|
|
|
|
|
2019-08-13 20:35:19 +02:00
|
|
|
metricIDCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
metricNameCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
defer metricIDCache.Stop()
|
|
|
|
defer metricNameCache.Stop()
|
2019-06-25 19:09:57 +02:00
|
|
|
|
|
|
|
var hmCurr atomic.Value
|
|
|
|
hmCurr.Store(&hourMetricIDs{})
|
|
|
|
var hmPrev atomic.Value
|
|
|
|
hmPrev.Store(&hourMetricIDs{})
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
const dbName = "bench-index-db-add-tsids"
|
2019-06-25 19:09:57 +02:00
|
|
|
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
2019-05-22 23:16:55 +02:00
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("cannot open indexDB: %s", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
db.MustClose()
|
|
|
|
if err := os.RemoveAll(dbName); err != nil {
|
|
|
|
b.Fatalf("cannot remove indexDB: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.SetBytes(recordsPerLoop)
|
|
|
|
b.ResetTimer()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
var mn MetricName
|
|
|
|
var tsid TSID
|
|
|
|
|
|
|
|
// The most common tags.
|
|
|
|
mn.Tags = []Tag{
|
|
|
|
{
|
|
|
|
Key: []byte("job"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: []byte("instance"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
startOffset := 0
|
|
|
|
for pb.Next() {
|
|
|
|
benchmarkIndexDBAddTSIDs(db, &tsid, &mn, startOffset, recordsPerLoop)
|
|
|
|
startOffset += recordsPerLoop
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.StopTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
func benchmarkIndexDBAddTSIDs(db *indexDB, tsid *TSID, mn *MetricName, startOffset, recordsPerLoop int) {
|
|
|
|
var metricName []byte
|
|
|
|
is := db.getIndexSearch()
|
|
|
|
defer db.putIndexSearch(is)
|
|
|
|
for i := 0; i < recordsPerLoop; i++ {
|
|
|
|
mn.MetricGroup = strconv.AppendUint(mn.MetricGroup[:0], uint64(i+startOffset), 10)
|
|
|
|
for j := range mn.Tags {
|
|
|
|
mn.Tags[j].Value = strconv.AppendUint(mn.Tags[j].Value[:0], uint64(i*j), 16)
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
|
|
|
metricName = mn.Marshal(metricName[:0])
|
|
|
|
if err := is.GetOrCreateTSIDByName(tsid, metricName); err != nil {
|
|
|
|
panic(fmt.Errorf("cannot insert record: %s", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
lib/storage: add BenchmarkHeadPostingForMatchers similar to the benchmark from Prometheus
See the corresponding benchmark in Prometheus - https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L52
The benchmark allows performing apples-to-apples comparison of time series search
in Prometheus and VictoriaMetrics. The following article - https://www.robustperception.io/evaluating-performance-and-correctness -
contains incorrect numbers for VictoriaMetrics, since there wasn't this benchmark yet. Fix this.
Benchmarks can be repeated with the following commands from Prometheus and VictoriaMetrics source code roots:
- Prometheus: GOMAXPROCS=1 go test ./tsdb/ -run=111 -bench=BenchmarkHeadPostingForMatchers
- VictoriaMetrics: GOMAXPROCS=1 go test ./lib/storage/ -run=111 -bench=BenchmarkHeadPostingForMatchers
Benchmark results:
benchmark old ns/op new ns/op delta
BenchmarkHeadPostingForMatchers/n="1" 272756688 364977 -99.87%
BenchmarkHeadPostingForMatchers/n="1",j="foo" 138132923 1181636 -99.14%
BenchmarkHeadPostingForMatchers/j="foo",n="1" 134723762 1141578 -99.15%
BenchmarkHeadPostingForMatchers/n="1",j!="foo" 195823953 1148056 -99.41%
BenchmarkHeadPostingForMatchers/i=~".*" 7962582919 8716755 -99.89%
BenchmarkHeadPostingForMatchers/i=~".+" 7589543864 12096587 -99.84%
BenchmarkHeadPostingForMatchers/i=~"" 1142371741 16164560 -98.59%
BenchmarkHeadPostingForMatchers/i!="" 9964150263 12230021 -99.88%
BenchmarkHeadPostingForMatchers/n="1",i=~".*",j="foo" 216995884 1173476 -99.46%
BenchmarkHeadPostingForMatchers/n="1",i=~".*",i!="2",j="foo" 202541348 1299743 -99.36%
BenchmarkHeadPostingForMatchers/n="1",i!="" 486285711 11555193 -97.62%
BenchmarkHeadPostingForMatchers/n="1",i!="",j="foo" 350776931 5607506 -98.40%
BenchmarkHeadPostingForMatchers/n="1",i=~".+",j="foo" 380888565 6380335 -98.32%
BenchmarkHeadPostingForMatchers/n="1",i=~"1.+",j="foo" 89500296 2078970 -97.68%
BenchmarkHeadPostingForMatchers/n="1",i=~".+",i!="2",j="foo" 379529654 6561368 -98.27%
BenchmarkHeadPostingForMatchers/n="1",i=~".+",i!~"2.*",j="foo" 424563825 6757132 -98.41%
The first column (old) is for Prometheus, the second column (new) is for VictoriaMetrics.
As you can see, VictoriaMetrics outperforms Prometheus by more than 100x in almost all the test cases of this benchmark.
Prometheus was using 3.5GB of RAM during the benchmark, while VictoriaMetrics was using 400MB of RAM.
2019-11-18 17:21:27 +01:00
|
|
|
func BenchmarkHeadPostingForMatchers(b *testing.B) {
|
|
|
|
// This benchmark is equivalent to https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L52
|
|
|
|
// See https://www.robustperception.io/evaluating-performance-and-correctness for more details.
|
|
|
|
metricIDCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
metricNameCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
defer metricIDCache.Stop()
|
|
|
|
defer metricNameCache.Stop()
|
|
|
|
|
|
|
|
var hmCurr atomic.Value
|
|
|
|
hmCurr.Store(&hourMetricIDs{})
|
|
|
|
var hmPrev atomic.Value
|
|
|
|
hmPrev.Store(&hourMetricIDs{})
|
|
|
|
|
|
|
|
const dbName = "bench-head-posting-for-matchers"
|
|
|
|
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("cannot open indexDB: %s", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
db.MustClose()
|
|
|
|
if err := os.RemoveAll(dbName); err != nil {
|
|
|
|
b.Fatalf("cannot remove indexDB: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Fill the db with data as in https://github.com/prometheus/prometheus/blob/23c0299d85bfeb5d9b59e994861553a25ca578e5/tsdb/head_bench_test.go#L66
|
|
|
|
var mn MetricName
|
|
|
|
var metricName []byte
|
|
|
|
var tsid TSID
|
|
|
|
addSeries := func(kvs ...string) {
|
|
|
|
mn.Reset()
|
|
|
|
for i := 0; i < len(kvs); i += 2 {
|
|
|
|
mn.AddTag(kvs[i], kvs[i+1])
|
|
|
|
}
|
|
|
|
mn.sortTags()
|
|
|
|
metricName = mn.Marshal(metricName[:0])
|
|
|
|
if err := db.createTSIDByName(&tsid, metricName); err != nil {
|
|
|
|
b.Fatalf("cannot insert record: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for n := 0; n < 10; n++ {
|
|
|
|
for i := 0; i < 10000; i++ {
|
|
|
|
addSeries("i", strconv.Itoa(i), "n", strconv.Itoa(n), "j", "foo")
|
|
|
|
// Have some series that won't be matched, to properly test inverted matches.
|
|
|
|
addSeries("i", strconv.Itoa(i), "n", strconv.Itoa(n), "j", "bar")
|
|
|
|
addSeries("i", strconv.Itoa(i), "n", "0_"+strconv.Itoa(n), "j", "bar")
|
|
|
|
addSeries("i", strconv.Itoa(i), "n", "1_"+strconv.Itoa(n), "j", "bar")
|
|
|
|
addSeries("i", strconv.Itoa(i), "n", "2_"+strconv.Itoa(n), "j", "foo")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure all the items can be searched.
|
|
|
|
db.tb.DebugFlush()
|
|
|
|
b.ResetTimer()
|
|
|
|
|
|
|
|
benchSearch := func(b *testing.B, tfs *TagFilters) {
|
|
|
|
is := db.getIndexSearch()
|
|
|
|
defer db.putIndexSearch(is)
|
|
|
|
tfss := []*TagFilters{tfs}
|
|
|
|
tr := TimeRange{
|
|
|
|
MinTimestamp: 0,
|
|
|
|
MaxTimestamp: timestampFromTime(time.Now()),
|
|
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
_, err := is.searchMetricIDs(tfss, tr, 2e9)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("unexpected error in searchMetricIDs: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addTagFilter := func(tfs *TagFilters, key, value string, isNegative, isRegexp bool) {
|
|
|
|
if err := tfs.Add([]byte(key), []byte(value), isNegative, isRegexp); err != nil {
|
|
|
|
b.Fatalf("cannot add tag filter %q=%q, isNegative=%v, isRegexp=%v", key, value, isNegative, isRegexp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b.Run(`n="1"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`j="foo",n="1"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",j!="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "j", "foo", true, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`i=~".*"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "i", ".*", false, true)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`i=~".+"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "i", ".+", false, true)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`i=~""`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "i", "", false, true)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`i!=""`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "i", "", true, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~".*",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", ".*", false, true)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~".*",i!="2",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", ".*", false, true)
|
|
|
|
addTagFilter(tfs, "i", "2", true, false)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i!=""`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", "", true, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i!="",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", "", true, false)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~".+",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", ".+", false, true)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~"1.+",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", "1.+", false, true)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~".+",i!="2",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", ".+", false, true)
|
|
|
|
addTagFilter(tfs, "i", "2", true, false)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
b.Run(`n="1",i=~".+",i!~"2.*",j="foo"`, func(b *testing.B) {
|
|
|
|
tfs := NewTagFilters()
|
|
|
|
addTagFilter(tfs, "n", "1", false, false)
|
|
|
|
addTagFilter(tfs, "i", ".+", false, true)
|
|
|
|
addTagFilter(tfs, "i", "2.*", true, true)
|
|
|
|
addTagFilter(tfs, "j", "foo", false, false)
|
|
|
|
benchSearch(b, tfs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
func BenchmarkIndexDBGetTSIDs(b *testing.B) {
|
2019-08-13 20:35:19 +02:00
|
|
|
metricIDCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
metricNameCache := workingsetcache.New(1234, time.Hour)
|
|
|
|
defer metricIDCache.Stop()
|
|
|
|
defer metricNameCache.Stop()
|
2019-06-25 19:09:57 +02:00
|
|
|
|
|
|
|
var hmCurr atomic.Value
|
|
|
|
hmCurr.Store(&hourMetricIDs{})
|
|
|
|
var hmPrev atomic.Value
|
|
|
|
hmPrev.Store(&hourMetricIDs{})
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
const dbName = "bench-index-db-get-tsids"
|
2019-06-25 19:09:57 +02:00
|
|
|
db, err := openIndexDB(dbName, metricIDCache, metricNameCache, &hmCurr, &hmPrev)
|
2019-05-22 23:16:55 +02:00
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("cannot open indexDB: %s", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
db.MustClose()
|
|
|
|
if err := os.RemoveAll(dbName); err != nil {
|
|
|
|
b.Fatalf("cannot remove indexDB: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
const recordsPerLoop = 1000
|
|
|
|
const recordsCount = 1e5
|
|
|
|
|
|
|
|
// Fill the db with recordsCount records.
|
|
|
|
var mn MetricName
|
|
|
|
mn.MetricGroup = []byte("rps")
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
key := fmt.Sprintf("key_%d", i)
|
|
|
|
value := fmt.Sprintf("value_%d", i)
|
|
|
|
mn.AddTag(key, value)
|
|
|
|
}
|
|
|
|
var tsid TSID
|
|
|
|
var metricName []byte
|
|
|
|
|
|
|
|
is := db.getIndexSearch()
|
|
|
|
defer db.putIndexSearch(is)
|
|
|
|
for i := 0; i < recordsCount; i++ {
|
|
|
|
mn.sortTags()
|
|
|
|
metricName = mn.Marshal(metricName[:0])
|
|
|
|
if err := is.GetOrCreateTSIDByName(&tsid, metricName); err != nil {
|
|
|
|
b.Fatalf("cannot insert record: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b.SetBytes(recordsPerLoop)
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.ResetTimer()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
var tsidLocal TSID
|
|
|
|
var metricNameLocal []byte
|
|
|
|
mnLocal := mn
|
|
|
|
is := db.getIndexSearch()
|
|
|
|
defer db.putIndexSearch(is)
|
|
|
|
for pb.Next() {
|
|
|
|
for i := 0; i < recordsPerLoop; i++ {
|
|
|
|
mnLocal.sortTags()
|
|
|
|
metricNameLocal = mnLocal.Marshal(metricNameLocal[:0])
|
|
|
|
if err := is.GetOrCreateTSIDByName(&tsidLocal, metricNameLocal); err != nil {
|
|
|
|
panic(fmt.Errorf("cannot obtain tsid: %s", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
b.StopTimer()
|
|
|
|
}
|