@@ -46,6 +46,28 @@ const (
4646 samplesPerChunk = chunkLength / sampleRate
4747)
4848
49+ type wrappedQuerier struct {
50+ storage.Querier
51+ selectCallsArgs [][]interface {}
52+ }
53+
54+ func (q * wrappedQuerier ) Select (sortSeries bool , hints * storage.SelectHints , matchers ... * labels.Matcher ) storage.SeriesSet {
55+ q .selectCallsArgs = append (q .selectCallsArgs , []interface {}{sortSeries , hints , matchers })
56+ return q .Querier .Select (sortSeries , hints , matchers ... )
57+ }
58+
59+ type wrappedSampleAndChunkQueryable struct {
60+ QueryableWithFilter
61+ queriers []* wrappedQuerier
62+ }
63+
64+ func (q * wrappedSampleAndChunkQueryable ) Querier (ctx context.Context , mint , maxt int64 ) (storage.Querier , error ) {
65+ querier , err := q .QueryableWithFilter .Querier (ctx , mint , maxt )
66+ wQuerier := & wrappedQuerier {Querier : querier }
67+ q .queriers = append (q .queriers , wQuerier )
68+ return wQuerier , err
69+ }
70+
4971type query struct {
5072 query string
5173 labels labels.Labels
@@ -132,14 +154,159 @@ var (
132154 }
133155)
134156
157+ func TestShouldSortSeriesIfQueryingMultipleQueryables (t * testing.T ) {
158+ start := time .Now ().Add (- 2 * time .Hour )
159+ end := time .Now ()
160+ ctx := user .InjectOrgID (context .Background (), "0" )
161+ var cfg Config
162+ flagext .DefaultValues (& cfg )
163+ overrides , err := validation .NewOverrides (DefaultLimitsConfig (), nil )
164+ const chunks = 1
165+ require .NoError (t , err )
166+
167+ labelsSets := []labels.Labels {
168+ {
169+ {Name : model .MetricNameLabel , Value : "foo" },
170+ {Name : "order" , Value : "1" },
171+ },
172+ {
173+ {Name : model .MetricNameLabel , Value : "foo" },
174+ {Name : "order" , Value : "2" },
175+ },
176+ }
177+
178+ db , samples := mockTSDB (t , labelsSets , model .Time (start .Unix ()* 1000 ), int (chunks * samplesPerChunk ), sampleRate , chunkOffset , int (samplesPerChunk ))
179+ samplePairs := []model.SamplePair {}
180+
181+ for _ , s := range samples {
182+ samplePairs = append (samplePairs , model.SamplePair {Timestamp : model .Time (s .TimestampMs ), Value : model .SampleValue (s .Value )})
183+ }
184+
185+ distributor := & MockDistributor {}
186+
187+ unorderedResponse := client.QueryStreamResponse {
188+ Timeseries : []cortexpb.TimeSeries {
189+ {
190+ Labels : []cortexpb.LabelAdapter {
191+ {Name : model .MetricNameLabel , Value : "foo" },
192+ {Name : "order" , Value : "2" },
193+ },
194+ Samples : samples ,
195+ },
196+ {
197+ Labels : []cortexpb.LabelAdapter {
198+ {Name : model .MetricNameLabel , Value : "foo" },
199+ {Name : "order" , Value : "1" },
200+ },
201+ Samples : samples ,
202+ },
203+ },
204+ }
205+
206+ unorderedResponseMatrix := model.Matrix {
207+ {
208+ Metric : util .LabelsToMetric (cortexpb .FromLabelAdaptersToLabels (unorderedResponse .Timeseries [0 ].Labels )),
209+ Values : samplePairs ,
210+ },
211+ {
212+ Metric : util .LabelsToMetric (cortexpb .FromLabelAdaptersToLabels (unorderedResponse .Timeseries [1 ].Labels )),
213+ Values : samplePairs ,
214+ },
215+ }
216+
217+ distributor .On ("QueryStream" , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).Return (& unorderedResponse , nil )
218+ distributor .On ("Query" , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).Return (unorderedResponseMatrix , nil )
219+ distributorQueryableStreaming := newDistributorQueryable (distributor , true , cfg .IngesterMetadataStreaming , batch .NewChunkMergeIterator , cfg .QueryIngestersWithin )
220+ distributorQueryable := newDistributorQueryable (distributor , false , cfg .IngesterMetadataStreaming , batch .NewChunkMergeIterator , cfg .QueryIngestersWithin )
221+
222+ tCases := []struct {
223+ name string
224+ distributorQueryable QueryableWithFilter
225+ storeQueriables []QueryableWithFilter
226+ sorted bool
227+ }{
228+ {
229+ name : "should sort if querying 2 queryables" ,
230+ distributorQueryable : distributorQueryableStreaming ,
231+ storeQueriables : []QueryableWithFilter {UseAlwaysQueryable (db )},
232+ sorted : true ,
233+ },
234+ {
235+ name : "should not sort if querying only ingesters" ,
236+ distributorQueryable : distributorQueryableStreaming ,
237+ storeQueriables : []QueryableWithFilter {UseBeforeTimestampQueryable (db , start .Add (- 1 * time .Hour ))},
238+ sorted : false ,
239+ },
240+ {
241+ name : "should not sort if querying only stores" ,
242+ distributorQueryable : UseBeforeTimestampQueryable (distributorQueryableStreaming , start .Add (- 1 * time .Hour )),
243+ storeQueriables : []QueryableWithFilter {UseAlwaysQueryable (db )},
244+ sorted : false ,
245+ },
246+ {
247+ name : "should sort if querying 2 queryables with streaming off" ,
248+ distributorQueryable : distributorQueryable ,
249+ storeQueriables : []QueryableWithFilter {UseAlwaysQueryable (db )},
250+ sorted : true ,
251+ },
252+ {
253+ name : "should not sort if querying only ingesters with streaming off" ,
254+ distributorQueryable : distributorQueryable ,
255+ storeQueriables : []QueryableWithFilter {UseBeforeTimestampQueryable (db , start .Add (- 1 * time .Hour ))},
256+ sorted : false ,
257+ },
258+ {
259+ name : "should not sort if querying only stores with streaming off" ,
260+ distributorQueryable : UseBeforeTimestampQueryable (distributorQueryable , start .Add (- 1 * time .Hour )),
261+ storeQueriables : []QueryableWithFilter {UseAlwaysQueryable (db )},
262+ sorted : false ,
263+ },
264+ }
265+
266+ for _ , tc := range tCases {
267+ t .Run (tc .name , func (t * testing.T ) {
268+ wDistributorQueriable := & wrappedSampleAndChunkQueryable {QueryableWithFilter : tc .distributorQueryable }
269+ var wQueriables []QueryableWithFilter
270+ for _ , queriable := range tc .storeQueriables {
271+ wQueriables = append (wQueriables , & wrappedSampleAndChunkQueryable {QueryableWithFilter : queriable })
272+ }
273+ queryable := NewQueryable (wDistributorQueriable , wQueriables , batch .NewChunkMergeIterator , cfg , overrides , purger .NewNoopTombstonesLoader ())
274+ queryTracker := promql .NewActiveQueryTracker (t .TempDir (), 10 , log .NewNopLogger ())
275+
276+ engine := promql .NewEngine (promql.EngineOpts {
277+ Logger : log .NewNopLogger (),
278+ ActiveQueryTracker : queryTracker ,
279+ MaxSamples : 1e6 ,
280+ Timeout : 1 * time .Minute ,
281+ })
282+
283+ query , err := engine .NewRangeQuery (queryable , nil , "foo" , start , end , 1 * time .Minute )
284+ r := query .Exec (ctx )
285+
286+ require .NoError (t , err )
287+ require .Equal (t , 2 , r .Value .(promql.Matrix ).Len ())
288+
289+ for _ , queryable := range append (wQueriables , wDistributorQueriable ) {
290+ var wQueryable = queryable .(* wrappedSampleAndChunkQueryable )
291+ if wQueryable .UseQueryable (time .Now (), start .Unix ()* 1000 , end .Unix ()* 1000 ) {
292+ require .Equal (t , tc .sorted , wQueryable .queriers [0 ].selectCallsArgs [0 ][0 ])
293+ }
294+ }
295+ })
296+ }
297+ }
298+
135299func TestQuerier (t * testing.T ) {
136300 var cfg Config
137301 flagext .DefaultValues (& cfg )
138302
139303 const chunks = 24
140304
141305 // Generate TSDB head with the same samples as makeMockChunkStore.
142- db := mockTSDB (t , model .Time (0 ), int (chunks * samplesPerChunk ), sampleRate , chunkOffset , int (samplesPerChunk ))
306+ lset := labels.Labels {
307+ {Name : model .MetricNameLabel , Value : "foo" },
308+ }
309+ db , _ := mockTSDB (t , []labels.Labels {lset }, model .Time (0 ), int (chunks * samplesPerChunk ), sampleRate , chunkOffset , int (samplesPerChunk ))
143310
144311 for _ , query := range queries {
145312 for _ , encoding := range encodings {
@@ -165,7 +332,7 @@ func TestQuerier(t *testing.T) {
165332 }
166333}
167334
168- func mockTSDB (t * testing.T , mint model.Time , samples int , step , chunkOffset time.Duration , samplesPerChunk int ) storage.Queryable {
335+ func mockTSDB (t * testing.T , labels []labels. Labels , mint model.Time , samples int , step , chunkOffset time.Duration , samplesPerChunk int ) ( storage.Queryable , []cortexpb. Sample ) {
169336 opts := tsdb .DefaultHeadOptions ()
170337 opts .ChunkDirRoot = t .TempDir ()
171338 // We use TSDB head only. By using full TSDB DB, and appending samples to it, closing it would cause unnecessary HEAD compaction, which slows down the test.
@@ -176,32 +343,33 @@ func mockTSDB(t *testing.T, mint model.Time, samples int, step, chunkOffset time
176343 })
177344
178345 app := head .Appender (context .Background ())
346+ rSamples := []cortexpb.Sample {}
347+
348+ for _ , lset := range labels {
349+ cnt := 0
350+ chunkStartTs := mint
351+ ts := chunkStartTs
352+ for i := 0 ; i < samples ; i ++ {
353+ _ , err := app .Append (0 , lset , int64 (ts ), float64 (ts ))
354+ rSamples = append (rSamples , cortexpb.Sample {TimestampMs : int64 (ts ), Value : float64 (ts )})
355+ require .NoError (t , err )
356+ cnt ++
179357
180- l := labels.Labels {
181- {Name : model .MetricNameLabel , Value : "foo" },
182- }
183-
184- cnt := 0
185- chunkStartTs := mint
186- ts := chunkStartTs
187- for i := 0 ; i < samples ; i ++ {
188- _ , err := app .Append (0 , l , int64 (ts ), float64 (ts ))
189- require .NoError (t , err )
190- cnt ++
191-
192- ts = ts .Add (step )
358+ ts = ts .Add (step )
193359
194- if cnt % samplesPerChunk == 0 {
195- // Simulate next chunk, restart timestamp.
196- chunkStartTs = chunkStartTs .Add (chunkOffset )
197- ts = chunkStartTs
360+ if cnt % samplesPerChunk == 0 {
361+ // Simulate next chunk, restart timestamp.
362+ chunkStartTs = chunkStartTs .Add (chunkOffset )
363+ ts = chunkStartTs
364+ }
198365 }
199366 }
200367
201368 require .NoError (t , app .Commit ())
369+
202370 return storage .QueryableFunc (func (ctx context.Context , mint , maxt int64 ) (storage.Querier , error ) {
203371 return tsdb .NewBlockQuerier (head , mint , maxt )
204- })
372+ }), rSamples
205373}
206374
207375func TestNoHistoricalQueryToIngester (t * testing.T ) {
0 commit comments