github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/dynamic_cluster.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 m3 22 23 import ( 24 "errors" 25 "fmt" 26 "sync" 27 28 "github.com/m3db/m3/src/dbnode/namespace" 29 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 30 xerrors "github.com/m3db/m3/src/x/errors" 31 "github.com/m3db/m3/src/x/ident" 32 "github.com/m3db/m3/src/x/instrument" 33 34 "go.uber.org/zap" 35 ) 36 37 var ( 38 errAlreadyInitialized = errors.New("instance already initialized") 39 errDynamicClusterNamespaceConfigurationNotSet = errors.New("dynamicClusterNamespaceConfiguration not set") 40 errInstrumentOptionsNotSet = errors.New("instrumentOptions not set") 41 errClusterNamespacesWatcherNotSet = errors.New("clusterNamespacesWatcher not set") 42 errNsWatchAlreadyClosed = errors.New("namespace watch already closed") 43 ) 44 45 type dynamicCluster struct { 46 clusterCfgs []DynamicClusterNamespaceConfiguration 47 logger *zap.Logger 48 iOpts instrument.Options 49 clusterNamespacesWatcher ClusterNamespacesWatcher 50 51 sync.RWMutex 52 53 allNamespaces ClusterNamespaces 54 nonReadyNamespaces ClusterNamespaces 55 unaggregatedNamespace ClusterNamespace 56 aggregatedNamespaces map[RetentionResolution]ClusterNamespace 57 namespacesByEtcdCluster map[int]clusterNamespaceLookup 58 59 nsWatches []namespace.NamespaceWatch 60 closed bool 61 initialized bool 62 } 63 64 // NewDynamicClusters creates an implementation of the Clusters interface 65 // supports dynamic updating of cluster namespaces. 66 func NewDynamicClusters(opts DynamicClusterOptions) (Clusters, error) { 67 if err := opts.Validate(); err != nil { 68 return nil, err 69 } 70 71 cluster := &dynamicCluster{ 72 clusterCfgs: opts.DynamicClusterNamespaceConfiguration(), 73 logger: opts.InstrumentOptions().Logger(), 74 iOpts: opts.InstrumentOptions(), 75 clusterNamespacesWatcher: opts.ClusterNamespacesWatcher(), 76 namespacesByEtcdCluster: make(map[int]clusterNamespaceLookup), 77 } 78 79 if err := cluster.init(); err != nil { 80 if cErr := cluster.Close(); cErr != nil { 81 cluster.logger.Error("failed to initialize namespaces watchers", zap.Error(err)) 82 return nil, cErr 83 } 84 85 return nil, err 86 } 87 88 return cluster, nil 89 } 90 91 func (d *dynamicCluster) init() error { 92 if d.initialized { 93 return errAlreadyInitialized 94 } 95 96 d.initialized = true 97 98 d.logger.Info("creating namespaces watcher", zap.Int("clusters", len(d.clusterCfgs))) 99 100 var ( 101 wg sync.WaitGroup 102 multiErr xerrors.MultiError 103 errLock sync.Mutex 104 ) 105 // Configure watch for each cluster provided 106 for i, cfg := range d.clusterCfgs { 107 i := i 108 cfg := cfg 109 110 wg.Add(1) 111 go func() { 112 if err := d.initNamespaceWatch(i, cfg); err != nil { 113 errLock.Lock() 114 multiErr = multiErr.Add(err) 115 errLock.Unlock() 116 } 117 wg.Done() 118 }() 119 } 120 121 wg.Wait() 122 if !multiErr.Empty() { 123 return multiErr.FinalError() 124 } 125 126 return nil 127 } 128 129 func (d *dynamicCluster) initNamespaceWatch(etcdClusterID int, cfg DynamicClusterNamespaceConfiguration) error { 130 registry, err := cfg.nsInitializer.Init() 131 if err != nil { 132 return err 133 } 134 135 // Get a namespace watch. 136 watch, err := registry.Watch() 137 if err != nil { 138 return err 139 } 140 141 // Set method to invoke upon receiving updates and start watching. 142 updater := func(namespaces namespace.Map) error { 143 d.updateNamespaces(etcdClusterID, cfg, namespaces) 144 return nil 145 } 146 147 nsMap := watch.Get() 148 if nsMap != nil { 149 // When watches are created, a notification is generated if the initial value is not nil. Therefore, 150 // since we've performed a successful get, consume the initial notification so that once the nsWatch is 151 // started below, we do not trigger a duplicate update. 152 <-watch.C() 153 d.updateNamespaces(etcdClusterID, cfg, nsMap) 154 } else { 155 d.logger.Debug("initial namespace get was empty") 156 } 157 158 nsWatch := namespace.NewNamespaceWatch(updater, watch, d.iOpts) 159 if err = nsWatch.Start(); err != nil { 160 return err 161 } 162 163 d.Lock() 164 d.nsWatches = append(d.nsWatches, nsWatch) 165 d.Unlock() 166 167 return nil 168 } 169 170 func (d *dynamicCluster) updateNamespaces( 171 etcdClusterID int, 172 clusterCfg DynamicClusterNamespaceConfiguration, 173 newNamespaces namespace.Map, 174 ) { 175 if newNamespaces == nil { 176 d.logger.Debug("ignoring empty namespace map", zap.Int("cluster", etcdClusterID)) 177 return 178 } 179 180 d.Lock() 181 d.updateNamespacesByEtcdClusterWithLock(etcdClusterID, clusterCfg, newNamespaces) 182 d.updateClusterNamespacesWithLock() 183 d.Unlock() 184 185 if err := d.clusterNamespacesWatcher.Update(d.ClusterNamespaces()); err != nil { 186 d.logger.Error("failed to update cluster namespaces watcher", zap.Error(err)) 187 } 188 } 189 190 func (d *dynamicCluster) updateNamespacesByEtcdClusterWithLock( 191 etcdClusterID int, 192 clusterCfg DynamicClusterNamespaceConfiguration, 193 newNamespaces namespace.Map, 194 ) { 195 // Check if existing namespaces still exist or need to be updated. 196 existing, ok := d.namespacesByEtcdCluster[etcdClusterID] 197 if !ok { 198 existing = newClusterNamespaceLookup(len(newNamespaces.IDs())) 199 d.namespacesByEtcdCluster[etcdClusterID] = existing 200 } 201 var ( 202 sz = len(newNamespaces.Metadatas()) 203 added = make([]string, 0, sz) 204 updated = make([]string, 0, sz) 205 removed = make([]string, 0, sz) 206 ) 207 for nsID, nsMd := range existing.idToMetadata { 208 newNsMd, err := newNamespaces.Get(ident.StringID(nsID)) 209 // non-nil error here means namespace is not present (i.e. namespace has been removed) 210 if err != nil { 211 existing.remove(nsID) 212 removed = append(removed, nsID) 213 continue 214 } 215 216 if nsMd.Equal(newNsMd) { 217 continue 218 } 219 220 // Namespace options have been updated; regenerate cluster namespaces. 221 newClusterNamespaces, err := toClusterNamespaces(clusterCfg, newNsMd) 222 if err != nil { 223 // Log error, but don't allow singular failed namespace update to fail all namespace updates. 224 d.logger.Error("failed to update namespace", zap.String("namespace", nsID), 225 zap.Error(err)) 226 continue 227 } 228 // Replace with new metadata and cluster namespaces. 229 existing.update(nsID, newNsMd, newClusterNamespaces) 230 updated = append(updated, nsID) 231 } 232 233 // Check for new namespaces to add. 234 for _, newNsMd := range newNamespaces.Metadatas() { 235 if existing.exists(newNsMd.ID().String()) { 236 continue 237 } 238 239 // Namespace has been added. 240 newClusterNamespaces, err := toClusterNamespaces(clusterCfg, newNsMd) 241 if err != nil { 242 // Log error, but don't allow singular failed namespace update to fail all namespace updates. 243 d.logger.Error("failed to update namespace", zap.String("namespace", newNsMd.ID().String()), 244 zap.Error(err)) 245 continue 246 } 247 existing.add(newNsMd.ID().String(), newNsMd, newClusterNamespaces) 248 added = append(added, newNsMd.ID().String()) 249 } 250 251 if len(added) > 0 || len(updated) > 0 || len(removed) > 0 { 252 d.logger.Info("refreshed cluster namespaces", 253 zap.Strings("added", added), 254 zap.Strings("updated", updated), 255 zap.Strings("removed", removed)) 256 } 257 } 258 259 func toClusterNamespaces(clusterCfg DynamicClusterNamespaceConfiguration, md namespace.Metadata) (ClusterNamespaces, error) { 260 aggOpts := md.Options().AggregationOptions() 261 if aggOpts == nil { 262 return nil, fmt.Errorf("no aggregationOptions present for namespace %v", md.ID().String()) 263 } 264 265 if len(aggOpts.Aggregations()) == 0 { 266 return nil, fmt.Errorf("no aggregations present for namespace %v", md.ID().String()) 267 } 268 269 retOpts := md.Options().RetentionOptions() 270 if retOpts == nil { 271 return nil, fmt.Errorf("no retentionOptions present for namespace %v", md.ID().String()) 272 } 273 274 clusterNamespaces := make(ClusterNamespaces, 0, len(aggOpts.Aggregations())) 275 for _, agg := range aggOpts.Aggregations() { 276 var ( 277 clusterNamespace ClusterNamespace 278 err error 279 ) 280 if agg.Aggregated { 281 clusterNamespace, err = newAggregatedClusterNamespace(AggregatedClusterNamespaceDefinition{ 282 NamespaceID: md.ID(), 283 Session: clusterCfg.session, 284 Retention: retOpts.RetentionPeriod(), 285 Resolution: agg.Attributes.Resolution, 286 Downsample: &ClusterNamespaceDownsampleOptions{ 287 All: agg.Attributes.DownsampleOptions.All, 288 }, 289 }) 290 if err != nil { 291 return nil, err 292 } 293 } else { 294 clusterNamespace, err = newUnaggregatedClusterNamespace(UnaggregatedClusterNamespaceDefinition{ 295 NamespaceID: md.ID(), 296 Session: clusterCfg.session, 297 Retention: retOpts.RetentionPeriod(), 298 }) 299 if err != nil { 300 return nil, err 301 } 302 } 303 clusterNamespaces = append(clusterNamespaces, clusterNamespace) 304 } 305 306 return clusterNamespaces, nil 307 } 308 309 func (d *dynamicCluster) updateClusterNamespacesWithLock() { 310 nsCount := 0 311 for _, nsMap := range d.namespacesByEtcdCluster { 312 for _, clusterNamespaces := range nsMap.metadataToClusterNamespaces { 313 nsCount += len(clusterNamespaces) 314 } 315 } 316 317 var ( 318 newNamespaces = make(ClusterNamespaces, 0, nsCount) 319 newNonReadyNamespaces = make(ClusterNamespaces, 0, nsCount) 320 newAggregatedNamespaces = make(map[RetentionResolution]ClusterNamespace) 321 newUnaggregatedNamespace ClusterNamespace 322 ) 323 324 for _, nsMap := range d.namespacesByEtcdCluster { 325 for md, clusterNamespaces := range nsMap.metadataToClusterNamespaces { 326 for _, clusterNamespace := range clusterNamespaces { 327 status := md.Options().StagingState().Status() 328 // Don't make non-ready namespaces available for read/write in coordinator, but track 329 // them so that we can have the DB session available when we need to check their 330 // readiness in the /namespace/ready check. 331 if status != namespace.ReadyStagingStatus { 332 d.logger.Info("namespace has non-ready staging state status", 333 zap.String("namespace", md.ID().String()), 334 zap.String("status", status.String())) 335 336 newNonReadyNamespaces = append(newNonReadyNamespaces, clusterNamespace) 337 continue 338 } 339 340 attrs := clusterNamespace.Options().Attributes() 341 if attrs.MetricsType == storagemetadata.UnaggregatedMetricsType { 342 if newUnaggregatedNamespace != nil { 343 d.logger.Warn("more than one unaggregated namespace found. using most recently "+ 344 "discovered unaggregated namespace", 345 zap.String("existing", newUnaggregatedNamespace.NamespaceID().String()), 346 zap.String("new", clusterNamespace.NamespaceID().String())) 347 } 348 newUnaggregatedNamespace = clusterNamespace 349 } else { 350 retRes := RetentionResolution{ 351 Retention: attrs.Retention, 352 Resolution: attrs.Resolution, 353 } 354 existing, ok := newAggregatedNamespaces[retRes] 355 if ok { 356 d.logger.Warn("more than one aggregated namespace found for retention and resolution. "+ 357 "using most recently discovered aggregated namespace", 358 zap.String("retention", retRes.Retention.String()), 359 zap.String("resolution", retRes.Resolution.String()), 360 zap.String("existing", existing.NamespaceID().String()), 361 zap.String("new", clusterNamespace.NamespaceID().String())) 362 } 363 newAggregatedNamespaces[retRes] = clusterNamespace 364 } 365 } 366 } 367 } 368 369 if newUnaggregatedNamespace != nil { 370 newNamespaces = append(newNamespaces, newUnaggregatedNamespace) 371 } 372 for _, ns := range newAggregatedNamespaces { 373 newNamespaces = append(newNamespaces, ns) 374 } 375 376 d.unaggregatedNamespace = newUnaggregatedNamespace 377 d.aggregatedNamespaces = newAggregatedNamespaces 378 d.nonReadyNamespaces = newNonReadyNamespaces 379 d.allNamespaces = newNamespaces 380 } 381 382 func (d *dynamicCluster) Close() error { 383 d.Lock() 384 defer d.Unlock() 385 386 if d.closed { 387 return errNsWatchAlreadyClosed 388 } 389 390 d.closed = true 391 392 var multiErr xerrors.MultiError 393 for _, watch := range d.nsWatches { 394 if err := watch.Close(); err != nil { 395 multiErr = multiErr.Add(err) 396 } 397 } 398 399 if !multiErr.Empty() { 400 return multiErr.FinalError() 401 } 402 403 return nil 404 } 405 406 func (d *dynamicCluster) ClusterNamespaces() ClusterNamespaces { 407 d.RLock() 408 allNamespaces := d.allNamespaces 409 d.RUnlock() 410 411 return allNamespaces 412 } 413 414 func (d *dynamicCluster) NonReadyClusterNamespaces() ClusterNamespaces { 415 d.RLock() 416 nonReadyNamespaces := d.nonReadyNamespaces 417 d.RUnlock() 418 419 return nonReadyNamespaces 420 } 421 422 func (d *dynamicCluster) UnaggregatedClusterNamespace() (ClusterNamespace, bool) { 423 d.RLock() 424 unaggregatedNamespace := d.unaggregatedNamespace 425 d.RUnlock() 426 427 return unaggregatedNamespace, (unaggregatedNamespace != nil) 428 } 429 430 func (d *dynamicCluster) AggregatedClusterNamespace(attrs RetentionResolution) (ClusterNamespace, bool) { 431 d.RLock() 432 namespace, ok := d.aggregatedNamespaces[attrs] 433 d.RUnlock() 434 435 return namespace, ok 436 } 437 438 func (d *dynamicCluster) ConfigType() ClusterConfigType { 439 return ClusterConfigTypeDynamic 440 } 441 442 // clusterNamespaceLookup is a helper to track namespace changes. Two maps are necessary 443 // to handle the update case which causes the metadata for a previously seen namespaces to change. 444 // idToMetadata map allows us to find the previous metadata to detect changes. metadataToClusterNamespaces 445 // map allows us to find ClusterNamespaces generated from the metadata's AggregationOptions. 446 type clusterNamespaceLookup struct { 447 idToMetadata map[string]namespace.Metadata 448 metadataToClusterNamespaces map[namespace.Metadata]ClusterNamespaces 449 } 450 451 func newClusterNamespaceLookup(size int) clusterNamespaceLookup { 452 return clusterNamespaceLookup{ 453 idToMetadata: make(map[string]namespace.Metadata, size), 454 metadataToClusterNamespaces: make(map[namespace.Metadata]ClusterNamespaces, size), 455 } 456 } 457 458 func (c *clusterNamespaceLookup) exists(nsID string) bool { 459 _, ok := c.idToMetadata[nsID] 460 return ok 461 } 462 463 func (c *clusterNamespaceLookup) add(nsID string, nsMd namespace.Metadata, clusterNamespaces ClusterNamespaces) { 464 c.idToMetadata[nsID] = nsMd 465 c.metadataToClusterNamespaces[nsMd] = clusterNamespaces 466 } 467 468 func (c *clusterNamespaceLookup) update(nsID string, nsMd namespace.Metadata, clusterNamespaces ClusterNamespaces) { 469 existingMd := c.idToMetadata[nsID] 470 c.idToMetadata[nsID] = nsMd 471 delete(c.metadataToClusterNamespaces, existingMd) 472 c.metadataToClusterNamespaces[nsMd] = clusterNamespaces 473 } 474 475 func (c *clusterNamespaceLookup) remove(nsID string) { 476 existingMd := c.idToMetadata[nsID] 477 delete(c.metadataToClusterNamespaces, existingMd) 478 delete(c.idToMetadata, nsID) 479 }