github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/index_claims_manager.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 fs
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  	"sync/atomic"
    27  
    28  	"go.uber.org/zap"
    29  
    30  	"github.com/m3db/m3/src/dbnode/namespace"
    31  	"github.com/m3db/m3/src/dbnode/retention"
    32  	"github.com/m3db/m3/src/x/clock"
    33  	"github.com/m3db/m3/src/x/ident"
    34  	"github.com/m3db/m3/src/x/instrument"
    35  	xtime "github.com/m3db/m3/src/x/time"
    36  )
    37  
    38  var (
    39  	// errMustUseSingleClaimsManager returned when a second claims manager
    40  	// created, since this is a violation of expected behavior.
    41  	errMustUseSingleClaimsManager = errors.New("not using single global claims manager")
    42  
    43  	globalIndexClaimsManagers uint64
    44  
    45  	globalIndexClaimsManagersCheckEnabled = true
    46  )
    47  
    48  // ResetIndexClaimsManagersUnsafe should only be used from tests or integration
    49  // tests, it resets the count of index claim managers to allow new claim
    50  // managers to be created.
    51  // By default this is restricted to just once instantiation since otherwise
    52  // concurrency issues can be skipped without realizing.
    53  func ResetIndexClaimsManagersUnsafe() {
    54  	atomic.StoreUint64(&globalIndexClaimsManagers, 0)
    55  }
    56  
    57  // DisableIndexClaimsManagersCheckUnsafe disables the global index claims
    58  // managers check which is useful during tests that spin up multiple DB
    59  // nodes pointed at different data directories within the same process.
    60  // Outside of that specific context, this method should never be used.
    61  func DisableIndexClaimsManagersCheckUnsafe() {
    62  	globalIndexClaimsManagersCheckEnabled = false
    63  }
    64  
    65  type indexClaimsManager struct {
    66  	sync.Mutex
    67  
    68  	filePathPrefix                string
    69  	nowFn                         clock.NowFn
    70  	nextIndexFileSetVolumeIndexFn nextIndexFileSetVolumeIndexFn
    71  
    72  	// Map of ns ID string -> blockStart -> volumeIndexClaim.
    73  	volumeIndexClaims map[string]map[xtime.UnixNano]volumeIndexClaim
    74  }
    75  
    76  type volumeIndexClaim struct {
    77  	volumeIndex int
    78  }
    79  
    80  // NewIndexClaimsManager returns an instance of the index claim manager. This manages
    81  // concurrent claims for volume indices per ns and block start.
    82  // NB(bodu): There should be only a single shared index claim manager among all threads
    83  // writing index data filesets.
    84  func NewIndexClaimsManager(opts Options) (IndexClaimsManager, error) {
    85  	if globalIndexClaimsManagersCheckEnabled && atomic.AddUint64(&globalIndexClaimsManagers, 1) != 1 {
    86  		err := errMustUseSingleClaimsManager
    87  		instrument.EmitAndLogInvariantViolation(opts.InstrumentOptions(),
    88  			func(l *zap.Logger) {
    89  				l.Error(err.Error())
    90  			})
    91  		return nil, err
    92  	}
    93  
    94  	return &indexClaimsManager{
    95  		filePathPrefix:                opts.FilePathPrefix(),
    96  		nowFn:                         opts.ClockOptions().NowFn(),
    97  		volumeIndexClaims:             make(map[string]map[xtime.UnixNano]volumeIndexClaim),
    98  		nextIndexFileSetVolumeIndexFn: NextIndexFileSetVolumeIndex,
    99  	}, nil
   100  }
   101  
   102  func (i *indexClaimsManager) ClaimNextIndexFileSetVolumeIndex(
   103  	md namespace.Metadata,
   104  	blockStart xtime.UnixNano,
   105  ) (int, error) {
   106  	i.Lock()
   107  	now := xtime.ToUnixNano(i.nowFn())
   108  	earliestBlockStart := retention.FlushTimeStartForRetentionPeriod(
   109  		md.Options().RetentionOptions().RetentionPeriod(),
   110  		md.Options().IndexOptions().BlockSize(),
   111  		now,
   112  	)
   113  	defer func() {
   114  		i.deleteOutOfRetentionEntriesWithLock(md.ID(), earliestBlockStart)
   115  		i.Unlock()
   116  	}()
   117  
   118  	// Reject out of retention claims.
   119  	if blockStart.Before(earliestBlockStart) {
   120  		return 0, ErrIndexOutOfRetention
   121  	}
   122  
   123  	volumeIndexClaimsByBlockStart, ok := i.volumeIndexClaims[md.ID().String()]
   124  	if !ok {
   125  		volumeIndexClaimsByBlockStart = make(map[xtime.UnixNano]volumeIndexClaim)
   126  		i.volumeIndexClaims[md.ID().String()] = volumeIndexClaimsByBlockStart
   127  	}
   128  
   129  	if curr, ok := volumeIndexClaimsByBlockStart[blockStart]; ok {
   130  		// Already had a previous claim, return the next claim.
   131  		next := curr
   132  		next.volumeIndex++
   133  		volumeIndexClaimsByBlockStart[blockStart] = next
   134  		return next.volumeIndex, nil
   135  	}
   136  
   137  	volumeIndex, err := i.nextIndexFileSetVolumeIndexFn(i.filePathPrefix, md.ID(),
   138  		blockStart)
   139  	if err != nil {
   140  		return 0, err
   141  	}
   142  	volumeIndexClaimsByBlockStart[blockStart] = volumeIndexClaim{
   143  		volumeIndex: volumeIndex,
   144  	}
   145  	return volumeIndex, nil
   146  }
   147  
   148  func (i *indexClaimsManager) deleteOutOfRetentionEntriesWithLock(
   149  	nsID ident.ID,
   150  	earliestBlockStart xtime.UnixNano,
   151  ) {
   152  	// ns ID already exists at this point since the delete call is deferred.
   153  	for blockStart := range i.volumeIndexClaims[nsID.String()] {
   154  		if blockStart.Before(earliestBlockStart) {
   155  			delete(i.volumeIndexClaims[nsID.String()], blockStart)
   156  		}
   157  	}
   158  }
   159  
   160  type nextIndexFileSetVolumeIndexFn func(
   161  	filePathPrefix string,
   162  	namespace ident.ID,
   163  	blockStart xtime.UnixNano,
   164  ) (int, error)