github.com/m3db/m3@v1.5.0/src/dbnode/client/config.go (about)

     1  // Copyright (c) 2017 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 client
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/encoding"
    29  	"github.com/m3db/m3/src/dbnode/encoding/m3tsz"
    30  	"github.com/m3db/m3/src/dbnode/environment"
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	"github.com/m3db/m3/src/dbnode/topology"
    33  	"github.com/m3db/m3/src/x/clock"
    34  	xerrors "github.com/m3db/m3/src/x/errors"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	"github.com/m3db/m3/src/x/instrument"
    37  	"github.com/m3db/m3/src/x/retry"
    38  	"github.com/m3db/m3/src/x/sampler"
    39  	xsync "github.com/m3db/m3/src/x/sync"
    40  )
    41  
    42  const (
    43  	asyncWriteWorkerPoolDefaultSize = 128
    44  )
    45  
    46  // Configuration is a configuration that can be used to construct a client.
    47  type Configuration struct {
    48  	// The environment (static or dynamic) configuration.
    49  	EnvironmentConfig *environment.Configuration `yaml:"config"`
    50  
    51  	// WriteConsistencyLevel specifies the write consistency level.
    52  	WriteConsistencyLevel *topology.ConsistencyLevel `yaml:"writeConsistencyLevel"`
    53  
    54  	// ReadConsistencyLevel specifies the read consistency level.
    55  	ReadConsistencyLevel *topology.ReadConsistencyLevel `yaml:"readConsistencyLevel"`
    56  
    57  	// ConnectConsistencyLevel specifies the cluster connect consistency level.
    58  	ConnectConsistencyLevel *topology.ConnectConsistencyLevel `yaml:"connectConsistencyLevel"`
    59  
    60  	// WriteTimeout is the write request timeout.
    61  	WriteTimeout *time.Duration `yaml:"writeTimeout"`
    62  
    63  	// FetchTimeout is the fetch request timeout.
    64  	FetchTimeout *time.Duration `yaml:"fetchTimeout"`
    65  
    66  	// ConnectTimeout is the cluster connect timeout.
    67  	ConnectTimeout *time.Duration `yaml:"connectTimeout"`
    68  
    69  	// WriteRetry is the write retry config.
    70  	WriteRetry *retry.Configuration `yaml:"writeRetry"`
    71  
    72  	// FetchRetry is the fetch retry config.
    73  	FetchRetry *retry.Configuration `yaml:"fetchRetry"`
    74  
    75  	// LogErrorSampleRate is the log error sample rate.
    76  	LogErrorSampleRate sampler.Rate `yaml:"logErrorSampleRate"`
    77  
    78  	// BackgroundHealthCheckFailLimit is the amount of times a background check
    79  	// must fail before a connection is taken out of consideration.
    80  	BackgroundHealthCheckFailLimit *int `yaml:"backgroundHealthCheckFailLimit"`
    81  
    82  	// BackgroundHealthCheckFailThrottleFactor is the factor of the host connect
    83  	// time to use when sleeping between a failed health check and the next check.
    84  	BackgroundHealthCheckFailThrottleFactor *float64 `yaml:"backgroundHealthCheckFailThrottleFactor"`
    85  
    86  	// HashingConfiguration is the configuration for hashing of IDs to shards.
    87  	HashingConfiguration *HashingConfiguration `yaml:"hashing"`
    88  
    89  	// Proto contains the configuration specific to running in the ProtoDataMode.
    90  	Proto *ProtoConfiguration `yaml:"proto"`
    91  
    92  	// AsyncWriteWorkerPoolSize is the worker pool size for async write requests.
    93  	AsyncWriteWorkerPoolSize *int `yaml:"asyncWriteWorkerPoolSize"`
    94  
    95  	// AsyncWriteMaxConcurrency is the maximum concurrency for async write requests.
    96  	AsyncWriteMaxConcurrency *int `yaml:"asyncWriteMaxConcurrency"`
    97  
    98  	// UseV2BatchAPIs determines whether the V2 batch APIs are used. Note that the M3DB nodes must
    99  	// have support for the V2 APIs in order for this feature to be used.
   100  	UseV2BatchAPIs *bool `yaml:"useV2BatchAPIs"`
   101  
   102  	// WriteTimestampOffset offsets all writes by specified duration into the past.
   103  	WriteTimestampOffset *time.Duration `yaml:"writeTimestampOffset"`
   104  
   105  	// FetchSeriesBlocksBatchConcurrency sets the number of batches of blocks to retrieve
   106  	// in parallel from a remote peer. Defaults to NumCPU / 2.
   107  	FetchSeriesBlocksBatchConcurrency *int `yaml:"fetchSeriesBlocksBatchConcurrency"`
   108  
   109  	// FetchSeriesBlocksBatchSize sets the number of blocks to retrieve in a single batch
   110  	// from the remote peer. Defaults to 4096.
   111  	FetchSeriesBlocksBatchSize *int `yaml:"fetchSeriesBlocksBatchSize"`
   112  
   113  	// WriteShardsInitializing sets whether or not writes to leaving shards
   114  	// count towards consistency, by default they do not.
   115  	WriteShardsInitializing *bool `yaml:"writeShardsInitializing"`
   116  
   117  	// ShardsLeavingCountTowardsConsistency sets whether or not writes to leaving shards
   118  	// count towards consistency, by default they do not.
   119  	ShardsLeavingCountTowardsConsistency *bool `yaml:"shardsLeavingCountTowardsConsistency"`
   120  
   121  	// IterateEqualTimestampStrategy specifies the iterate equal timestamp strategy.
   122  	IterateEqualTimestampStrategy *encoding.IterateEqualTimestampStrategy `yaml:"iterateEqualTimestampStrategy"`
   123  }
   124  
   125  // ProtoConfiguration is the configuration for running with ProtoDataMode enabled.
   126  type ProtoConfiguration struct {
   127  	// Enabled specifies whether proto is enabled.
   128  	Enabled bool `yaml:"enabled"`
   129  	// load user schema from client configuration into schema registry
   130  	// at startup/initialization time.
   131  	SchemaRegistry map[string]NamespaceProtoSchema `yaml:"schema_registry"`
   132  }
   133  
   134  // NamespaceProtoSchema is the protobuf schema for a namespace.
   135  type NamespaceProtoSchema struct {
   136  	MessageName    string `yaml:"messageName"`
   137  	SchemaDeployID string `yaml:"schemaDeployID"`
   138  	SchemaFilePath string `yaml:"schemaFilePath"`
   139  }
   140  
   141  // Validate validates the NamespaceProtoSchema.
   142  func (c NamespaceProtoSchema) Validate() error {
   143  	if c.SchemaFilePath == "" {
   144  		return errors.New("schemaFilePath is required for Proto data mode")
   145  	}
   146  
   147  	if c.MessageName == "" {
   148  		return errors.New("messageName is required for Proto data mode")
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  // Validate validates the ProtoConfiguration.
   155  func (c *ProtoConfiguration) Validate() error {
   156  	if c == nil || !c.Enabled {
   157  		return nil
   158  	}
   159  
   160  	for _, schema := range c.SchemaRegistry {
   161  		if err := schema.Validate(); err != nil {
   162  			return err
   163  		}
   164  	}
   165  	return nil
   166  }
   167  
   168  // Validate validates the configuration.
   169  func (c *Configuration) Validate() error {
   170  	if c.WriteTimeout != nil && *c.WriteTimeout < 0 {
   171  		return fmt.Errorf("m3db client writeTimeout was: %d but must be >= 0", *c.WriteTimeout)
   172  	}
   173  
   174  	if c.FetchTimeout != nil && *c.FetchTimeout < 0 {
   175  		return fmt.Errorf("m3db client fetchTimeout was: %d but must be >= 0", *c.FetchTimeout)
   176  	}
   177  
   178  	if c.ConnectTimeout != nil && *c.ConnectTimeout < 0 {
   179  		return fmt.Errorf("m3db client connectTimeout was: %d but must be >= 0", *c.ConnectTimeout)
   180  	}
   181  
   182  	if err := c.LogErrorSampleRate.Validate(); err != nil {
   183  		return fmt.Errorf("m3db client error validating log error sample rate: %v", err)
   184  	}
   185  
   186  	if c.BackgroundHealthCheckFailLimit != nil &&
   187  		(*c.BackgroundHealthCheckFailLimit < 0 || *c.BackgroundHealthCheckFailLimit > 10) {
   188  		return fmt.Errorf(
   189  			"m3db client backgroundHealthCheckFailLimit was: %d but must be >= 0 and <=10",
   190  			*c.BackgroundHealthCheckFailLimit)
   191  	}
   192  
   193  	if c.BackgroundHealthCheckFailThrottleFactor != nil &&
   194  		(*c.BackgroundHealthCheckFailThrottleFactor < 0 || *c.BackgroundHealthCheckFailThrottleFactor > 10) {
   195  		return fmt.Errorf(
   196  			"m3db client backgroundHealthCheckFailThrottleFactor was: %f but must be >= 0 and <=10",
   197  			*c.BackgroundHealthCheckFailThrottleFactor)
   198  	}
   199  
   200  	if c.AsyncWriteWorkerPoolSize != nil && *c.AsyncWriteWorkerPoolSize <= 0 {
   201  		return fmt.Errorf("m3db client async write worker pool size was: %d but must be >0",
   202  			*c.AsyncWriteWorkerPoolSize)
   203  	}
   204  
   205  	if c.AsyncWriteMaxConcurrency != nil && *c.AsyncWriteMaxConcurrency <= 0 {
   206  		return fmt.Errorf("m3db client async write max concurrency was: %d but must be >0",
   207  			*c.AsyncWriteMaxConcurrency)
   208  	}
   209  
   210  	if err := c.Proto.Validate(); err != nil {
   211  		return fmt.Errorf("error validating M3DB client proto configuration: %v", err)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  // HashingConfiguration is the configuration for hashing
   218  type HashingConfiguration struct {
   219  	// Murmur32 seed value
   220  	Seed uint32 `yaml:"seed"`
   221  }
   222  
   223  // ConfigurationParameters are optional parameters that can be specified
   224  // when creating a client from configuration, this is specified using
   225  // a struct so that adding fields do not cause breaking changes to callers.
   226  type ConfigurationParameters struct {
   227  	// InstrumentOptions is a required argument when
   228  	// constructing a client from configuration.
   229  	InstrumentOptions instrument.Options
   230  
   231  	// ClockOptions is an optional argument when
   232  	// constructing a client from configuration.
   233  	ClockOptions clock.Options
   234  
   235  	// TopologyInitializer is an optional argument when
   236  	// constructing a client from configuration.
   237  	TopologyInitializer topology.Initializer
   238  
   239  	// EncodingOptions is an optional argument when
   240  	// constructing a client from configuration.
   241  	EncodingOptions encoding.Options
   242  }
   243  
   244  // CustomOption is a programatic method for setting a client
   245  // option after all the options have been set by configuration.
   246  type CustomOption func(v Options) Options
   247  
   248  // CustomAdminOption is a programatic method for setting a client
   249  // admin option after all the options have been set by configuration.
   250  type CustomAdminOption func(v AdminOptions) AdminOptions
   251  
   252  // NewClient creates a new M3DB client using
   253  // specified params and custom options.
   254  func (c Configuration) NewClient(
   255  	params ConfigurationParameters,
   256  	custom ...CustomOption,
   257  ) (Client, error) {
   258  	customAdmin := make([]CustomAdminOption, 0, len(custom))
   259  	for _, opt := range custom {
   260  		customAdmin = append(customAdmin, func(v AdminOptions) AdminOptions {
   261  			return opt(Options(v)).(AdminOptions)
   262  		})
   263  	}
   264  
   265  	v, err := c.NewAdminClient(params, customAdmin...)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	return v, err
   271  }
   272  
   273  // NewAdminClient creates a new M3DB admin client using
   274  // specified params and custom options.
   275  func (c Configuration) NewAdminClient(
   276  	params ConfigurationParameters,
   277  	custom ...CustomAdminOption,
   278  ) (AdminClient, error) {
   279  	err := c.Validate()
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	iopts := params.InstrumentOptions
   285  	if iopts == nil {
   286  		iopts = instrument.NewOptions()
   287  	}
   288  	writeRequestScope := iopts.MetricsScope().SubScope("write-req")
   289  	fetchRequestScope := iopts.MetricsScope().SubScope("fetch-req")
   290  
   291  	cfgParams := environment.ConfigurationParameters{
   292  		InstrumentOpts:                     iopts,
   293  		AllowEmptyInitialNamespaceRegistry: true,
   294  	}
   295  	if c.HashingConfiguration != nil {
   296  		cfgParams.HashingSeed = c.HashingConfiguration.Seed
   297  	}
   298  
   299  	var (
   300  		syncTopoInit         = params.TopologyInitializer
   301  		syncClientOverrides  environment.ClientOverrides
   302  		syncNsInit           namespace.Initializer
   303  		asyncTopoInits       = []topology.Initializer{}
   304  		asyncClientOverrides = []environment.ClientOverrides{}
   305  	)
   306  
   307  	var buildAsyncPool bool
   308  	if syncTopoInit == nil {
   309  		envCfgs, err := c.EnvironmentConfig.Configure(cfgParams)
   310  		if err != nil {
   311  			err = fmt.Errorf("unable to create topology initializer, err: %v", err)
   312  			return nil, err
   313  		}
   314  
   315  		for _, envCfg := range envCfgs {
   316  			if envCfg.Async {
   317  				asyncTopoInits = append(asyncTopoInits, envCfg.TopologyInitializer)
   318  				asyncClientOverrides = append(asyncClientOverrides, envCfg.ClientOverrides)
   319  				buildAsyncPool = true
   320  			} else {
   321  				syncTopoInit = envCfg.TopologyInitializer
   322  				syncClientOverrides = envCfg.ClientOverrides
   323  				syncNsInit = envCfg.NamespaceInitializer
   324  			}
   325  		}
   326  	}
   327  
   328  	v := NewAdminOptions().
   329  		SetTopologyInitializer(syncTopoInit).
   330  		SetNamespaceInitializer(syncNsInit).
   331  		SetAsyncTopologyInitializers(asyncTopoInits).
   332  		SetInstrumentOptions(iopts).
   333  		SetLogErrorSampleRate(c.LogErrorSampleRate)
   334  
   335  	if params.ClockOptions != nil {
   336  		v = v.SetClockOptions(params.ClockOptions)
   337  	}
   338  
   339  	if c.UseV2BatchAPIs != nil {
   340  		v = v.SetUseV2BatchAPIs(*c.UseV2BatchAPIs)
   341  	}
   342  
   343  	if buildAsyncPool {
   344  		var size int
   345  		if c.AsyncWriteWorkerPoolSize == nil {
   346  			size = asyncWriteWorkerPoolDefaultSize
   347  		} else {
   348  			size = *c.AsyncWriteWorkerPoolSize
   349  		}
   350  
   351  		workerPoolInstrumentOpts := iopts.SetMetricsScope(writeRequestScope.SubScope("workerpool"))
   352  		workerPoolOpts := xsync.NewPooledWorkerPoolOptions().
   353  			SetGrowOnDemand(true).
   354  			SetInstrumentOptions(workerPoolInstrumentOpts)
   355  		workerPool, err := xsync.NewPooledWorkerPool(size, workerPoolOpts)
   356  		if err != nil {
   357  			return nil, fmt.Errorf("unable to create async worker pool: %v", err)
   358  		}
   359  		workerPool.Init()
   360  		v = v.SetAsyncWriteWorkerPool(workerPool)
   361  	}
   362  
   363  	if c.AsyncWriteMaxConcurrency != nil {
   364  		v = v.SetAsyncWriteMaxConcurrency(*c.AsyncWriteMaxConcurrency)
   365  	}
   366  
   367  	if c.WriteConsistencyLevel != nil {
   368  		v = v.SetWriteConsistencyLevel(*c.WriteConsistencyLevel)
   369  	}
   370  	if c.ReadConsistencyLevel != nil {
   371  		v = v.SetReadConsistencyLevel(*c.ReadConsistencyLevel)
   372  	}
   373  	if c.ConnectConsistencyLevel != nil {
   374  		v = v.SetClusterConnectConsistencyLevel(*c.ConnectConsistencyLevel)
   375  	}
   376  	if c.BackgroundHealthCheckFailLimit != nil {
   377  		v = v.SetBackgroundHealthCheckFailLimit(*c.BackgroundHealthCheckFailLimit)
   378  	}
   379  	if c.BackgroundHealthCheckFailThrottleFactor != nil {
   380  		v = v.SetBackgroundHealthCheckFailThrottleFactor(*c.BackgroundHealthCheckFailThrottleFactor)
   381  	}
   382  	if c.WriteTimeout != nil {
   383  		v = v.SetWriteRequestTimeout(*c.WriteTimeout)
   384  	}
   385  	if c.FetchTimeout != nil {
   386  		v = v.SetFetchRequestTimeout(*c.FetchTimeout)
   387  	}
   388  	if c.ConnectTimeout != nil {
   389  		v = v.SetClusterConnectTimeout(*c.ConnectTimeout)
   390  	}
   391  	if c.WriteRetry != nil {
   392  		v = v.SetWriteRetrier(c.WriteRetry.NewRetrier(writeRequestScope))
   393  	} else {
   394  		// Have not set write retry explicitly, but would like metrics
   395  		// emitted for the write retrier with the scope for write requests.
   396  		retrierOpts := v.WriteRetrier().Options().
   397  			SetMetricsScope(writeRequestScope)
   398  		v = v.SetWriteRetrier(retry.NewRetrier(retrierOpts))
   399  	}
   400  	if c.FetchRetry != nil {
   401  		v = v.SetFetchRetrier(c.FetchRetry.NewRetrier(fetchRequestScope))
   402  	} else {
   403  		// Have not set fetch retry explicitly, but would like metrics
   404  		// emitted for the fetch retrier with the scope for fetch requests.
   405  		retrierOpts := v.FetchRetrier().Options().
   406  			SetMetricsScope(fetchRequestScope)
   407  		v = v.SetFetchRetrier(retry.NewRetrier(retrierOpts))
   408  	}
   409  	if syncClientOverrides.TargetHostQueueFlushSize != nil {
   410  		v = v.SetHostQueueOpsFlushSize(*syncClientOverrides.TargetHostQueueFlushSize)
   411  	}
   412  	if syncClientOverrides.HostQueueFlushInterval != nil {
   413  		v = v.SetHostQueueOpsFlushInterval(*syncClientOverrides.HostQueueFlushInterval)
   414  	}
   415  
   416  	if c.IterateEqualTimestampStrategy != nil {
   417  		o := v.IterationOptions()
   418  		o.IterateEqualTimestampStrategy = *c.IterateEqualTimestampStrategy
   419  		v = v.SetIterationOptions(o)
   420  	}
   421  
   422  	encodingOpts := params.EncodingOptions
   423  	if encodingOpts == nil {
   424  		encodingOpts = encoding.NewOptions()
   425  	}
   426  
   427  	v = v.SetReaderIteratorAllocate(m3tsz.DefaultReaderIteratorAllocFn(encodingOpts))
   428  
   429  	if c.Proto != nil && c.Proto.Enabled {
   430  		v = v.SetEncodingProto(encodingOpts)
   431  		schemaRegistry := namespace.NewSchemaRegistry(true, nil)
   432  		// Load schema registry from file.
   433  		deployID := "fromfile"
   434  		for nsID, protoConfig := range c.Proto.SchemaRegistry {
   435  			err = namespace.LoadSchemaRegistryFromFile(schemaRegistry, ident.StringID(nsID), deployID, protoConfig.SchemaFilePath, protoConfig.MessageName)
   436  			if err != nil {
   437  				return nil, xerrors.Wrapf(err, "could not load schema registry from file %s for namespace %s", protoConfig.SchemaFilePath, nsID)
   438  			}
   439  		}
   440  		v = v.SetSchemaRegistry(schemaRegistry)
   441  	}
   442  
   443  	if c.WriteShardsInitializing != nil {
   444  		v = v.SetWriteShardsInitializing(*c.WriteShardsInitializing)
   445  	}
   446  	if c.ShardsLeavingCountTowardsConsistency != nil {
   447  		v = v.SetShardsLeavingCountTowardsConsistency(*c.ShardsLeavingCountTowardsConsistency)
   448  	}
   449  
   450  	// Cast to admin options to apply admin config options.
   451  	opts := v.(AdminOptions)
   452  
   453  	if c.WriteTimestampOffset != nil {
   454  		opts = opts.SetWriteTimestampOffset(*c.WriteTimestampOffset)
   455  	}
   456  
   457  	if c.FetchSeriesBlocksBatchConcurrency != nil {
   458  		opts = opts.SetFetchSeriesBlocksBatchConcurrency(*c.FetchSeriesBlocksBatchConcurrency)
   459  	}
   460  	if c.FetchSeriesBlocksBatchSize != nil {
   461  		opts = opts.SetFetchSeriesBlocksBatchSize(*c.FetchSeriesBlocksBatchSize)
   462  	}
   463  
   464  	// Apply programmatic custom options last.
   465  	for _, opt := range custom {
   466  		opts = opt(opts)
   467  	}
   468  
   469  	asyncClusterOpts := NewOptionsForAsyncClusters(opts, asyncTopoInits, asyncClientOverrides)
   470  	return NewAdminClient(opts, asyncClusterOpts...)
   471  }