github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/consolidators/multi_fetch_result_test.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package consolidators 22 23 import ( 24 "fmt" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/dbnode/encoding" 29 "github.com/m3db/m3/src/query/block" 30 "github.com/m3db/m3/src/query/models" 31 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 32 "github.com/m3db/m3/src/x/ident" 33 xtest "github.com/m3db/m3/src/x/test" 34 xtime "github.com/m3db/m3/src/x/time" 35 36 "github.com/golang/mock/gomock" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 ) 40 41 var defaultTestOpts = MatchOptions{ 42 MatchType: defaultMatchType, 43 } 44 45 const ( 46 common = "common" 47 short = "short" 48 long = "long" 49 unaggregated = "unaggregated" 50 ) 51 52 // NB: Each seriesIterators has two seriesIterator; one with a constant ID which 53 // will be overwritten as necessary by multi_fetch_result, and one per namespace 54 // which should not be overwritten and should appear in the results. 55 func generateSeriesIterators(ctrl *gomock.Controller, ns string) encoding.SeriesIterators { 56 var ( 57 end = xtime.Now().Truncate(time.Hour) 58 start = end.Add(-24 * time.Hour) 59 ) 60 61 iter := encoding.NewMockSeriesIterator(ctrl) 62 iter.EXPECT().ID().Return(ident.StringID(common)).MinTimes(1) 63 iter.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes() 64 iter.EXPECT().Tags().Return(ident.EmptyTagIterator).AnyTimes() 65 iter.EXPECT().Start().Return(start).AnyTimes() 66 iter.EXPECT().End().Return(end).AnyTimes() 67 iter.EXPECT().Close() 68 69 unique := encoding.NewMockSeriesIterator(ctrl) 70 unique.EXPECT().ID().Return(ident.StringID(ns)).MinTimes(1) 71 unique.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes() 72 unique.EXPECT().Tags().Return(ident.EmptyTagIterator).AnyTimes() 73 unique.EXPECT().Start().Return(start).AnyTimes() 74 unique.EXPECT().End().Return(end).AnyTimes() 75 unique.EXPECT().Close() 76 77 return encoding.NewSeriesIterators([]encoding.SeriesIterator{iter, unique}) 78 } 79 80 func generateUnreadSeriesIterators(ctrl *gomock.Controller, ns string) encoding.SeriesIterators { 81 iter := encoding.NewMockSeriesIterator(ctrl) 82 iter.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes() 83 iter.EXPECT().Close() 84 85 unique := encoding.NewMockSeriesIterator(ctrl) 86 unique.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes() 87 unique.EXPECT().Close() 88 89 return encoding.NewSeriesIterators([]encoding.SeriesIterator{iter, unique}) 90 } 91 92 var namespaces = []struct { 93 attrs storagemetadata.Attributes 94 ns string 95 }{ 96 { 97 attrs: storagemetadata.Attributes{ 98 MetricsType: storagemetadata.UnaggregatedMetricsType, 99 Retention: 24 * time.Hour, 100 Resolution: 0 * time.Minute, 101 }, 102 ns: unaggregated, 103 }, 104 { 105 attrs: storagemetadata.Attributes{ 106 MetricsType: storagemetadata.AggregatedMetricsType, 107 Retention: 360 * time.Hour, 108 Resolution: 2 * time.Minute, 109 }, 110 ns: short, 111 }, 112 { 113 attrs: storagemetadata.Attributes{ 114 MetricsType: storagemetadata.AggregatedMetricsType, 115 Retention: 17520 * time.Hour, 116 Resolution: 10 * time.Minute, 117 }, 118 ns: long, 119 }, 120 } 121 122 func TestMultiResultPartialQueryRange(t *testing.T) { 123 testMultiResult(t, NamespaceCoversPartialQueryRange, long) 124 } 125 126 func TestMultiResultAllQueryRange(t *testing.T) { 127 testMultiResult(t, NamespaceCoversAllQueryRange, unaggregated) 128 } 129 130 func testMultiResult(t *testing.T, fanoutType QueryFanoutType, expected string) { 131 ctrl := xtest.NewController(t) 132 133 r := NewMultiFetchResult(fanoutType, 134 defaultTestOpts, models.NewTagOptions(), LimitOptions{Limit: 1000}) 135 136 meta := block.NewResultMetadata() 137 meta.FetchedSeriesCount = 4 138 for _, ns := range namespaces { 139 iters := generateSeriesIterators(ctrl, ns.ns) 140 res := MultiFetchResults{ 141 SeriesIterators: iters, 142 Metadata: meta, 143 Attrs: ns.attrs, 144 Err: nil, 145 } 146 r.Add(res) 147 } 148 149 result, err := r.FinalResult() 150 assert.NoError(t, err) 151 152 assert.True(t, result.Metadata.Exhaustive) 153 assert.True(t, result.Metadata.LocalOnly) 154 assert.Equal(t, 0, len(result.Metadata.Warnings)) 155 156 iters := result.seriesData.seriesIterators 157 assert.Equal(t, 4, iters.Len()) 158 assert.Equal(t, 4, len(iters.Iters())) 159 160 for _, n := range iters.Iters() { 161 id := n.ID().String() 162 // NB: if this is the common id, check against expected for the fanout type. 163 if id == common { 164 assert.Equal(t, expected, n.Namespace().String()) 165 } else { 166 assert.Equal(t, id, n.Namespace().String()) 167 } 168 } 169 170 assert.NoError(t, r.Close()) 171 } 172 173 func TestLimit(t *testing.T) { 174 ctrl := xtest.NewController(t) 175 defer ctrl.Finish() 176 177 r := NewMultiFetchResult(NamespaceCoversPartialQueryRange, 178 defaultTestOpts, models.NewTagOptions(), LimitOptions{ 179 Limit: 2, 180 RequireExhaustive: false, 181 }) 182 183 meta := block.NewResultMetadata() 184 for _, ns := range namespaces[0:2] { 185 iters := generateSeriesIterators(ctrl, ns.ns) 186 res := MultiFetchResults{ 187 SeriesIterators: iters, 188 Metadata: meta, 189 Attrs: ns.attrs, 190 Err: nil, 191 } 192 r.Add(res) 193 } 194 longNs := namespaces[2] 195 res := MultiFetchResults{ 196 SeriesIterators: generateUnreadSeriesIterators(ctrl, longNs.ns), 197 Metadata: meta, 198 Attrs: longNs.attrs, 199 Err: nil, 200 } 201 r.Add(res) 202 203 result, err := r.FinalResult() 204 assert.NoError(t, err) 205 assert.False(t, result.Metadata.Exhaustive) 206 assert.True(t, result.Metadata.LocalOnly) 207 assert.Equal(t, 2, result.Metadata.FetchedSeriesCount) 208 assert.Equal(t, 0, len(result.Metadata.Warnings)) 209 210 iters := result.seriesData.seriesIterators 211 assert.Equal(t, 2, iters.Len()) 212 assert.Equal(t, 2, len(iters.Iters())) 213 214 for _, iter := range iters.Iters() { 215 ns := iter.Namespace().String() 216 if ns != short { 217 assert.Equal(t, iter.ID().String(), ns) 218 } 219 } 220 assert.NoError(t, r.Close()) 221 } 222 223 func TestLimitRequireExhaustive(t *testing.T) { 224 ctrl := xtest.NewController(t) 225 defer ctrl.Finish() 226 227 r := NewMultiFetchResult(NamespaceCoversPartialQueryRange, 228 defaultTestOpts, models.NewTagOptions(), LimitOptions{ 229 Limit: 2, 230 RequireExhaustive: true, 231 }) 232 233 meta := block.NewResultMetadata() 234 for _, ns := range namespaces[0:2] { 235 iters := generateSeriesIterators(ctrl, ns.ns) 236 res := MultiFetchResults{ 237 SeriesIterators: iters, 238 Metadata: meta, 239 Attrs: ns.attrs, 240 Err: nil, 241 } 242 r.Add(res) 243 } 244 longNs := namespaces[2] 245 res := MultiFetchResults{ 246 SeriesIterators: generateUnreadSeriesIterators(ctrl, longNs.ns), 247 Metadata: meta, 248 Attrs: longNs.attrs, 249 Err: nil, 250 } 251 r.Add(res) 252 253 _, err := r.FinalResult() 254 require.Error(t, err) 255 assert.NoError(t, r.Close()) 256 } 257 258 var exhaustTests = []struct { 259 name string 260 exhaustives []bool 261 expected bool 262 }{ 263 {"single exhaustive", []bool{true}, true}, 264 {"single non-exhaustive", []bool{false}, false}, 265 {"multiple exhaustive", []bool{true, true}, true}, 266 {"multiple non-exhaustive", []bool{false, false}, false}, 267 {"some exhaustive", []bool{true, false}, false}, 268 {"mixed", []bool{true, false, true}, false}, 269 } 270 271 func TestExhaustiveMerge(t *testing.T) { 272 ctrl := xtest.NewController(t) 273 defer ctrl.Finish() 274 275 for _, tt := range exhaustTests { 276 t.Run(tt.name, func(t *testing.T) { 277 r := NewMultiFetchResult(NamespaceCoversAllQueryRange, 278 defaultTestOpts, models.NewTagOptions(), LimitOptions{Limit: 1000}) 279 for i, ex := range tt.exhaustives { 280 iters := encoding.NewSeriesIterators([]encoding.SeriesIterator{ 281 encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{ 282 ID: ident.StringID(fmt.Sprint(i)), 283 Namespace: ident.StringID("ns"), 284 }, nil), 285 }) 286 287 meta := block.NewResultMetadata() 288 meta.Exhaustive = ex 289 res := MultiFetchResults{ 290 SeriesIterators: iters, 291 Metadata: meta, 292 Attrs: storagemetadata.Attributes{}, 293 Err: nil, 294 } 295 r.Add(res) 296 } 297 298 result, err := r.FinalResult() 299 assert.NoError(t, err) 300 assert.Equal(t, tt.expected, result.Metadata.Exhaustive) 301 assert.NoError(t, r.Close()) 302 }) 303 } 304 } 305 306 func TestAddWarningsPreservedFollowedByAdd(t *testing.T) { 307 ctrl := xtest.NewController(t) 308 defer ctrl.Finish() 309 310 r := NewMultiFetchResult(NamespaceCoversPartialQueryRange, 311 defaultTestOpts, models.NewTagOptions(), LimitOptions{ 312 Limit: 100, 313 RequireExhaustive: true, 314 }) 315 316 r.AddWarnings(block.Warning{ 317 Name: "foo", 318 Message: "bar", 319 }) 320 r.AddWarnings(block.Warning{ 321 Name: "baz", 322 Message: "qux", 323 }) 324 325 for i := 0; i < 3; i++ { 326 iters := encoding.NewSeriesIterators([]encoding.SeriesIterator{ 327 encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{ 328 ID: ident.StringID(fmt.Sprintf("series-%d", i)), 329 Namespace: ident.StringID(fmt.Sprintf("ns-%d", i)), 330 }, nil), 331 }) 332 333 meta := block.NewResultMetadata() 334 meta.Exhaustive = true 335 res := MultiFetchResults{ 336 SeriesIterators: iters, 337 Metadata: meta, 338 Attrs: storagemetadata.Attributes{}, 339 Err: nil, 340 } 341 r.Add(res) 342 } 343 344 finalResult, err := r.FinalResult() 345 require.NoError(t, err) 346 347 assert.Equal(t, 2, len(finalResult.Metadata.Warnings)) 348 349 assert.NoError(t, r.Close()) 350 }