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  }