Skip to content

Commit 72d6e0c

Browse files
authored
Let store-gateways ignore blocks that are too young. (#502)
* Let store-gateways ignore blocks that are too young. Signed-off-by: Peter Štibraný <[email protected]> * CHANGELOG.md Signed-off-by: Peter Štibraný <[email protected]> * Renamed TimeMetaFilter to minTimeMetaFilter, added it to TestBucketIndexMetadataFetcher_Fetch test. Signed-off-by: Peter Štibraný <[email protected]> * Unexport NewMinTimeMetaFilter. Signed-off-by: Peter Štibraný <[email protected]> * Enhanced field description. Signed-off-by: Peter Štibraný <[email protected]> * Fix wording as suggested by Arve. Signed-off-by: Peter Štibraný <[email protected]> * Address review feedback. Signed-off-by: Peter Štibraný <[email protected]> * Address review feedback. Signed-off-by: Peter Štibraný <[email protected]> * Fix tests after changing label value. Signed-off-by: Peter Štibraný <[email protected]>
1 parent 63ec943 commit 72d6e0c

File tree

10 files changed

+112
-4
lines changed

10 files changed

+112
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
* [ENHANCEMENT] Querier&Ruler: reduce cpu usage, latency and peak memory consumption. #459 #463
118118
* [ENHANCEMENT] Overrides Exporter: Add `max_fetched_chunks_per_query` limit to the default and per-tenant limits exported as metrics. #471
119119
* [ENHANCEMENT] Compactor (blocks cleaner): Delete blocks marked for deletion faster. #490
120+
* [ENHANCEMENT] Store-gateway: store-gateway can now ignore blocks with minimum time within `-blocks-storage.bucket-store.ignore-blocks-within` duration. Useful when used together with `-querier.query-store-after`. #502
120121
* [BUGFIX] Frontend: Fixes @ modifier functions (start/end) when splitting queries by time. #206
121122
* [BUGFIX] Fixes a panic in the query-tee when comparing result. #207
122123
* [BUGFIX] Upgrade Prometheus. TSDB now waits for pending readers before truncating Head block, fixing the `chunk not found` error and preventing wrong query results. #16

docs/blocks-storage/querier.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,14 @@ blocks_storage:
731731
# CLI flag: -blocks-storage.bucket-store.bucket-index.max-stale-period
732732
[max_stale_period: <duration> | default = 1h]
733733
734+
# Blocks with minimum time within this duration are ignored, and not loaded
735+
# by store-gateway. Useful when used together with
736+
# -querier.query-store-after to prevent loading young blocks, because there
737+
# are usually many of them (depending on number of ingesters) and they are
738+
# not yet compacted. Negative values or 0 disable the filter.
739+
# CLI flag: -blocks-storage.bucket-store.ignore-blocks-within
740+
[ignore_blocks_within: <duration> | default = 0s]
741+
734742
# Max size - in bytes - of a chunks pool, used to reduce memory allocations.
735743
# The pool is shared across all tenants. 0 to disable the limit.
736744
# CLI flag: -blocks-storage.bucket-store.max-chunk-pool-bytes

docs/blocks-storage/store-gateway.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,14 @@ blocks_storage:
784784
# CLI flag: -blocks-storage.bucket-store.bucket-index.max-stale-period
785785
[max_stale_period: <duration> | default = 1h]
786786
787+
# Blocks with minimum time within this duration are ignored, and not loaded
788+
# by store-gateway. Useful when used together with
789+
# -querier.query-store-after to prevent loading young blocks, because there
790+
# are usually many of them (depending on number of ingesters) and they are
791+
# not yet compacted. Negative values or 0 disable the filter.
792+
# CLI flag: -blocks-storage.bucket-store.ignore-blocks-within
793+
[ignore_blocks_within: <duration> | default = 0s]
794+
787795
# Max size - in bytes - of a chunks pool, used to reduce memory allocations.
788796
# The pool is shared across all tenants. 0 to disable the limit.
789797
# CLI flag: -blocks-storage.bucket-store.max-chunk-pool-bytes

docs/configuration/config-file-reference.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4985,6 +4985,14 @@ bucket_store:
49854985
# CLI flag: -blocks-storage.bucket-store.bucket-index.max-stale-period
49864986
[max_stale_period: <duration> | default = 1h]
49874987
4988+
# Blocks with minimum time within this duration are ignored, and not loaded by
4989+
# store-gateway. Useful when used together with -querier.query-store-after to
4990+
# prevent loading young blocks, because there are usually many of them
4991+
# (depending on number of ingesters) and they are not yet compacted. Negative
4992+
# values or 0 disable the filter.
4993+
# CLI flag: -blocks-storage.bucket-store.ignore-blocks-within
4994+
[ignore_blocks_within: <duration> | default = 0s]
4995+
49884996
# Max size - in bytes - of a chunks pool, used to reduce memory allocations.
49894997
# The pool is shared across all tenants. 0 to disable the limit.
49904998
# CLI flag: -blocks-storage.bucket-store.max-chunk-pool-bytes

pkg/storage/tsdb/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ type BucketStoreConfig struct {
259259
MetadataCache MetadataCacheConfig `yaml:"metadata_cache"`
260260
IgnoreDeletionMarksDelay time.Duration `yaml:"ignore_deletion_mark_delay"`
261261
BucketIndex BucketIndexConfig `yaml:"bucket_index"`
262+
IgnoreBlocksWithin time.Duration `yaml:"ignore_blocks_within"`
262263

263264
// Chunk pool.
264265
MaxChunkPoolBytes uint64 `yaml:"max_chunk_pool_bytes"`
@@ -305,6 +306,7 @@ func (cfg *BucketStoreConfig) RegisterFlags(f *flag.FlagSet) {
305306
f.DurationVar(&cfg.IgnoreDeletionMarksDelay, "blocks-storage.bucket-store.ignore-deletion-marks-delay", time.Hour*6, "Duration after which the blocks marked for deletion will be filtered out while fetching blocks. "+
306307
"The idea of ignore-deletion-marks-delay is to ignore blocks that are marked for deletion with some delay. This ensures store can still serve blocks that are meant to be deleted but do not have a replacement yet. "+
307308
"Default is 6h, half of the default value for -compactor.deletion-delay.")
309+
f.DurationVar(&cfg.IgnoreBlocksWithin, "blocks-storage.bucket-store.ignore-blocks-within", 0, "Blocks with minimum time within this duration are ignored, and not loaded by store-gateway. Useful when used together with -querier.query-store-after to prevent loading young blocks, because there are usually many of them (depending on number of ingesters) and they are not yet compacted. Negative values or 0 disable the filter.")
308310
f.IntVar(&cfg.PostingOffsetsInMemSampling, "blocks-storage.bucket-store.posting-offsets-in-mem-sampling", DefaultPostingOffsetInMemorySampling, "Controls what is the ratio of postings offsets that the store will hold in memory.")
309311
f.BoolVar(&cfg.IndexHeaderLazyLoadingEnabled, "blocks-storage.bucket-store.index-header-lazy-loading-enabled", false, "If enabled, store-gateway will lazy load an index-header only once required by a query.")
310312
f.DurationVar(&cfg.IndexHeaderLazyLoadingIdleTimeout, "blocks-storage.bucket-store.index-header-lazy-loading-idle-timeout", 20*time.Minute, "If index-header lazy loading is enabled and this setting is > 0, the store-gateway will offload unused index-headers after 'idle timeout' inactivity.")

pkg/storegateway/bucket_index_metadata_fetcher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func NewBucketIndexMetadataFetcher(
5757
logger: logger,
5858
filters: filters,
5959
modifiers: modifiers,
60-
metrics: block.NewFetcherMetrics(reg, [][]string{{corruptedBucketIndex}, {noBucketIndex}}, nil),
60+
metrics: block.NewFetcherMetrics(reg, [][]string{{corruptedBucketIndex}, {noBucketIndex}, {minTimeExcludedMeta}}, nil),
6161
}
6262
}
6363

pkg/storegateway/bucket_index_metadata_fetcher_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/oklog/ulid"
1919
"github.com/prometheus/client_golang/prometheus"
2020
"github.com/prometheus/client_golang/prometheus/testutil"
21+
"github.com/prometheus/prometheus/pkg/timestamp"
2122
"github.com/stretchr/testify/assert"
2223
"github.com/stretchr/testify/mock"
2324
"github.com/stretchr/testify/require"
@@ -43,19 +44,22 @@ func TestBucketIndexMetadataFetcher_Fetch(t *testing.T) {
4344
block1 := &bucketindex.Block{ID: ulid.MustNew(1, nil)}
4445
block2 := &bucketindex.Block{ID: ulid.MustNew(2, nil)}
4546
block3 := &bucketindex.Block{ID: ulid.MustNew(3, nil)}
47+
block4 := &bucketindex.Block{ID: ulid.MustNew(4, nil), MinTime: timestamp.FromTime(now.Add(-30 * time.Minute))} // Has most-recent data, to be ignored by minTimeMetaFilter.
48+
4649
mark1 := &bucketindex.BlockDeletionMark{ID: block1.ID, DeletionTime: now.Add(-time.Hour).Unix()} // Below the ignore delay threshold.
4750
mark2 := &bucketindex.BlockDeletionMark{ID: block2.ID, DeletionTime: now.Add(-3 * time.Hour).Unix()} // Above the ignore delay threshold.
4851

4952
require.NoError(t, bucketindex.WriteIndex(ctx, bkt, userID, nil, &bucketindex.Index{
5053
Version: bucketindex.IndexVersion1,
51-
Blocks: bucketindex.Blocks{block1, block2, block3},
54+
Blocks: bucketindex.Blocks{block1, block2, block3, block4},
5255
BlockDeletionMarks: bucketindex.BlockDeletionMarks{mark1, mark2},
5356
UpdatedAt: now.Unix(),
5457
}))
5558

5659
// Create a metadata fetcher with filters.
5760
filters := []block.MetadataFilter{
5861
NewIgnoreDeletionMarkFilter(logger, bucket.NewUserBucketClient(userID, bkt, nil), 2*time.Hour, 1),
62+
newMinTimeMetaFilter(1 * time.Hour),
5963
}
6064

6165
fetcher := NewBucketIndexMetadataFetcher(userID, bkt, NewNoShardingStrategy(), nil, logger, reg, filters, nil)
@@ -90,6 +94,7 @@ func TestBucketIndexMetadataFetcher_Fetch(t *testing.T) {
9094
blocks_meta_synced{state="no-bucket-index"} 0
9195
blocks_meta_synced{state="no-meta-json"} 0
9296
blocks_meta_synced{state="time-excluded"} 0
97+
blocks_meta_synced{state="min-time-excluded"} 1
9398
blocks_meta_synced{state="too-fresh"} 0
9499
95100
# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
@@ -141,6 +146,7 @@ func TestBucketIndexMetadataFetcher_Fetch_NoBucketIndex(t *testing.T) {
141146
blocks_meta_synced{state="no-bucket-index"} 1
142147
blocks_meta_synced{state="no-meta-json"} 0
143148
blocks_meta_synced{state="time-excluded"} 0
149+
blocks_meta_synced{state="min-time-excluded"} 0
144150
blocks_meta_synced{state="too-fresh"} 0
145151
146152
# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
@@ -195,6 +201,7 @@ func TestBucketIndexMetadataFetcher_Fetch_CorruptedBucketIndex(t *testing.T) {
195201
blocks_meta_synced{state="no-bucket-index"} 0
196202
blocks_meta_synced{state="no-meta-json"} 0
197203
blocks_meta_synced{state="time-excluded"} 0
204+
blocks_meta_synced{state="min-time-excluded"} 0
198205
blocks_meta_synced{state="too-fresh"} 0
199206
200207
# HELP blocks_meta_syncs_total Total blocks metadata synchronization attempts
@@ -241,6 +248,7 @@ func TestBucketIndexMetadataFetcher_Fetch_ShouldResetGaugeMetrics(t *testing.T)
241248
blocks_meta_synced{state="no-bucket-index"} 0
242249
blocks_meta_synced{state="no-meta-json"} 0
243250
blocks_meta_synced{state="time-excluded"} 0
251+
blocks_meta_synced{state="min-time-excluded"} 0
244252
blocks_meta_synced{state="too-fresh"} 0
245253
`), "blocks_meta_synced"))
246254

@@ -265,6 +273,7 @@ func TestBucketIndexMetadataFetcher_Fetch_ShouldResetGaugeMetrics(t *testing.T)
265273
blocks_meta_synced{state="no-bucket-index"} 1
266274
blocks_meta_synced{state="no-meta-json"} 0
267275
blocks_meta_synced{state="time-excluded"} 0
276+
blocks_meta_synced{state="min-time-excluded"} 0
268277
blocks_meta_synced{state="too-fresh"} 0
269278
`), "blocks_meta_synced"))
270279

@@ -297,6 +306,7 @@ func TestBucketIndexMetadataFetcher_Fetch_ShouldResetGaugeMetrics(t *testing.T)
297306
blocks_meta_synced{state="no-bucket-index"} 0
298307
blocks_meta_synced{state="no-meta-json"} 0
299308
blocks_meta_synced{state="time-excluded"} 0
309+
blocks_meta_synced{state="min-time-excluded"} 0
300310
blocks_meta_synced{state="too-fresh"} 0
301311
`), "blocks_meta_synced"))
302312

@@ -323,6 +333,7 @@ func TestBucketIndexMetadataFetcher_Fetch_ShouldResetGaugeMetrics(t *testing.T)
323333
blocks_meta_synced{state="no-bucket-index"} 0
324334
blocks_meta_synced{state="no-meta-json"} 0
325335
blocks_meta_synced{state="time-excluded"} 0
336+
blocks_meta_synced{state="min-time-excluded"} 0
326337
blocks_meta_synced{state="too-fresh"} 0
327338
`), "blocks_meta_synced"))
328339
}

