@@ -40,6 +40,173 @@ import (
4040 "github.com/cortexproject/cortex/pkg/util/validation"
4141)
4242
43+ // BenchmarkParquetBucketStore_ProjectionHints compares query performance
44+ // with and without projectionHints enabled.
45+ func BenchmarkParquetBucketStore_ProjectionHints (b * testing.B ) {
46+ seriesNum := []int {100 , 1000 , 10000 }
47+ samplePerSeries := 100
48+
49+ // projectionLabels is the subset of labels requested via projection hints.
50+ // The test data contains: __name__, idx, job, instance
51+ projectionLabels := []string {"job" }
52+
53+ for _ , numSeries := range seriesNum {
54+ b .Run (fmt .Sprintf ("series_%d" , numSeries ), func (b * testing.B ) {
55+ ctx := context .Background ()
56+ tmpDir := b .TempDir ()
57+ storageDir := filepath .Join (tmpDir , "storage" )
58+ dataDir := filepath .Join (tmpDir , "data" )
59+ userID := "user-1"
60+
61+ storageCfg := cortex_tsdb.BlocksStorageConfig {
62+ UsersScanner : users.UsersScannerConfig {
63+ Strategy : users .UserScanStrategyList ,
64+ UpdateInterval : time .Second ,
65+ },
66+ Bucket : bucket.Config {
67+ Backend : "filesystem" ,
68+ Filesystem : filesystem.Config {
69+ Directory : storageDir ,
70+ },
71+ },
72+ BucketStore : cortex_tsdb.BucketStoreConfig {
73+ SyncDir : filepath .Join (tmpDir , "sync" ),
74+ BucketStoreType : "parquet" ,
75+ BlockDiscoveryStrategy : string (cortex_tsdb .RecursiveDiscovery ),
76+ },
77+ }
78+
79+ bucketClient , err := bucket .NewClient (ctx , storageCfg .Bucket , nil , "test" , log .NewNopLogger (), prometheus .NewRegistry ())
80+ require .NoError (b , err )
81+
82+ blockID := prepareParquetBlock (b , ctx , storageCfg , bucketClient , dataDir , userID , numSeries , samplePerSeries )
83+
84+ startGRPCServer := func (honorProjectionHints bool ) (storepb.StoreClient , func ()) {
85+ cfg := storageCfg
86+ cfg .BucketStore .HonorProjectionHints = honorProjectionHints
87+
88+ reg := prometheus .NewPedanticRegistry ()
89+ stores , err := NewBucketStores (cfg , NewNoShardingStrategy (log .NewNopLogger (), nil ), objstore .WithNoopInstr (bucketClient ), defaultLimitsOverrides (nil ), mockLoggingLevel (), log .NewNopLogger (), reg )
90+ require .NoError (b , err )
91+
92+ listener , err := net .Listen ("tcp" , "localhost:0" )
93+ require .NoError (b , err )
94+
95+ gRPCServer := grpc .NewServer (
96+ grpc .StreamInterceptor (middleware .StreamServerUserHeaderInterceptor ),
97+ )
98+ storepb .RegisterStoreServer (gRPCServer , stores )
99+
100+ go func () {
101+ if err := gRPCServer .Serve (listener ); err != nil && err != grpc .ErrServerStopped {
102+ b .Error (err )
103+ }
104+ }()
105+
106+ conn , err := grpc .NewClient (listener .Addr ().String (),
107+ grpc .WithTransportCredentials (insecure .NewCredentials ()),
108+ grpc .WithDefaultCallOptions (grpc .MaxCallRecvMsgSize (math .MaxInt32 )),
109+ )
110+ require .NoError (b , err )
111+
112+ return storepb .NewStoreClient (conn ), func () {
113+ _ = conn .Close ()
114+ gRPCServer .Stop ()
115+ }
116+ }
117+
118+ // Benchmark without projectionHints (full scan)
119+ b .Run ("without_projection_hints" , func (b * testing.B ) {
120+ client , stop := startGRPCServer (false )
121+ defer stop ()
122+
123+ b .ReportAllocs ()
124+ for b .Loop () {
125+ benchmarkProjectionHints (b , client , userID , blockID , numSeries , nil )
126+ }
127+ })
128+
129+ // Benchmark with projectionHints enabled and projection labels specified
130+ b .Run ("with_projection_hints" , func (b * testing.B ) {
131+ client , stop := startGRPCServer (true )
132+ defer stop ()
133+
134+ b .ReportAllocs ()
135+ for b .Loop () {
136+ benchmarkProjectionHints (b , client , userID , blockID , numSeries , projectionLabels )
137+ }
138+ })
139+ })
140+ }
141+ }
142+
143+ func benchmarkProjectionHints (b * testing.B , client storepb.StoreClient , userID , blockID string , expectedSeries int , projectionLabels []string ) {
144+ b .Helper ()
145+
146+ ctx := grpcMetadata .NewOutgoingContext (context .Background (), grpcMetadata .Pairs (cortex_tsdb .TenantIDExternalLabel , userID ))
147+ ctx , err := user .InjectIntoGRPCRequest (user .InjectOrgID (ctx , userID ))
148+ require .NoError (b , err )
149+
150+ hintMatchers := []storepb.LabelMatcher {
151+ {
152+ Type : storepb .LabelMatcher_RE ,
153+ Name : block .BlockIDLabel ,
154+ Value : blockID ,
155+ },
156+ }
157+
158+ dataMatchers := []storepb.LabelMatcher {
159+ {
160+ Type : storepb .LabelMatcher_RE ,
161+ Name : "__name__" ,
162+ Value : ".+" ,
163+ },
164+ }
165+
166+ hints := & hintspb.SeriesRequestHints {
167+ BlockMatchers : hintMatchers ,
168+ }
169+ hintsAny , err := types .MarshalAny (hints )
170+ require .NoError (b , err )
171+
172+ req := & storepb.SeriesRequest {
173+ MinTime : 0 ,
174+ MaxTime : math .MaxInt64 ,
175+ Matchers : dataMatchers ,
176+ ResponseBatchSize : 1000 ,
177+ Hints : hintsAny ,
178+ }
179+
180+ if len (projectionLabels ) > 0 {
181+ req .QueryHints = & storepb.QueryHints {
182+ ProjectionInclude : true ,
183+ ProjectionLabels : projectionLabels ,
184+ }
185+ }
186+
187+ stream , err := client .Series (ctx , req )
188+ require .NoError (b , err )
189+
190+ got := 0
191+ for {
192+ resp , err := stream .Recv ()
193+ if err == io .EOF {
194+ break
195+ }
196+ require .NoError (b , err )
197+
198+ if series := resp .GetSeries (); series != nil {
199+ got ++
200+ } else if batch := resp .GetBatch (); batch != nil {
201+ got += len (batch .Series )
202+ }
203+ }
204+
205+ if got != expectedSeries {
206+ b .Fatalf ("expected %d series, got %d" , expectedSeries , got )
207+ }
208+ }
209+
43210func BenchmarkParquetBucketStore_SeriesBatch (b * testing.B ) {
44211 seriesNum := []int {100 , 1000 , 10000 , 100000 }
45212 samplePerSeries := 100
0 commit comments