Repository: matrixorigin/matrixone
Pull request #3054 (2022-06-16 08:10:28)

Move operator out of rsm
**What type of PR is this?**

- [ ] API-change
- [ ] BUG
- [x] Improvement
- [ ] Documentation
- [ ] Feature
- [ ] Test and CI
- [ ] Code Refactoring

**Which issue(s) this PR fixes:**

issue #

**What this PR does / why we need it:**

Moved checker out of the HAKeeper RSM

**Special notes for your reviewer:**

Not Available

**Additional documentation (e.g. design docs, usage docs, etc.):**

Not Available



Patch:
diff --git a/go.mod b/go.mod
index f41b34152..61440682f 100644
--- a/go.mod
+++ b/go.mod
@@ -21,8 +21,8 @@ require (
 	github.com/golang/mock v1.6.0
 	github.com/google/btree v1.0.1
 	github.com/google/gofuzz v1.2.0
+	github.com/lni/dragonboat/v4 v4.0.0-20220615141520-7b2d6c11e2b6
 	github.com/google/uuid v1.3.0
-	github.com/lni/dragonboat/v4 v4.0.0-20220612172217-6ae1d0b2e535
 	github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4
 	github.com/matrixorigin/matrixcube v0.3.1-0.20220606032431-c944d801f1e5
 	github.com/matrixorigin/simdcsv v0.0.0-20210926114300-591bf748a770
diff --git a/go.sum b/go.sum
index b3f27f718..dc3127f62 100644
--- a/go.sum
+++ b/go.sum
@@ -472,8 +472,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
 github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/lni/dragonboat/v4 v4.0.0-20220612172217-6ae1d0b2e535 h1:tjaMFsEucNN3tOkzT6FHxEWJQZW0skBmK7CXR25bVHo=
-github.com/lni/dragonboat/v4 v4.0.0-20220612172217-6ae1d0b2e535/go.mod h1:5dd7hX8qUJ+72UVKdZZIBw5UbhZTaGY5PIt/TTdIeSY=
+github.com/lni/dragonboat/v4 v4.0.0-20220615141520-7b2d6c11e2b6 h1:999pFKGEp7qc/1wI13FtvAgGhmWENuscl1fqrfjNMes=
+github.com/lni/dragonboat/v4 v4.0.0-20220615141520-7b2d6c11e2b6/go.mod h1:5dd7hX8qUJ+72UVKdZZIBw5UbhZTaGY5PIt/TTdIeSY=
 github.com/lni/goutils v1.3.0/go.mod h1:PUPtBAnZlRPUKWUXCsYkIRWubJbtNHpTAee0sczhlf4=
 github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4 h1:6gzI38ZJmbzZ7oZebXz6jII0uVK+MZ3+ds+7mIt1ioI=
 github.com/lni/goutils v1.3.1-0.20220604063047-388d67b4dbc4/go.mod h1:LIHvF0fflR+zyXUQFQOiHPpKANf3UIr7DFIv5CBPOoU=
diff --git a/pkg/container/ring/anyvalue/anyvalue.go b/pkg/container/ring/anyvalue/anyvalue.go
index e3d995c86..97398fc99 100644
--- a/pkg/container/ring/anyvalue/anyvalue.go
+++ b/pkg/container/ring/anyvalue/anyvalue.go
@@ -17,6 +17,7 @@ package anyvalue
 import (
 	"bytes"
 	"fmt"
+
 	"github.com/matrixorigin/matrixone/pkg/container/nulls"
 	"github.com/matrixorigin/matrixone/pkg/container/ring"
 	"github.com/matrixorigin/matrixone/pkg/container/types"
@@ -459,7 +460,7 @@ func (r *AnyVRing2) BatchFill(start int64, os []uint8, vps []uint64, zs []int64,
 				r.Ns[vps[i]-1] += zs[int64(i)+start]
 			} else {
 				j := vps[i] - 1
-				if v := vs.Get(int64(i) + start); shouldSet(r.Set[i]) {
+				if v := vs.Get(int64(i) + start); shouldSet(r.Set[j]) {
 					r.Vs[j] = append(r.Vs[j][:0], v...)
 					r.Set[j] = true
 				}
@@ -468,7 +469,7 @@ func (r *AnyVRing2) BatchFill(start int64, os []uint8, vps []uint64, zs []int64,
 	} else {
 		for i := range os {
 			j := vps[i] - 1
-			if v := vs.Get(int64(i) + start); shouldSet(r.Set[i]) {
+			if v := vs.Get(int64(i) + start); shouldSet(r.Set[j]) {
 				r.Vs[j] = append(r.Vs[j][:0], v...)
 				r.Set[j] = true
 			}
diff --git a/pkg/frontend/mysql_protocol.go b/pkg/frontend/mysql_protocol.go
index b2357e750..20516528a 100644
--- a/pkg/frontend/mysql_protocol.go
+++ b/pkg/frontend/mysql_protocol.go
@@ -540,7 +540,7 @@ func (mp *MysqlProtocolImpl) appendStringLenEncOfUint64(data []byte, value uint6
 //return the buffer
 func (mp *MysqlProtocolImpl) appendStringLenEncOfFloat64(data []byte, value float64, bitSize int) []byte {
 	mp.strconvBuffer = mp.strconvBuffer[:0]
-	mp.strconvBuffer = strconv.AppendFloat(mp.strconvBuffer, value, 'f', 4, bitSize)
+	mp.strconvBuffer = strconv.AppendFloat(mp.strconvBuffer, value, 'f', -1, bitSize)
 	return mp.appendCountOfBytesLenEnc(data, mp.strconvBuffer)
 }
 
diff --git a/pkg/hakeeper/checker.go b/pkg/hakeeper/checker.go
index 95218afd0..9f0e53611 100644
--- a/pkg/hakeeper/checker.go
+++ b/pkg/hakeeper/checker.go
@@ -22,6 +22,15 @@ type Operator struct {
 	// feel free to add whatever you need
 }
 
+type IDAllocator interface {
+	// Next returns a new ID that can be used as the replica ID of a DN shard or
+	// Log shard. When the return boolean value is false, it means no more ID
+	// can be allocated at this time.
+	Next() (uint64, bool)
+	Set(first uint64, last uint64)
+	Capacity() uint64
+}
+
 // Checker is the interface suppose to be implemented by HAKeeper's
 // coordinator. Checker is suppose to be stateless - Checker is free to
 // maintain whatever internal states, but those states should never be
@@ -30,6 +39,6 @@ type Checker interface {
 	// Check is periodically called by the HAKeeper for checking the cluster
 	// health status, a list of Operator instances will be returned describing
 	// actions required to ensure the high availability of the cluster.
-	Check(cluster ClusterInfo,
+	Check(alloc IDAllocator, cluster ClusterInfo,
 		dn DNState, log LogState, currentTick uint64) []Operator
 }
diff --git a/pkg/hakeeper/rsm.go b/pkg/hakeeper/rsm.go
index 0efffdd16..f79f808f4 100644
--- a/pkg/hakeeper/rsm.go
+++ b/pkg/hakeeper/rsm.go
@@ -38,6 +38,8 @@ var (
 const (
 	// TickDuration defines the frequency of ticks.
 	TickDuration = time.Second
+	// CheckDuration defines how often HAKeeper checks the health state of the cluster
+	CheckDuration = 2 * time.Second
 	// DefaultHAKeeperShardID is the shard ID assigned to the special HAKeeper
 	// shard.
 	DefaultHAKeeperShardID uint64 = 0
@@ -50,9 +52,11 @@ const (
 	tickTag
 	dnHeartbeatTag
 	logHeartbeatTag
-	checkTag
+	getIDTag
 )
 
+type StateQuery struct{}
+
 type logShardIDQuery struct {
 	name string
 }
@@ -63,25 +67,27 @@ type logShardIDQueryResult struct {
 }
 
 type stateMachine struct {
-	replicaID uint64
-
-	// TODO: add tests to ensure these are not included in snapshots
-	checker Checker
-	// keyed by UUID
-	operators map[string][]Operator
-
-	Tick   uint64
-	NextID uint64
-
-	LogShards map[string]uint64
-	DNState   DNState
-	LogState  LogState
+	replicaID   uint64
+	operators   map[string][]Operator // keyed by UUID
+	Tick        uint64
+	NextID      uint64
+	LogShards   map[string]uint64 // keyed by Log Shard name
+	DNState     DNState
+	LogState    LogState
+	ClusterInfo ClusterInfo
 }
 
 func parseCmdTag(cmd []byte) uint16 {
 	return binaryEnc.Uint16(cmd)
 }
 
+func GetGetIDCmd(count uint64) []byte {
+	cmd := make([]byte, headerSize+8)
+	binaryEnc.PutUint16(cmd, getIDTag)
+	binaryEnc.PutUint64(cmd[headerSize:], count)
+	return cmd
+}
+
 func getCreateLogShardCmd(name string) []byte {
 	return getLogShardCmd(name, createLogShardTag)
 }
@@ -130,8 +136,12 @@ func isTickCmd(cmd []byte) bool {
 	return len(cmd) == headerSize && binaryEnc.Uint16(cmd) == tickTag
 }
 
-func isCheckCmd(cmd []byte) bool {
-	return len(cmd) == headerSize && binaryEnc.Uint16(cmd) == checkTag
+func isGetIDCmd(cmd []byte) bool {
+	return len(cmd) == headerSize+8 && binaryEnc.Uint16(cmd) == getIDTag
+}
+
+func parseGetIDCmd(cmd []byte) uint64 {
+	return binaryEnc.Uint64(cmd[headerSize:])
 }
 
 func GetTickCmd() []byte {
@@ -215,21 +225,13 @@ func (s *stateMachine) handleTick(cmd []byte) (sm.Result, error) {
 	return sm.Result{}, nil
 }
 
-func (s *stateMachine) handleCheck(cmd []byte) (sm.Result, error) {
-	// TODO: remove this != nil check
-	if s.checker != nil {
-		ops := s.checker.Check(ClusterInfo{}, s.DNState, s.LogState, s.Tick)
-		// add ops to the s.Operators map, keyed by the UUID of the target
-		for _, op := range ops {
-			l, ok := s.operators[op.UUID]
-			if !ok {
-				l = make([]Operator, 0)
-			}
-			l = append(l, op)
-			s.operators[op.UUID] = l
-		}
-	}
-	return sm.Result{}, nil
+func (s *stateMachine) handleGetIDCmd(cmd []byte) (sm.Result, error) {
+	count := parseGetIDCmd(cmd)
+	s.NextID++
+	v := s.NextID
+	s.NextID += (count - 1)
+	plog.Infof("get id returned [%d, %d)", v, v+count)
+	return sm.Result{Value: v}, nil
 }
 
 func (s *stateMachine) Update(e sm.Entry) (sm.Result, error) {
@@ -242,12 +244,22 @@ func (s *stateMachine) Update(e sm.Entry) (sm.Result, error) {
 		return s.handleLogHeartbeat(cmd)
 	} else if isTickCmd(cmd) {
 		return s.handleTick(cmd)
-	} else if isCheckCmd(cmd) {
-		return s.handleCheck(cmd)
+	} else if isGetIDCmd(cmd) {
+		return s.handleGetIDCmd(cmd)
 	}
 	panic(moerr.NewError(moerr.INVALID_INPUT, "unexpected haKeeper cmd"))
 }
 
+func (s *stateMachine) handleStateQuery() (interface{}, error) {
+	// FIXME: pretty sure we need to deepcopy here
+	return &HAKeeperState{
+		Tick:        s.Tick,
+		ClusterInfo: s.ClusterInfo,
+		DNState:     s.DNState,
+		LogState:    s.LogState,
+	}, nil
+}
+
 func (s *stateMachine) Lookup(query interface{}) (interface{}, error) {
 	if q, ok := query.(*logShardIDQuery); ok {
 		id, ok := s.LogShards[q.name]
@@ -256,6 +268,9 @@ func (s *stateMachine) Lookup(query interface{}) (interface{}, error) {
 		}
 		return &logShardIDQueryResult{found: false}, nil
 	}
+	if _, ok := query.(*StateQuery); ok {
+		return s.handleStateQuery()
+	}
 	panic("unknown query type")
 }
 
diff --git a/pkg/hakeeper/state.go b/pkg/hakeeper/state.go
index c3bf69b54..00a3041a8 100644
--- a/pkg/hakeeper/state.go
+++ b/pkg/hakeeper/state.go
@@ -27,6 +27,13 @@ const (
 	NoLeader uint64 = 0
 )
 
+type HAKeeperState struct {
+	Tick        uint64
+	ClusterInfo ClusterInfo
+	DNState     DNState
+	LogState    LogState
+}
+
 // ClusterInfo provides a global view of all shards in the cluster. It
 // describes the logical sharding of the system, rather than physical
 // distribution of all replicas that belong to those shards.
diff --git a/pkg/logservice/hachecker.go b/pkg/logservice/hachecker.go
new file mode 100644
index 000000000..74f31788c
--- /dev/null
+++ b/pkg/logservice/hachecker.go
@@ -0,0 +1,105 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logservice
+
+import (
+	"context"
+	"time"
+
+	"github.com/matrixorigin/matrixone/pkg/hakeeper"
+)
+
+const (
+	minIDAllocCapacity uint64 = 1024
+	defaultIDBatchSize uint64 = 1024 * 10
+
+	hakeeperDefaultTimeout = time.Second
+)
+
+type idAllocator struct {
+	// [nextID, lastID] is the range of IDs that can be assigned.
+	// the next ID to be assigned is nextID
+	nextID uint64
+	lastID uint64
+}
+
+var _ hakeeper.IDAllocator = (*idAllocator)(nil)
+
+func newIDAllocator() hakeeper.IDAllocator {
+	return &idAllocator{nextID: 1, lastID: 0}
+}
+
+func (a *idAllocator) Next() (uint64, bool) {
+	if a.nextID <= a.lastID {
+		v := a.nextID
+		a.nextID++
+		return v, true
+	}
+	return 0, false
+}
+
+func (a *idAllocator) Set(next uint64, last uint64) {
+	a.nextID = next
+	a.lastID = last
+}
+
+func (a *idAllocator) Capacity() uint64 {
+	if a.nextID <= a.lastID {
+		return (a.lastID - a.nextID) + 1
+	}
+	return 0
+}
+
+func (l *logStore) updateIDAlloc(count uint64) error {
+	cmd := hakeeper.GetGetIDCmd(count)
+	ctx, cancel := context.WithTimeout(context.Background(), hakeeperDefaultTimeout)
+	defer cancel()
+	session := l.nh.GetNoOPSession(hakeeper.DefaultHAKeeperShardID)
+	result, err := l.propose(ctx, session, cmd)
+	if err != nil {
+		plog.Errorf("propose get id failed, %v", err)
+		return err
+	}
+	// TODO: add a test for this
+	l.alloc.Set(result.Value, result.Value+count-1)
+	return nil
+}
+
+func (l *logStore) healthCheck() {
+	leaderID, _, ok, err := l.nh.GetLeaderID(hakeeper.DefaultHAKeeperShardID)
+	if err != nil {
+		plog.Errorf("failed to get HAKeeper Leader ID, %v", err)
+		return
+	}
+	if ok && leaderID == l.haKeeperReplicaID {
+		if l.alloc.Capacity() < minIDAllocCapacity {
+			if err := l.updateIDAlloc(defaultIDBatchSize); err != nil {
+				// TODO: check whether this is temp error
+				plog.Errorf("failed to update ID alloc, %v", err)
+				return
+			}
+		}
+		ctx, cancel := context.WithTimeout(context.Background(), hakeeperDefaultTimeout)
+		defer cancel()
+		s, err := l.read(ctx, hakeeper.DefaultHAKeeperShardID, &hakeeper.StateQuery{})
+		if err != nil {
+			// TODO: check whether this is temp error
+			return
+		}
+		state := s.(*hakeeper.HAKeeperState)
+		l.checker.Check(l.alloc,
+			state.ClusterInfo, state.DNState, state.LogState, state.Tick)
+	}
+}
diff --git a/pkg/logservice/store.go b/pkg/logservice/store.go
index 50b2670b9..4b43176e7 100644
--- a/pkg/logservice/store.go
+++ b/pkg/logservice/store.go
@@ -95,6 +95,8 @@ type logStore struct {
 	cfg               Config
 	nh                *dragonboat.NodeHost
 	haKeeperReplicaID uint64
+	checker           hakeeper.Checker
+	alloc             hakeeper.IDAllocator
 	stopper           *syncutil.Stopper
 
 	mu struct {
@@ -399,13 +401,15 @@ func (l *logStore) QueryLog(ctx context.Context, shardID uint64,
 func (l *logStore) ticker() {
 	ticker := time.NewTicker(hakeeper.TickDuration)
 	defer ticker.Stop()
+	haTicker := time.NewTicker(hakeeper.CheckDuration)
+	defer haTicker.Stop()
 
 	for {
 		select {
 		case <-ticker.C:
-			if err := l.hakeeperTick(); err != nil {
-				panic(err)
-			}
+			l.hakeeperTick()
+		case <-haTicker.C:
+			l.healthCheck()
 		case <-l.stopper.ShouldStop():
 			return
 		}
@@ -426,24 +430,22 @@ func (l *logStore) truncationWorker() {
 }
 
 // TODO: add test for this
-// FIXME: ignore temp errors
-func (l *logStore) hakeeperTick() error {
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-	defer cancel()
-	leaderID, ok, err := l.nh.GetLeaderID(hakeeper.DefaultHAKeeperShardID)
+func (l *logStore) hakeeperTick() {
+	leaderID, _, ok, err := l.nh.GetLeaderID(hakeeper.DefaultHAKeeperShardID)
 	if err != nil {
 		plog.Errorf("failed to get HAKeeper Leader ID, %v", err)
-		return err
+		return
 	}
 	if ok && leaderID == l.haKeeperReplicaID {
 		cmd := hakeeper.GetTickCmd()
+		ctx, cancel := context.WithTimeout(context.Background(), hakeeperDefaultTimeout)
+		defer cancel()
 		session := l.nh.GetNoOPSession(hakeeper.DefaultHAKeeperShardID)
 		if _, err := l.propose(ctx, session, cmd); err != nil {
-			plog.Errorf("propose failed, %v", err)
-			return err
+			plog.Errorf("propose tick failed, %v", err)
+			return
 		}
 	}
-	return nil
 }
 
 // TODO: add tests for this
diff --git a/pkg/pb/hakeeper/hakeeper.pb.go b/pkg/pb/hakeeper/hakeeper.pb.go
index ce59125b7..78f4a1e1e 100644
--- a/pkg/pb/hakeeper/hakeeper.pb.go
+++ b/pkg/pb/hakeeper/hakeeper.pb.go
@@ -23,7 +23,7 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
-// ConfigChangeType change replica type
+// ConfigChangeType indicates config change command type.
 type ConfigChangeType int32
 
 const (
@@ -83,9 +83,9 @@ func (ServiceType) EnumDescriptor() ([]byte, []int) {
 
 // Replica of the shard
 type Replica struct {
-	ID                   uint64   `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
-	Uuid                 string   `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"`
-	InitialMember        bool     `protobuf:"varint,4,opt,name=initialMember,proto3" json:"initialMember,omitempty"`
+	ShardID              uint64   `protobuf:"varint,1,opt,name=ShardID,proto3" json:"ShardID,omitempty"`
+	ReplicaID            uint64   `protobuf:"varint,2,opt,name=ReplicaID,proto3" json:"ReplicaID,omitempty"`
+	Epoch                uint64   `protobuf:"varint,3,opt,name=Epoch,proto3" json:"Epoch,omitempty"`
 	XXX_NoUnkeyedLiteral struct{} `json:"-"`
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
@@ -124,31 +124,31 @@ func (m *Replica) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_Replica proto.InternalMessageInfo
 
-func (m *Replica) GetID() uint64 {
+func (m *Replica) GetShardID() uint64 {
 	if m != nil {
-		return m.ID
+		return m.ShardID
 	}
 	return 0
 }
 
-func (m *Replica) GetUuid() string {
+func (m *Replica) GetReplicaID() uint64 {
 	if m != nil {
-		return m.Uuid
+		return m.ReplicaID
 	}
-	return ""
+	return 0
 }
 
-func (m *Replica) GetInitialMember() bool {
+func (m *Replica) GetEpoch() uint64 {
 	if m != nil {
-		return m.InitialMember
+		return m.Epoch
 	}
-	return false
+	return 0
 }
 
-// ChangePeer change peer
+// ConfigChange is the detail of a config change.
 type ConfigChange struct {
-	Replica              Replica          `protobuf:"bytes,1,opt,name=replica,proto3" json:"replica"`
-	ChangeType           ConfigChangeType `protobuf:"varint,2,opt,name=changeType,proto3,enum=pb.ConfigChangeType" json:"changeType,omitempty"`
+	Replica              Replica          `protobuf:"bytes,1,opt,name=Replica,proto3" json:"Replica"`
+	ChangeType           ConfigChangeType `protobuf:"varint,2,opt,name=ChangeType,proto3,enum=pb.ConfigChangeType" json:"ChangeType,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
 	XXX_unrecognized     []byte           `json:"-"`
 	XXX_sizecache        int32            `json:"-"`
@@ -201,16 +201,14 @@ func (m *ConfigChange) GetChangeType() ConfigChangeType {
 	return AddNode
 }
 
-// ScheduleCommand shard heartbeat response.
+// ScheduleCommand contains a shard schedule command.
 type ScheduleCommand struct {
-	ShardID      uint64        `protobuf:"varint,1,opt,name=shardID,proto3" json:"shardID,omitempty"`
-	ShardEpoch   uint64        `protobuf:"varint,2,opt,name=shardEpoch,proto3" json:"shardEpoch,omitempty"`
-	ConfigChange *ConfigChange `protobuf:"bytes,4,opt,name=configChange,proto3" json:"configChange,omitempty"`
-	// Which service does this command schedule for
-	ServiceType          ServiceType `protobuf:"varint,11,opt,name=serviceType,proto3,enum=pb.ServiceType" json:"serviceType,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
-	XXX_unrecognized     []byte      `json:"-"`
-	XXX_sizecache        int32       `json:"-"`
+	UUID                 string        `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	ConfigChange         *ConfigChange `protobuf:"bytes,2,opt,name=ConfigChange,proto3" json:"ConfigChange,omitempty"`
+	ServiceType          ServiceType   `protobuf:"varint,3,opt,name=ServiceType,proto3,enum=pb.ServiceType" json:"ServiceType,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
 }
 
 func (m *ScheduleCommand) Reset()         { *m = ScheduleCommand{} }
@@ -246,18 +244,11 @@ func (m *ScheduleCommand) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_ScheduleCommand proto.InternalMessageInfo
 
-func (m *ScheduleCommand) GetShardID() uint64 {
-	if m != nil {
-		return m.ShardID
-	}
-	return 0
-}
-
-func (m *ScheduleCommand) GetShardEpoch() uint64 {
+func (m *ScheduleCommand) GetUUID() string {
 	if m != nil {
-		return m.ShardEpoch
+		return m.UUID
 	}
-	return 0
+	return ""
 }
 
 func (m *ScheduleCommand) GetConfigChange() *ConfigChange {
@@ -285,32 +276,29 @@ func init() {
 func init() { proto.RegisterFile("hakeeper.proto", fileDescriptor_5e1506f3aa5330eb) }
 
 var fileDescriptor_5e1506f3aa5330eb = []byte{
-	// 392 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0xd1, 0x8e, 0x93, 0x40,
-	0x14, 0x86, 0x19, 0x24, 0xcb, 0xee, 0xa1, 0xdb, 0x25, 0x13, 0x63, 0x88, 0x17, 0x6c, 0xd3, 0x78,
-	0xd1, 0xac, 0xca, 0xc6, 0xba, 0x2f, 0x60, 0x5b, 0x2f, 0x36, 0xa9, 0x5e, 0x0c, 0xde, 0x79, 0x05,
-	0xcc, 0x14, 0x26, 0x16, 0x66, 0xa4, 0xd0, 0xc4, 0xf7, 0xf2, 0x21, 0x7a, 0xd9, 0x27, 0x68, 0x94,
-	0x27, 0x31, 0x1c, 0xda, 0x48, 0x7b, 0x77, 0xfe, 0x7f, 0xfe, 0x9f, 0xef, 0x9c, 0x04, 0x18, 0x66,
-	0xd1, 0x0f, 0x21, 0xb4, 0x28, 0x03, 0x5d, 0xaa, 0x4a, 0x51, 0x53, 0xc7, 0xaf, 0xdf, 0xa7, 0xb2,
-	0xca, 0xea, 0x38, 0x48, 0x54, 0xfe, 0x98, 0xaa, 0x54, 0x3d, 0xe2, 0x53, 0x5c, 0xaf, 0x50, 0xa1,
-	0xc0, 0xa9, 0xab, 0x8c, 0xbf, 0x83, 0xcd, 0x84, 0x5e, 0xcb, 0x24, 0xa2, 0xaf, 0xc0, 0x94, 0xdc,
-	0x23, 0x23, 0x32, 0xb1, 0x66, 0x57, 0xcd, 0xe1, 0xde, 0x7c, 0x5e, 0x30, 0x53, 0x72, 0x4a, 0xc1,
-	0xaa, 0x6b, 0xc9, 0x3d, 0x73, 0x44, 0x26, 0x37, 0x0c, 0x67, 0xfa, 0x06, 0x6e, 0x65, 0x21, 0x2b,
-	0x19, 0xad, 0xbf, 0x88, 0x3c, 0x16, 0xa5, 0x67, 0x8d, 0xc8, 0xe4, 0x9a, 0x9d, 0x9b, 0xe3, 0x9f,
-	0x30, 0x98, 0xab, 0x62, 0x25, 0xd3, 0x79, 0x16, 0x15, 0xa9, 0xa0, 0x6f, 0xc1, 0x2e, 0x3b, 0x18,
-	0x62, 0x9c, 0xa9, 0x13, 0xe8, 0x38, 0x38, 0xf2, 0x67, 0xd6, 0xee, 0x70, 0x6f, 0xb0, 0x53, 0x82,
-	0x3e, 0x01, 0x24, 0x58, 0xfb, 0xf6, 0x4b, 0x0b, 0x84, 0x0f, 0xa7, 0x2f, 0xdb, 0x7c, 0xff, 0x93,
-	0xed, 0x1b, 0xeb, 0xe5, 0xc6, 0xbf, 0x09, 0xdc, 0x85, 0x49, 0x26, 0x78, 0xbd, 0x16, 0x73, 0x95,
-	0xe7, 0x51, 0xc1, 0xa9, 0x07, 0xf6, 0x26, 0x8b, 0x4a, 0xfe, 0xbc, 0xe8, 0xae, 0x63, 0x27, 0x49,
-	0x7d, 0x00, 0x1c, 0x3f, 0x6b, 0x95, 0x64, 0xc8, 0xb0, 0x58, 0xcf, 0xa1, 0x4f, 0x30, 0x48, 0x7a,
-	0x34, 0xbc, 0xd2, 0x99, 0xba, 0x97, 0x5b, 0xb0, 0xb3, 0x14, 0xfd, 0x00, 0xce, 0x46, 0x94, 0x5b,
-	0x99, 0x74, 0xab, 0x3b, 0xb8, 0xfa, 0x5d, 0x5b, 0x0a, 0xff, 0xdb, 0xac, 0x9f, 0x79, 0x58, 0x82,
-	0x7b, 0x79, 0x16, 0x75, 0xc0, 0xfe, 0xc4, 0xf9, 0x57, 0xc5, 0x85, 0x6b, 0xd0, 0x21, 0x00, 0x13,
-	0xb9, 0xda, 0x0a, 0xd4, 0x84, 0xde, 0xc2, 0x4d, 0x58, 0x45, 0x65, 0x85, 0xd2, 0xa4, 0x03, 0xb8,
-	0x0e, 0x2b, 0xa5, 0x51, 0xbd, 0x78, 0x78, 0x07, 0x4e, 0x8f, 0xd4, 0x76, 0x97, 0x2a, 0x3d, 0x3a,
-	0xae, 0xd1, 0x76, 0x17, 0xc5, 0x49, 0x92, 0x99, 0xbb, 0xff, 0xeb, 0x1b, 0xbb, 0xc6, 0x27, 0xfb,
-	0xc6, 0x27, 0x7f, 0x1a, 0x9f, 0xc4, 0x57, 0xf8, 0x6f, 0x7c, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff,
-	0xb5, 0x33, 0xb3, 0x0b, 0x60, 0x02, 0x00, 0x00,
+	// 352 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x91, 0xcf, 0x4e, 0xea, 0x40,
+	0x14, 0xc6, 0x3b, 0xc0, 0xbd, 0x5c, 0x4e, 0xb9, 0xd0, 0x4c, 0x58, 0x34, 0xc6, 0x54, 0xc3, 0xca,
+	0xa0, 0x96, 0x88, 0xbc, 0x80, 0x80, 0x0b, 0x12, 0xe2, 0x62, 0x2a, 0x71, 0xdd, 0x3f, 0x43, 0xdb,
+	0x48, 0x3b, 0x63, 0x53, 0x48, 0x7c, 0x09, 0x9f, 0x8b, 0x25, 0x4f, 0x60, 0x94, 0x27, 0x31, 0x3d,
+	0x2d, 0x58, 0xd8, 0xcd, 0xef, 0x9c, 0xef, 0xfb, 0xe6, 0x9b, 0x0c, 0xb4, 0x02, 0xfb, 0x95, 0x73,
+	0xc9, 0x13, 0x53, 0x26, 0x22, 0x15, 0xb4, 0x22, 0x9d, 0xb3, 0x5b, 0x3f, 0x4c, 0x83, 0x95, 0x63,
+	0xba, 0x22, 0xea, 0xfb, 0xc2, 0x17, 0x7d, 0x5c, 0x39, 0xab, 0x05, 0x12, 0x02, 0x9e, 0x72, 0x4b,
+	0xf7, 0x05, 0xea, 0x8c, 0xcb, 0x65, 0xe8, 0xda, 0x54, 0x87, 0xba, 0x15, 0xd8, 0x89, 0x37, 0x9d,
+	0xe8, 0xe4, 0x92, 0x5c, 0xd5, 0xd8, 0x1e, 0xe9, 0x39, 0x34, 0x0a, 0xd1, 0x74, 0xa2, 0x57, 0x70,
+	0xf7, 0x3b, 0xa0, 0x1d, 0xf8, 0xf3, 0x28, 0x85, 0x1b, 0xe8, 0x55, 0xdc, 0xe4, 0xd0, 0x7d, 0x83,
+	0xe6, 0x58, 0xc4, 0x8b, 0xd0, 0x1f, 0x07, 0x76, 0xec, 0x73, 0x7a, 0x7d, 0xb8, 0x08, 0xd3, 0xd5,
+	0x81, 0x6a, 0x4a, 0xc7, 0x2c, 0x46, 0xa3, 0xda, 0xe6, 0xf3, 0x42, 0x61, 0x87, 0x2a, 0x43, 0x80,
+	0xdc, 0xf6, 0xfc, 0x2e, 0x39, 0xde, 0xd8, 0x1a, 0x74, 0x32, 0x7d, 0x39, 0x32, 0xdb, 0xb1, 0x92,
+	0xae, 0xfb, 0x41, 0xa0, 0x6d, 0xb9, 0x01, 0xf7, 0x56, 0x4b, 0x3e, 0x16, 0x51, 0x64, 0xc7, 0x1e,
+	0xa5, 0x50, 0x9b, 0xcf, 0x8b, 0x17, 0x35, 0x18, 0x9e, 0xe9, 0xf0, 0xb8, 0x1a, 0xe6, 0xab, 0x03,
+	0xed, 0x34, 0x9f, 0x1d, 0x3f, 0xe0, 0x0e, 0x54, 0x8b, 0x27, 0xeb, 0xd0, 0xcd, 0x4b, 0x55, 0xb1,
+	0x54, 0x3b, 0x33, 0x95, 0xc6, 0xac, 0xac, 0xe9, 0xcd, 0x40, 0x3b, 0x2d, 0x4c, 0x55, 0xa8, 0x3f,
+	0x78, 0xde, 0x93, 0xf0, 0xb8, 0xa6, 0xd0, 0x16, 0x00, 0xe3, 0x91, 0x58, 0x73, 0x64, 0x42, 0xff,
+	0x43, 0xc3, 0x4a, 0xed, 0x24, 0x45, 0xac, 0xd0, 0x26, 0xfc, 0xb3, 0x52, 0x21, 0x91, 0xaa, 0xbd,
+	0x9b, 0xa3, 0x02, 0x99, 0x77, 0x26, 0xfc, 0x62, 0xa2, 0x29, 0x99, 0x77, 0x12, 0xef, 0x91, 0x8c,
+	0xb4, 0xed, 0xb7, 0xa1, 0x6c, 0x76, 0x06, 0xd9, 0xee, 0x0c, 0xf2, 0xb5, 0x33, 0x88, 0xf3, 0x17,
+	0x7f, 0xfc, 0xfe, 0x27, 0x00, 0x00, 0xff, 0xff, 0xbe, 0xfd, 0xc8, 0x40, 0x36, 0x02, 0x00, 0x00,
 }
 
 func (m *Replica) Marshal() (dAtA []byte, err error) {
@@ -337,25 +325,18 @@ func (m *Replica) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
-	if m.InitialMember {
+	if m.Epoch != 0 {
+		i = encodeVarintHakeeper(dAtA, i, uint64(m.Epoch))
 		i--
-		if m.InitialMember {
-			dAtA[i] = 1
-		} else {
-			dAtA[i] = 0
-		}
-		i--
-		dAtA[i] = 0x20
+		dAtA[i] = 0x18
 	}
-	if len(m.Uuid) > 0 {
-		i -= len(m.Uuid)
-		copy(dAtA[i:], m.Uuid)
-		i = encodeVarintHakeeper(dAtA, i, uint64(len(m.Uuid)))
+	if m.ReplicaID != 0 {
+		i = encodeVarintHakeeper(dAtA, i, uint64(m.ReplicaID))
 		i--
-		dAtA[i] = 0x12
+		dAtA[i] = 0x10
 	}
-	if m.ID != 0 {
-		i = encodeVarintHakeeper(dAtA, i, uint64(m.ID))
+	if m.ShardID != 0 {
+		i = encodeVarintHakeeper(dAtA, i, uint64(m.ShardID))
 		i--
 		dAtA[i] = 0x8
 	}
@@ -431,7 +412,7 @@ func (m *ScheduleCommand) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	if m.ServiceType != 0 {
 		i = encodeVarintHakeeper(dAtA, i, uint64(m.ServiceType))
 		i--
-		dAtA[i] = 0x58
+		dAtA[i] = 0x18
 	}
 	if m.ConfigChange != nil {
 		{
@@ -443,17 +424,14 @@ func (m *ScheduleCommand) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 			i = encodeVarintHakeeper(dAtA, i, uint64(size))
 		}
 		i--
-		dAtA[i] = 0x22
+		dAtA[i] = 0x12
 	}
-	if m.ShardEpoch != 0 {
-		i = encodeVarintHakeeper(dAtA, i, uint64(m.ShardEpoch))
+	if len(m.UUID) > 0 {
+		i -= len(m.UUID)
+		copy(dAtA[i:], m.UUID)
+		i = encodeVarintHakeeper(dAtA, i, uint64(len(m.UUID)))
 		i--
-		dAtA[i] = 0x10
-	}
-	if m.ShardID != 0 {
-		i = encodeVarintHakeeper(dAtA, i, uint64(m.ShardID))
-		i--
-		dAtA[i] = 0x8
+		dAtA[i] = 0xa
 	}
 	return len(dAtA) - i, nil
 }
@@ -475,15 +453,14 @@ func (m *Replica) Size() (n int) {
 	}
 	var l int
 	_ = l
-	if m.ID != 0 {
-		n += 1 + sovHakeeper(uint64(m.ID))
+	if m.ShardID != 0 {
+		n += 1 + sovHakeeper(uint64(m.ShardID))
 	}
-	l = len(m.Uuid)
-	if l > 0 {
-		n += 1 + l + sovHakeeper(uint64(l))
+	if m.ReplicaID != 0 {
+		n += 1 + sovHakeeper(uint64(m.ReplicaID))
 	}
-	if m.InitialMember {
-		n += 2
+	if m.Epoch != 0 {
+		n += 1 + sovHakeeper(uint64(m.Epoch))
 	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
@@ -514,11 +491,9 @@ func (m *ScheduleCommand) Size() (n int) {
 	}
 	var l int
 	_ = l
-	if m.ShardID != 0 {
-		n += 1 + sovHakeeper(uint64(m.ShardID))
-	}
-	if m.ShardEpoch != 0 {
-		n += 1 + sovHakeeper(uint64(m.ShardEpoch))
+	l = len(m.UUID)
+	if l > 0 {
+		n += 1 + l + sovHakeeper(uint64(l))
 	}
 	if m.ConfigChange != nil {
 		l = m.ConfigChange.Size()
@@ -570,9 +545,9 @@ func (m *Replica) Unmarshal(dAtA []byte) error {
 		switch fieldNum {
 		case 1:
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
 			}
-			m.ID = 0
+			m.ShardID = 0
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowHakeeper
@@ -582,16 +557,16 @@ func (m *Replica) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				m.ID |= uint64(b&0x7F) << shift
+				m.ShardID |= uint64(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
 		case 2:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Uuid", wireType)
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ReplicaID", wireType)
 			}
-			var stringLen uint64
+			m.ReplicaID = 0
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowHakeeper
@@ -601,29 +576,16 @@ func (m *Replica) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
+				m.ReplicaID |= uint64(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthHakeeper
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthHakeeper
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.Uuid = string(dAtA[iNdEx:postIndex])
-			iNdEx = postIndex
-		case 4:
+		case 3:
 			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field InitialMember", wireType)
+				return fmt.Errorf("proto: wrong wireType = %d for field Epoch", wireType)
 			}
-			var v int
+			m.Epoch = 0
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowHakeeper
@@ -633,12 +595,11 @@ func (m *Replica) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				v |= int(b&0x7F) << shift
+				m.Epoch |= uint64(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
-			m.InitialMember = bool(v != 0)
 		default:
 			iNdEx = preIndex
 			skippy, err := skipHakeeper(dAtA[iNdEx:])
@@ -794,10 +755,10 @@ func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 		}
 		switch fieldNum {
 		case 1:
-			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field ShardID", wireType)
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field UUID", wireType)
 			}
-			m.ShardID = 0
+			var stringLen uint64
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowHakeeper
@@ -807,31 +768,25 @@ func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				m.ShardID |= uint64(b&0x7F) << shift
+				stringLen |= uint64(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
-		case 2:
-			if wireType != 0 {
-				return fmt.Errorf("proto: wrong wireType = %d for field ShardEpoch", wireType)
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthHakeeper
 			}
-			m.ShardEpoch = 0
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowHakeeper
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				m.ShardEpoch |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthHakeeper
 			}
-		case 4:
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.UUID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field ConfigChange", wireType)
 			}
@@ -867,7 +822,7 @@ func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
-		case 11:
+		case 3:
 			if wireType != 0 {
 				return fmt.Errorf("proto: wrong wireType = %d for field ServiceType", wireType)
 			}
diff --git a/proto/hakeeper.proto b/proto/hakeeper.proto
index 92b3d61e1..8f7311598 100644
--- a/proto/hakeeper.proto
+++ b/proto/hakeeper.proto
@@ -23,12 +23,13 @@ option (gogoproto.goproto_enum_prefix_all) = false;
 
 // Replica of the shard
 message Replica {
-  uint64 id          = 1 [(gogoproto.customname) = "ID"];
-  string uuid        = 2;
-  bool initialMember = 4;
+  uint64 ShardID   = 1;
+  uint64 ReplicaID = 2;
+
+  uint64 Epoch = 3;
 }
 
-// ConfigChangeType change replica type
+// ConfigChangeType indicates config change command type.
 enum ConfigChangeType {
   AddNode    = 0;
   RemoveNode = 1;
@@ -36,10 +37,10 @@ enum ConfigChangeType {
   StopNode   = 3;
 }
 
-// ChangePeer change peer
+// ConfigChange is the detail of a config change.
 message ConfigChange {
-  Replica replica             = 1 [(gogoproto.nullable) = false];
-  ConfigChangeType changeType = 2;
+  Replica Replica             = 1 [(gogoproto.nullable) = false];
+  ConfigChangeType ChangeType = 2;
 }
 
 // ServiceType specifies type of service
@@ -48,15 +49,9 @@ enum ServiceType {
   DnService  = 1;
 }
 
-// ScheduleCommand shard heartbeat response.
+// ScheduleCommand contains a shard schedule command.
 message ScheduleCommand {
-  uint64 shardID    = 1;
-  uint64 shardEpoch = 2;
-
-  ConfigChange configChange = 4;
-
-  // In order to keep back-compatibility, new field starts from 11
-
-  // Which service does this command schedule for
-  ServiceType serviceType = 11;
+  string UUID        = 1;
+  ConfigChange ConfigChange = 2;
+  ServiceType ServiceType = 3;
 }


Test patch:
diff --git a/pkg/frontend/mysql_protocol_test.go b/pkg/frontend/mysql_protocol_test.go
index 6b8fb878d..cf8e123a9 100644
--- a/pkg/frontend/mysql_protocol_test.go
+++ b/pkg/frontend/mysql_protocol_test.go
@@ -1382,11 +1382,11 @@ func do_query_resp_resultset(t *testing.T, db *sql.DB, wantErr bool, skipResults
 					case defines.MYSQL_TYPE_FLOAT:
 						value, err := mrs.GetFloat64(rowIdx, i)
 						require.NoError(t, err)
-						data = strconv.AppendFloat(data, value, 'f', 4, 32)
+						data = strconv.AppendFloat(data, value, 'f', -1, 32)
 					case defines.MYSQL_TYPE_DOUBLE:
 						value, err := mrs.GetFloat64(rowIdx, i)
 						require.NoError(t, err)
-						data = strconv.AppendFloat(data, value, 'f', 4, 64)
+						data = strconv.AppendFloat(data, value, 'f', -1, 64)
 					case defines.MYSQL_TYPE_DATE:
 						value, err := mrs.GetValue(rowIdx, i)
 						require.NoError(t, err)
diff --git a/pkg/hakeeper/rsm_test.go b/pkg/hakeeper/rsm_test.go
index 1b2b64e32..d2da45e40 100644
--- a/pkg/hakeeper/rsm_test.go
+++ b/pkg/hakeeper/rsm_test.go
@@ -209,3 +209,19 @@ func TestHandleDNHeartbeat(t *testing.T) {
 	require.Equal(t, 3, len(dninfo.Shards))
 	assert.Equal(t, hb.Shards, dninfo.Shards)
 }
+
+func TestGetIDCmd(t *testing.T) {
+	tsm1 := NewStateMachine(0, 1).(*stateMachine)
+	cmd := GetGetIDCmd(100)
+	result, err := tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	assert.Equal(t, sm.Result{Value: 1}, result)
+	result, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	assert.Equal(t, sm.Result{Value: 101}, result)
+	assert.Equal(t, uint64(201), tsm1.assignID())
+
+	result, err = tsm1.Update(sm.Entry{Cmd: cmd})
+	assert.NoError(t, err)
+	assert.Equal(t, sm.Result{Value: 202}, result)
+}
diff --git a/pkg/logservice/hachecker_test.go b/pkg/logservice/hachecker_test.go
new file mode 100644
index 000000000..05b11f821
--- /dev/null
+++ b/pkg/logservice/hachecker_test.go
@@ -0,0 +1,75 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logservice
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIDAllocatorCapacity(t *testing.T) {
+	tests := []struct {
+		next     uint64
+		last     uint64
+		capacity uint64
+	}{
+		{1, 1, 1},
+		{2, 1, 0},
+		{1, 2, 2},
+		{100, 200, 101},
+	}
+
+	for _, tt := range tests {
+		alloc := idAllocator{nextID: tt.next, lastID: tt.last}
+		assert.Equal(t, tt.capacity, alloc.Capacity())
+	}
+}
+
+func TestIDAllocatorSet(t *testing.T) {
+	alloc := idAllocator{nextID: 100, lastID: 200}
+	alloc.Set(200, 300)
+	assert.Equal(t, idAllocator{nextID: 200, lastID: 300}, alloc)
+}
+
+func TestIDAllocatorNext(t *testing.T) {
+	tests := []struct {
+		next     uint64
+		last     uint64
+		capacity uint64
+	}{
+		{1, 1, 1},
+		{2, 1, 0},
+		{1, 2, 2},
+		{100, 200, 101},
+	}
+
+	for _, tt := range tests {
+		expected := tt.next
+		alloc := idAllocator{nextID: tt.next, lastID: tt.last}
+		for {
+			hasID := alloc.Capacity() != 0
+			v, ok := alloc.Next()
+			assert.Equal(t, hasID, ok)
+			if hasID {
+				assert.Equal(t, expected, v)
+				expected++
+			} else {
+				assert.Equal(t, uint64(0), v)
+				break
+			}
+		}
+	}
+}
diff --git a/pkg/logservice/store_test.go b/pkg/logservice/store_test.go
index 466688400..975f79846 100644
--- a/pkg/logservice/store_test.go
+++ b/pkg/logservice/store_test.go
@@ -244,8 +244,7 @@ func TestHAKeeperTick(t *testing.T) {
 		peers := make(map[uint64]dragonboat.Target)
 		peers[1] = store.ID()
 		assert.NoError(t, store.StartHAKeeperReplica(1, peers))
-
-		assert.NoError(t, store.hakeeperTick())
+		store.hakeeperTick()
 	}
 	runStoreTest(t, fn)
 }