Skip to content

Commit 3a82ce3

Browse files
committed
Introduce appender for backfilling to avoid 1 appender per sample
Signed-off-by: Ganesh Vernekar <[email protected]>
1 parent 013e7e4 commit 3a82ce3

File tree

3 files changed

+132
-54
lines changed

3 files changed

+132
-54
lines changed

pkg/ingester/ingester_v2.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/prometheus/prometheus/pkg/labels"
2121
"github.com/prometheus/prometheus/storage"
2222
"github.com/prometheus/prometheus/tsdb"
23+
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
2324
"github.com/thanos-io/thanos/pkg/block/metadata"
2425
"github.com/thanos-io/thanos/pkg/objstore"
2526
"github.com/thanos-io/thanos/pkg/shipper"
@@ -158,6 +159,7 @@ func newTSDBState(bucketClient objstore.Bucket, registerer prometheus.Registerer
158159
tsdbMetrics: newTSDBMetrics(registerer),
159160
forceCompactTrigger: make(chan chan<- struct{}),
160161
shipTrigger: make(chan chan<- struct{}),
162+
backfillDBs: newBackfillTSDBs(),
161163

162164
compactionsTriggered: promauto.With(registerer).NewCounter(prometheus.CounterOpts{
163165
Name: "cortex_ingester_tsdb_compactions_triggered_total",
@@ -426,6 +428,7 @@ func (i *Ingester) v2Push(ctx context.Context, req *client.WriteRequest) (*clien
426428

427429
// Walk the samples, appending them to the users database
428430
app := db.Appender()
431+
var backfillApp *backfillAppender
429432
for _, ts := range req.Timeseries {
430433
// Check if we already have a cached reference for this series. Be aware
431434
// that even if we have a reference it's not guaranteed to be still valid.
@@ -470,7 +473,10 @@ func (i *Ingester) v2Push(ctx context.Context, req *client.WriteRequest) (*clien
470473
if cause == storage.ErrOutOfBounds &&
471474
i.cfg.TSDBConfig.BackfillLimit > 0 &&
472475
s.TimestampMs > db.Head().MaxTime()-i.cfg.TSDBConfig.BackfillLimit.Milliseconds() {
473-
err := i.v2BackfillPush(userID, ts.Labels, s)
476+
if backfillApp == nil {
477+
backfillApp = i.newBackfillAppender(userID)
478+
}
479+
err := backfillApp.add(ts.Labels, s)
474480
if err == nil {
475481
succeededSamplesCount++
476482
continue
@@ -512,8 +518,13 @@ func (i *Ingester) v2Push(ctx context.Context, req *client.WriteRequest) (*clien
512518
}
513519

514520
// The error looks an issue on our side, so we should rollback
515-
if rollbackErr := app.Rollback(); rollbackErr != nil {
516-
level.Warn(util.Logger).Log("msg", "failed to rollback on error", "user", userID, "err", rollbackErr)
521+
var merr tsdb_errors.MultiError
522+
merr.Add(errors.Wrap(app.Rollback(), "main appender"))
523+
if backfillApp != nil {
524+
merr.Add(errors.Wrap(backfillApp.rollback(), "backfill appender"))
525+
}
526+
if merr.Err() != nil {
527+
level.Warn(util.Logger).Log("msg", "failed to rollback on error", "user", userID, "err", merr.Err())
517528
}
518529

519530
return nil, wrapWithUser(err, userID)
@@ -524,8 +535,13 @@ func (i *Ingester) v2Push(ctx context.Context, req *client.WriteRequest) (*clien
524535
i.TSDBState.appenderAddDuration.Observe(time.Since(startAppend).Seconds())
525536

526537
startCommit := time.Now()
527-
if err := app.Commit(); err != nil {
528-
return nil, wrapWithUser(err, userID)
538+
var merr tsdb_errors.MultiError
539+
merr.Add(errors.Wrap(app.Commit(), "main appender"))
540+
if backfillApp != nil {
541+
merr.Add(errors.Wrap(backfillApp.commit(), "backfill appender"))
542+
}
543+
if merr.Err() != nil {
544+
return nil, wrapWithUser(merr.Err(), userID)
529545
}
530546
i.TSDBState.appenderCommitDuration.Observe(time.Since(startCommit).Seconds())
531547

@@ -989,7 +1005,7 @@ func (i *Ingester) createNewTSDB(userID, dbDir string, minBlockDuration, maxBloc
9891005
WALCompression: i.cfg.TSDBConfig.WALCompressionEnabled,
9901006
})
9911007
if err != nil {
992-
return nil, errors.Wrapf(err, "failed to open TSDB: %s", udir)
1008+
return nil, errors.Wrapf(err, "failed to open TSDB: %s", dbDir)
9931009
}
9941010
db.DisableCompactions() // we will compact on our own schedule
9951011

@@ -999,7 +1015,7 @@ func (i *Ingester) createNewTSDB(userID, dbDir string, minBlockDuration, maxBloc
9991015
level.Info(userLogger).Log("msg", "Running compaction after WAL replay")
10001016
err = db.Compact()
10011017
if err != nil {
1002-
return nil, errors.Wrapf(err, "failed to compact TSDB: %s", udir)
1018+
return nil, errors.Wrapf(err, "failed to compact TSDB: %s", dbDir)
10031019
}
10041020

10051021
userDB.DB = db

pkg/ingester/ingester_v2_backfill.go

Lines changed: 109 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,53 @@ import (
1919
"github.com/prometheus/prometheus/pkg/gate"
2020
"github.com/prometheus/prometheus/pkg/labels"
2121
"github.com/prometheus/prometheus/storage"
22+
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
2223

2324
"github.com/cortexproject/cortex/pkg/ingester/client"
2425
"github.com/cortexproject/cortex/pkg/util"
2526
)
2627

27-
func (i *Ingester) v2BackfillPush(userID string, la []client.LabelAdapter, s client.Sample) error {
28-
bucket, err := i.getOrCreateBackfillTSDB(userID, s.TimestampMs)
29-
if err != nil {
30-
return err
28+
// backfillAppender is an appender to ingest old data.
29+
// This _does not_ implement storage.Appender interface.
30+
// The methods of this appender should not be called concurrently.
31+
type backfillAppender struct {
32+
userID string
33+
ingester *Ingester
34+
buckets []*tsdbBucket
35+
appenders map[int]storage.Appender
36+
}
37+
38+
func (i *Ingester) newBackfillAppender(userID string) *backfillAppender {
39+
return &backfillAppender{
40+
userID: userID,
41+
ingester: i,
42+
buckets: i.TSDBState.backfillDBs.getBucketsForUser(userID),
43+
appenders: make(map[int]storage.Appender),
44+
}
45+
}
46+
47+
func (a *backfillAppender) add(la []client.LabelAdapter, s client.Sample) (err error) {
48+
bucket := getBucketForTimestamp(s.TimestampMs, a.buckets)
49+
if bucket == nil {
50+
var userBuckets []*tsdbBucket
51+
bucket, userBuckets, err = a.ingester.getOrCreateBackfillTSDB(a.userID, s.TimestampMs)
52+
if err != nil {
53+
return err
54+
}
55+
a.buckets = userBuckets
3156
}
3257

33-
startAppend := time.Now()
3458
db := bucket.db
35-
cachedRef, cachedRefExists := db.refCache.Ref(startAppend, client.FromLabelAdaptersToLabels(la))
59+
var app storage.Appender
60+
if ap, ok := a.appenders[bucket.id]; ok {
61+
app = ap
62+
} else {
63+
app = db.Appender()
64+
a.appenders[bucket.id] = app
65+
}
3666

37-
app := db.Appender()
67+
startAppend := time.Now()
68+
cachedRef, cachedRefExists := db.refCache.Ref(startAppend, client.FromLabelAdaptersToLabels(la))
3869
// If the cached reference exists, we try to use it.
3970
if cachedRefExists {
4071
err = app.AddFast(cachedRef, s.TimestampMs, s.Value)
@@ -46,46 +77,66 @@ func (i *Ingester) v2BackfillPush(userID string, la []client.LabelAdapter, s cli
4677

4778
// If the cached reference doesn't exist, we (re)try without using the reference.
4879
if !cachedRefExists {
49-
var ref uint64
50-
5180
// Copy the label set because both TSDB and the cache may retain it.
5281
copiedLabels := client.FromLabelAdaptersToLabelsWithCopy(la)
53-
54-
if ref, err = app.Add(copiedLabels, s.TimestampMs, s.Value); err == nil {
82+
if ref, err := app.Add(copiedLabels, s.TimestampMs, s.Value); err == nil {
5583
db.refCache.SetRef(startAppend, copiedLabels, ref)
56-
cachedRef = ref
57-
cachedRefExists = true
5884
}
5985
}
6086

61-
if err == nil {
62-
err = app.Commit()
87+
return err
88+
}
89+
90+
func (a *backfillAppender) commit() error {
91+
var merr tsdb_errors.MultiError
92+
for _, app := range a.appenders {
93+
merr.Add(app.Commit())
6394
}
95+
return merr.Err()
96+
}
6497

65-
return err
98+
func (a *backfillAppender) rollback() error {
99+
var merr tsdb_errors.MultiError
100+
for _, app := range a.appenders {
101+
merr.Add(app.Rollback())
102+
}
103+
return merr.Err()
66104
}
67105

68-
func (i *Ingester) getOrCreateBackfillTSDB(userID string, ts int64) (*tsdbBucket, error) {
69-
userBuckets := i.TSDBState.backfillDBs.getBucketsForUser(userID)
106+
func getBucketForTimestamp(ts int64, userBuckets []*tsdbBucket) *tsdbBucket {
107+
// As the number of buckets will be small, we are iterating instead of binary search.
108+
for _, b := range userBuckets {
109+
if ts >= b.bucketStart && ts < b.bucketEnd {
110+
return b
111+
}
112+
}
113+
return nil
114+
}
115+
116+
func (i *Ingester) getOrCreateBackfillTSDB(userID string, ts int64) (*tsdbBucket, []*tsdbBucket, error) {
117+
i.TSDBState.backfillDBs.tsdbsMtx.Lock()
118+
defer i.TSDBState.backfillDBs.tsdbsMtx.Unlock()
119+
120+
userBuckets := i.TSDBState.backfillDBs.tsdbs[userID]
70121

71122
start, end := getBucketRangesForTimestamp(ts, 1)
72123

73-
var bucket *tsdbBucket
74124
insertIdx := len(userBuckets)
75125
for idx, b := range userBuckets {
76126
if ts >= b.bucketStart && ts < b.bucketEnd {
77-
bucket = b
78-
break
127+
return b, userBuckets, nil
79128
}
80129

81-
// Existing: |-----------|
82-
// New: |------------|
130+
// Existing: |-----------|
131+
// New: |------------|
132+
// Changed to: |------| (no overlaps)
83133
if b.bucketStart < start && start < b.bucketEnd {
84134
start = b.bucketEnd
85135
}
86136

87-
// Existing: |-----------|
88-
// New: |------------|
137+
// Existing: |-----------|
138+
// New: |------------|
139+
// Changed to: |------| (no overlaps)
89140
if end > b.bucketStart && end < b.bucketEnd {
90141
end = b.bucketStart
91142
insertIdx = idx
@@ -98,26 +149,26 @@ func (i *Ingester) getOrCreateBackfillTSDB(userID string, ts int64) (*tsdbBucket
98149
}
99150
}
100151

101-
if bucket == nil {
102-
tsdb, err := i.createNewTSDB(
103-
userID, filepath.Join(userID, getBucketName(start, end)),
104-
(end-start)*2, (end-start)*2, prometheus.NewRegistry(),
105-
)
106-
if err != nil {
107-
return nil, err
108-
}
109-
bucket = &tsdbBucket{
110-
db: tsdb,
111-
bucketStart: start,
112-
bucketEnd: end,
113-
}
114-
userBuckets = append(userBuckets[:insertIdx], append([]*tsdbBucket{bucket}, userBuckets[insertIdx:]...)...)
115-
i.TSDBState.backfillDBs.tsdbsMtx.Lock()
116-
i.TSDBState.backfillDBs.tsdbs[userID] = userBuckets
117-
i.TSDBState.backfillDBs.tsdbsMtx.Unlock()
152+
tsdb, err := i.createNewTSDB(
153+
userID, filepath.Join(userID, getBucketName(start, end)),
154+
(end-start)*2, (end-start)*2, prometheus.NewRegistry(),
155+
)
156+
if err != nil {
157+
return nil, nil, err
158+
}
159+
bucket := &tsdbBucket{
160+
db: tsdb,
161+
bucketStart: start,
162+
bucketEnd: end,
163+
}
164+
if len(userBuckets) > 0 {
165+
bucket.id = userBuckets[len(userBuckets)-1].id + 1
118166
}
167+
userBuckets = append(userBuckets[:insertIdx], append([]*tsdbBucket{bucket}, userBuckets[insertIdx:]...)...)
119168

120-
return bucket, nil
169+
i.TSDBState.backfillDBs.tsdbs[userID] = userBuckets
170+
171+
return bucket, userBuckets, nil
121172
}
122173

123174
func (i *Ingester) openExistingBackfillTSDB(ctx context.Context) error {
@@ -127,6 +178,9 @@ func (i *Ingester) openExistingBackfillTSDB(ctx context.Context) error {
127178

128179
users, err := ioutil.ReadDir(i.cfg.TSDBConfig.BackfillDir)
129180
if err != nil {
181+
if os.IsNotExist(err) {
182+
return nil
183+
}
130184
return err
131185
}
132186

@@ -145,7 +199,7 @@ func (i *Ingester) openExistingBackfillTSDB(ctx context.Context) error {
145199
continue
146200
}
147201

148-
for _, bucketName := range bucketNames {
202+
for bucketID, bucketName := range bucketNames {
149203
if bucketName.IsDir() {
150204
continue
151205
}
@@ -174,21 +228,26 @@ func (i *Ingester) openExistingBackfillTSDB(ctx context.Context) error {
174228
}
175229

176230
wg.Add(1)
177-
go func(userID, bucketName string) {
231+
go func(bucketID int, userID, bucketName string) {
178232
defer wg.Done()
179233
defer openGate.Done()
180234
defer func(ts time.Time) {
181235
i.TSDBState.walReplayTime.Observe(time.Since(ts).Seconds())
182236
}(time.Now())
183237

184238
start, end, err := getBucketRangesForBucketName(bucketName)
239+
if err != nil {
240+
level.Error(util.Logger).Log("msg", "unable to get bucket range", "err", err, "user", userID, "bucketName", bucketName)
241+
return
242+
}
185243
db, err := i.createNewTSDB(userID, filepath.Join(userID, bucketName), (end-start)*2, (end-start)*2, prometheus.NewRegistry())
186244
if err != nil {
187245
level.Error(util.Logger).Log("msg", "unable to open user backfill TSDB", "err", err, "user", userID)
188246
return
189247
}
190248

191249
bucket := &tsdbBucket{
250+
id: bucketID,
192251
db: db,
193252
bucketStart: start,
194253
bucketEnd: end,
@@ -198,7 +257,7 @@ func (i *Ingester) openExistingBackfillTSDB(ctx context.Context) error {
198257
// Append at the end, we will sort it at the end.
199258
i.TSDBState.backfillDBs.tsdbs[userID] = append(i.TSDBState.backfillDBs.tsdbs[userID], bucket)
200259
i.TSDBState.backfillDBs.tsdbsMtx.Unlock()
201-
}(userID, bucketName.Name())
260+
}(bucketID, userID, bucketName.Name())
202261
}
203262

204263
if runErr != nil {
@@ -278,6 +337,7 @@ func (i *Ingester) backfillSelect(ctx context.Context, userID string, from, thro
278337
select {
279338
case err := <-errC:
280339
return nil, err
340+
default:
281341
}
282342

283343
return result, nil
@@ -286,7 +346,8 @@ func (i *Ingester) backfillSelect(ctx context.Context, userID string, from, thro
286346
// Assumes 1h bucket range for . TODO(codesome): protect stuff with locks.
287347
type backfillTSDBs struct {
288348
tsdbsMtx sync.RWMutex
289-
tsdbs map[string][]*tsdbBucket
349+
// TODO(codesome): have more granular locks.
350+
tsdbs map[string][]*tsdbBucket
290351
}
291352

292353
func newBackfillTSDBs() *backfillTSDBs {
@@ -302,6 +363,7 @@ func (b *backfillTSDBs) getBucketsForUser(userID string) []*tsdbBucket {
302363
}
303364

304365
type tsdbBucket struct {
366+
id int // This is any number but should be unique among all buckets of a user.
305367
db *userTSDB
306368
bucketStart, bucketEnd int64
307369
}
Binary file not shown.

0 commit comments

Comments
 (0)