github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/bootstrap/bootstrapper/base.go (about)

     1  // Copyright (c) 2016 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 bootstrapper
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    27  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    28  	"github.com/m3db/m3/src/x/context"
    29  
    30  	"go.uber.org/zap"
    31  	"go.uber.org/zap/zapcore"
    32  )
    33  
    34  const (
    35  	baseBootstrapperName = "base"
    36  )
    37  
    38  // baseBootstrapper provides a skeleton for the interface methods.
    39  type baseBootstrapper struct {
    40  	opts result.Options
    41  	log  *zap.Logger
    42  	name string
    43  	src  bootstrap.Source
    44  	next bootstrap.Bootstrapper
    45  }
    46  
    47  // NewBaseBootstrapper creates a new base bootstrapper.
    48  func NewBaseBootstrapper(
    49  	name string,
    50  	src bootstrap.Source,
    51  	opts result.Options,
    52  	next bootstrap.Bootstrapper,
    53  ) (bootstrap.Bootstrapper, error) {
    54  	var (
    55  		bs  = next
    56  		err error
    57  	)
    58  	if next == nil {
    59  		bs, err = NewNoOpNoneBootstrapperProvider().Provide()
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  	}
    64  	return baseBootstrapper{
    65  		opts: opts,
    66  		log:  opts.InstrumentOptions().Logger(),
    67  		name: name,
    68  		src:  src,
    69  		next: bs,
    70  	}, nil
    71  }
    72  
    73  // String returns the name of the bootstrapper.
    74  func (b baseBootstrapper) String() string {
    75  	return baseBootstrapperName
    76  }
    77  
    78  func (b baseBootstrapper) Bootstrap(
    79  	ctx context.Context,
    80  	namespaces bootstrap.Namespaces,
    81  	cache bootstrap.Cache,
    82  ) (bootstrap.NamespaceResults, error) {
    83  	logFields := []zapcore.Field{
    84  		zap.String("bootstrapper", b.name),
    85  	}
    86  
    87  	curr := bootstrap.Namespaces{
    88  		Namespaces: bootstrap.NewNamespacesMap(bootstrap.NamespacesMapOptions{}),
    89  	}
    90  	for _, elem := range namespaces.Namespaces.Iter() {
    91  		id := elem.Key()
    92  
    93  		// Shallow copy the namespace, do not modify namespaces input to bootstrap call.
    94  		currNamespace := elem.Value()
    95  
    96  		b.logShardTimeRanges("bootstrap from source requested",
    97  			logFields, currNamespace)
    98  
    99  		dataAvailable, err := b.src.AvailableData(currNamespace.Metadata,
   100  			currNamespace.DataRunOptions.ShardTimeRanges.Copy(), cache,
   101  			currNamespace.DataRunOptions.RunOptions)
   102  		if err != nil {
   103  			return bootstrap.NamespaceResults{}, err
   104  		}
   105  
   106  		currNamespace.DataRunOptions.ShardTimeRanges = dataAvailable
   107  
   108  		// Prepare index if required.
   109  		if currNamespace.Metadata.Options().IndexOptions().Enabled() {
   110  			indexAvailable, err := b.src.AvailableIndex(currNamespace.Metadata,
   111  				currNamespace.IndexRunOptions.ShardTimeRanges.Copy(), cache,
   112  				currNamespace.IndexRunOptions.RunOptions)
   113  			if err != nil {
   114  				return bootstrap.NamespaceResults{}, err
   115  			}
   116  
   117  			currNamespace.IndexRunOptions.ShardTimeRanges = indexAvailable
   118  		}
   119  
   120  		// Set the namespace options for the current bootstrapper source.
   121  		curr.Namespaces.Set(id, currNamespace)
   122  
   123  		// Log the metadata about bootstrapping this namespace based on
   124  		// the availability returned.
   125  		b.logShardTimeRanges("bootstrap from source ready after availability query",
   126  			logFields, currNamespace)
   127  	}
   128  
   129  	nowFn := b.opts.ClockOptions().NowFn()
   130  	begin := nowFn()
   131  
   132  	// Run the bootstrap source begin hook.
   133  	b.log.Info("bootstrap from source hook begin started", logFields...)
   134  	if err := namespaces.Hooks().BootstrapSourceBegin(); err != nil {
   135  		return bootstrap.NamespaceResults{}, err
   136  	}
   137  
   138  	b.log.Info("bootstrap from source started", logFields...)
   139  
   140  	// Run the bootstrap source.
   141  	currResults, err := b.src.Read(ctx, curr, cache)
   142  
   143  	logFields = append(logFields, zap.Duration("took", nowFn().Sub(begin)))
   144  	if err != nil {
   145  		errorLogFields := append(logFieldsCopy(logFields), zap.Error(err))
   146  		b.log.Error("error bootstrapping from source", errorLogFields...)
   147  		return bootstrap.NamespaceResults{}, err
   148  	}
   149  
   150  	// Run the bootstrap source end hook.
   151  	b.log.Info("bootstrap from source hook end started", logFields...)
   152  	if err := namespaces.Hooks().BootstrapSourceEnd(); err != nil {
   153  		return bootstrap.NamespaceResults{}, err
   154  	}
   155  
   156  	b.log.Info("bootstrap from source completed", logFields...)
   157  	// Determine the unfulfilled and the unattempted ranges to execute next.
   158  	next, err := b.logSuccessAndDetermineCurrResultsUnfulfilledAndNextBootstrapRanges(namespaces,
   159  		curr, currResults, logFields)
   160  	if err != nil {
   161  		return bootstrap.NamespaceResults{}, err
   162  	}
   163  
   164  	// Unless next bootstrapper is required, this is the final results.
   165  	finalResults := currResults
   166  
   167  	// If there are some time ranges the current bootstrapper could not fulfill,
   168  	// that we can attempt then pass it along to the next bootstrapper.
   169  	if next.Namespaces.Len() > 0 {
   170  		nextResults, err := b.next.Bootstrap(ctx, next, cache)
   171  		if err != nil {
   172  			return bootstrap.NamespaceResults{}, err
   173  		}
   174  
   175  		// Now merge the final results.
   176  		for _, elem := range nextResults.Results.Iter() {
   177  			id := elem.Key()
   178  			currNamespace := elem.Value()
   179  
   180  			finalResult, ok := finalResults.Results.Get(id)
   181  			if !ok {
   182  				return bootstrap.NamespaceResults{},
   183  					fmt.Errorf("expected result for namespace: %s", id.String())
   184  			}
   185  
   186  			// NB(r): Since we originally passed all unfulfilled ranges to the
   187  			// next bootstrapper, the final unfulfilled is simply what it could
   188  			// not fulfill.
   189  			finalResult.DataResult.SetUnfulfilled(currNamespace.DataResult.Unfulfilled().Copy())
   190  			if currNamespace.Metadata.Options().IndexOptions().Enabled() {
   191  				finalResult.IndexResult.SetUnfulfilled(currNamespace.IndexResult.Unfulfilled().Copy())
   192  				finalResult.IndexResult.IndexResults().AddResults(currNamespace.IndexResult.IndexResults())
   193  			}
   194  
   195  			// Map is by value, set the result altered struct.
   196  			finalResults.Results.Set(id, finalResult)
   197  		}
   198  	}
   199  
   200  	return finalResults, nil
   201  }
   202  
   203  func (b baseBootstrapper) logSuccessAndDetermineCurrResultsUnfulfilledAndNextBootstrapRanges(
   204  	requested bootstrap.Namespaces,
   205  	curr bootstrap.Namespaces,
   206  	currResults bootstrap.NamespaceResults,
   207  	baseLogFields []zapcore.Field,
   208  ) (bootstrap.Namespaces, error) {
   209  	next := bootstrap.Namespaces{
   210  		Namespaces: bootstrap.NewNamespacesMap(bootstrap.NamespacesMapOptions{}),
   211  	}
   212  	for _, elem := range requested.Namespaces.Iter() {
   213  		id := elem.Key()
   214  		requestedNamespace := elem.Value()
   215  
   216  		currResult, ok := currResults.Results.Get(id)
   217  		if !ok {
   218  			return bootstrap.Namespaces{},
   219  				fmt.Errorf("namespace result not returned by bootstrapper: %v", id.String())
   220  		}
   221  
   222  		currNamespace, ok := curr.Namespaces.Get(id)
   223  		if !ok {
   224  			return bootstrap.Namespaces{},
   225  				fmt.Errorf("namespace prepared request not found: %v", id.String())
   226  		}
   227  
   228  		// Shallow copy the current namespace for the next namespace prepared request.
   229  		nextNamespace := currNamespace
   230  
   231  		// Calculate bootstrap time ranges.
   232  		dataRequired := requestedNamespace.DataRunOptions.ShardTimeRanges.Copy()
   233  		dataCurrRequested := currNamespace.DataRunOptions.ShardTimeRanges.Copy()
   234  		dataCurrFulfilled := dataCurrRequested.Copy()
   235  		dataCurrFulfilled.Subtract(currResult.DataResult.Unfulfilled())
   236  
   237  		dataUnfulfilled := dataRequired.Copy()
   238  		dataUnfulfilled.Subtract(dataCurrFulfilled)
   239  
   240  		// Modify the unfulfilled result.
   241  		currResult.DataResult.SetUnfulfilled(dataUnfulfilled.Copy())
   242  
   243  		// Set the next bootstrapper required ranges.
   244  		nextNamespace.DataRunOptions.ShardTimeRanges = dataUnfulfilled.Copy()
   245  
   246  		var (
   247  			indexCurrRequested = result.NewShardTimeRanges()
   248  			indexCurrFulfilled = result.NewShardTimeRanges()
   249  			indexUnfulfilled   = result.NewShardTimeRanges()
   250  		)
   251  		if currNamespace.Metadata.Options().IndexOptions().Enabled() {
   252  			// Calculate bootstrap time ranges.
   253  			indexRequired := requestedNamespace.IndexRunOptions.ShardTimeRanges.Copy()
   254  			indexCurrRequested = currNamespace.IndexRunOptions.ShardTimeRanges.Copy()
   255  			indexCurrFulfilled = indexCurrRequested.Copy()
   256  			indexCurrFulfilled.Subtract(currResult.IndexResult.Unfulfilled())
   257  
   258  			indexUnfulfilled = indexRequired.Copy()
   259  			indexUnfulfilled.Subtract(indexCurrFulfilled)
   260  
   261  			// Modify the unfulfilled result.
   262  			currResult.IndexResult.SetUnfulfilled(indexUnfulfilled.Copy())
   263  		}
   264  
   265  		// Set the next bootstrapper required ranges.
   266  		// NB(r): Make sure to always set an empty requested range so IsEmpty
   267  		// does not cause nil ptr deref.
   268  		nextNamespace.IndexRunOptions.ShardTimeRanges = indexUnfulfilled.Copy()
   269  
   270  		// Set the modified result.
   271  		currResults.Results.Set(id, currResult)
   272  
   273  		// Always set the next bootstrapper namespace run options regardless of
   274  		// whether there are unfulfilled index/data shard time ranges.
   275  		// NB(bodu): We perform short circuiting directly in the peers bootstrapper and the
   276  		// commitlog bootstrapper should always run for all time ranges.
   277  		next.Namespaces.Set(id, nextNamespace)
   278  
   279  		// Log the result.
   280  		_, _, dataRangeRequested := dataCurrRequested.MinMaxRange()
   281  		_, _, dataRangeFulfilled := dataCurrFulfilled.MinMaxRange()
   282  		successLogFields := append(logFieldsCopy(baseLogFields),
   283  			zap.String("namespace", id.String()),
   284  			zap.Int("numShards", len(currNamespace.Shards)),
   285  			zap.Duration("dataRangeRequested", dataRangeRequested),
   286  			zap.Duration("dataRangeFulfilled", dataRangeFulfilled),
   287  		)
   288  
   289  		if currNamespace.Metadata.Options().IndexOptions().Enabled() {
   290  			_, _, indexRangeRequested := indexCurrRequested.MinMaxRange()
   291  			_, _, indexRangeFulfilled := indexCurrFulfilled.MinMaxRange()
   292  			successLogFields = append(successLogFields,
   293  				zap.Duration("indexRangeRequested", indexRangeRequested),
   294  				zap.Duration("indexRangeFulfilled", indexRangeFulfilled),
   295  				zap.Int("numIndexBlocks", len(currResult.IndexResult.IndexResults())),
   296  			)
   297  		}
   298  
   299  		b.log.Info("bootstrapping from source completed successfully",
   300  			successLogFields...)
   301  	}
   302  
   303  	return next, nil
   304  }
   305  
   306  func (b baseBootstrapper) logShardTimeRanges(
   307  	msg string,
   308  	baseLogFields []zapcore.Field,
   309  	currNamespace bootstrap.Namespace,
   310  ) {
   311  	dataShardTimeRanges := currNamespace.DataRunOptions.ShardTimeRanges
   312  	dataMin, dataMax, dataRange := dataShardTimeRanges.MinMaxRange()
   313  	logFields := append(logFieldsCopy(baseLogFields),
   314  		zap.Stringer("namespace", currNamespace.Metadata.ID()),
   315  		zap.Int("numShards", len(currNamespace.Shards)),
   316  		zap.Duration("dataRange", dataRange),
   317  	)
   318  	if dataRange > 0 {
   319  		logFields = append(logFields,
   320  			zap.Time("dataFrom", dataMin.ToTime()),
   321  			zap.Time("dataTo", dataMax.ToTime()),
   322  		)
   323  	}
   324  	if currNamespace.Metadata.Options().IndexOptions().Enabled() {
   325  		indexShardTimeRanges := currNamespace.IndexRunOptions.ShardTimeRanges
   326  		indexMin, indexMax, indexRange := indexShardTimeRanges.MinMaxRange()
   327  		logFields = append(logFields,
   328  			zap.Duration("indexRange", indexRange),
   329  		)
   330  		if indexRange > 0 {
   331  			logFields = append(logFields,
   332  				zap.Time("indexFrom", indexMin.ToTime()),
   333  				zap.Time("indexTo", indexMax.ToTime()),
   334  			)
   335  		}
   336  	}
   337  
   338  	b.log.Info(msg, logFields...)
   339  }
   340  
   341  func logFieldsCopy(logFields []zapcore.Field) []zapcore.Field {
   342  	return append(make([]zapcore.Field, 0, 2*len(logFields)), logFields...)
   343  }