github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }