github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/consolidators/multi_fetch_result.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 consolidators
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  
    28  	"github.com/m3db/m3/src/dbnode/encoding"
    29  	terrors "github.com/m3db/m3/src/dbnode/network/server/tchannelthrift/errors"
    30  	"github.com/m3db/m3/src/query/block"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    33  	xerrors "github.com/m3db/m3/src/x/errors"
    34  )
    35  
    36  type fetchDedupeMap interface {
    37  	add(iter encoding.SeriesIterator, attrs storagemetadata.Attributes) error
    38  	update(iter encoding.SeriesIterator, attrs storagemetadata.Attributes) (bool, error)
    39  	list() []multiResultSeries
    40  	len() int
    41  	close()
    42  }
    43  
    44  type multiResult struct {
    45  	sync.Mutex
    46  	metadata       block.ResultMetadata
    47  	fanout         QueryFanoutType
    48  	seenFirstAttrs storagemetadata.Attributes
    49  
    50  	all             []MultiFetchResults
    51  	seenIters       []encoding.SeriesIterators // track known iterators to avoid leaking
    52  	mergedIterators encoding.MutableSeriesIterators
    53  	mergedTags      []*models.Tags
    54  	dedupeMap       fetchDedupeMap
    55  	err             xerrors.MultiError
    56  	matchOpts       MatchOptions
    57  	tagOpts         models.TagOptions
    58  	limitOpts       LimitOptions
    59  }
    60  
    61  // LimitOptions specifies the limits when accumulating results in consolidators.
    62  type LimitOptions struct {
    63  	Limit             int
    64  	RequireExhaustive bool
    65  }
    66  
    67  // NewMultiFetchResult builds a new multi fetch result.
    68  func NewMultiFetchResult(
    69  	fanout QueryFanoutType,
    70  	opts MatchOptions,
    71  	tagOpts models.TagOptions,
    72  	limitOpts LimitOptions,
    73  ) MultiFetchResult {
    74  	return &multiResult{
    75  		metadata:  block.NewResultMetadata(),
    76  		fanout:    fanout,
    77  		matchOpts: opts,
    78  		tagOpts:   tagOpts,
    79  		limitOpts: limitOpts,
    80  	}
    81  }
    82  
    83  type multiResultSeries struct {
    84  	attrs storagemetadata.Attributes
    85  	iter  encoding.SeriesIterator
    86  	tags  models.Tags
    87  }
    88  
    89  func (r *multiResult) Close() error {
    90  	r.Lock()
    91  	defer r.Unlock()
    92  
    93  	for _, iters := range r.seenIters {
    94  		if iters != nil {
    95  			iters.Close()
    96  		}
    97  	}
    98  	r.seenIters = nil
    99  
   100  	if r.mergedIterators != nil {
   101  		// NB(r): Since all the series iterators in the final result are held onto
   102  		// by the original iters in the seenIters slice we allow those iterators
   103  		// to free iterators held onto by final result, and reset the slice for
   104  		// the final result to zero so we avoid double returning the iterators
   105  		// themselves.
   106  		r.mergedIterators.Reset(0)
   107  		r.mergedIterators.Close()
   108  		r.mergedIterators = nil
   109  	}
   110  
   111  	r.dedupeMap = nil
   112  	r.err = xerrors.NewMultiError()
   113  
   114  	return nil
   115  }
   116  
   117  func (r *multiResult) FinalResultWithAttrs() (
   118  	SeriesFetchResult, []storagemetadata.Attributes, error,
   119  ) {
   120  	r.Lock()
   121  	defer r.Unlock()
   122  
   123  	result, dedupedList, err := r.finalResultWithLock()
   124  	if err != nil {
   125  		return result, nil, err
   126  	}
   127  
   128  	var attrs []storagemetadata.Attributes
   129  	seriesData := result.seriesData
   130  	if iters := seriesData.seriesIterators; iters != nil {
   131  		l := iters.Len()
   132  		attrs = make([]storagemetadata.Attributes, 0, l)
   133  		if r.dedupeMap == nil {
   134  			for i := 0; i < l; i++ {
   135  				attrs = append(attrs, r.seenFirstAttrs)
   136  			}
   137  		} else {
   138  			for _, res := range dedupedList {
   139  				attrs = append(attrs, res.attrs)
   140  			}
   141  		}
   142  	}
   143  
   144  	return result, attrs, nil
   145  }
   146  
   147  func (r *multiResult) FinalResult() (SeriesFetchResult, error) {
   148  	r.Lock()
   149  	defer r.Unlock()
   150  
   151  	res, _, err := r.finalResultWithLock()
   152  
   153  	return res, err
   154  }
   155  
   156  func (r *multiResult) finalResultWithLock() (SeriesFetchResult, []multiResultSeries, error) {
   157  	err := r.err.LastError()
   158  	if err != nil {
   159  		return NewEmptyFetchResult(r.metadata), nil, err
   160  	}
   161  
   162  	if r.mergedIterators != nil {
   163  		res, err := NewSeriesFetchResult(r.mergedIterators, nil, r.metadata)
   164  		return res, nil, err
   165  	}
   166  
   167  	if len(r.seenIters) == 0 {
   168  		res, err := NewSeriesFetchResult(encoding.EmptySeriesIterators, nil, r.metadata)
   169  		return res, nil, err
   170  	}
   171  
   172  	// otherwise have to create a new seriesiters
   173  	dedupedList := r.dedupeMap.list()
   174  	numSeries := len(dedupedList)
   175  	r.mergedIterators = encoding.NewSizedSeriesIterators(numSeries)
   176  	if r.mergedTags == nil {
   177  		r.mergedTags = make([]*models.Tags, numSeries)
   178  	}
   179  
   180  	lenCurr, lenNext := len(r.mergedTags), len(dedupedList)
   181  	if lenCurr < lenNext {
   182  		// If incoming list is longer, expand the stored list.
   183  		r.mergedTags = append(r.mergedTags, make([]*models.Tags, lenNext-lenCurr)...)
   184  	} else if lenCurr > lenNext {
   185  		// If incoming list somehow shorter, shrink stored list.
   186  		r.mergedTags = r.mergedTags[:lenNext]
   187  	}
   188  
   189  	for i, res := range dedupedList {
   190  		r.mergedIterators.SetAt(i, res.iter)
   191  		r.mergedTags[i] = &dedupedList[i].tags
   192  	}
   193  
   194  	res, err := NewSeriesFetchResult(r.mergedIterators, r.mergedTags, r.metadata)
   195  
   196  	return res, dedupedList, err
   197  }
   198  
   199  func (r *multiResult) Results() []MultiFetchResults {
   200  	r.Lock()
   201  	defer r.Unlock()
   202  	return r.all
   203  }
   204  
   205  func (r *multiResult) Add(add MultiFetchResults) {
   206  	var (
   207  		newIterators = add.SeriesIterators
   208  		metadata     = add.Metadata
   209  		attrs        = add.Attrs
   210  	)
   211  
   212  	r.Lock()
   213  	defer r.Unlock()
   214  
   215  	r.all = append(r.all, add)
   216  
   217  	if err := add.Err; err != nil {
   218  		r.err = r.err.Add(err)
   219  		return
   220  	}
   221  
   222  	if newIterators == nil || newIterators.Len() == 0 {
   223  		return
   224  	}
   225  
   226  	nsID := ""
   227  	if newIterators.Iters()[0].Namespace() != nil {
   228  		nsID = newIterators.Iters()[0].Namespace().String() // sometimes the namespace ID is empty
   229  	}
   230  
   231  	r.seenIters = append(r.seenIters, newIterators)
   232  
   233  	// the series limit was reached within this namespace.
   234  	if !metadata.Exhaustive && r.limitOpts.RequireExhaustive {
   235  		r.err = r.err.Add(NewLimitError(fmt.Sprintf("series limit exceeded for namespace %s", nsID)))
   236  		return
   237  	}
   238  
   239  	if len(r.seenIters) == 1 {
   240  		// store the first attributes seen
   241  		r.seenFirstAttrs = attrs
   242  	} else if !r.metadata.Exhaustive {
   243  		// a previous namespace result already hit the limit, so bail. this handles the case of RequireExhaustive=false
   244  		// and there is no error to short circuit.
   245  		return
   246  	}
   247  
   248  	// NB: any non-exhaustive result set added makes the entire
   249  	// result non-exhaustive
   250  	// Note: must never override metadata and always use CombineMetadata
   251  	// in case warnings were first set with call to AddWarnings(..) and
   252  	// then must be combined before first result is ever set.
   253  	r.metadata = r.metadata.CombineMetadata(metadata)
   254  
   255  	// Need to check the error to bail early after accumulating the iterators
   256  	// otherwise when we close the the multi fetch result
   257  	if !r.err.Empty() {
   258  		// don't need to do anything if the final result is going to be an error
   259  		return
   260  	}
   261  
   262  	var added bool
   263  	if len(r.seenIters) == 1 {
   264  		// need to backfill the dedupe map from the first result first
   265  		opts := dedupeMapOpts{
   266  			fanout:  r.fanout,
   267  			size:    newIterators.Len(),
   268  			tagOpts: r.tagOpts,
   269  		}
   270  
   271  		if r.matchOpts.MatchType == MatchIDs {
   272  			r.dedupeMap = newIDDedupeMap(opts)
   273  		} else {
   274  			r.dedupeMap = newTagDedupeMap(opts)
   275  		}
   276  
   277  		added = r.addOrUpdateDedupeMap(r.seenFirstAttrs, newIterators)
   278  	} else {
   279  		// Now de-duplicate
   280  		added = r.addOrUpdateDedupeMap(attrs, newIterators)
   281  	}
   282  
   283  	// the series limit was reached by adding the results of this namespace to the existing results.
   284  	if !added && r.err.Empty() {
   285  		r.metadata.Exhaustive = false
   286  		if r.limitOpts.RequireExhaustive {
   287  			r.err = r.err.Add(
   288  				NewLimitError(fmt.Sprintf("series limit exceeded adding namespace %s to results", nsID)))
   289  		}
   290  	}
   291  }
   292  
   293  func (r *multiResult) AddWarnings(warnings ...block.Warning) {
   294  	r.Lock()
   295  	defer r.Unlock()
   296  	r.metadata.AddWarnings(warnings...)
   297  }
   298  
   299  // NewLimitError returns a limit error so that it's the same type as the query
   300  // limit error returned from a single database instance to receive the same
   301  // error behavior as a database limit error.
   302  func NewLimitError(msg string) error {
   303  	return terrors.NewResourceExhaustedError(errors.New(msg))
   304  }
   305  
   306  func (r *multiResult) addOrUpdateDedupeMap(
   307  	attrs storagemetadata.Attributes,
   308  	newIterators encoding.SeriesIterators,
   309  ) bool {
   310  	for _, iter := range newIterators.Iters() {
   311  		tagIter := iter.Tags()
   312  		shouldFilter, err := filterTagIterator(tagIter, r.tagOpts.Filters())
   313  		if err != nil {
   314  			r.err = r.err.Add(err)
   315  			return false
   316  		}
   317  
   318  		if shouldFilter {
   319  			// NB: skip here, the closer will free the series iterator regardless.
   320  			continue
   321  		}
   322  
   323  		if r.dedupeMap.len() == r.limitOpts.Limit {
   324  			updated, err := r.dedupeMap.update(iter, attrs)
   325  			if err != nil {
   326  				r.err = r.err.Add(err)
   327  				return false
   328  			}
   329  			if !updated {
   330  				return false
   331  			}
   332  		} else if err := r.dedupeMap.add(iter, attrs); err != nil {
   333  			r.err = r.err.Add(err)
   334  			return false
   335  		}
   336  	}
   337  	return true
   338  }