github.com/m3db/m3@v1.5.0/src/query/storage/m3/consolidators/id_dedupe_map.go (about)

     1  // Copyright (c) 2020 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  	"sort"
    26  
    27  	"github.com/m3db/m3/src/dbnode/encoding"
    28  	"github.com/m3db/m3/src/query/models"
    29  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    30  )
    31  
    32  type idDedupeMap struct {
    33  	fanout  QueryFanoutType
    34  	series  map[string]multiResultSeries
    35  	tagOpts models.TagOptions
    36  }
    37  
    38  func newIDDedupeMap(opts dedupeMapOpts) fetchDedupeMap {
    39  	return &idDedupeMap{
    40  		fanout:  opts.fanout,
    41  		series:  make(map[string]multiResultSeries, opts.size),
    42  		tagOpts: opts.tagOpts,
    43  	}
    44  }
    45  
    46  func (m *idDedupeMap) close() {}
    47  
    48  func (m *idDedupeMap) list() []multiResultSeries {
    49  	// Return list by sorted id's so this method is actually deterministic and
    50  	// multiple calls to this remain consistent.
    51  	ids := make([]string, 0, len(m.series))
    52  	for id := range m.series {
    53  		ids = append(ids, id)
    54  	}
    55  	sort.Strings(ids)
    56  	result := make([]multiResultSeries, 0, len(m.series))
    57  	for _, id := range ids {
    58  		result = append(result, m.series[id])
    59  	}
    60  	return result
    61  }
    62  
    63  func (m *idDedupeMap) len() int {
    64  	return len(m.series)
    65  }
    66  
    67  func (m *idDedupeMap) update(
    68  	iter encoding.SeriesIterator,
    69  	attrs storagemetadata.Attributes,
    70  ) (bool, error) {
    71  	id := iter.ID().String()
    72  	existing, exists := m.series[id]
    73  	if !exists {
    74  		return false, nil
    75  	}
    76  	tags, err := FromIdentTagIteratorToTags(iter.Tags(), m.tagOpts)
    77  	if err != nil {
    78  		return false, err
    79  	}
    80  	return true, m.doUpdate(id, existing, tags, iter, attrs)
    81  }
    82  
    83  func (m *idDedupeMap) add(
    84  	iter encoding.SeriesIterator,
    85  	attrs storagemetadata.Attributes,
    86  ) error {
    87  	id := iter.ID().String()
    88  	tags, err := FromIdentTagIteratorToTags(iter.Tags(), m.tagOpts)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	iter.Tags().Rewind()
    94  	existing, exists := m.series[id]
    95  	if !exists {
    96  		// Does not exist, new addition
    97  		m.series[id] = multiResultSeries{
    98  			attrs: attrs,
    99  			iter:  iter,
   100  			tags:  tags,
   101  		}
   102  		return nil
   103  	}
   104  	return m.doUpdate(id, existing, tags, iter, attrs)
   105  }
   106  
   107  func (m *idDedupeMap) doUpdate(
   108  	id string,
   109  	existing multiResultSeries,
   110  	tags models.Tags,
   111  	iter encoding.SeriesIterator,
   112  	attrs storagemetadata.Attributes,
   113  ) error {
   114  	if stitched, ok, err := stitchIfNeeded(existing, iter, attrs); err != nil {
   115  		return err
   116  	} else if ok {
   117  		m.series[id] = stitched
   118  		return nil
   119  	}
   120  
   121  	var existsBetter bool
   122  	switch m.fanout {
   123  	case NamespaceCoversAllQueryRange:
   124  		// Already exists and resolution of result we are adding is not as precise
   125  		existsBetter = existing.attrs.Resolution <= attrs.Resolution
   126  	case NamespaceCoversPartialQueryRange:
   127  		// Already exists and either has longer retention, or the same retention
   128  		// and result we are adding is not as precise
   129  		existsLongerRetention := existing.attrs.Retention > attrs.Retention
   130  		existsSameRetentionEqualOrBetterResolution :=
   131  			existing.attrs.Retention == attrs.Retention &&
   132  				existing.attrs.Resolution <= attrs.Resolution
   133  		existsBetter = existsLongerRetention || existsSameRetentionEqualOrBetterResolution
   134  	default:
   135  		return fmt.Errorf("unknown query fanout type: %d", m.fanout)
   136  	}
   137  
   138  	if existsBetter {
   139  		// Existing result is already better
   140  		return nil
   141  	}
   142  
   143  	// Override
   144  	m.series[id] = multiResultSeries{
   145  		attrs: attrs,
   146  		iter:  iter,
   147  		tags:  tags,
   148  	}
   149  
   150  	return nil
   151  }