github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/m3ninx/index/batch.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 index
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"sync"
    28  
    29  	"github.com/m3db/m3/src/m3ninx/doc"
    30  )
    31  
    32  var (
    33  	// ErrDuplicateID is the error returned when a batch contains duplicate IDs.
    34  	ErrDuplicateID = errors.New("a batch cannot contain duplicate IDs")
    35  )
    36  
    37  // Batch represents a batch of documents that should be inserted into the index.
    38  type Batch struct {
    39  	Docs []doc.Metadata
    40  
    41  	// If AllowPartialUpdates is true the index will continue to index documents in the batch
    42  	// even if it encounters an error attempting to index a previous document in the batch.
    43  	// If false, on the other hand, then any errors encountered indexing a document will cause
    44  	// the entire batch to fail and none of the documents in the batch will be indexed.
    45  	AllowPartialUpdates bool
    46  }
    47  
    48  // BatchOption is an option for a Batch.
    49  type BatchOption interface {
    50  	apply(Batch) Batch
    51  }
    52  
    53  // batchOptionFunc is an adaptor to allow the use of functions as BatchOptions.
    54  type batchOptionFunc func(Batch) Batch
    55  
    56  func (f batchOptionFunc) apply(b Batch) Batch {
    57  	return f(b)
    58  }
    59  
    60  // AllowPartialUpdates permits an index to continue indexing documents in a batch even if
    61  // it encountered an error inserting a prior document.
    62  func AllowPartialUpdates() BatchOption {
    63  	return batchOptionFunc(func(b Batch) Batch {
    64  		b.AllowPartialUpdates = true
    65  		return b
    66  	})
    67  }
    68  
    69  // NewBatch returns a Batch of documents.
    70  func NewBatch(docs []doc.Metadata, opts ...BatchOption) Batch {
    71  	b := Batch{Docs: docs}
    72  
    73  	for _, opt := range opts {
    74  		b = opt.apply(b)
    75  	}
    76  
    77  	return b
    78  }
    79  
    80  // BatchPartialError indicates an error was encountered inserting some documents in a batch.
    81  // It is not safe for concurrent use.
    82  type BatchPartialError struct {
    83  	sync.Mutex
    84  
    85  	errs []BatchError
    86  }
    87  
    88  // BatchError is an error that occurred for a document being inserted.
    89  type BatchError struct {
    90  	Err error
    91  	Idx int
    92  }
    93  
    94  // NewBatchPartialError returns a new BatchPartialError.
    95  func NewBatchPartialError() *BatchPartialError {
    96  	return &BatchPartialError{}
    97  }
    98  
    99  func (e *BatchPartialError) Error() string {
   100  	var b bytes.Buffer
   101  	for i := range e.errs {
   102  		b.WriteString(fmt.Sprintf("failed to insert document at index %v in batch: %v",
   103  			e.errs[i].Idx, e.errs[i].Err))
   104  		if i != len(e.errs)-1 {
   105  			b.WriteString("\n")
   106  		}
   107  	}
   108  	return b.String()
   109  }
   110  
   111  // FilterDuplicateIDErrors returns a new BatchPartialError (or nil), without any DuplicateIDError(s).
   112  // NB(prateek): it mutates the order of errors in the original error to avoid allocations.
   113  // NB(bodu): we return an `error` here since go does not evaluate `nil` errors correctly when
   114  // we return a custom type (*BatchPartialError) here and cast it to `error`.
   115  func (e *BatchPartialError) FilterDuplicateIDErrors() error {
   116  	// cheap to do the copy as it's just pointers for the slices
   117  	var (
   118  		errs = e.errs
   119  		i    = 0
   120  	)
   121  
   122  	for i < len(errs) {
   123  		if errs[i].Err == ErrDuplicateID {
   124  			errs[i], errs[len(errs)-1] = errs[len(errs)-1], errs[i]
   125  			errs = errs[:len(errs)-1]
   126  			continue
   127  		}
   128  		i++
   129  	}
   130  	if len(errs) == 0 {
   131  		return nil
   132  	}
   133  	return &BatchPartialError{errs: errs}
   134  }
   135  
   136  // Add adds an error to e. Any nil errors are ignored.
   137  func (e *BatchPartialError) Add(err BatchError) {
   138  	if err.Err == nil {
   139  		return
   140  	}
   141  	e.errs = append(e.errs, err)
   142  }
   143  
   144  // AddWithLock adds an error to e with a lock. Any nil errors are ignored.
   145  func (e *BatchPartialError) AddWithLock(err BatchError) {
   146  	if err.Err == nil {
   147  		return
   148  	}
   149  	e.Lock()
   150  	e.errs = append(e.errs, err)
   151  	e.Unlock()
   152  }
   153  
   154  // Errs returns the errors with the indexes of the documents in the batch
   155  // which were not indexed.
   156  func (e *BatchPartialError) Errs() []BatchError {
   157  	return e.errs
   158  }
   159  
   160  // IsEmpty returns a bool indicating whether e is empty or not.
   161  func (e *BatchPartialError) IsEmpty() bool {
   162  	return len(e.errs) == 0
   163  }
   164  
   165  // IsBatchPartialError returns a bool indicating whether err is a BatchPartialError or not.
   166  func IsBatchPartialError(err error) bool {
   167  	_, ok := err.(*BatchPartialError)
   168  	return ok
   169  }