github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/result/shard_ranges.go (about) 1 // Copyright (c) 2018 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 result 22 23 import ( 24 "bytes" 25 "fmt" 26 "sort" 27 "time" 28 29 xtime "github.com/m3db/m3/src/x/time" 30 ) 31 32 // NewShardTimeRangesFromRange returns a new ShardTimeRanges with provided shards and time range. 33 func NewShardTimeRangesFromRange(start, end xtime.UnixNano, shards ...uint32) ShardTimeRanges { 34 timeRange := xtime.NewRanges(xtime.Range{Start: start, End: end}) 35 ranges := make(shardTimeRanges, len(shards)) 36 for _, s := range shards { 37 ranges[s] = timeRange 38 } 39 return ranges 40 } 41 42 // NewShardTimeRangesFromSize returns a new ShardTimeRanges with provided shards and time range. 43 func NewShardTimeRangesFromSize(size int) ShardTimeRanges { 44 return make(shardTimeRanges, size) 45 } 46 47 // NewShardTimeRanges returns an empty ShardTimeRanges. 48 func NewShardTimeRanges() ShardTimeRanges { 49 return make(shardTimeRanges) 50 } 51 52 // Get time ranges for a shard. 53 func (r shardTimeRanges) Get(shard uint32) (xtime.Ranges, bool) { 54 tr, ok := r[shard] 55 return tr, ok 56 } 57 58 // Set time ranges for a shard. 59 func (r shardTimeRanges) Set(shard uint32, ranges xtime.Ranges) ShardTimeRanges { 60 r[shard] = ranges 61 return r 62 } 63 64 // GetOrAdd gets or adds time ranges for a shard. 65 func (r shardTimeRanges) GetOrAdd(shard uint32) xtime.Ranges { 66 if r[shard] == nil { 67 r[shard] = xtime.NewRanges() 68 } 69 return r[shard] 70 } 71 72 // Len returns then number of shards. 73 func (r shardTimeRanges) Len() int { 74 return len(r) 75 } 76 77 // Iter returns the underlying map. 78 func (r shardTimeRanges) Iter() map[uint32]xtime.Ranges { 79 return r 80 } 81 82 // IsEmpty returns whether the shard time ranges is empty or not. 83 func (r shardTimeRanges) IsEmpty() bool { 84 for _, ranges := range r { 85 if !ranges.IsEmpty() { 86 return false 87 } 88 } 89 return true 90 } 91 92 // Equal returns whether two shard time ranges are equal. 93 func (r shardTimeRanges) Equal(other ShardTimeRanges) bool { 94 if len(r) != other.Len() { 95 return false 96 } 97 for shard, ranges := range r { 98 otherRanges := other.GetOrAdd(shard) 99 if otherRanges == nil { 100 return false 101 } 102 if ranges.Len() != otherRanges.Len() { 103 return false 104 } 105 it := ranges.Iter() 106 otherIt := otherRanges.Iter() 107 if it.Next() && otherIt.Next() { 108 value := it.Value() 109 otherValue := otherIt.Value() 110 if !value.Start.Equal(otherValue.Start) || 111 !value.End.Equal(otherValue.End) { 112 return false 113 } 114 } 115 } 116 return true 117 } 118 119 // IsSuperset returns whether the current shard time ranges is a superset of the 120 // other shard time ranges. 121 // 122 // The following are superset conditions. 123 // - Always superset if other shard time ranges are empty. (Even empty vs empty). 124 // - Shards must be a superset of shards in other. 125 // - Time ranges must be a superset of time ranges in other. 126 // 127 // The following are superset failure conditions: 128 // - Partially overlapping shards. 129 // - Non overlapping shards. 130 // - Partially overlapping time ranges. 131 // - Non overlapping time ranges. 132 func (r shardTimeRanges) IsSuperset(other ShardTimeRanges) bool { 133 // If other shard time ranges is empty then the curr shard time ranges 134 // is considered a superset. 135 if other.Len() == 0 { 136 return true 137 } 138 139 // Check if other ranges has any shards we do not have. 140 for shard := range other.Iter() { 141 if _, ok := r.Get(shard); !ok { 142 return false 143 } 144 } 145 146 for shard, ranges := range r { 147 otherRanges, ok := other.Get(shard) 148 if !ok { 149 continue 150 } 151 152 it := ranges.Iter() 153 otherIt := otherRanges.Iter() 154 155 // NB(bodu): Both of these iterators are sorted by time 156 // and the block sizes are expected to line up. 157 // The logic is that if we finish iterating through otherIt then 158 // the current ranges are a superset of the other ranges. 159 missedRange := false 160 otherIteratorNext: 161 for otherIt.Next() { 162 for it.Next() { 163 if otherIt.Value().Equal(it.Value()) { 164 continue otherIteratorNext 165 } 166 } 167 168 missedRange = true 169 break 170 } 171 172 // If there is an unmatched range (not empty) left in `otherIt` then the current shard ranges 173 // are NOT a superset of the other shard ranges. 174 if missedRange { 175 return false 176 } 177 } 178 return true 179 } 180 181 // Copy will return a copy of the current shard time ranges. 182 func (r shardTimeRanges) Copy() ShardTimeRanges { 183 result := make(shardTimeRanges, len(r)) 184 for shard, ranges := range r { 185 newRanges := xtime.NewRanges() 186 newRanges.AddRanges(ranges) 187 result[shard] = newRanges 188 } 189 return result 190 } 191 192 // AddRanges adds other shard time ranges to the current shard time ranges. 193 func (r shardTimeRanges) AddRanges(other ShardTimeRanges) { 194 if other == nil { 195 return 196 } 197 for shard, ranges := range other.Iter() { 198 if ranges.IsEmpty() { 199 continue 200 } 201 if existing, ok := r[shard]; ok { 202 existing.AddRanges(ranges) 203 } else { 204 r[shard] = ranges.Clone() 205 } 206 } 207 } 208 209 // FilterShards returns the shard time ranges after omitting shard not in the list. 210 func (r shardTimeRanges) FilterShards(shards []uint32) ShardTimeRanges { 211 filtered := NewShardTimeRanges() 212 for _, s := range shards { 213 if ranges, ok := r.Get(s); ok { 214 filtered.Set(s, ranges) 215 } 216 } 217 return filtered 218 } 219 220 // ToUnfulfilledDataResult will return a result that is comprised of wholly 221 // unfufilled time ranges from the set of shard time ranges. 222 func (r shardTimeRanges) ToUnfulfilledDataResult() DataBootstrapResult { 223 result := NewDataBootstrapResult() 224 result.SetUnfulfilled(r.Copy()) 225 return result 226 } 227 228 // ToUnfulfilledIndexResult will return a result that is comprised of wholly 229 // unfufilled time ranges from the set of shard time ranges. 230 func (r shardTimeRanges) ToUnfulfilledIndexResult() IndexBootstrapResult { 231 result := NewIndexBootstrapResult() 232 result.SetUnfulfilled(r.Copy()) 233 return result 234 } 235 236 // Subtract will subtract another range from the current range. 237 func (r shardTimeRanges) Subtract(other ShardTimeRanges) { 238 if other == nil { 239 return 240 } 241 for shard, ranges := range r { 242 otherRanges, ok := other.Get(shard) 243 if !ok { 244 continue 245 } 246 247 subtractedRanges := ranges.Clone() 248 subtractedRanges.RemoveRanges(otherRanges) 249 if subtractedRanges.IsEmpty() { 250 delete(r, shard) 251 } else { 252 r[shard] = subtractedRanges 253 } 254 } 255 } 256 257 // MinMax will return the very minimum time as a start and the 258 // maximum time as an end in the ranges. 259 func (r shardTimeRanges) MinMax() (xtime.UnixNano, xtime.UnixNano) { 260 min, max := xtime.UnixNano(0), xtime.UnixNano(0) 261 for _, ranges := range r { 262 if ranges.IsEmpty() { 263 continue 264 } 265 it := ranges.Iter() 266 for it.Next() { 267 curr := it.Value() 268 if min.IsZero() || curr.Start.Before(min) { 269 min = curr.Start 270 } 271 if max.IsZero() || curr.End.After(max) { 272 max = curr.End 273 } 274 } 275 } 276 return min, max 277 } 278 279 // MinMaxRange returns the min and max times, and the duration for this range. 280 func (r shardTimeRanges) MinMaxRange() (xtime.UnixNano, xtime.UnixNano, time.Duration) { 281 min, max := r.MinMax() 282 return min, max, max.Sub(min) 283 } 284 285 type summaryFn func(xtime.Ranges) string 286 287 func (r shardTimeRanges) summarize(sfn summaryFn) string { 288 values := make([]shardTimeRangesPair, 0, len(r)) 289 for shard, ranges := range r { 290 values = append(values, shardTimeRangesPair{shard: shard, value: ranges}) 291 } 292 sort.Sort(shardTimeRangesByShard(values)) 293 294 var ( 295 buf bytes.Buffer 296 hasPrev = false 297 ) 298 299 buf.WriteString("{") 300 for _, v := range values { 301 shard, ranges := v.shard, v.value 302 if hasPrev { 303 buf.WriteString(", ") 304 } 305 hasPrev = true 306 307 buf.WriteString(fmt.Sprintf("%d: %s", shard, sfn(ranges))) 308 } 309 buf.WriteString("}") 310 311 return buf.String() 312 } 313 314 // String returns a description of the time ranges 315 func (r shardTimeRanges) String() string { 316 return r.summarize(xtime.Ranges.String) 317 } 318 319 func rangesDuration(ranges xtime.Ranges) string { 320 var ( 321 duration time.Duration 322 it = ranges.Iter() 323 ) 324 for it.Next() { 325 curr := it.Value() 326 duration += curr.End.Sub(curr.Start) 327 } 328 return duration.String() 329 } 330 331 // SummaryString returns a summary description of the time ranges 332 func (r shardTimeRanges) SummaryString() string { 333 return r.summarize(rangesDuration) 334 } 335 336 type shardTimeRangesPair struct { 337 shard uint32 338 value xtime.Ranges 339 } 340 341 type shardTimeRangesByShard []shardTimeRangesPair 342 343 func (str shardTimeRangesByShard) Len() int { return len(str) } 344 func (str shardTimeRangesByShard) Swap(i, j int) { str[i], str[j] = str[j], str[i] } 345 func (str shardTimeRangesByShard) Less(i, j int) bool { 346 return str[i].shard < str[j].shard 347 }