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)