Repository: matrixorigin/matrixone
Pull request #4221 (2022-07-26 11:13:51)

logservice: added the ability to build log service
**What type of PR is this?**

- [ ] API-change
- [ ] BUG
- [ ] Improvement
- [ ] Documentation
- [x] Feature
- [ ] Test and CI
- [ ] Code Refactoring

**Which issue(s) this PR fixes:**

issue #

**What this PR does / why we need it:**

added the ability to build log service. as expected, the generated service binary would require some more refactorings/fixes to work.  

**Special notes for your reviewer:**

Not Available

**Additional documentation (e.g. design docs, usage docs, etc.):**

Not Available



Patch:
diff --git a/Makefile b/Makefile
index 850e64e42..b3457ec6c 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@
 ROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 GOBIN := go
 BIN_NAME := mo-server
+SERVICE_BIN_NAME := mo-service
 BUILD_CFG := gen_config
 UNAME_S := $(shell uname -s)
 GOPATH := $(shell go env GOPATH)
@@ -92,7 +93,7 @@ pb: generate-pb fmt
 	$(info all protos are generated) 
 
 ###############################################################################
-# build MatrixOne
+# build mo-server
 ###############################################################################
 
 RACE_OPT := 
@@ -109,7 +110,7 @@ BUILD_NAME=binary
 .PHONY: build
 build: config cgo cmd/db-server/$(wildcard *.go)
 	$(info [Build $(BUILD_NAME)])
-	$(GO) build $(RACE_OPT) $(GOLDFLAGS) -o $(BIN_NAME) ./cmd/db-server/
+	$(GO) build $(RACE_OPT) $(GOLDFLAGS) -o $(BIN_NAME) ./cmd/db-server
 
 # build mo-server binary for debugging with go's race detector enabled
 # produced executable is 10x slower and consumes much more memory
@@ -118,6 +119,22 @@ debug: override BUILD_NAME := debug-binary
 debug: override RACE_OPT := -race
 debug: build
 
+###############################################################################
+# build mo-service
+###############################################################################
+
+# build mo-service binary
+.PHONY: service
+service: config cgo cmd/mo-service/$(wildcard *.go)
+	$(info [Build $(BUILD_NAME)])
+	$(GO) build $(RACE_OPT) $(GOLDFLAGS) -o $(SERVICE_BIN_NAME) ./cmd/mo-service
+
+.PHONY: debug-service
+debug-service: override BUILD_NAME := debug-binary
+debug-service: override RACE_OPT := -race
+debug-service: service
+
+
 ###############################################################################
 # run unit tests
 ###############################################################################
@@ -141,15 +158,7 @@ clean:
 	$(info [Clean up])
 	$(info Clean go test cache)
 	@go clean -testcache
-	@rm -f $(CONFIG_CODE_GENERATED)
-ifneq ($(wildcard $(BIN_NAME)),)
-	$(info Remove file $(BIN_NAME))
-	@rm -f $(BIN_NAME)
-endif
-ifneq ($(wildcard $(BUILD_CFG)),)
-	$(info Remove file $(BUILD_CFG))
-	@rm -f $(BUILD_CFG)
-endif
+	rm -f $(CONFIG_CODE_GENERATED) $(BIN_NAME) $(SERVICE_BIN_NAME) $(BUILD_CFG)
 	$(MAKE) -C cgo clean
 
 ###############################################################################
diff --git a/cmd/mo-service/cfg.go b/cmd/mo-service/cfg.go
index 83bb0c685..6f8180b47 100644
--- a/cmd/mo-service/cfg.go
+++ b/cmd/mo-service/cfg.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/BurntSushi/toml"
 	"github.com/matrixorigin/matrixone/pkg/dnservice"
+	"github.com/matrixorigin/matrixone/pkg/logservice"
 	"github.com/matrixorigin/matrixone/pkg/logutil"
 )
 
@@ -49,6 +50,8 @@ type Config struct {
 	ServiceType string `toml:"service-type"`
 	// DN dn service config
 	DN dnservice.Config `toml:"dn"`
+	// LogService is the config for log service
+	LogService logservice.Config `toml:"logservice"`
 }
 
 func parseConfigFromFile(file string) (*Config, error) {
diff --git a/cmd/mo-service/main.go b/cmd/mo-service/main.go
index 4c5cd34db..0bf2b843e 100644
--- a/cmd/mo-service/main.go
+++ b/cmd/mo-service/main.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/matrixorigin/matrixone/pkg/common/stopper"
 	"github.com/matrixorigin/matrixone/pkg/dnservice"
+	"github.com/matrixorigin/matrixone/pkg/logservice"
 	"github.com/matrixorigin/matrixone/pkg/logutil"
 )
 
