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 }