github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/bootstrap/bootstrapper/uninitialized/source.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 uninitialized 22 23 import ( 24 "fmt" 25 26 "github.com/m3db/m3/src/cluster/shard" 27 "github.com/m3db/m3/src/dbnode/namespace" 28 "github.com/m3db/m3/src/dbnode/storage/bootstrap" 29 "github.com/m3db/m3/src/dbnode/storage/bootstrap/result" 30 "github.com/m3db/m3/src/dbnode/topology" 31 "github.com/m3db/m3/src/x/context" 32 ) 33 34 // The purpose of the unitializedSource is to succeed bootstraps for any 35 // shard/time-ranges if the cluster they're associated with has never 36 // been completely initialized (is a new cluster). This is required for 37 // allowing us to configure the bootstrappers such that the commitlog 38 // bootstrapper can precede the peers bootstrapper and still succeed bootstraps 39 // for brand new namespaces without permitting unintentional data loss by 40 // putting the noop-all or noop-none bootstrappers at the end of the process. 41 // Behavior is best understood by reading the test cases for the test: 42 // TestUnitializedSourceAvailableDataAndAvailableIndex 43 type uninitializedTopologySource struct { 44 opts Options 45 instrumentation *instrumentation 46 } 47 48 // newTopologyUninitializedSource creates a new uninitialized source. 49 func newTopologyUninitializedSource(opts Options) bootstrap.Source { 50 return &uninitializedTopologySource{ 51 opts: opts, 52 instrumentation: newInstrumentation(opts), 53 } 54 } 55 56 func (s *uninitializedTopologySource) AvailableData( 57 ns namespace.Metadata, 58 shardsTimeRanges result.ShardTimeRanges, 59 _ bootstrap.Cache, 60 runOpts bootstrap.RunOptions, 61 ) (result.ShardTimeRanges, error) { 62 return s.availability(ns, shardsTimeRanges, runOpts) 63 } 64 65 func (s *uninitializedTopologySource) AvailableIndex( 66 ns namespace.Metadata, 67 shardsTimeRanges result.ShardTimeRanges, 68 _ bootstrap.Cache, 69 runOpts bootstrap.RunOptions, 70 ) (result.ShardTimeRanges, error) { 71 return s.availability(ns, shardsTimeRanges, runOpts) 72 } 73 74 func (s *uninitializedTopologySource) availability( 75 _ namespace.Metadata, 76 shardsTimeRanges result.ShardTimeRanges, 77 runOpts bootstrap.RunOptions, 78 ) (result.ShardTimeRanges, error) { 79 var ( 80 topoState = runOpts.InitialTopologyState() 81 availableShardTimeRanges = result.NewShardTimeRanges() 82 ) 83 84 for shardIDUint := range shardsTimeRanges.Iter() { 85 shardID := topology.ShardID(shardIDUint) 86 hostShardStates, ok := topoState.ShardStates[shardID] 87 if !ok { 88 // This shard was not part of the topology when the bootstrapping 89 // process began. 90 continue 91 } 92 93 // The basic idea for the algorithm is that on a shard-by-shard basis we 94 // need to determine if the cluster is "new" in the sense that it has 95 // never been completely initialized (reached a state where all the hosts 96 // in the topology are "available" for that specific shard). 97 // In order to determine this, we simply count the number of hosts in the 98 // "initializing" state. If this number is larger than zero, than the 99 // cluster is "new". 100 // The one exception to this case is when we perform topology changes and 101 // we end up with one extra node that is initializing which should be offset 102 // by the corresponding node that is leaving. I.E if numInitializing > 0 103 // BUT numLeaving >= numInitializing then it is still not a new namespace. 104 // See the TestUnitializedSourceAvailableDataAndAvailableIndex test for more details. 105 var ( 106 numAvailable = 0 107 numInitializing = 0 108 numLeaving = 0 109 ) 110 for _, hostState := range hostShardStates { 111 shardState := hostState.ShardState 112 switch shardState { 113 case shard.Initializing: 114 numInitializing++ 115 case shard.Leaving: 116 numLeaving++ 117 case shard.Available: 118 numAvailable++ 119 case shard.Unknown: 120 fallthrough 121 default: 122 return nil, fmt.Errorf("unknown shard state: %v", shardState) 123 } 124 } 125 126 // This heuristic works for all scenarios except for if we tried to change the replication 127 // factor of a cluster that was already initialized. In that case, we might have to come 128 // up with a new heuristic, or simply require that the peers bootstrapper be configured as 129 // a bootstrapper if users want to change the replication factor dynamically, which is fine 130 // because otherwise you'd have to wait for one entire retention period for the replicaiton 131 // factor to actually increase correctly. 132 shardHasNeverBeenCompletelyInitialized := numInitializing-numLeaving > 0 133 if shardHasNeverBeenCompletelyInitialized { 134 if tr, ok := shardsTimeRanges.Get(shardIDUint); ok { 135 availableShardTimeRanges.Set(shardIDUint, tr) 136 } 137 } 138 } 139 140 return availableShardTimeRanges, nil 141 } 142 143 func (s *uninitializedTopologySource) Read( 144 ctx context.Context, 145 namespaces bootstrap.Namespaces, 146 _ bootstrap.Cache, 147 ) (bootstrap.NamespaceResults, error) { 148 instrCtx := s.instrumentation.uninitializedBootstrapperSourceReadStarted(ctx) 149 defer instrCtx.finish() 150 151 results := bootstrap.NamespaceResults{ 152 Results: bootstrap.NewNamespaceResultsMap(bootstrap.NamespaceResultsMapOptions{}), 153 } 154 for _, elem := range namespaces.Namespaces.Iter() { 155 ns := elem.Value() 156 157 namespaceResult := bootstrap.NamespaceResult{ 158 Metadata: ns.Metadata, 159 Shards: ns.Shards, 160 } 161 162 availability, err := s.availability(ns.Metadata, 163 ns.DataRunOptions.ShardTimeRanges, ns.DataRunOptions.RunOptions) 164 if err != nil { 165 return bootstrap.NamespaceResults{}, err 166 } 167 168 missing := ns.DataRunOptions.ShardTimeRanges.Copy() 169 missing.Subtract(availability) 170 171 if missing.IsEmpty() { 172 namespaceResult.DataResult = result.NewDataBootstrapResult() 173 } else { 174 namespaceResult.DataResult = missing.ToUnfulfilledDataResult() 175 } 176 177 if ns.Metadata.Options().IndexOptions().Enabled() { 178 if missing.IsEmpty() { 179 namespaceResult.IndexResult = result.NewIndexBootstrapResult() 180 } else { 181 namespaceResult.IndexResult = missing.ToUnfulfilledIndexResult() 182 } 183 } 184 185 results.Results.Set(ns.Metadata.ID(), namespaceResult) 186 } 187 188 return results, nil 189 }