github.com/matrixorigin/matrixone@v1.2.0/cmd/mo-service/config.go (about) 1 // Copyright 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "context" 19 "fmt" 20 "hash/fnv" 21 "math" 22 "net" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 logservicepb "github.com/matrixorigin/matrixone/pkg/pb/logservice" 29 "github.com/matrixorigin/matrixone/pkg/util" 30 31 "github.com/BurntSushi/toml" 32 "github.com/matrixorigin/matrixone/pkg/cnservice" 33 "github.com/matrixorigin/matrixone/pkg/common/chaos" 34 "github.com/matrixorigin/matrixone/pkg/common/moerr" 35 "github.com/matrixorigin/matrixone/pkg/config" 36 "github.com/matrixorigin/matrixone/pkg/defines" 37 "github.com/matrixorigin/matrixone/pkg/fileservice" 38 "github.com/matrixorigin/matrixone/pkg/logservice" 39 "github.com/matrixorigin/matrixone/pkg/logutil" 40 "github.com/matrixorigin/matrixone/pkg/objectio" 41 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 42 "github.com/matrixorigin/matrixone/pkg/perfcounter" 43 "github.com/matrixorigin/matrixone/pkg/proxy" 44 "github.com/matrixorigin/matrixone/pkg/tnservice" 45 "github.com/matrixorigin/matrixone/pkg/udf/pythonservice" 46 "github.com/matrixorigin/matrixone/pkg/util/debug/goroutine" 47 "github.com/matrixorigin/matrixone/pkg/util/metric/stats" 48 tomlutil "github.com/matrixorigin/matrixone/pkg/util/toml" 49 "github.com/matrixorigin/matrixone/pkg/version" 50 "go.uber.org/zap" 51 ) 52 53 var ( 54 defaultMaxClockOffset = time.Millisecond * 500 55 defaultMemoryLimit = 1 << 40 56 57 supportServiceTypes = map[string]metadata.ServiceType{ 58 metadata.ServiceType_CN.String(): metadata.ServiceType_CN, 59 metadata.ServiceType_TN.String(): metadata.ServiceType_TN, 60 metadata.ServiceType_LOG.String(): metadata.ServiceType_LOG, 61 metadata.ServiceType_PROXY.String(): metadata.ServiceType_PROXY, 62 metadata.ServiceType_PYTHON_UDF.String(): metadata.ServiceType_PYTHON_UDF, 63 } 64 ) 65 66 // LaunchConfig Start a MO cluster with launch 67 type LaunchConfig struct { 68 // LogServiceConfigFiles log service config files 69 LogServiceConfigFiles []string `toml:"logservices"` 70 // TNServiceConfigsFiles log service config files 71 TNServiceConfigsFiles []string `toml:"tnservices"` 72 // CNServiceConfigsFiles log service config files 73 CNServiceConfigsFiles []string `toml:"cnservices"` 74 // CNServiceConfigsFiles log service config files 75 ProxyServiceConfigsFiles []string `toml:"proxy-services"` 76 // PythonUdfServiceConfigsFiles python udf service config files 77 PythonUdfServiceConfigsFiles []string `toml:"python-udf-services"` 78 // Dynamic dynamic cn service config 79 Dynamic Dynamic `toml:"dynamic"` 80 } 81 82 // Dynamic dynamic cn config 83 type Dynamic struct { 84 // Enable enable dynamic cn config 85 Enable bool `toml:"enable"` 86 // CtlAddress http server port for ctl dynamic cn 87 CtlAddress string `toml:"ctl-address"` 88 // CNTemplate cn template file 89 CNTemplate string `toml:"cn-template"` 90 // ServiceCount how many cn services to start 91 ServiceCount int `toml:"service-count"` 92 // CpuCount how many cpu can used pr cn instance 93 CpuCount int `toml:"cpu-count"` 94 // Chaos chaos test config 95 Chaos chaos.Config `toml:"chaos"` 96 } 97 98 // Config mo-service configuration 99 type Config struct { 100 // DataDir data dir 101 DataDir string `toml:"data-dir"` 102 // Log log config 103 Log logutil.LogConfig `toml:"log"` 104 // ServiceType service type, select the corresponding configuration to start the 105 // service according to the service type. [CN|TN|LOG|PROXY] 106 ServiceType string `toml:"service-type"` 107 // FileServices the config for file services 108 FileServices []fileservice.Config `toml:"fileservice"` 109 // HAKeeperClient hakeeper client config 110 HAKeeperClient logservice.HAKeeperClientConfig `toml:"hakeeper-client"` 111 // TN tn service config 112 TN_please_use_getTNServiceConfig *tnservice.Config `toml:"tn"` 113 TNCompatible *tnservice.Config `toml:"dn"` // for old config files compatibility 114 // LogService is the config for log service 115 LogService logservice.Config `toml:"logservice"` 116 // CN cn service config 117 CN cnservice.Config `toml:"cn"` 118 // ProxyConfig is the config of proxy. 119 ProxyConfig proxy.Config `toml:"proxy"` 120 // PythonUdfServerConfig is the config of python udf server 121 PythonUdfServerConfig pythonservice.Config `toml:"python-udf-server"` 122 // Observability parameters for the metric/trace 123 Observability config.ObservabilityParameters `toml:"observability"` 124 125 // Clock txn clock type. [LOCAL|HLC]. Default is LOCAL. 126 Clock struct { 127 // Backend clock backend implementation. [LOCAL|HLC], default LOCAL. 128 Backend string `toml:"source"` 129 // MaxClockOffset max clock offset between two nodes. Default is 500ms. 130 // Only valid when enable-check-clock-offset is true 131 MaxClockOffset tomlutil.Duration `toml:"max-clock-offset"` 132 // EnableCheckMaxClockOffset enable local clock offset checker 133 EnableCheckMaxClockOffset bool `toml:"enable-check-clock-offset"` 134 } 135 136 // Limit limit configuration 137 Limit struct { 138 // Memory memory usage limit, see mpool for details 139 Memory tomlutil.ByteSize `toml:"memory"` 140 } 141 142 // MetaCache the config for objectio metacache 143 MetaCache objectio.CacheConfig `toml:"metacache"` 144 145 // IsStandalone denotes the matrixone is running in standalone mode 146 // For the tn does not boost an independent queryservice. 147 // cn,tn shares the same queryservice in standalone mode. 148 // Under distributed deploy mode, cn,tn are independent os process. 149 // they have their own queryservice. 150 IsStandalone bool 151 152 // Goroutine goroutine config 153 Goroutine goroutine.Config `toml:"goroutine"` 154 } 155 156 // NewConfig return Config with default values. 157 func NewConfig() *Config { 158 return &Config{ 159 HAKeeperClient: logservice.HAKeeperClientConfig{ 160 DiscoveryAddress: "", 161 ServiceAddresses: []string{logservice.DefaultLogServiceServiceAddress}, 162 AllocateIDBatch: 100, 163 EnableCompress: false, 164 }, 165 Observability: *config.NewObservabilityParameters(), 166 LogService: logservice.DefaultConfig(), 167 CN: cnservice.Config{ 168 AutomaticUpgrade: true, 169 }, 170 } 171 } 172 173 func parseConfigFromFile(file string, cfg any) error { 174 if file == "" { 175 return moerr.NewInternalError(context.Background(), "toml config file not set") 176 } 177 data, err := os.ReadFile(file) 178 if err != nil { 179 return err 180 } 181 return parseFromString(string(data), cfg) 182 } 183 184 func parseFromString(data string, cfg any) error { 185 if _, err := toml.Decode(data, cfg); err != nil { 186 return err 187 } 188 return nil 189 } 190 191 func (c *Config) validate() error { 192 if c.DataDir == "" || 193 c.DataDir == "./mo-data" || 194 c.DataDir == "mo-data" { 195 path, err := os.Getwd() 196 if err != nil { 197 panic(err) 198 } 199 c.DataDir = filepath.Join(path, "mo-data") 200 } 201 if _, err := c.getServiceType(); err != nil { 202 return err 203 } 204 if c.Clock.MaxClockOffset.Duration == 0 { 205 c.Clock.MaxClockOffset.Duration = defaultMaxClockOffset 206 } 207 if c.Clock.Backend == "" { 208 c.Clock.Backend = localClockBackend 209 } 210 if _, ok := supportTxnClockBackends[strings.ToUpper(c.Clock.Backend)]; !ok { 211 return moerr.NewInternalError(context.Background(), "%s clock backend not support", c.Clock.Backend) 212 } 213 if !c.Clock.EnableCheckMaxClockOffset { 214 c.Clock.MaxClockOffset.Duration = 0 215 } 216 for i, config := range c.FileServices { 217 // rename 's3' to 'shared' 218 if strings.EqualFold(config.Name, "s3") { 219 c.FileServices[i].Name = defines.SharedFileServiceName 220 } 221 // set default data dir 222 if config.DataDir == "" { 223 c.FileServices[i].DataDir = c.defaultFileServiceDataDir(config.Name) 224 } 225 // set default disk cache dir 226 if config.Cache.DiskPath == nil { 227 path := filepath.Join(c.DataDir, strings.ToLower(config.Name)+"-cache") 228 c.FileServices[i].Cache.DiskPath = &path 229 } 230 } 231 if c.Limit.Memory == 0 { 232 c.Limit.Memory = tomlutil.ByteSize(defaultMemoryLimit) 233 } 234 if c.Log.StacktraceLevel == "" { 235 c.Log.StacktraceLevel = zap.PanicLevel.String() 236 } 237 return nil 238 } 239 240 func (c *Config) setDefaultValue() error { 241 if c.DataDir == "" { 242 c.DataDir = "./mo-data" 243 } 244 if c.Clock.MaxClockOffset.Duration == 0 { 245 c.Clock.MaxClockOffset.Duration = defaultMaxClockOffset 246 } 247 if c.Clock.Backend == "" { 248 c.Clock.Backend = localClockBackend 249 } 250 if _, ok := supportTxnClockBackends[strings.ToUpper(c.Clock.Backend)]; !ok { 251 return moerr.NewInternalError(context.Background(), "%s clock backend not support", c.Clock.Backend) 252 } 253 if !c.Clock.EnableCheckMaxClockOffset { 254 c.Clock.MaxClockOffset.Duration = 0 255 } 256 for i, config := range c.FileServices { 257 // rename 's3' to 'shared' 258 if strings.EqualFold(config.Name, "s3") { 259 c.FileServices[i].Name = defines.SharedFileServiceName 260 } 261 // set default data dir 262 if config.DataDir == "" { 263 c.FileServices[i].DataDir = c.defaultFileServiceDataDir(config.Name) 264 } 265 // set default disk cache dir 266 if config.Cache.DiskPath == nil { 267 path := filepath.Join(c.DataDir, strings.ToLower(config.Name)+"-cache") 268 c.FileServices[i].Cache.DiskPath = &path 269 } 270 } 271 if c.Limit.Memory == 0 { 272 c.Limit.Memory = tomlutil.ByteSize(defaultMemoryLimit) 273 } 274 if c.Log.StacktraceLevel == "" { 275 c.Log.StacktraceLevel = zap.PanicLevel.String() 276 } 277 //set set default value 278 c.Log = logutil.GetDefaultConfig() 279 // HAKeeperClient has been set in NewConfig 280 if c.TN_please_use_getTNServiceConfig != nil { 281 c.TN_please_use_getTNServiceConfig.SetDefaultValue() 282 } 283 if c.TNCompatible != nil { 284 c.TNCompatible.SetDefaultValue() 285 } 286 // LogService has been set in NewConfig 287 c.CN.SetDefaultValue() 288 //no default proxy config 289 // Observability has been set in NewConfig 290 c.initMetaCache() 291 return nil 292 } 293 294 func (c *Config) initMetaCache() { 295 if c.MetaCache.MemoryCapacity > 0 { 296 objectio.InitMetaCache(int64(c.MetaCache.MemoryCapacity)) 297 } 298 } 299 300 func (c *Config) defaultFileServiceDataDir(name string) string { 301 return filepath.Join(c.DataDir, strings.ToLower(name)) 302 } 303 304 func (c *Config) createFileService( 305 ctx context.Context, 306 serviceType metadata.ServiceType, 307 nodeUUID string, 308 ) (*fileservice.FileServices, error) { 309 // create all services 310 services := make([]fileservice.FileService, 0, len(c.FileServices)) 311 312 // default LOCAL fs 313 ok := false 314 for _, config := range c.FileServices { 315 if strings.EqualFold(config.Name, defines.LocalFileServiceName) { 316 ok = true 317 break 318 } 319 } 320 // default to local disk 321 if !ok { 322 c.FileServices = append(c.FileServices, fileservice.Config{ 323 Name: defines.LocalFileServiceName, 324 Backend: "DISK", 325 DataDir: c.defaultFileServiceDataDir(defines.LocalFileServiceName), 326 }) 327 } 328 329 // default SHARED fs 330 ok = false 331 for _, config := range c.FileServices { 332 if strings.EqualFold(config.Name, defines.SharedFileServiceName) { 333 ok = true 334 break 335 } 336 } 337 // default to local disk 338 if !ok { 339 c.FileServices = append(c.FileServices, fileservice.Config{ 340 Name: defines.SharedFileServiceName, 341 Backend: "DISK", 342 DataDir: c.defaultFileServiceDataDir(defines.SharedFileServiceName), 343 }) 344 } 345 346 // default ETL fs 347 ok = false 348 for _, config := range c.FileServices { 349 if strings.EqualFold(config.Name, defines.ETLFileServiceName) { 350 ok = true 351 break 352 } 353 } 354 // default to local disk 355 if !ok { 356 c.FileServices = append(c.FileServices, fileservice.Config{ 357 Name: defines.ETLFileServiceName, 358 Backend: "DISK-ETL", // must be ETL 359 DataDir: c.defaultFileServiceDataDir(defines.ETLFileServiceName), 360 }) 361 } 362 363 // set distributed cache callbacks 364 for i := range c.FileServices { 365 c.setCacheCallbacks(&c.FileServices[i]) 366 } 367 368 for _, config := range c.FileServices { 369 counterSet := new(perfcounter.CounterSet) 370 service, err := fileservice.NewFileService( 371 ctx, 372 config, 373 []*perfcounter.CounterSet{ 374 counterSet, 375 }, 376 ) 377 if err != nil { 378 return nil, err 379 } 380 services = append(services, service) 381 382 // perf counter 383 counterSetName := perfcounter.NameForFileService( 384 serviceType.String(), 385 nodeUUID, 386 service.Name(), 387 ) 388 perfcounter.Named.Store(counterSetName, counterSet) 389 390 // set shared fs perf counter as node perf counter 391 if service.Name() == defines.SharedFileServiceName { 392 perfcounter.Named.Store( 393 perfcounter.NameForNode(serviceType.String(), nodeUUID), 394 counterSet, 395 ) 396 } 397 398 // Create "Log Exporter" for this PerfCounter 399 counterLogExporter := perfcounter.NewCounterLogExporter(counterSet) 400 // Register this PerfCounter's "Log Exporter" to global stats registry. 401 stats.Register(counterSetName, stats.WithLogExporter(counterLogExporter)) 402 } 403 404 // create FileServices 405 fs, err := fileservice.NewFileServices( 406 "", 407 services..., 408 ) 409 if err != nil { 410 return nil, err 411 } 412 413 // ensure local exists 414 _, err = fileservice.Get[fileservice.FileService](fs, defines.LocalFileServiceName) 415 if err != nil { 416 return nil, err 417 } 418 419 // ensure shared exists 420 _, err = fileservice.Get[fileservice.FileService](fs, defines.SharedFileServiceName) 421 if err != nil { 422 return nil, err 423 } 424 425 // ensure etl exists and is ETL 426 if !c.Observability.DisableMetric || !c.Observability.DisableTrace { 427 _, err = fileservice.Get[fileservice.ETLFileService](fs, defines.ETLFileServiceName) 428 if err != nil { 429 return nil, moerr.ConvertPanicError(context.Background(), err) 430 } 431 } 432 433 return fs, nil 434 } 435 436 func (c *Config) getLogServiceConfig() logservice.Config { 437 cfg := c.LogService 438 logutil.Infof("hakeeper client cfg: %v", c.HAKeeperClient) 439 cfg.HAKeeperClientConfig = c.HAKeeperClient 440 cfg.DataDir = filepath.Join(c.DataDir, "logservice-data", cfg.UUID) 441 var hostname string 442 var err error 443 hostname = cfg.ExplicitHostname 444 if len(hostname) == 0 { 445 // Should sync directory structure with dragonboat. 446 hostname, err = os.Hostname() 447 if err != nil { 448 panic(fmt.Sprintf("cannot get hostname: %s", err)) 449 } 450 } 451 cfg.SnapshotExportDir = filepath.Join(cfg.DataDir, hostname, 452 fmt.Sprintf("%020d", cfg.DeploymentID), "exported-snapshot") 453 return cfg 454 } 455 456 func (c *Config) getTNServiceConfig() tnservice.Config { 457 if c.TN_please_use_getTNServiceConfig == nil && c.TNCompatible != nil { 458 c.TN_please_use_getTNServiceConfig = c.TNCompatible 459 } 460 var cfg tnservice.Config 461 if c.TN_please_use_getTNServiceConfig != nil { 462 cfg = *c.TN_please_use_getTNServiceConfig 463 } 464 cfg.HAKeeper.ClientConfig = c.HAKeeperClient 465 cfg.DataDir = filepath.Join(c.DataDir, "dn-data", cfg.UUID) 466 return cfg 467 } 468 469 func (c *Config) getCNServiceConfig() cnservice.Config { 470 cfg := c.CN 471 cfg.HAKeeper.ClientConfig = c.HAKeeperClient 472 cfg.Frontend.SetLogAndVersion(&c.Log, version.Version) 473 if cfg.Txn.Trace.Dir == "" { 474 cfg.Txn.Trace.Dir = "trace" 475 } 476 return cfg 477 } 478 479 func (c *Config) getProxyConfig() proxy.Config { 480 cfg := c.ProxyConfig 481 cfg.HAKeeper.ClientConfig = c.HAKeeperClient 482 return cfg 483 } 484 485 func (c *Config) getObservabilityConfig() config.ObservabilityParameters { 486 cfg := c.Observability 487 cfg.SetDefaultValues(version.Version) 488 return cfg 489 } 490 491 // memberlist requires all gossip seed addresses to be provided as IP:PORT 492 func (c *Config) resolveGossipSeedAddresses() error { 493 result := make([]string, 0) 494 for _, addr := range c.LogService.GossipSeedAddresses { 495 host, port, err := net.SplitHostPort(addr) 496 if err != nil { 497 return err 498 } 499 ips, err := net.LookupIP(host) 500 if err != nil { 501 // the configured member may be failed currently, keep the host name anyway since 502 // memberlist would try to resolve it again 503 result = append(result, addr) 504 continue 505 } 506 // only keep IPv4 addresses 507 filtered := make([]string, 0) 508 for _, ip := range ips { 509 if ip.To4() != nil { 510 filtered = append(filtered, ip.String()) 511 } 512 } 513 if len(filtered) != 1 { 514 return moerr.NewBadConfig(context.Background(), "GossipSeedAddress %s", addr) 515 } 516 result = append(result, net.JoinHostPort(filtered[0], port)) 517 } 518 c.LogService.GossipSeedAddresses = result 519 return nil 520 } 521 522 func (c *Config) hashNodeID() uint16 { 523 st, err := c.getServiceType() 524 if err != nil { 525 panic(err) 526 } 527 528 uuid := "" 529 switch st { 530 case metadata.ServiceType_CN: 531 uuid = c.CN.UUID 532 case metadata.ServiceType_TN: 533 uuid = c.getTNServiceConfig().UUID 534 case metadata.ServiceType_LOG: 535 uuid = c.LogService.UUID 536 } 537 if uuid == "" { 538 return 0 539 } 540 541 h := fnv.New32() 542 if _, err := h.Write([]byte(uuid)); err != nil { 543 panic(err) 544 } 545 v := h.Sum32() 546 return uint16(v % math.MaxUint16) 547 } 548 549 func (c *Config) getServiceType() (metadata.ServiceType, error) { 550 if c.ServiceType == "DN" { // for old config files compatibility 551 c.ServiceType = metadata.ServiceType_TN.String() 552 } 553 if v, ok := supportServiceTypes[strings.ToUpper(c.ServiceType)]; ok { 554 return v, nil 555 } 556 return metadata.ServiceType(0), moerr.NewInternalError(context.Background(), "service type %s not support", c.ServiceType) 557 } 558 559 func (c *Config) mustGetServiceType() metadata.ServiceType { 560 v, err := c.getServiceType() 561 if err != nil { 562 panic(err) 563 } 564 return v 565 } 566 567 func (c *Config) mustGetServiceUUID() string { 568 switch c.mustGetServiceType() { 569 case metadata.ServiceType_CN: 570 return c.CN.UUID 571 case metadata.ServiceType_TN: 572 return c.getTNServiceConfig().UUID 573 case metadata.ServiceType_LOG: 574 return c.LogService.UUID 575 case metadata.ServiceType_PROXY: 576 return c.ProxyConfig.UUID 577 } 578 panic("impossible") 579 } 580 581 func (c *Config) setCacheCallbacks(fsConfig *fileservice.Config) { 582 fsConfig.Cache.SetRemoteCacheCallback() 583 } 584 585 // dumpCommonConfig gets the common config items except cn,tn,log,proxy 586 func dumpCommonConfig(cfg Config) (map[string]*logservicepb.ConfigItem, error) { 587 defCfg := *NewConfig() 588 err := defCfg.setDefaultValue() 589 if err != nil { 590 return nil, err 591 } 592 ret, err := util.DumpConfig(cfg, defCfg) 593 if err != nil { 594 return nil, err 595 } 596 597 //specific config items should be remoted 598 filters := []string{ 599 "config.tn_please_use_gettnserviceconfig", 600 "config.tncompatible", 601 "config.logservice", 602 "config.cn", 603 "config.proxyconfig", 604 } 605 606 //denote the common for cn,tn,log or proxy 607 prefix := "common" 608 609 newMap := make(map[string]*logservicepb.ConfigItem) 610 for s, item := range ret { 611 needDrop := false 612 for _, filter := range filters { 613 if strings.HasPrefix(strings.ToLower(s), strings.ToLower(filter)) { 614 needDrop = true 615 break 616 } 617 } 618 if needDrop { 619 continue 620 } 621 622 s = prefix + s 623 item.Name = s 624 newMap[s] = item 625 } 626 627 return newMap, err 628 }