@@ -67,10 +68,17 @@ func waitSignalToStop(stopper *stopper.Stopper) {
 func startService(cfg *Config, stopper *stopper.Stopper) error {
 	// TODO: start other service
 	switch strings.ToUpper(cfg.ServiceType) {
+	case cnServiceType:
+		panic("not implemented")
 	case dnServiceType:
 		return startDNService(cfg, stopper)
+	case logServiceType:
+		return startLogService(cfg, stopper)
+	case standaloneServiceType:
+		panic("not implemented")
+	default:
+		panic("unknown service type")
 	}
-	return nil
 }
 
 func startDNService(cfg *Config, stopper *stopper.Stopper) error {
@@ -93,3 +101,20 @@ func startDNService(cfg *Config, stopper *stopper.Stopper) error {
 		}
 	})
 }
+
+func startLogService(cfg *Config, stopper *stopper.Stopper) error {
+	return stopper.RunNamedTask("log-service", func(ctx context.Context) {
+		s, err := logservice.NewService(cfg.LogService)
+		if err != nil {
+			panic(err)
+		}
+		if err := s.Start(); err != nil {
+			panic(err)
+		}
+
+		<-ctx.Done()
+		if err := s.Close(); err != nil {
+			panic(err)
+		}
+	})
+}
diff --git a/pkg/logservice/commands.go b/pkg/logservice/commands.go
index 17ed7b099..fad35a2fb 100644
--- a/pkg/logservice/commands.go
+++ b/pkg/logservice/commands.go
@@ -100,10 +100,10 @@ func (s *Service) handleShutdownStore(cmd pb.ScheduleCommand) {
 
 func (s *Service) heartbeatWorker(ctx context.Context) {
 	// TODO: check tick interval
-	if s.cfg.HeartbeatInterval == 0 {
+	if s.cfg.HeartbeatInterval.Duration == 0 {
 		panic("invalid heartbeat interval")
 	}
-	ticker := time.NewTicker(s.cfg.HeartbeatInterval)
+	ticker := time.NewTicker(s.cfg.HeartbeatInterval.Duration)
 	defer ticker.Stop()
 
 	for {
diff --git a/pkg/logservice/config.go b/pkg/logservice/config.go
index 0501296d3..220104b1a 100644
--- a/pkg/logservice/config.go
+++ b/pkg/logservice/config.go
@@ -15,6 +15,7 @@
 package logservice
 
 import (
+	"strings"
 	"time"
 
 	"github.com/cockroachdb/errors"
@@ -22,17 +23,18 @@ import (
 
 	"github.com/matrixorigin/matrixone/pkg/common/moerr"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper"
+	"github.com/matrixorigin/matrixone/pkg/util/toml"
 )
 
 const (
-	defaultDataDir        = "mo-logservice-data"
+	defaultDataDir        = "mo-data/logservice"
 	defaultServiceAddress = "0.0.0.0:32000"
 	defaultRaftAddress    = "0.0.0.0:32001"
 	defaultGossipAddress  = "0.0.0.0:32002"
 )
 
 var (
-	ErrInvalidConfig = moerr.NewError(moerr.BAD_CONFIGURATION, "invalid log service configuration")
+	ErrInvalidConfig = moerr.NewError(moerr.BAD_CONFIGURATION, "invalid log configuration")
 )
 
 const (
@@ -49,22 +51,46 @@ type HAKeeperClientConfig struct {
 
 // Config defines the Configurations supported by the Log Service.
 type Config struct {
-	FS                   vfs.FS
-	DeploymentID         uint64   `toml:"deployment-id"`
-	UUID                 string   `toml:"uuid"`
-	RTTMillisecond       uint64   `toml:"rttmillisecond"`
-	DataDir              string   `toml:"data-dir"`
-	ServiceAddress       string   `toml:"service-address"`
-	ServiceListenAddress string   `toml:"serfice-listen-address"`
-	RaftAddress          string   `toml:"raft-address"`
-	RaftListenAddress    string   `toml:"raft-listen-address"`
-	GossipAddress        string   `toml:"gossip-address"`
-	GossipListenAddress  string   `toml:"gossip-listen-address"`
-	GossipSeedAddresses  []string `toml:"uuid"`
-
-	HeartbeatInterval     time.Duration `toml:"heartbeat-interval"`
-	HAKeeperTickInterval  time.Duration `toml:"tick-interval"`
-	HAKeeperCheckInterval time.Duration `toml:"check-interval"`
+	// FS is the underlying virtual FS used by the log service. Leave it as empty
+	// in production.
+	FS vfs.FS
+	// DeploymentID is basically the Cluster ID, nodes with different DeploymentID
+	// will not be able to communicate via raft.
+	DeploymentID uint64 `toml:"deployment-id"`
+	// UUID is the UUID of the log service node. UUID value must be set.
+	UUID string `toml:"uuid"`
+	// RTTMillisecond is the average round trip time between log service nodes in
+	// milliseconds.
+	RTTMillisecond uint64 `toml:"rttmillisecond"`
+	// DataDir is the name of the directory for storing all log service data. It
+	// should a locally mount partition with good write and fsync performance.
+	DataDir string `toml:"data-dir"`
+	// ServiceAddress is log service's service address that can be reached by
+	// other nodes such as DN nodes.
+	ServiceAddress string `toml:"logservice-address"`
+	// ServiceListenAddress is the local listen address of the ServiceAddress.
+	ServiceListenAddress string `toml:"logservice-listen-address"`
+	// RaftAddress is the address that can be reached by other log service nodes
+	// via their raft layer.
+	RaftAddress string `toml:"raft-address"`
+	// RaftListenAddress is the local listen address of the RaftAddress.
+	RaftListenAddress string `toml:"raft-listen-address"`
+	// GossipAddress is the address used for accepting gossip communication.
+	GossipAddress string `toml:"gossip-address"`
+	// GossipListenAddress is the local listen address of the GossipAddress
+	GossipListenAddress string `toml:"gossip-listen-address"`
+	// GossipSeedAddresses is list of semicolon separated seed addresses that
+	// are used for introducing the local node into the gossip network.
+	GossipSeedAddresses string `toml:"gossip-seed-addresses"`
+	// HeartbeatInterval is the interval of how often log service node should be
+	// sending heartbeat message to the HAKeeper.
+	HeartbeatInterval toml.Duration `toml:"logservice-heartbeat-interval"`
+	// HAKeeperTickInterval is the interval of how often log service node should
+	// tick the HAKeeper.
+	HAKeeperTickInterval toml.Duration `toml:"hakeeper-tick-interval"`
+	// HAKeeperCheckInterval is the internval of how often HAKeeper should run
+	// cluster health checks.
+	HAKeeperCheckInterval toml.Duration `toml:"hakeeper-check-interval"`
 
 	HAKeeperConfig struct {
 		// TickPerSecond indicates how many ticks every second.
@@ -74,18 +100,19 @@ type Config struct {
 		// LogStoreTimeout is the actual time limit between a log store's heartbeat.
 		// If HAKeeper does not receive two heartbeat within LogStoreTimeout,
 		// it regards the log store as down.
-		LogStoreTimeout time.Duration `toml:"log-store-timeout"`
+		LogStoreTimeout toml.Duration `toml:"log-store-timeout"`
 		// DnStoreTimeout is the actual time limit between a dn store's heartbeat.
 		// If HAKeeper does not receive two heartbeat within DnStoreTimeout,
 		// it regards the dn store as down.
-		DnStoreTimeout time.Duration `toml:"dn-store-timeout"`
+		DnStoreTimeout toml.Duration `toml:"dn-store-timeout"`
 	}
 
 	HAKeeperClientConfig struct {
 		// DiscoveryAddress is the Log Service discovery address provided by k8s.
 		DiscoveryAddress string `toml:"hakeeper-discovery-address"`
-		// ServiceAddresses is a list of well known Log Services' service addresses.
-		ServiceAddresses []string `toml:"hakeeper-service-addresses"`
+		// ServiceAddresses is a list of semicolon separated well known Log Services'
+		// service addresses.
+		ServiceAddresses string `toml:"hakeeper-service-addresses"`
 	}
 
 	// DisableWorkers disables the HAKeeper ticker and HAKeeper client in tests.
@@ -93,25 +120,36 @@ type Config struct {
 	DisableWorkers bool
 }
 
+// GetGossipSeedAddresses returns a slice of gossip seed addresses split from
+// the GossipSeedAddresses field.
+func (c *Config) GetGossipSeedAddresses() []string {
+	return splitAddresses(c.GossipSeedAddresses)
+}
+
+func (c *Config) GetHAKeeperServiceAddresses() []string {
+	return splitAddresses(c.HAKeeperClientConfig.ServiceAddresses)
+}
+
 func (c *Config) GetHAKeeperConfig() hakeeper.Config {
 	return hakeeper.Config{
 		TickPerSecond:   c.HAKeeperConfig.TickPerSecond,
-		LogStoreTimeout: c.HAKeeperConfig.LogStoreTimeout,
-		DnStoreTimeout:  c.HAKeeperConfig.DnStoreTimeout,
+		LogStoreTimeout: c.HAKeeperConfig.LogStoreTimeout.Duration,
+		DnStoreTimeout:  c.HAKeeperConfig.DnStoreTimeout.Duration,
 	}
 }
 
 func (c *Config) GetHAKeeperClientConfig() HAKeeperClientConfig {
-	addrs := make([]string, 0)
-	addrs = append(addrs, c.HAKeeperClientConfig.ServiceAddresses...)
 	return HAKeeperClientConfig{
 		DiscoveryAddress: c.HAKeeperClientConfig.DiscoveryAddress,
-		ServiceAddresses: addrs,
+		ServiceAddresses: c.GetHAKeeperServiceAddresses(),
 	}
 }
 
 // Validate validates the configuration.
 func (c *Config) Validate() error {
+	if len(c.UUID) == 0 {
+		return errors.Wrapf(ErrInvalidConfig, "UUID not set")
+	}
 	if c.DeploymentID == 0 {
 		return errors.Wrapf(ErrInvalidConfig, "DeploymentID not set")
 	}
@@ -132,24 +170,24 @@ func (c *Config) Validate() error {
 	if c.HAKeeperConfig.TickPerSecond == 0 {
 		return errors.Wrapf(ErrInvalidConfig, "TickPerSecond not set")
 	}
-	if c.HAKeeperConfig.LogStoreTimeout == 0 {
+	if c.HAKeeperConfig.LogStoreTimeout.Duration == 0 {
 		return errors.Wrapf(ErrInvalidConfig, "LogStoreTimeout not set")
 	}
-	if c.HAKeeperConfig.DnStoreTimeout == 0 {
+	if c.HAKeeperConfig.DnStoreTimeout.Duration == 0 {
 		return errors.Wrapf(ErrInvalidConfig, "DnStoreTimeout not set")
 	}
 	return nil
 }
 
 func (c *Config) Fill() {
-	if c.HeartbeatInterval == 0 {
-		c.HeartbeatInterval = defaultHeartbeatInterval
+	if c.HeartbeatInterval.Duration == 0 {
+		c.HeartbeatInterval.Duration = defaultHeartbeatInterval
 	}
-	if c.HAKeeperTickInterval == 0 {
-		c.HAKeeperTickInterval = hakeeper.TickDuration
+	if c.HAKeeperTickInterval.Duration == 0 {
+		c.HAKeeperTickInterval.Duration = hakeeper.TickDuration
 	}
-	if c.HAKeeperCheckInterval == 0 {
-		c.HAKeeperCheckInterval = hakeeper.CheckDuration
+	if c.HAKeeperCheckInterval.Duration == 0 {
+		c.HAKeeperCheckInterval.Duration = hakeeper.CheckDuration
 	}
 	if c.RTTMillisecond == 0 {
 		c.RTTMillisecond = 200
@@ -178,10 +216,19 @@ func (c *Config) Fill() {
 	if c.HAKeeperConfig.TickPerSecond == 0 {
 		c.HAKeeperConfig.TickPerSecond = hakeeper.DefaultTickPerSecond
 	}
-	if c.HAKeeperConfig.LogStoreTimeout == 0 {
-		c.HAKeeperConfig.LogStoreTimeout = hakeeper.DefaultLogStoreTimeout
+	if c.HAKeeperConfig.LogStoreTimeout.Duration == 0 {
+		c.HAKeeperConfig.LogStoreTimeout.Duration = hakeeper.DefaultLogStoreTimeout
+	}
+	if c.HAKeeperConfig.DnStoreTimeout.Duration == 0 {
+		c.HAKeeperConfig.DnStoreTimeout.Duration = hakeeper.DefaultDnStoreTimeout
 	}
-	if c.HAKeeperConfig.DnStoreTimeout == 0 {
-		c.HAKeeperConfig.DnStoreTimeout = hakeeper.DefaultDnStoreTimeout
+}
+
+func splitAddresses(v string) []string {
+	results := make([]string, 0)
+	parts := strings.Split(v, ";")
+	for _, v := range parts {
+		results = append(results, strings.TrimSpace(v))
 	}
+	return results
 }
diff --git a/pkg/logservice/store.go b/pkg/logservice/store.go
index 3e18a9fa3..fc1838e58 100644
--- a/pkg/logservice/store.go
+++ b/pkg/logservice/store.go
@@ -85,7 +85,7 @@ func getNodeHostConfig(cfg Config) config.NodeHostConfig {
 		Gossip: config.GossipConfig{
 			BindAddress:      cfg.GossipListenAddress,
 			AdvertiseAddress: cfg.GossipAddress,
-			Seed:             cfg.GossipSeedAddresses,
+			Seed:             cfg.GetGossipSeedAddresses(),
 			Meta:             meta.marshal(),
 		},
 	}
@@ -588,15 +588,15 @@ func (l *store) queryLog(ctx context.Context, shardID uint64,
 }
 
 func (l *store) ticker(ctx context.Context) {
-	if l.cfg.HAKeeperTickInterval == 0 {
+	if l.cfg.HAKeeperTickInterval.Duration == 0 {
 		panic("invalid HAKeeperTickInterval")
 	}
-	ticker := time.NewTicker(l.cfg.HAKeeperTickInterval)
+	ticker := time.NewTicker(l.cfg.HAKeeperTickInterval.Duration)
 	defer ticker.Stop()
-	if l.cfg.HAKeeperCheckInterval == 0 {
+	if l.cfg.HAKeeperCheckInterval.Duration == 0 {
 		panic("invalid HAKeeperCheckInterval")
 	}
-	haTicker := time.NewTicker(l.cfg.HAKeeperCheckInterval)
+	haTicker := time.NewTicker(l.cfg.HAKeeperCheckInterval.Duration)
 	defer haTicker.Stop()
 
 	for {


Test patch:
diff --git a/pkg/logservice/commands_test.go b/pkg/logservice/commands_test.go
index cc94000e4..a5cfd6ff4 100644
--- a/pkg/logservice/commands_test.go
+++ b/pkg/logservice/commands_test.go
@@ -31,18 +31,19 @@ import (
 func TestBackgroundTickAndHeartbeat(t *testing.T) {
 	defer leaktest.AfterTest(t)()
 	cfg := Config{
-		FS:                   vfs.NewStrictMem(),
-		DeploymentID:         1,
-		RTTMillisecond:       5,
-		DataDir:              "data-1",
-		ServiceAddress:       "127.0.0.1:9002",
-		RaftAddress:          "127.0.0.1:9000",
-		GossipAddress:        "127.0.0.1:9001",
-		GossipSeedAddresses:  []string{"127.0.0.1:9010"},
-		HeartbeatInterval:    5 * time.Millisecond,
-		HAKeeperTickInterval: 5 * time.Millisecond,
+		UUID:                uuid.New().String(),
+		FS:                  vfs.NewStrictMem(),
+		DeploymentID:        1,
+		RTTMillisecond:      5,
+		DataDir:             "data-1",
+		ServiceAddress:      "127.0.0.1:9002",
+		RaftAddress:         "127.0.0.1:9000",
+		GossipAddress:       "127.0.0.1:9001",
+		GossipSeedAddresses: "127.0.0.1:9010",
 	}
-	cfg.HAKeeperClientConfig.ServiceAddresses = []string{"127.0.0.1:9002"}
+	cfg.HeartbeatInterval.Duration = 5 * time.Millisecond
+	cfg.HAKeeperTickInterval.Duration = 5 * time.Millisecond
+	cfg.HAKeeperClientConfig.ServiceAddresses = "127.0.0.1:9002"
 	cfg.Fill()
 	service, err := NewService(cfg)
 	require.NoError(t, err)
diff --git a/pkg/logservice/config_test.go b/pkg/logservice/config_test.go
index 256c70790..e6fe43301 100644
--- a/pkg/logservice/config_test.go
+++ b/pkg/logservice/config_test.go
@@ -25,6 +25,7 @@ import (
 
 func getTestConfig() Config {
 	c := Config{
+		UUID:                 "uuid",
 		DeploymentID:         100,
 		DataDir:              "/mydata/dir",
 		ServiceAddress:       "localhost:9000",
@@ -33,12 +34,20 @@ func getTestConfig() Config {
 		RaftListenAddress:    "localhost:9001",
 		GossipAddress:        "localhost:9002",
 		GossipListenAddress:  "localhost:9002",
-		GossipSeedAddresses:  []string{"localhost:9002"},
+		GossipSeedAddresses:  "localhost:9002",
 	}
 	c.Fill()
 	return c
 }
 
+func TestGetGossipSeedAddresses(t *testing.T) {
+	cfg := Config{
+		GossipSeedAddresses: "localhost:9000;localhost:9001 ; localhost:9002 ",
+	}
+	values := cfg.GetGossipSeedAddresses()
+	assert.Equal(t, []string{"localhost:9000", "localhost:9001", "localhost:9002"}, values)
+}
+
 func TestConfigCanBeValidated(t *testing.T) {
 	c := getTestConfig()
 	assert.Nil(t, c.Validate())
@@ -60,7 +69,7 @@ func TestConfigCanBeValidated(t *testing.T) {
 	assert.True(t, errors.Is(c4.Validate(), ErrInvalidConfig))
 
 	c5 := c
-	c5.GossipSeedAddresses = []string{}
+	c5.GossipSeedAddresses = ""
 	assert.True(t, errors.Is(c5.Validate(), ErrInvalidConfig))
 }
 
@@ -78,8 +87,8 @@ func TestFillConfig(t *testing.T) {
 	assert.Equal(t, defaultGossipAddress, c.GossipListenAddress)
 	assert.Equal(t, 0, len(c.GossipSeedAddresses))
 	assert.Equal(t, hakeeper.DefaultTickPerSecond, c.HAKeeperConfig.TickPerSecond)
-	assert.Equal(t, hakeeper.DefaultLogStoreTimeout, c.HAKeeperConfig.LogStoreTimeout)
-	assert.Equal(t, hakeeper.DefaultDnStoreTimeout, c.HAKeeperConfig.DnStoreTimeout)
+	assert.Equal(t, hakeeper.DefaultLogStoreTimeout, c.HAKeeperConfig.LogStoreTimeout.Duration)
+	assert.Equal(t, hakeeper.DefaultDnStoreTimeout, c.HAKeeperConfig.DnStoreTimeout.Duration)
 }
 
 func TestListenAddressCanBeFilled(t *testing.T) {
@@ -90,7 +99,7 @@ func TestListenAddressCanBeFilled(t *testing.T) {
 		ServiceAddress:      "127.0.0.1:9002",
 		RaftAddress:         "127.0.0.1:9000",
 		GossipAddress:       "127.0.0.1:9001",
-		GossipSeedAddresses: []string{"127.0.0.1:9011"},
+		GossipSeedAddresses: "127.0.0.1:9011",
 	}
 	cfg.Fill()
 	assert.Equal(t, cfg.ServiceAddress, cfg.ServiceListenAddress)
diff --git a/pkg/logservice/hakeeper_checker_test.go b/pkg/logservice/hakeeper_checker_test.go
index 39913d459..b46c11a41 100644
--- a/pkg/logservice/hakeeper_checker_test.go
+++ b/pkg/logservice/hakeeper_checker_test.go
@@ -134,6 +134,7 @@ func runHAKeeperStoreTest(t *testing.T, startLogReplica bool, fn func(*testing.T
 func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 	defer leaktest.AfterTest(t)()
 	cfg1 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -141,13 +142,14 @@ func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 		ServiceAddress:      "127.0.0.1:9002",
 		RaftAddress:         "127.0.0.1:9000",
 		GossipAddress:       "127.0.0.1:9001",
-		GossipSeedAddresses: []string{"127.0.0.1:9011", "127.0.0.1:9021", "127.0.0.1:9031"},
+		GossipSeedAddresses: "127.0.0.1:9011;127.0.0.1:9021;127.0.0.1:9031",
 		DisableWorkers:      true,
 	}
 	cfg1.HAKeeperConfig.TickPerSecond = 10
-	cfg1.HAKeeperConfig.LogStoreTimeout = 5 * time.Second
-	cfg1.HAKeeperConfig.DnStoreTimeout = 10 * time.Second
+	cfg1.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
+	cfg1.HAKeeperConfig.DnStoreTimeout.Duration = 10 * time.Second
 	cfg2 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -155,13 +157,14 @@ func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 		ServiceAddress:      "127.0.0.1:9012",
 		RaftAddress:         "127.0.0.1:9010",
 		GossipAddress:       "127.0.0.1:9011",
-		GossipSeedAddresses: []string{"127.0.0.1:9001", "127.0.0.1:9021", "127.0.0.1:9031"},
+		GossipSeedAddresses: "127.0.0.1:9001;127.0.0.1:9021;127.0.0.1:9031",
 		DisableWorkers:      true,
 	}
 	cfg2.HAKeeperConfig.TickPerSecond = 10
-	cfg2.HAKeeperConfig.LogStoreTimeout = 5 * time.Second
-	cfg2.HAKeeperConfig.DnStoreTimeout = 10 * time.Second
+	cfg2.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
+	cfg2.HAKeeperConfig.DnStoreTimeout.Duration = 10 * time.Second
 	cfg3 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -169,13 +172,14 @@ func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 		ServiceAddress:      "127.0.0.1:9022",
 		RaftAddress:         "127.0.0.1:9020",
 		GossipAddress:       "127.0.0.1:9021",
-		GossipSeedAddresses: []string{"127.0.0.1:9001", "127.0.0.1:9011", "127.0.0.1:9031"},
+		GossipSeedAddresses: "127.0.0.1:9001;127.0.0.1:9011;127.0.0.1:9031",
 		DisableWorkers:      true,
 	}
 	cfg3.HAKeeperConfig.TickPerSecond = 10
-	cfg3.HAKeeperConfig.LogStoreTimeout = 5 * time.Second
-	cfg3.HAKeeperConfig.DnStoreTimeout = 10 * time.Second
+	cfg3.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
+	cfg3.HAKeeperConfig.DnStoreTimeout.Duration = 10 * time.Second
 	cfg4 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -183,12 +187,12 @@ func runHAKeeperClusterTest(t *testing.T, fn func(*testing.T, []*Service)) {
 		ServiceAddress:      "127.0.0.1:9032",
 		RaftAddress:         "127.0.0.1:9030",
 		GossipAddress:       "127.0.0.1:9031",
-		GossipSeedAddresses: []string{"127.0.0.1:9001", "127.0.0.1:9011", "127.0.0.1:9021"},
+		GossipSeedAddresses: "127.0.0.1:9001;127.0.0.1:9011;127.0.0.1:9021",
 		DisableWorkers:      true,
 	}
 	cfg4.HAKeeperConfig.TickPerSecond = 10
-	cfg4.HAKeeperConfig.LogStoreTimeout = 5 * time.Second
-	cfg4.HAKeeperConfig.DnStoreTimeout = 10 * time.Second
+	cfg4.HAKeeperConfig.LogStoreTimeout.Duration = 5 * time.Second
+	cfg4.HAKeeperConfig.DnStoreTimeout.Duration = 10 * time.Second
 	cfg1.Fill()
 	service1, err := NewService(cfg1)
 	require.NoError(t, err)
diff --git a/pkg/logservice/hakeeper_client_test.go b/pkg/logservice/hakeeper_client_test.go
index 2a8b29a8e..7ae22cdfb 100644
--- a/pkg/logservice/hakeeper_client_test.go
+++ b/pkg/logservice/hakeeper_client_test.go
@@ -20,6 +20,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/lni/dragonboat/v4"
 	"github.com/lni/goutils/leaktest"
 	"github.com/lni/vfs"
@@ -207,6 +208,7 @@ func TestHAKeeperClientSendLogHeartbeat(t *testing.T) {
 func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAKeeperClient)) {
 	defer leaktest.AfterTest(t)()
 	cfg1 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -214,10 +216,11 @@ func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAK
 		ServiceAddress:      "127.0.0.1:9002",
 		RaftAddress:         "127.0.0.1:9000",
 		GossipAddress:       "127.0.0.1:9001",
-		GossipSeedAddresses: []string{"127.0.0.1:9011"},
+		GossipSeedAddresses: "127.0.0.1:9011",
 		DisableWorkers:      true,
 	}
 	cfg2 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -225,7 +228,7 @@ func testNotHAKeeperErrorIsHandled(t *testing.T, fn func(*testing.T, *managedHAK
 		ServiceAddress:      "127.0.0.1:9012",
 		RaftAddress:         "127.0.0.1:9010",
 		GossipAddress:       "127.0.0.1:9011",
-		GossipSeedAddresses: []string{"127.0.0.1:9001"},
+		GossipSeedAddresses: "127.0.0.1:9001",
 		DisableWorkers:      true,
 	}
 	cfg1.Fill()
diff --git a/pkg/logservice/service_test.go b/pkg/logservice/service_test.go
index 0b7f26962..3b5b5cce7 100644
--- a/pkg/logservice/service_test.go
+++ b/pkg/logservice/service_test.go
@@ -22,6 +22,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/lni/dragonboat/v4"
 	"github.com/lni/goutils/leaktest"
 	"github.com/lni/vfs"
@@ -37,8 +38,9 @@ const (
 
 func getServiceTestConfig() Config {
 	c := Config{
+		UUID:                 uuid.New().String(),
 		RTTMillisecond:       10,
-		GossipSeedAddresses:  []string{"127.0.0.1:9000"},
+		GossipSeedAddresses:  "127.0.0.1:9000",
 		DeploymentID:         1,
 		FS:                   vfs.NewStrictMem(),
 		ServiceListenAddress: testServiceAddress,
@@ -482,6 +484,7 @@ func TestServiceCheckHAKeeper(t *testing.T) {
 func TestShardInfoCanBeQueried(t *testing.T) {
 	defer leaktest.AfterTest(t)()
 	cfg1 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -489,10 +492,11 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ServiceAddress:      "127.0.0.1:9002",
 		RaftAddress:         "127.0.0.1:9000",
 		GossipAddress:       "127.0.0.1:9001",
-		GossipSeedAddresses: []string{"127.0.0.1:9011"},
+		GossipSeedAddresses: "127.0.0.1:9011",
 		DisableWorkers:      true,
 	}
 	cfg2 := Config{
+		UUID:                uuid.New().String(),
 		FS:                  vfs.NewStrictMem(),
 		DeploymentID:        1,
 		RTTMillisecond:      5,
@@ -500,7 +504,7 @@ func TestShardInfoCanBeQueried(t *testing.T) {
 		ServiceAddress:      "127.0.0.1:9012",
 		RaftAddress:         "127.0.0.1:9010",
 		GossipAddress:       "127.0.0.1:9011",
-		GossipSeedAddresses: []string{"127.0.0.1:9001"},
+		GossipSeedAddresses: "127.0.0.1:9001",
 		DisableWorkers:      true,
 	}
 	cfg1.Fill()
@@ -609,7 +613,7 @@ func TestGossipConvergeDelay(t *testing.T) {
 			ServiceAddress:      fmt.Sprintf("127.0.0.1:%d", 6000+10*i),
 			RaftAddress:         fmt.Sprintf("127.0.0.1:%d", 6000+10*i+1),
 			GossipAddress:       fmt.Sprintf("127.0.0.1:%d", 6000+10*i+2),
-			GossipSeedAddresses: []string{"127.0.0.1:6002", "127.0.0.1:6012"},
+			GossipSeedAddresses: "127.0.0.1:6002;127.0.0.1:6012",
 			DisableWorkers:      true,
 		}
 		configs = append(configs, cfg)
diff --git a/pkg/logservice/store_test.go b/pkg/logservice/store_test.go
index 07f125c89..b4384c3e8 100644
--- a/pkg/logservice/store_test.go
+++ b/pkg/logservice/store_test.go
@@ -55,8 +55,9 @@ func TestRaftConfig(t *testing.T) {
 
 func getStoreTestConfig() Config {
 	cfg := Config{
+		UUID:                uuid.New().String(),
 		RTTMillisecond:      10,
-		GossipSeedAddresses: []string{"127.0.0.1:9000"},
+		GossipSeedAddresses: "127.0.0.1:9000",
 		DeploymentID:        1,
 		FS:                  vfs.NewStrictMem(),
 	}
@@ -417,7 +418,7 @@ func getTestStores() (*store, *store, error) {
 		ServiceAddress:      "127.0.0.1:9001",
 		RaftAddress:         "127.0.0.1:9002",
 		GossipAddress:       "127.0.0.1:9011",
-		GossipSeedAddresses: []string{"127.0.0.1:9011", "127.0.0.1:9012"},
+		GossipSeedAddresses: "127.0.0.1:9011;127.0.0.1:9012",
 	}
 	cfg1.Fill()
 	store1, err := newLogStore(cfg1)
@@ -432,7 +433,7 @@ func getTestStores() (*store, *store, error) {
 		ServiceAddress:      "127.0.0.1:9006",
 		RaftAddress:         "127.0.0.1:9007",
 		GossipAddress:       "127.0.0.1:9012",
-		GossipSeedAddresses: []string{"127.0.0.1:9011", "127.0.0.1:9012"},
+		GossipSeedAddresses: "127.0.0.1:9011;127.0.0.1:9012",
 	}
 	cfg2.Fill()
 	store2, err := newLogStore(cfg2)
diff --git a/pkg/logservice/testclient.go b/pkg/logservice/testclient.go
index 79f413f45..472cb6c78 100644
--- a/pkg/logservice/testclient.go
+++ b/pkg/logservice/testclient.go
@@ -17,14 +17,17 @@ package logservice
 import (
 	"github.com/lni/vfs"
 
+	"github.com/google/uuid"
+
 	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
 )
 
 func NewTestService(fs vfs.FS) (*Service, ClientConfig, error) {
 	addr := "localhost:9000"
 	cfg := Config{
+		UUID:                 uuid.New().String(),
 		RTTMillisecond:       10,
-		GossipSeedAddresses:  []string{addr},
+		GossipSeedAddresses:  addr,
 		DeploymentID:         1,
 		FS:                   fs,
 		ServiceListenAddress: addr,