github.com/m3db/m3@v1.5.0/src/m3ninx/search/executor/iterator.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 executor
    22  
    23  import (
    24  	"github.com/m3db/m3/src/dbnode/tracepoint"
    25  	"github.com/m3db/m3/src/m3ninx/doc"
    26  	"github.com/m3db/m3/src/m3ninx/index"
    27  	"github.com/m3db/m3/src/m3ninx/postings"
    28  	"github.com/m3db/m3/src/m3ninx/search"
    29  	"github.com/m3db/m3/src/x/context"
    30  	"github.com/m3db/m3/src/x/errors"
    31  )
    32  
    33  // iterator is a wrapper around many doc.Iterators (one per segment) that provides a stream of docs for a index block.
    34  //
    35  // all segments are eagerly searched on the first call to Next(). eagerly searching all the segments for a block allows
    36  // the iterator to yield without holding locks when processing the results set. yielding allows the goroutine to
    37  // yield the index worker to another goroutine waiting and then can resume the iterator when it acquires a worker again.
    38  // this prevents large queries with many result docs from starving small queries.
    39  //
    40  // for each segment, the searcher gets the postings list. the postings list are lazily processed by the returned
    41  // doc iterators. for each posting in the list, the encoded document is retrieved.
    42  type iterator struct {
    43  	// immutable state
    44  	query    search.Query
    45  	searcher search.Searcher
    46  	readers  index.Readers
    47  	ctx      context.Context
    48  
    49  	// immutable state after the first call to Next()
    50  	iters []doc.Iterator
    51  
    52  	// mutable state
    53  	idx     int
    54  	currDoc doc.Document
    55  	nextDoc doc.Document
    56  	done    bool
    57  	err     error
    58  }
    59  
    60  func newIterator(ctx context.Context, q search.Query, rs index.Readers) (doc.QueryDocIterator, error) {
    61  	s, err := q.Searcher()
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return &iterator{
    67  		ctx:      ctx,
    68  		query:    q,
    69  		searcher: s,
    70  		readers:  rs,
    71  	}, nil
    72  }
    73  
    74  func (it *iterator) Done() bool {
    75  	return it.err != nil || it.done
    76  }
    77  
    78  func (it *iterator) Next() bool {
    79  	if it.Done() {
    80  		return false
    81  	}
    82  	if it.iters == nil {
    83  		if err := it.initIters(); err != nil {
    84  			it.err = err
    85  			return false
    86  		}
    87  		if !it.next() {
    88  			it.done = true
    89  			return false
    90  		}
    91  		it.nextDoc = it.current()
    92  	}
    93  
    94  	it.currDoc = it.nextDoc
    95  	if it.next() {
    96  		it.nextDoc = it.current()
    97  	} else {
    98  		it.done = true
    99  	}
   100  	return true
   101  }
   102  
   103  func (it *iterator) next() bool {
   104  	if it.idx == len(it.iters) {
   105  		return false
   106  	}
   107  	currIter := it.iters[it.idx]
   108  	for !currIter.Next() {
   109  		// Check if the current iterator encountered an error.
   110  		if err := currIter.Err(); err != nil {
   111  			it.err = err
   112  			return false
   113  		}
   114  		// move to next iterator so we don't try to Close twice.
   115  		it.idx++
   116  
   117  		// Close current iterator now that we are finished with it.
   118  		if err := currIter.Close(); err != nil {
   119  			it.err = err
   120  			return false
   121  		}
   122  
   123  		if it.idx == len(it.iters) {
   124  			return false
   125  		}
   126  		currIter = it.iters[it.idx]
   127  	}
   128  	return true
   129  }
   130  
   131  func (it *iterator) current() doc.Document {
   132  	return it.iters[it.idx].Current()
   133  }
   134  
   135  func (it *iterator) Current() doc.Document {
   136  	return it.currDoc
   137  }
   138  
   139  func (it *iterator) Err() error {
   140  	return it.err
   141  }
   142  
   143  func (it *iterator) Close() error {
   144  	if it.iters == nil {
   145  		return nil
   146  	}
   147  	var multiErr errors.MultiError
   148  	// close any iterators that weren't closed in Next.
   149  	for i := it.idx; i < len(it.iters); i++ {
   150  		multiErr = multiErr.Add(it.iters[i].Close())
   151  	}
   152  	return multiErr.FinalError()
   153  }
   154  
   155  func (it *iterator) initIters() error {
   156  	it.iters = make([]doc.Iterator, len(it.readers))
   157  	for i, reader := range it.readers {
   158  		_, sp := it.ctx.StartTraceSpan(tracepoint.SearchExecutorIndexSearch)
   159  
   160  		var (
   161  			pl  postings.List
   162  			err error
   163  		)
   164  		if readThrough, ok := reader.(search.ReadThroughSegmentSearcher); ok {
   165  			pl, err = readThrough.Search(it.query, it.searcher)
   166  		} else {
   167  			pl, err = it.searcher.Search(reader)
   168  		}
   169  		sp.Finish()
   170  		if err != nil {
   171  			return err
   172  		}
   173  
   174  		iter, err := reader.Docs(pl)
   175  		if err != nil {
   176  			return err
   177  		}
   178  
   179  		it.iters[i] = iter
   180  	}
   181  	return nil
   182  }