Skip to content

Commit f7e1e36

Browse files
npazosmendezfrancoposa
authored andcommitted
parquet: optimize query sharding by leveraging materialized series hooks (#12136)
Leverage this feature prometheus-community/parquet-common#83 to optimize query sharding. Previously we were materializing all chunks and post-filtering them before sending through the wire. Now we can push down this filter to the materializer and avoid loading them entirely.
1 parent a8364d1 commit f7e1e36

File tree

10 files changed

+241
-118
lines changed

10 files changed

+241
-118
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ require (
3030
github.com/opentracing-contrib/go-stdlib v1.1.0 // indirect
3131
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
3232
github.com/pkg/errors v0.9.1
33-
github.com/prometheus-community/parquet-common v0.0.0-20250714085052-3b8805a2f9ad
33+
github.com/prometheus-community/parquet-common v0.0.0-20250716185251-4cfa597e936c
3434
github.com/prometheus/alertmanager v0.28.1
3535
github.com/prometheus/client_golang v1.23.0-rc.1
3636
github.com/prometheus/client_model v0.6.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,8 +895,8 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
895895
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
896896
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
897897
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
898-
github.com/prometheus-community/parquet-common v0.0.0-20250714085052-3b8805a2f9ad h1:w3igWmqBUdyOpiKoh8eO1j+Skd/kO+T+sC/OHP0uK6I=
899-
github.com/prometheus-community/parquet-common v0.0.0-20250714085052-3b8805a2f9ad/go.mod h1:MbAv/yCv9GORLj0XvXgRF913R9Jc04+BvVq4VJpPCi0=
898+
github.com/prometheus-community/parquet-common v0.0.0-20250716185251-4cfa597e936c h1:yDtT3c2klcWJj6A0osq72qM8rd1ohtl/J3rHD3FHuNw=
899+
github.com/prometheus-community/parquet-common v0.0.0-20250716185251-4cfa597e936c/go.mod h1:MbAv/yCv9GORLj0XvXgRF913R9Jc04+BvVq4VJpPCi0=
900900
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
901901
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
902902
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=

pkg/storage/parquet/chunk_querier.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,21 @@ import (
2323
var tracer = otel.Tracer("pkg/storage/parquet")
2424

2525
type querierOpts struct {
26-
concurrency int
27-
rowCountLimitFunc search.QuotaLimitFunc
28-
chunkBytesLimitFunc search.QuotaLimitFunc
29-
dataBytesLimitFunc search.QuotaLimitFunc
30-
materializedSeriesCallback search.MaterializedSeriesFunc
26+
concurrency int
27+
rowCountLimitFunc search.QuotaLimitFunc
28+
chunkBytesLimitFunc search.QuotaLimitFunc
29+
dataBytesLimitFunc search.QuotaLimitFunc
30+
materializedSeriesCallback search.MaterializedSeriesFunc
31+
materializedLabelsFilterCallback search.MaterializedLabelsFilterCallback
3132
}
3233

3334
var DefaultQuerierOpts = querierOpts{
34-
concurrency: runtime.GOMAXPROCS(0),
35-
rowCountLimitFunc: search.NoopQuotaLimitFunc,
36-
chunkBytesLimitFunc: search.NoopQuotaLimitFunc,
37-
dataBytesLimitFunc: search.NoopQuotaLimitFunc,
38-
materializedSeriesCallback: search.NoopMaterializedSeriesFunc,
35+
concurrency: runtime.GOMAXPROCS(0),
36+
rowCountLimitFunc: search.NoopQuotaLimitFunc,
37+
chunkBytesLimitFunc: search.NoopQuotaLimitFunc,
38+
dataBytesLimitFunc: search.NoopQuotaLimitFunc,
39+
materializedSeriesCallback: search.NoopMaterializedSeriesFunc,
40+
materializedLabelsFilterCallback: search.NoopMaterializedLabelsFilterCallback,
3941
}
4042

4143
type QuerierOpts func(*querierOpts)
@@ -76,6 +78,14 @@ func WithMaterializedSeriesCallback(fn search.MaterializedSeriesFunc) QuerierOpt
7678
}
7779
}
7880

81+
// WithMaterializedLabelsFilterCallback sets a callback function to create a filter that can be used
82+
// to filter series based on their labels before materializing chunks.
83+
func WithMaterializedLabelsFilterCallback(cb search.MaterializedLabelsFilterCallback) QuerierOpts {
84+
return func(opts *querierOpts) {
85+
opts.materializedLabelsFilterCallback = cb
86+
}
87+
}
88+
7989
func NewParquetChunkQuerier(d *schema.PrometheusParquetChunksDecoder, shardFinder queryable.ShardsFinderFunction, opts ...QuerierOpts) (prom_storage.ChunkQuerier, error) {
8090
cfg := DefaultQuerierOpts
8191

pkg/storage/parquet/queryable_shard.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func newQueryableShard(opts *querierOpts, block storage.ParquetShard, d *schema.
2828
if err != nil {
2929
return nil, err
3030
}
31-
m, err := search.NewMaterializer(s, d, block, opts.concurrency, rowCountQuota, chunkBytesQuota, dataBytesQuota, opts.materializedSeriesCallback)
31+
m, err := search.NewMaterializer(s, d, block, opts.concurrency, rowCountQuota, chunkBytesQuota, dataBytesQuota, opts.materializedSeriesCallback, opts.materializedLabelsFilterCallback)
3232
if err != nil {
3333
return nil, err
3434
}
@@ -66,7 +66,7 @@ func (b queryableShard) Query(ctx context.Context, sorted bool, mint, maxt int64
6666
return nil
6767
}
6868

69-
series, err := b.m.Materialize(ctx, rgi, mint, maxt, skipChunks, rr)
69+
series, err := b.m.Materialize(ctx, nil, rgi, mint, maxt, skipChunks, rr)
7070
if err != nil {
7171
return err
7272
}

pkg/storegateway/parquet_bucket_store.go

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/pkg/errors"
2727
"github.com/prometheus-community/parquet-common/queryable"
2828
"github.com/prometheus-community/parquet-common/schema"
29+
"github.com/prometheus-community/parquet-common/search"
2930
parquetStorage "github.com/prometheus-community/parquet-common/storage"
3031
"github.com/prometheus/prometheus/model/labels"
3132
"github.com/prometheus/prometheus/storage"
@@ -595,7 +596,15 @@ func (s *ParquetBucketStore) createLabelsAndChunksIterators(
595596
}
596597

597598
decoder := schema.NewPrometheusParquetChunksDecoder(chunkenc.NewPool())
598-
q, err := parquet.NewParquetChunkQuerier(decoder, shardsFinder, s.querierOpts...)
599+
opts := s.querierOpts
600+
if shardSelector != nil {
601+
shardFilter := func(_ context.Context, _ *storage.SelectHints) (search.MaterializedLabelsFilter, bool) {
602+
return materializedLabelsShardFilter{shardSelector: shardSelector}, true
603+
}
604+
opts = append(opts, parquet.WithMaterializedLabelsFilterCallback(shardFilter))
605+
}
606+
607+
q, err := parquet.NewParquetChunkQuerier(decoder, shardsFinder, opts...)
599608
if err != nil {
600609
return nil, nil, errors.Wrap(err, "error creating parquet queryable")
601610
}
@@ -618,7 +627,7 @@ func (s *ParquetBucketStore) createLabelsAndChunksIterators(
618627
// not support streaming results, the iterator we get from q.Select is
619628
// already backed by a slice. So we are not losing as much as it may seem.
620629
// We are planning to implement proper streaming.
621-
lbls, aggrChunks, err := toLabelsAndAggChunksSlice(chunkSeriesSet, shardSelector, req.SkipChunks)
630+
lbls, aggrChunks, err := toLabelsAndAggChunksSlice(chunkSeriesSet, req.SkipChunks)
622631
if err != nil {
623632
return nil, nil, errors.Wrap(err, "error converting parquet series set to labels and chunks slice")
624633
}
@@ -642,6 +651,31 @@ func (s *ParquetBucketStore) createLabelsAndChunksIterators(
642651
return labelsIt, newConcreteIterator(aggrChunks), nil
643652
}
644653

654+
type materializedLabelsShardFilter struct {
655+
shardSelector *sharding.ShardSelector
656+
}
657+
658+
func (f materializedLabelsShardFilter) Filter(lbls labels.Labels) bool {
659+
return shardOwnedUncached(f.shardSelector, lbls)
660+
}
661+
662+
func (f materializedLabelsShardFilter) Close() {
663+
}
664+
665+
// shardOwnedUncached checks if the given labels belong to the shard specified
666+
// by the shard selector. As opposed to shardOwned & friends from the
667+
// non-Parquet path, this function does not cache hashes. This is because, at
668+
// least yet, we don't have easy access to an identifier for the series in the
669+
// block to use as a cache key.
670+
func shardOwnedUncached(shard *sharding.ShardSelector, lset labels.Labels) bool {
671+
if shard == nil {
672+
return true
673+
}
674+
675+
hash := labels.StableHash(lset)
676+
return hash%shard.ShardCount == shard.ShardIndex
677+
}
678+
645679
func (s *ParquetBucketStore) sendStreamingSeriesLabelsAndStats(req *storepb.SeriesRequest, srv storegatewaypb.StoreGateway_SeriesServer, stats *safeQueryStats, labelsIt iterator[labels.Labels]) (numSeries int, err error) {
646680
var (
647681
encodeDuration = time.Duration(0)
@@ -1131,17 +1165,13 @@ func (s *ParquetBucketStore) cleanUpUnownedBlocks() error {
11311165
// storage.ChunkSeriesSet and returns them as slices, converting the chunks to
11321166
// storepb.AggrChunk format. If skipChunks is true, the chunks slice will be
11331167
// empty.
1134-
func toLabelsAndAggChunksSlice(chunkSeriesSet storage.ChunkSeriesSet, shardSelector *sharding.ShardSelector, skipChunks bool) ([]labels.Labels, [][]storepb.AggrChunk, error) {
1168+
func toLabelsAndAggChunksSlice(chunkSeriesSet storage.ChunkSeriesSet, skipChunks bool) ([]labels.Labels, [][]storepb.AggrChunk, error) {
11351169
var seriesLabels []labels.Labels
11361170
var aggrChunks [][]storepb.AggrChunk
11371171

11381172
for chunkSeriesSet.Next() {
11391173
chunkSeries := chunkSeriesSet.At()
11401174
lbls := chunkSeries.Labels()
1141-
if !shardOwnedUncached(shardSelector, lbls) {
1142-
continue
1143-
}
1144-
11451175
seriesLabels = append(seriesLabels, lbls)
11461176

11471177
if skipChunks {
@@ -1168,20 +1198,6 @@ func toLabelsAndAggChunksSlice(chunkSeriesSet storage.ChunkSeriesSet, shardSelec
11681198
return seriesLabels, aggrChunks, chunkSeriesSet.Err()
11691199
}
11701200

1171-
// shardOwnedUncached checks if the given labels belong to the shard specified
1172-
// by the shard selector. As opposed to shardOwned & friends from the
1173-
// non-Parquet path, this function does not cache hashes. This is because, at
1174-
// least yet, we don't have easy access to an identifier for the series in the
1175-
// block to use as a cache key.
1176-
func shardOwnedUncached(shard *sharding.ShardSelector, lset labels.Labels) bool {
1177-
if shard == nil {
1178-
return true
1179-
}
1180-
1181-
hash := labels.StableHash(lset)
1182-
return hash%shard.ShardCount == shard.ShardIndex
1183-
}
1184-
11851201
func prometheusChunkEncodingToStorePBChunkType(enc chunkenc.Encoding) storepb.Chunk_Encoding {
11861202
switch enc {
11871203
case chunkenc.EncXOR:

vendor/github.com/prometheus-community/parquet-common/queryable/parquet_queryable.go

Lines changed: 28 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/prometheus-community/parquet-common/search/constraint.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)