Skip to content

Commit a4a468a

Browse files
committed
Use TSDB's index of series, remove RefCache
prometheus/prometheus#8600 adds a method to `TSDB.Appender` which allows us to save building a parallel cache, reducing ingester heap by about 20%. We depend on values from GetRef() remaining valid while v2Push() uses them. Currently the only way a ref can be invalidated is by a head compaction, which cannot happen while v2Push() holds the append lock. Signed-off-by: Bryan Boreham <[email protected]>
1 parent 3c2aeb4 commit a4a468a

File tree

9 files changed

+29
-527
lines changed

9 files changed

+29
-527
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* `cortex_ruler_client_request_duration_seconds`
1818
* [ENHANCEMENT] Query-frontend/scheduler: added querier forget delay (`-query-frontend.querier-forget-delay` and `-query-scheduler.querier-forget-delay`) to mitigate the blast radius in the event queriers crash because of a repeatedly sent "query of death" when shuffle-sharding is enabled. #3901
1919
* [ENHANCEMENT] Ingester: reduce CPU and memory when an high number of errors are returned by the ingester on the write path with the blocks storage. #3969 #3971 #3973
20+
* [ENHANCEMENT] Reduce ingester memory by eliminating parallel series cache. #3951
2021
* [BUGFIX] Distributor: reverted changes done to rate limiting in #3825. #3948
2122
* [BUGFIX] Ingester: Fix race condition when opening and closing tsdb concurrently. #3959
2223
* [BUGFIX] Querier: streamline tracing spans. #3924

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ require (
4747
github.com/prometheus/client_golang v1.9.0
4848
github.com/prometheus/client_model v0.2.0
4949
github.com/prometheus/common v0.18.0
50-
github.com/prometheus/prometheus v1.8.2-0.20210321183757-31a518faab18
50+
github.com/prometheus/prometheus v1.8.2-0.20210319192855-d614ae9ecf1c
5151
github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e
5252
github.com/sony/gobreaker v0.4.1
5353
github.com/spf13/afero v1.2.2

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1100,8 +1100,8 @@ github.com/prometheus/prometheus v1.8.2-0.20201029103703-63be30dceed9/go.mod h1:
11001100
github.com/prometheus/prometheus v1.8.2-0.20201119142752-3ad25a6dc3d9/go.mod h1:1MDE/bXgu4gqd5w/otko6WQpXZX9vu8QX4KbitCmaPg=
11011101
github.com/prometheus/prometheus v1.8.2-0.20201119181812-c8f810083d3f/go.mod h1:1MDE/bXgu4gqd5w/otko6WQpXZX9vu8QX4KbitCmaPg=
11021102
github.com/prometheus/prometheus v1.8.2-0.20210215121130-6f488061dfb4/go.mod h1:NAYujktP0dmSSpeV155mtnwX2pndLpVVK/Ps68R01TA=
1103-
github.com/prometheus/prometheus v1.8.2-0.20210321183757-31a518faab18 h1:8chKJNOWv10FApdXgQ8Td8oYFrfFTbiBp/QpBaxEMRA=
1104-
github.com/prometheus/prometheus v1.8.2-0.20210321183757-31a518faab18/go.mod h1:MS/bpdil77lPbfQeKk6OqVQ9OLnpN3Rszd0hka0EOWE=
1103+
github.com/prometheus/prometheus v1.8.2-0.20210319192855-d614ae9ecf1c h1:qGkhJcR4jPjldlEtiRe2NHOtByRD39Y507miU1HoHD0=
1104+
github.com/prometheus/prometheus v1.8.2-0.20210319192855-d614ae9ecf1c/go.mod h1:MS/bpdil77lPbfQeKk6OqVQ9OLnpN3Rszd0hka0EOWE=
11051105
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
11061106
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
11071107
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=

pkg/ingester/ingester_v2.go

+23-44
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ const (
101101
type userTSDB struct {
102102
db *tsdb.DB
103103
userID string
104-
refCache *cortex_tsdb.RefCache
105104
activeSeries *ActiveSeries
106105
seriesInMetric *metricCounter
107106
limiter *Limiter
@@ -185,7 +184,8 @@ func (u *userTSDB) compactHead(blockDuration int64) error {
185184

186185
defer u.casState(forceCompacting, active)
187186

188-
// Ingestion of samples in parallel with forced compaction can lead to overlapping blocks.
187+
// Ingestion of samples in parallel with forced compaction can lead to overlapping blocks,
188+
// and possible invalidation of the references returned from Appender.GetRef().
189189
// So we wait for existing in-flight requests to finish. Future push requests would fail until compaction is over.
190190
u.pushesInFlight.Wait()
191191

@@ -383,7 +383,6 @@ type TSDBState struct {
383383
walReplayTime prometheus.Histogram
384384
appenderAddDuration prometheus.Histogram
385385
appenderCommitDuration prometheus.Histogram
386-
refCachePurgeDuration prometheus.Histogram
387386
idleTsdbChecks *prometheus.CounterVec
388387
}
389388

@@ -435,11 +434,6 @@ func newTSDBState(bucketClient objstore.Bucket, registerer prometheus.Registerer
435434
Help: "The total time it takes for a push request to commit samples appended to TSDB.",
436435
Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
437436
}),
438-
refCachePurgeDuration: promauto.With(registerer).NewHistogram(prometheus.HistogramOpts{
439-
Name: "cortex_ingester_tsdb_refcache_purge_duration_seconds",
440-
Help: "The total time it takes to purge the TSDB series reference cache for a single tenant.",
441-
Buckets: prometheus.DefBuckets,
442-
}),
443437

444438
idleTsdbChecks: idleTsdbChecks,
445439
}
@@ -619,11 +613,6 @@ func (i *Ingester) updateLoop(ctx context.Context) error {
619613
rateUpdateTicker := time.NewTicker(i.cfg.RateUpdatePeriod)
620614
defer rateUpdateTicker.Stop()
621615

622-
// We use an hardcoded value for this ticker because there should be no
623-
// real value in customizing it.
624-
refCachePurgeTicker := time.NewTicker(5 * time.Minute)
625-
defer refCachePurgeTicker.Stop()
626-
627616
var activeSeriesTickerChan <-chan time.Time
628617
if i.cfg.ActiveSeriesMetricsEnabled {
629618
t := time.NewTicker(i.cfg.ActiveSeriesMetricsUpdatePeriod)
@@ -646,17 +635,6 @@ func (i *Ingester) updateLoop(ctx context.Context) error {
646635
db.ingestedRuleSamples.tick()
647636
}
648637
i.userStatesMtx.RUnlock()
649-
case <-refCachePurgeTicker.C:
650-
for _, userID := range i.getTSDBUsers() {
651-
userDB := i.getTSDB(userID)
652-
if userDB == nil {
653-
continue
654-
}
655-
656-
startTime := time.Now()
657-
userDB.refCache.Purge(startTime.Add(-cortex_tsdb.DefaultRefCacheTTL))
658-
i.TSDBState.refCachePurgeDuration.Observe(time.Since(startTime).Seconds())
659-
}
660638

661639
case <-activeSeriesTickerChan:
662640
i.v2UpdateActiveSeries()
@@ -683,6 +661,12 @@ func (i *Ingester) v2UpdateActiveSeries() {
683661
}
684662
}
685663

664+
// GetRef() is an extra method added to TSDB to let Cortex check before calling Add()
665+
type extendedAppender interface {
666+
storage.Appender
667+
storage.GetRef
668+
}
669+
686670
// v2Push adds metrics to a block
687671
func (i *Ingester) v2Push(ctx context.Context, req *cortexpb.WriteRequest) (*cortexpb.WriteResponse, error) {
688672
var firstPartialErr error
@@ -738,13 +722,17 @@ func (i *Ingester) v2Push(ctx context.Context, req *cortexpb.WriteRequest) (*cor
738722
)
739723

740724
// Walk the samples, appending them to the users database
741-
app := db.Appender(ctx)
725+
app := db.Appender(ctx).(extendedAppender)
742726
for _, ts := range req.Timeseries {
743-
// Check if we already have a cached reference for this series. Be aware
744-
// that even if we have a reference it's not guaranteed to be still valid.
727+
// Keeps a reference to labels copy, if it was needed. This is to avoid making a copy twice,
728+
// once for TSDB, and second time for activeSeries map.
729+
var copiedLabels []labels.Label
730+
745731
// The labels must be sorted (in our case, it's guaranteed a write request
746732
// has sorted labels once hit the ingester).
747-
cachedRef, copiedLabels, cachedRefExists := db.refCache.Ref(startAppend, cortexpb.FromLabelAdaptersToLabels(ts.Labels))
733+
734+
// Look up a reference for this series. Holding the appendLock ensures that no compaction will happen while we use it.
735+
ref := app.GetRef(cortexpb.FromLabelAdaptersToLabels(ts.Labels))
748736

749737
// To find out if any sample was added to this series, we keep old value.
750738
oldSucceededSamplesCount := succeededSamplesCount
@@ -753,30 +741,19 @@ func (i *Ingester) v2Push(ctx context.Context, req *cortexpb.WriteRequest) (*cor
753741
var err error
754742

755743
// If the cached reference exists, we try to use it.
756-
if cachedRefExists {
757-
var ref uint64
758-
if ref, err = app.Append(cachedRef, copiedLabels, s.TimestampMs, s.Value); err == nil {
744+
if ref != 0 {
745+
labels := cortexpb.FromLabelAdaptersToLabels(ts.Labels)
746+
if _, err = app.Append(ref, labels, s.TimestampMs, s.Value); err == nil {
759747
succeededSamplesCount++
760-
// This means the reference changes which means we need to update our cache.
761-
if ref != cachedRef {
762-
db.refCache.SetRef(startAppend, copiedLabels, ref)
763-
}
764748
continue
765749
}
766750

767751
} else {
768-
var ref uint64
769-
770752
// Copy the label set because both TSDB and the cache may retain it.
771753
copiedLabels = cortexpb.FromLabelAdaptersToLabelsWithCopy(ts.Labels)
772754

755+
// Retain the reference in case there are multiple samples for the series.
773756
if ref, err = app.Append(0, copiedLabels, s.TimestampMs, s.Value); err == nil {
774-
db.refCache.SetRef(startAppend, copiedLabels, ref)
775-
776-
// Set these in case there are multiple samples for the series.
777-
cachedRef = ref
778-
cachedRefExists = true
779-
780757
succeededSamplesCount++
781758
continue
782759
}
@@ -812,6 +789,9 @@ func (i *Ingester) v2Push(ctx context.Context, req *cortexpb.WriteRequest) (*cor
812789
case errMaxSeriesPerMetricLimitExceeded:
813790
perMetricSeriesLimitCount++
814791
updateFirstPartial(func() error {
792+
if copiedLabels == nil {
793+
copiedLabels = cortexpb.FromLabelAdaptersToLabelsWithCopy(ts.Labels)
794+
}
815795
return makeMetricLimitError(perMetricSeriesLimit, copiedLabels, i.limiter.FormatError(userID, cause))
816796
})
817797
continue
@@ -1435,7 +1415,6 @@ func (i *Ingester) createTSDB(userID string) (*userTSDB, error) {
14351415

14361416
userDB := &userTSDB{
14371417
userID: userID,
1438-
refCache: cortex_tsdb.NewRefCache(),
14391418
activeSeries: NewActiveSeries(),
14401419
seriesInMetric: newMetricCounter(i.limiter),
14411420
ingestedAPISamples: newEWMARate(0.2, i.cfg.RateUpdatePeriod),

pkg/ingester/ingester_v2_test.go

-46
Original file line numberDiff line numberDiff line change
@@ -2997,52 +2997,6 @@ func TestHeadCompactionOnStartup(t *testing.T) {
29972997
require.Equal(t, 11, len(db.Blocks()))
29982998
}
29992999

3000-
func TestIngesterCacheUpdatesOnRefChange(t *testing.T) {
3001-
cfg := defaultIngesterTestConfig()
3002-
cfg.LifecyclerConfig.JoinAfter = 0
3003-
3004-
// Create ingester
3005-
i, err := prepareIngesterWithBlocksStorage(t, cfg, nil)
3006-
require.NoError(t, err)
3007-
3008-
require.NoError(t, services.StartAndAwaitRunning(context.Background(), i))
3009-
t.Cleanup(func() {
3010-
_ = services.StopAndAwaitTerminated(context.Background(), i)
3011-
})
3012-
3013-
// Wait until it's ACTIVE
3014-
test.Poll(t, 1*time.Second, ring.ACTIVE, func() interface{} {
3015-
return i.lifecycler.GetState()
3016-
})
3017-
3018-
// Push a sample, verify that the labels are in ref-cache.
3019-
// Compact the head to remove the labels from HEAD but they will still exist in ref-cache.
3020-
// Push again to make the ref change and verify the refCache is updated in this case.
3021-
3022-
pushSingleSampleAtTime(t, i, 10)
3023-
3024-
db := i.getTSDB(userID)
3025-
require.NotNil(t, db)
3026-
3027-
startAppend := time.Now()
3028-
l := labels.Labels{{Name: labels.MetricName, Value: "test"}}
3029-
cachedRef, _, cachedRefExists := db.refCache.Ref(startAppend, l)
3030-
require.True(t, cachedRefExists)
3031-
require.Equal(t, uint64(1), cachedRef)
3032-
3033-
// Compact to remove the series from HEAD.
3034-
i.compactBlocks(context.Background(), true)
3035-
cachedRef, _, cachedRefExists = db.refCache.Ref(startAppend, l)
3036-
require.True(t, cachedRefExists)
3037-
require.Equal(t, uint64(1), cachedRef)
3038-
3039-
// New sample to create a new ref.
3040-
pushSingleSampleAtTime(t, i, 11)
3041-
cachedRef, _, cachedRefExists = db.refCache.Ref(startAppend, l)
3042-
require.True(t, cachedRefExists)
3043-
require.Equal(t, uint64(2), cachedRef)
3044-
}
3045-
30463000
func TestIngester_CloseTSDBsOnShutdown(t *testing.T) {
30473001
cfg := defaultIngesterTestConfig()
30483002
cfg.LifecyclerConfig.JoinAfter = 0

pkg/storage/tsdb/ref_cache.go

-161
This file was deleted.

0 commit comments

Comments
 (0)