@@ -46,6 +46,28 @@ const (
46
46
samplesPerChunk = chunkLength / sampleRate
47
47
)
48
48
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
+
49
71
type query struct {
50
72
query string
51
73
labels labels.Labels
@@ -132,14 +154,159 @@ var (
132
154
}
133
155
)
134
156
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
+
135
299
func TestQuerier (t * testing.T ) {
136
300
var cfg Config
137
301
flagext .DefaultValues (& cfg )
138
302
139
303
const chunks = 24
140
304
141
305
// 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 ))
143
310
144
311
for _ , query := range queries {
145
312
for _ , encoding := range encodings {
@@ -165,7 +332,7 @@ func TestQuerier(t *testing.T) {
165
332
}
166
333
}
167
334
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 ) {
169
336
opts := tsdb .DefaultHeadOptions ()
170
337
opts .ChunkDirRoot = t .TempDir ()
171
338
// 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
176
343
})
177
344
178
345
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 ++
179
357
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 )
193
359
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
+ }
198
365
}
199
366
}
200
367
201
368
require .NoError (t , app .Commit ())
369
+
202
370
return storage .QueryableFunc (func (ctx context.Context , mint , maxt int64 ) (storage.Querier , error ) {
203
371
return tsdb .NewBlockQuerier (head , mint , maxt )
204
- })
372
+ }), rSamples
205
373
}
206
374
207
375
func TestNoHistoricalQueryToIngester (t * testing.T ) {
0 commit comments