pkg/storegateway/bucket_stores.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,15 +440,17 @@ func (u *BucketStores) getOrCreateStore(userID string) (*BucketStore, error) {
440440
fetcherReg := prometheus.NewRegistry()
441441

442442
// The sharding strategy filter MUST be before the ones we create here (order matters).
443-
filters := append([]block.MetadataFilter{NewShardingMetadataFilterAdapter(userID, u.shardingStrategy)}, []block.MetadataFilter{
443+
filters := []block.MetadataFilter{
444+
NewShardingMetadataFilterAdapter(userID, u.shardingStrategy),
444445
block.NewConsistencyDelayMetaFilter(userLogger, u.cfg.BucketStore.ConsistencyDelay, fetcherReg),
446+
newMinTimeMetaFilter(u.cfg.BucketStore.IgnoreBlocksWithin),
445447
// Use our own custom implementation.
446448
NewIgnoreDeletionMarkFilter(userLogger, userBkt, u.cfg.BucketStore.IgnoreDeletionMarksDelay, u.cfg.BucketStore.MetaSyncConcurrency),
447449
// The duplicate filter has been intentionally omitted because it could cause troubles with
448450
// the consistency check done on the querier. The duplicate filter removes redundant blocks
449451
// but if the store-gateway removes redundant blocks before the querier discovers them, the
450452
// consistency check on the querier will fail.
451-
}...)
453+
}
452454

453455
// Instantiate a different blocks metadata fetcher based on whether bucket index is enabled or not.
454456
var fetcher block.MetadataFetcher

pkg/storegateway/metadata_fetcher_filters.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/go-kit/log"
1313
"github.com/oklog/ulid"
14+
"github.com/prometheus/prometheus/pkg/timestamp"
1415
"github.com/thanos-io/thanos/pkg/block"
1516
"github.com/thanos-io/thanos/pkg/block/metadata"
1617
"github.com/thanos-io/thanos/pkg/extprom"
@@ -81,3 +82,32 @@ func (f *IgnoreDeletionMarkFilter) FilterWithBucketIndex(_ context.Context, meta
8182

8283
return nil
8384
}
85+
86+
const minTimeExcludedMeta = "min-time-excluded"
87+
88+
// minTimeMetaFilter filters out blocks that contain the most recent data (based on block MinTime).
89+
type minTimeMetaFilter struct {
90+
limit time.Duration
91+
}
92+
93+
func newMinTimeMetaFilter(limit time.Duration) *minTimeMetaFilter {
94+
return &minTimeMetaFilter{limit: limit}
95+
}
96+
97+
func (f *minTimeMetaFilter) Filter(_ context.Context, metas map[ulid.ULID]*metadata.Meta, synced *extprom.TxGaugeVec) error {
98+
if f.limit <= 0 {
99+
return nil
100+
}
101+
102+
limitTime := timestamp.FromTime(time.Now().Add(-f.limit))
103+
104+
for id, m := range metas {
105+
if m.MinTime < limitTime {
106+
continue
107+
}
108+
109+
synced.WithLabelValues(minTimeExcludedMeta).Inc()
110+
delete(metas, id)
111+
}
112+
return nil
113+
}

pkg/storegateway/metadata_fetcher_filters_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"github.com/oklog/ulid"
1818
"github.com/prometheus/client_golang/prometheus"
1919
promtest "github.com/prometheus/client_golang/prometheus/testutil"
20+
"github.com/prometheus/prometheus/pkg/timestamp"
21+
"github.com/prometheus/prometheus/tsdb"
2022
"github.com/stretchr/testify/assert"
2123
"github.com/stretchr/testify/require"
2224
"github.com/thanos-io/thanos/pkg/block"
@@ -110,3 +112,39 @@ func testIgnoreDeletionMarkFilter(t *testing.T, bucketIndexEnabled bool) {
110112
assert.Equal(t, expectedMetas, inputMetas)
111113
assert.Equal(t, expectedDeletionMarks, f.DeletionMarkBlocks())
112114
}
115+
116+
func TestTimeMetaFilter(t *testing.T) {
117+
now := time.Now()
118+
limit := 10 * time.Minute
119+
limitTime := now.Add(-limit)
120+
121+
ulid1 := ulid.MustNew(1, nil)
122+
ulid2 := ulid.MustNew(2, nil)
123+
ulid3 := ulid.MustNew(3, nil)
124+
ulid4 := ulid.MustNew(4, nil)
125+
126+
inputMetas := map[ulid.ULID]*metadata.Meta{
127+
ulid1: {BlockMeta: tsdb.BlockMeta{MinTime: 100}}, // Very old, keep it
128+
ulid2: {BlockMeta: tsdb.BlockMeta{MinTime: timestamp.FromTime(now)}}, // Fresh block, remove.
129+
ulid3: {BlockMeta: tsdb.BlockMeta{MinTime: timestamp.FromTime(limitTime.Add(time.Minute))}}, // Inside limit time, remove.
130+
ulid4: {BlockMeta: tsdb.BlockMeta{MinTime: timestamp.FromTime(limitTime.Add(-time.Minute))}}, // Before limit time, keep.
131+
}
132+
133+
expectedMetas := map[ulid.ULID]*metadata.Meta{}
134+
expectedMetas[ulid1] = inputMetas[ulid1]
135+
expectedMetas[ulid4] = inputMetas[ulid4]
136+
137+
synced := extprom.NewTxGaugeVec(nil, prometheus.GaugeOpts{Name: "synced"}, []string{"state"})
138+
139+
// Test negative limit.
140+
f := newMinTimeMetaFilter(-10 * time.Minute)
141+
require.NoError(t, f.Filter(context.Background(), inputMetas, synced))
142+
assert.Equal(t, inputMetas, inputMetas)
143+
assert.Equal(t, 0.0, promtest.ToFloat64(synced.WithLabelValues(minTimeExcludedMeta)))
144+
145+
f = newMinTimeMetaFilter(limit)
146+
require.NoError(t, f.Filter(context.Background(), inputMetas, synced))
147+
148+
assert.Equal(t, expectedMetas, inputMetas)
149+
assert.Equal(t, 2.0, promtest.ToFloat64(synced.WithLabelValues(minTimeExcludedMeta)))
150+
}

0 commit comments

Comments
 (0)