Repository: pingcap/tidb (go)

Issue:
race when updating labels and store topology runs concurrently
## Bug Report

Please answer these questions before submitting your issue. Thanks!

Found by @lcwangchao 's great code review.

### 1. Minimal reproduce step (Required)

<!-- a step by step guide for reproducing the bug. -->

1. Add a test into `pkg/server/handler/tests/http_handler_test.go`.

```go
func TestSetLabelsConcurrentWithStoreTopology(t *testing.T) {
	ts := createBasicHTTPHandlerTestSuite()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ts.startServer(t)
	defer ts.stopServer(t)

	integration.BeforeTestExternal(t)
	cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
	defer cluster.Terminate(t)
	client := cluster.RandClient()
	infosync.SetEtcdClient(client)

	ts.domain.InfoSyncer().Restart(ctx)
	ts.domain.InfoSyncer().RestartTopology(ctx)

	testUpdateLabels := func() {
		labels := map[string]string{}
		labels["zone"] = fmt.Sprintf("z-%v", rand.Intn(100000))
		buffer := bytes.NewBuffer([]byte{})
		require.Nil(t, json.NewEncoder(buffer).Encode(labels))
		resp, err := ts.PostStatus("/labels", "application/json", buffer)
		require.NoError(t, err)
		require.NotNil(t, resp)
		defer func() {
			require.NoError(t, resp.Body.Close())
		}()
		require.Equal(t, http.StatusOK, resp.StatusCode)
		newLabels := config.GetGlobalConfig().Labels
		require.Equal(t, newLabels, labels)
	}
	testStoreTopology := func() {
		require.NoError(t, ts.domain.InfoSyncer().StoreTopologyInfo(context.Background()))
	}

	done := make(chan struct{})
	go func() {
		for {
			select {
			case <-done:
				return
			default:
				testStoreTopology()
			}
		}
	}()
	for i := 0; i < 100; i++ {
		testUpdateLabels()
	}
	close(done)

	// reset the global variable
	config.UpdateGlobal(func(conf *config.Config) {
		conf.Labels = map[string]string{}
	})
}
```

2. Run the test with race detection.

```bash
go test ./pkg/server/handler/tests -run TestSetLabelsConcurrentWithStoreTopology -race
```

### 2. What did you expect to see? (Required)

Test pass.

### 3. What did you see instead (Required)

```
WARNING: DATA RACE
Write at 0x00c00a78ece8 by goroutine 1975:
  github.com/pingcap/tidb/pkg/domain/infosync.UpdateServerLabel()
      github.com/pingcap/tidb/pkg/domain/infosync/info.go:443 +0x33b
  github.com/pingcap/tidb/pkg/server/handler/tikvhandler.LabelHandler.ServeHTTP()
      github.com/pingcap/tidb/pkg/server/handler/tikvhandler/tikv_handler.go:2011 +0x55a
  github.com/pingcap/tidb/pkg/server/handler/tikvhandler.(*LabelHandler).ServeHTTP()
      <autogenerated>:1 +0x58
  github.com/gorilla/mux.(*Router).ServeHTTP()
      github.com/gorilla/mux@v1.8.1/mux.go:212 +0x371
  net/http.(*ServeMux).ServeHTTP()
      net/http/server.go:2747 +0x255
  github.com/pingcap/tidb/pkg/server/internal/util.CorsHandler.ServeHTTP()
      github.com/pingcap/tidb/pkg/server/internal/util/util.go:219 +0x29e
  github.com/pingcap/tidb/pkg/server/internal/util.(*CorsHandler).ServeHTTP()
      <autogenerated>:1 +0x74
  net/http.serverHandler.ServeHTTP()
      net/http/server.go:3210 +0x2a1
  net/http.(*conn).serve()
      net/http/server.go:2092 +0x12a4
  net/http.(*Server).Serve.gowrap3()
      net/http/server.go:3360 +0x4f

Previous read at 0x00c00a78ece8 by goroutine 1928:
  reflect.typedmemmove()
      runtime/mbarrier.go:225 +0x0
  reflect.copyVal()
      reflect/value.go:2048 +0x5b
  reflect.(*MapIter).Value()
      reflect/value.go:1945 +0x124
  encoding/json.mapEncoder.encode()
      encoding/json/encode.go:760 +0x61a
  encoding/json.mapEncoder.encode-fm()
      <autogenerated>:1 +0x84
  encoding/json.structEncoder.encode()
      encoding/json/encode.go:715 +0x2bd
  encoding/json.structEncoder.encode-fm()
      <autogenerated>:1 +0xe4
  encoding/json.(*encodeState).reflectValue()
      encoding/json/encode.go:322 +0x83
  encoding/json.(*encodeState).marshal()
      encoding/json/encode.go:298 +0xea
  encoding/json.Marshal()
      encoding/json/encode.go:164 +0x12b
  github.com/pingcap/tidb/pkg/domain/infosync.(*InfoSyncer).StoreTopologyInfo()
      github.com/pingcap/tidb/pkg/domain/infosync/info.go:724 +0xf5
  github.com/pingcap/tidb/pkg/server/handler/tests.TestSetLabelsConcurrentWithStoreTopology.func2()
      github.com/pingcap/tidb/pkg/server/handler/tests/http_handler_test.go:1564 +0x84
  github.com/pingcap/tidb/pkg/server/handler/tests.TestSetLabelsConcurrentWithStoreTopology.func3()
      github.com/pingcap/tidb/pkg/server/handler/tests/http_handler_test.go:1574 +0x35
```

### 4. What is your TiDB version? (Required)

<!-- Paste the output of SELECT tidb_version() -->

nightly


Pull request:
infosync: refactor server config into dynamic and static sections (#58473)
This is an automated cherry-pick of #58473

<!--

Thank you for contributing to TiDB!

PR Title Format:
1. pkg [, pkg2, pkg3]: what's changed
2. *: what's changed

-->

### What problem does this PR solve?
<!--

Please create an issue first to describe the problem.

There MUST be one line starting with "Issue Number:  " and
linking the relevant issues via the "close" or "ref".

For more info, check https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue.

-->

Issue Number: close #58405

Problem Summary:

The labels of TiDB are stored within a static structure, meanwhile TiDB have update-label API. Before this PR, the way TiDB handles the mutations of labels have data-race risk.

### What changed and how does it work?

Refactor the `ServerInfo` into static and dynamic sections and fix the data race by atomic operations.

### Check List

Tests <!-- At least one of them must be included. -->

- [x] Unit test
- [ ] Integration test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test
  > - [ ] I checked and no code files have been changed.
  > <!-- Or your custom  "No need to test" reasons -->

Side effects

- [ ] Performance regression: Consumes more CPU
- [ ] Performance regression: Consumes more Memory
- [ ] Breaking backward compatibility

Documentation

- [ ] Affects user behaviors
- [ ] Contains syntax changes
- [ ] Contains variable changes
- [ ] Contains experimental features
- [ ] Changes MySQL compatibility

### Release note

<!-- compatibility change, improvement, bugfix, and new feature need a release note -->

Please refer to [Release Notes Language Style Guide](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/release-notes-style-guide.html) to write a quality release note.

```release-note
None
```


Patch:
diff --git a/br/pkg/storage/azblob.go b/br/pkg/storage/azblob.go
index 2ada42dffa..d8ee26fd57 100644
--- a/br/pkg/storage/azblob.go
+++ b/br/pkg/storage/azblob.go
@@ -593,6 +593,8 @@ type azblobObjectReader struct {
 	ctx context.Context
 
 	cpkInfo *blob.CPKInfo
+	// opened lazily
+	reader io.ReadCloser
 }
 
 // Read implement the io.Reader interface.
@@ -604,30 +606,27 @@ func (r *azblobObjectReader) Read(p []byte) (n int, err error) {
 	if maxCnt == 0 {
 		return 0, io.EOF
 	}
-	resp, err := r.blobClient.DownloadStream(r.ctx, &blob.DownloadStreamOptions{
-		Range: blob.HTTPRange{
-			Offset: r.pos,
-			Count:  maxCnt,
-		},
-
-		CPKInfo: r.cpkInfo,
-	})
-	if err != nil {
-		return 0, errors.Annotatef(err, "Failed to read data from azure blob, data info: pos='%d', count='%d'", r.pos, maxCnt)
+	if r.reader == nil {
+		if err2 := r.reopenReader(); err2 != nil {
+			return 0, err2
+		}
 	}
-	body := resp.NewRetryReader(r.ctx, &blob.RetryReaderOptions{
-		MaxRetries: azblobRetryTimes,
-	})
-	n, err = body.Read(p)
+	buf := p[:maxCnt]
+	n, err = r.reader.Read(buf)
 	if err != nil && err != io.EOF {
 		return 0, errors.Annotatef(err, "Failed to read data from azure blob response, data info: pos='%d', count='%d'", r.pos, maxCnt)
 	}
 	r.pos += int64(n)
-	return n, body.Close()
+	return n, nil
 }
 
 // Close implement the io.Closer interface.
-func (*azblobObjectReader) Close() error {
+func (r *azblobObjectReader) Close() error {
+	if r.reader != nil {
+		err := errors.Trace(r.reader.Close())
+		r.reader = nil
+		return err
+	}
 	return nil
 }
 
@@ -653,13 +652,44 @@ func (r *azblobObjectReader) Seek(offset int64, whence int) (int64, error) {
 		return 0, errors.Annotatef(berrors.ErrStorageUnknown, "Seek: invalid whence '%d'", whence)
 	}
 
-	if realOffset < 0 {
+	if realOffset < 0 || realOffset > r.totalSize {
 		return 0, errors.Annotatef(berrors.ErrInvalidArgument, "Seek: offset is %d, but length of content is only %d", realOffset, r.totalSize)
 	}
+	if realOffset == r.pos {
+		return r.pos, nil
+	}
 	r.pos = realOffset
+	// azblob reader can only read forward, so we need to reopen the reader
+	if err := r.reopenReader(); err != nil {
+		return 0, err
+	}
 	return r.pos, nil
 }
 
+func (r *azblobObjectReader) reopenReader() error {
+	if r.reader != nil {
+		err := errors.Trace(r.reader.Close())
+		if err != nil {
+			log.Warn("failed to close azblob reader", zap.Error(err))
+		}
+	}
+
+	resp, err := r.blobClient.DownloadStream(r.ctx, &blob.DownloadStreamOptions{
+		Range: blob.HTTPRange{
+			Offset: r.pos,
+		},
+		CPKInfo: r.cpkInfo,
+	})
+	if err != nil {
+		return errors.Annotatef(err, "Failed to read data from azure blob, data info: pos='%d'", r.pos)
+	}
+	body := resp.NewRetryReader(r.ctx, &blob.RetryReaderOptions{
+		MaxRetries: azblobRetryTimes,
+	})
+	r.reader = body
+	return nil
+}
+
 func (r *azblobObjectReader) GetFileSize() (int64, error) {
 	return r.totalSize, nil
 }
diff --git a/pkg/domain/infosync/BUILD.bazel b/pkg/domain/infosync/BUILD.bazel
index 2c34d10110..b55c303638 100644
--- a/pkg/domain/infosync/BUILD.bazel
+++ b/pkg/domain/infosync/BUILD.bazel
@@ -63,7 +63,7 @@ go_test(
     srcs = ["info_test.go"],
     embed = [":infosync"],
     flaky = True,
-    shard_count = 3,
+    shard_count = 4,
     deps = [
         "//pkg/ddl/placement",
         "//pkg/ddl/util",
diff --git a/pkg/domain/infosync/info.go b/pkg/domain/infosync/info.go
index 59c2e5a003..93528541e8 100644
--- a/pkg/domain/infosync/info.go
+++ b/pkg/domain/infosync/info.go
@@ -19,6 +19,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"maps"
 	"net"
 	"net/http"
 	"os"
@@ -109,11 +110,12 @@ type InfoSyncer struct {
 	// See keyspace RFC: https://github.com/pingcap/tidb/pull/39685
 	unprefixedEtcdCli *clientv3.Client
 	pdHTTPCli         pdhttp.Client
-	info              *ServerInfo
-	serverInfoPath    string
-	minStartTS        uint64
-	minStartTSPath    string
-	managerMu         struct {
+	info              atomic.Pointer[ServerInfo]
+
+	serverInfoPath string
+	minStartTS     uint64
+	minStartTSPath string
+	managerMu      struct {
 		mu sync.RWMutex
 		util2.SessionManager
 	}
@@ -128,24 +130,21 @@ type InfoSyncer struct {
 	resourceManagerClient pd.ResourceManagerClient
 }
 
-// ServerInfo is server static information.
-// It will not be updated when tidb-server running. So please only put static information in ServerInfo struct.
+// ServerInfo represents the server's basic information.
+// It consists of two sections: static and dynamic.
+// The static information is generated during the startup of the TiDB server and should never be modified while the TiDB server is running.
+// The dynamic information can be updated while the TiDB server is running and should be synchronized with PD's etcd.
 type ServerInfo struct {
-	ServerVersionInfo
-	ID             string            `json:"ddl_id"`
-	IP             string            `json:"ip"`
-	Port           uint              `json:"listening_port"`
-	StatusPort     uint              `json:"status_port"`
-	Lease          string            `json:"lease"`
-	BinlogStatus   string            `json:"binlog_status"`
-	StartTimestamp int64             `json:"start_timestamp"`
-	Labels         map[string]string `json:"labels"`
-	// ServerID is a function, to always retrieve latest serverID from `Domain`,
-	// which will be changed on occasions such as connection to PD is restored after broken.
-	ServerIDGetter func() uint64 `json:"-"`
+	StaticServerInfo
+	DynamicServerInfo
+}
 
-	// JSONServerID is `serverID` for json marshal/unmarshal ONLY.
-	JSONServerID uint64 `json:"server_id"`
+// clone the ServerInfo.
+func (info *ServerInfo) clone() *ServerInfo {
+	return &ServerInfo{
+		StaticServerInfo:  info.StaticServerInfo,
+		DynamicServerInfo: *info.DynamicServerInfo.clone(),
+	}
 }
 
 // Marshal `ServerInfo` into bytes.
@@ -169,6 +168,42 @@ func (info *ServerInfo) Unmarshal(v []byte) error {
 	return nil
 }
 
+// StaticServerInfo is server static information.
+// It will not be updated when tidb-server running. So please only put static information in ServerInfo struct.
+// DO NOT edit it after tidb-server started.
+type StaticServerInfo struct {
+	ServerVersionInfo
+	ID             string `json:"ddl_id"`
+	IP             string `json:"ip"`
+	Port           uint   `json:"listening_port"`
+	StatusPort     uint   `json:"status_port"`
+	Lease          string `json:"lease"`
+	StartTimestamp int64  `json:"start_timestamp"`
+	// ServerID is a function, to always retrieve latest serverID from `Domain`,
+	// which will be changed on occasions such as connection to PD is restored after broken.
+	ServerIDGetter func() uint64 `json:"-"`
+
+	// JSONServerID is `serverID` for json marshal/unmarshal ONLY.
+	JSONServerID uint64 `json:"server_id"`
+}
+
+// DynamicServerInfo represents the dynamic information of the server.
+// Please note that it may change when TiDB is running.
+// To update the dynamic server information, use `InfoSyncer.cloneDynamicServerInfo` to obtain a copy of the dynamic server info.
+// After making modifications, use `InfoSyncer.setDynamicServerInfo` to update the dynamic server information.
+type DynamicServerInfo struct {
+	BinlogStatus string            `json:"binlog_status"`
+	Labels       map[string]string `json:"labels"`
+}
+
+// clone the DynamicServerInfo.
+func (d *DynamicServerInfo) clone() *DynamicServerInfo {
+	return &DynamicServerInfo{
+		BinlogStatus: d.BinlogStatus,
+		Labels:       maps.Clone(d.Labels),
+	}
+}
+
 // ServerVersionInfo is the server version and git_hash.
 type ServerVersionInfo struct {
 	Version string `json:"version"`
@@ -211,10 +246,10 @@ func GlobalInfoSyncerInit(
 		etcdCli:           etcdCli,
 		unprefixedEtcdCli: unprefixedEtcdCli,
 		pdHTTPCli:         pdHTTPCli,
-		info:              getServerInfo(id, serverIDGetter),
 		serverInfoPath:    fmt.Sprintf("%s/%s", ServerInformationPath, id),
 		minStartTSPath:    fmt.Sprintf("%s/%s", ServerMinStartTSPath, id),
 	}
+	is.info.Store(getServerInfo(id, serverIDGetter))
 	err := is.init(ctx, skipRegisterToDashBoard)
 	if err != nil {
 		return nil, err
@@ -363,7 +398,7 @@ func GetServerInfo() (*ServerInfo, error) {
 	if err != nil {
 		return nil, err
 	}
-	return is.info, nil
+	return is.info.Load(), nil
 }
 
 // GetServerInfoByID gets specified server static information from etcd.
@@ -376,8 +411,9 @@ func GetServerInfoByID(ctx context.Context, id string) (*ServerInfo, error) {
 }
 
 func (is *InfoSyncer) getServerInfoByID(ctx context.Context, id string) (*ServerInfo, error) {
-	if is.etcdCli == nil || id == is.info.ID {
-		return is.info, nil
+	localInfo := is.info.Load()
+	if is.etcdCli == nil || id == localInfo.ID {
+		return localInfo, nil
 	}
 	key := fmt.Sprintf("%s/%s", ServerInformationPath, id)
 	infoMap, err := getInfo(ctx, is.etcdCli, key, keyOpDefaultRetryCnt, keyOpDefaultTimeout)
@@ -415,27 +451,31 @@ func UpdateServerLabel(ctx context.Context, labels map[string]string) error {
 	if is.etcdCli == nil {
 		return nil
 	}
-	selfInfo, err := is.getServerInfoByID(ctx, is.info.ID)
-	if err != nil {
-		return err
-	}
+	dynamicInfo := is.cloneDynamicServerInfo()
 	changed := false
 	for k, v := range labels {
-		if selfInfo.Labels[k] != v {
+		if dynamicInfo.Labels[k] != v {
 			changed = true
-			selfInfo.Labels[k] = v
+			dynamicInfo.Labels[k] = v
 		}
 	}
 	if !changed {
 		return nil
 	}
-	infoBuf, err := selfInfo.Marshal()
+	info := is.getLocalServerInfo().clone()
+	info.DynamicServerInfo = *dynamicInfo
+	infoBuf, err := info.Marshal()
 	if err != nil {
 		return errors.Trace(err)
 	}
 	str := string(hack.String(infoBuf))
 	err = util.PutKVToEtcd(ctx, is.etcdCli, keyOpDefaultRetryCnt, is.serverInfoPath, str, clientv3.WithLease(is.session.Lease()))
-	return err
+	if err != nil {
+		return err
+	}
+	// update the dynamic info in the global info syncer after put etcd success.
+	is.setDynamicServerInfo(dynamicInfo)
+	return nil
 }
 
 // DeleteTiFlashTableSyncProgress is used to delete the tiflash table replica sync progress.
@@ -633,7 +673,8 @@ func PutRuleBundlesWithDefaultRetry(ctx context.Context, bundles []*placement.Bu
 func (is *InfoSyncer) getAllServerInfo(ctx context.Context) (map[string]*ServerInfo, error) {
 	allInfo := make(map[string]*ServerInfo)
 	if is.etcdCli == nil {
-		allInfo[is.info.ID] = getServerInfo(is.info.ID, is.info.ServerIDGetter)
+		info := is.info.Load()
+		allInfo[info.ID] = getServerInfo(info.ID, info.ServerIDGetter)
 		return allInfo, nil
 	}
 	allInfo, err := getInfo(ctx, is.etcdCli, ServerInformationPath, keyOpDefaultRetryCnt, keyOpDefaultTimeout, clientv3.WithPrefix())
@@ -648,7 +689,8 @@ func (is *InfoSyncer) StoreServerInfo(ctx context.Context) error {
 	if is.etcdCli == nil {
 		return nil
 	}
-	infoBuf, err := is.info.Marshal()
+	info := is.info.Load()
+	infoBuf, err := info.Marshal()
 	if err != nil {
 		return errors.Trace(err)
 	}
@@ -678,7 +720,7 @@ type TopologyInfo struct {
 	Labels         map[string]string `json:"labels"`
 }
 
-func (is *InfoSyncer) getTopologyInfo() TopologyInfo {
+func (info *ServerInfo) asTopologyInfo() TopologyInfo {
 	s, err := os.Executable()
 	if err != nil {
 		s = ""
@@ -687,28 +729,29 @@ func (is *InfoSyncer) getTopologyInfo() TopologyInfo {
 	return TopologyInfo{
 		ServerVersionInfo: ServerVersionInfo{
 			Version: mysql.TiDBReleaseVersion,
-			GitHash: is.info.ServerVersionInfo.GitHash,
+			GitHash: info.ServerVersionInfo.GitHash,
 		},
-		IP:             is.info.IP,
-		StatusPort:     is.info.StatusPort,
+		IP:             info.IP,
+		StatusPort:     info.StatusPort,
 		DeployPath:     dir,
-		StartTimestamp: is.info.StartTimestamp,
-		Labels:         is.info.Labels,
+		StartTimestamp: info.StartTimestamp,
+		Labels:         info.Labels,
 	}
 }
 
-// StoreTopologyInfo  stores the topology of tidb to etcd.
+// StoreTopologyInfo stores the topology of tidb to etcd.
 func (is *InfoSyncer) StoreTopologyInfo(ctx context.Context) error {
 	if is.etcdCli == nil {
 		return nil
 	}
-	topologyInfo := is.getTopologyInfo()
+	info := is.info.Load()
+	topologyInfo := info.asTopologyInfo()
 	infoBuf, err := json.Marshal(topologyInfo)
 	if err != nil {
 		return errors.Trace(err)
 	}
 	str := string(hack.String(infoBuf))
-	key := fmt.Sprintf("%s/%s/info", TopologyInformationPath, net.JoinHostPort(is.info.IP, strconv.Itoa(int(is.info.Port))))
+	key := fmt.Sprintf("%s/%s/info", TopologyInformationPath, net.JoinHostPort(info.IP, strconv.Itoa(int(info.Port))))
 	// Note: no lease is required here.
 	err = util.PutKVToEtcd(ctx, is.etcdCli, keyOpDefaultRetryCnt, key, str)
 	if err != nil {
@@ -848,7 +891,13 @@ func (is *InfoSyncer) newSessionAndStoreServerInfo(ctx context.Context, retryCnt
 	}
 	is.session = session
 	binloginfo.RegisterStatusListener(func(status binloginfo.BinlogStatus) error {
-		is.info.BinlogStatus = status.String()
+		binlogStatus := status.String()
+		dynamicInfo := is.cloneDynamicServerInfo()
+		if dynamicInfo.BinlogStatus == binlogStatus {
+			return nil
+		}
+		dynamicInfo.BinlogStatus = binlogStatus
+		is.setDynamicServerInfo(dynamicInfo)
 		err := is.StoreServerInfo(ctx)
 		return errors.Trace(err)
 	})
@@ -860,7 +909,8 @@ func (is *InfoSyncer) newTopologySessionAndStoreServerInfo(ctx context.Context,
 	if is.etcdCli == nil {
 		return nil
 	}
-	logPrefix := fmt.Sprintf("[topology-syncer] %s/%s", TopologyInformationPath, net.JoinHostPort(is.info.IP, strconv.Itoa(int(is.info.Port))))
+	info := is.getLocalServerInfo()
+	logPrefix := fmt.Sprintf("[topology-syncer] %s/%s", TopologyInformationPath, net.JoinHostPort(info.IP, strconv.Itoa(int(info.Port))))
 	session, err := util2.NewSession(ctx, logPrefix, is.etcdCli, retryCnt, TopologySessionTTL)
 	if err != nil {
 		return err
@@ -875,7 +925,8 @@ func (is *InfoSyncer) updateTopologyAliveness(ctx context.Context) error {
 	if is.etcdCli == nil {
 		return nil
 	}
-	key := fmt.Sprintf("%s/%s/ttl", TopologyInformationPath, net.JoinHostPort(is.info.IP, strconv.Itoa(int(is.info.Port))))
+	info := is.getLocalServerInfo()
+	key := fmt.Sprintf("%s/%s/ttl", TopologyInformationPath, net.JoinHostPort(info.IP, strconv.Itoa(int(info.Port))))
 	return util.PutKVToEtcd(ctx, is.etcdCli, keyOpDefaultRetryCnt, key,
 		fmt.Sprintf("%v", time.Now().UnixNano()),
 		clientv3.WithLease(is.topologySession.Lease()))
@@ -989,7 +1040,9 @@ func getInfo(ctx context.Context, etcdCli *clientv3.Client, key string, retryCnt
 		}
 		for _, kv := range resp.Kvs {
 			info := &ServerInfo{
-				BinlogStatus: binloginfo.BinlogStatusUnknown.String(),
+				DynamicServerInfo: DynamicServerInfo{
+					BinlogStatus: binloginfo.BinlogStatusUnknown.String(),
+				},
 			}
 			err = info.Unmarshal(kv.Value)
 			if err != nil {
@@ -1008,15 +1061,19 @@ func getInfo(ctx context.Context, etcdCli *clientv3.Client, key string, retryCnt
 func getServerInfo(id string, serverIDGetter func() uint64) *ServerInfo {
 	cfg := config.GetGlobalConfig()
 	info := &ServerInfo{
-		ID:             id,
-		IP:             cfg.AdvertiseAddress,
-		Port:           cfg.Port,
-		StatusPort:     cfg.Status.StatusPort,
-		Lease:          cfg.Lease,
-		BinlogStatus:   binloginfo.GetStatus().String(),
-		StartTimestamp: time.Now().Unix(),
-		Labels:         cfg.Labels,
-		ServerIDGetter: serverIDGetter,
+		StaticServerInfo: StaticServerInfo{
+			ID:             id,
+			IP:             cfg.AdvertiseAddress,
+			Port:           cfg.Port,
+			StatusPort:     cfg.Status.StatusPort,
+			Lease:          cfg.Lease,
+			StartTimestamp: time.Now().Unix(),
+			ServerIDGetter: serverIDGetter,
+		},
+		DynamicServerInfo: DynamicServerInfo{
+			BinlogStatus: binloginfo.GetStatus().String(),
+			Labels:       maps.Clone(cfg.Labels),
+		},
 	}
 	info.Version = mysql.ServerVersion
 	info.GitHash = versioninfo.TiDBGitHash
@@ -1277,6 +1334,9 @@ func DeleteInternalSession(se any) {
 }
 
 // SetEtcdClient is only used for test.
+// SetEtcdClient is not thread-safe and may cause data race with the initialization of the domain.
+// Because this usage is test-only, we don't need to introduce a lock or atomic variable for it.
+// Use it after the domain initialization is done.
 func SetEtcdClient(etcdCli *clientv3.Client) {
 	is, err := getGlobalInfoSyncer()
 
@@ -1444,3 +1504,23 @@ func (is *InfoSyncer) getTiCDCServerInfo(ctx context.Context) ([]*TiCDCInfo, err
 	}
 	return nil, errors.Trace(err)
 }
+
+// getLocalServerInfo returns the local server info.
+func (is *InfoSyncer) getLocalServerInfo() *ServerInfo {
+	return is.info.Load()
+}
+
+// cloneDynamicServerInfo returns a clone of the dynamic server info.
+func (is *InfoSyncer) cloneDynamicServerInfo() *DynamicServerInfo {
+	return is.info.Load().DynamicServerInfo.clone()
+}
+
+// setDynamicServerInfo updates the dynamic server info.
+func (is *InfoSyncer) setDynamicServerInfo(ds *DynamicServerInfo) {
+	staticInfo := is.info.Load()
+	newInfo := &ServerInfo{
+		StaticServerInfo:  staticInfo.StaticServerInfo,
+		DynamicServerInfo: *ds,
+	}
+	is.info.Store(newInfo)
+}
diff --git a/pkg/domain/infosync/mock_info.go b/pkg/domain/infosync/mock_info.go
index 40a5d0880d..1064fa7f4f 100644
--- a/pkg/domain/infosync/mock_info.go
+++ b/pkg/domain/infosync/mock_info.go
@@ -86,15 +86,19 @@ func (m *MockGlobalServerInfoManager) getServerInfo(id string, serverIDGetter fu
 
 	// TODO: each mock server can have different config
 	info := &ServerInfo{
-		ID:             id,
-		IP:             cfg.AdvertiseAddress,
-		Port:           m.mockServerPort,
-		StatusPort:     cfg.Status.StatusPort,
-		Lease:          cfg.Lease,
-		BinlogStatus:   binloginfo.GetStatus().String(),
-		StartTimestamp: time.Now().Unix(),
-		Labels:         cfg.Labels,
-		ServerIDGetter: serverIDGetter,
+		StaticServerInfo: StaticServerInfo{
+			ID:             id,
+			IP:             cfg.AdvertiseAddress,
+			Port:           m.mockServerPort,
+			StatusPort:     cfg.Status.StatusPort,
+			Lease:          cfg.Lease,
+			StartTimestamp: time.Now().Unix(),
+			ServerIDGetter: serverIDGetter,
+		},
+		DynamicServerInfo: DynamicServerInfo{
+			BinlogStatus: binloginfo.GetStatus().String(),
+			Labels:       cfg.Labels,
+		},
 	}
 
 	m.mockServerPort++
diff --git a/pkg/lightning/config/config.go b/pkg/lightning/config/config.go
index 4fd41a08bc..735f7ce08b 100644
--- a/pkg/lightning/config/config.go
+++ b/pkg/lightning/config/config.go
@@ -104,7 +104,7 @@ const (
 )
 
 var (
-	supportedStorageTypes = []string{"file", "local", "s3", "noop", "gcs", "gs"}
+	supportedStorageTypes = []string{"file", "local", "s3", "noop", "gcs", "gs", "azure", "azblob"}
 
 	defaultFilter = []string{
 		"*.*",
diff --git a/pkg/planner/core/find_best_task.go b/pkg/planner/core/find_best_task.go
index a8188841f7..35fe1bea65 100644
--- a/pkg/planner/core/find_best_task.go
+++ b/pkg/planner/core/find_best_task.go
@@ -2854,10 +2854,12 @@ func (ds *DataSource) getOriginalPhysicalIndexScan(prop *property.PhysicalProper
 	rowCount := path.CountAfterAccess
 	is.initSchema(append(path.FullIdxCols, ds.commonHandleCols...), !isSingleScan)
 
-	// If (1) there exists an index whose selectivity is smaller than the threshold,
-	// and (2) there is Selection on the IndexScan, we don't use the ExpectedCnt to
+	// If (1) tidb_opt_ordering_index_selectivity_threshold is enabled (not 0)
+	// and (2) there exists an index whose selectivity is smaller than or equal to the threshold,
+	// and (3) there is Selection on the IndexScan, we don't use the ExpectedCnt to
 	// adjust the estimated row count of the IndexScan.
-	ignoreExpectedCnt := ds.accessPathMinSelectivity < ds.SCtx().GetSessionVars().OptOrderingIdxSelThresh &&
+	ignoreExpectedCnt := ds.SCtx().GetSessionVars().OptOrderingIdxSelThresh != 0 &&
+		ds.accessPathMinSelectivity <= ds.SCtx().GetSessionVars().OptOrderingIdxSelThresh &&
 		len(path.IndexFilters)+len(path.TableFilters) > 0
 
 	if (isMatchProp || prop.IsSortItemEmpty()) && prop.ExpectedCnt < ds.StatsInfo().RowCount && !ignoreExpectedCnt {
diff --git a/pkg/statistics/handle/globalstats/BUILD.bazel b/pkg/statistics/handle/globalstats/BUILD.bazel
index b97a69f799..e249c8cdfe 100644
--- a/pkg/statistics/handle/globalstats/BUILD.bazel
+++ b/pkg/statistics/handle/globalstats/BUILD.bazel
@@ -45,7 +45,7 @@ go_test(
     ],
     embed = [":globalstats"],
     flaky = True,
-    shard_count = 26,
+    shard_count = 27,
     deps = [
         "//pkg/domain",
         "//pkg/parser/model",
diff --git a/pkg/statistics/handle/globalstats/global_stats_async.go b/pkg/statistics/handle/globalstats/global_stats_async.go
index 2ef30c6cdb..9623d08c3f 100644
--- a/pkg/statistics/handle/globalstats/global_stats_async.go
+++ b/pkg/statistics/handle/globalstats/global_stats_async.go
@@ -384,9 +384,6 @@ func (a *AsyncMergePartitionStats2GlobalStats) loadCMsketch(sctx sessionctx.Cont
 			if err != nil {
 				return err
 			}
-			a.cmsketch <- mergeItem[*statistics.CMSketch]{
-				cmsketch, i,
-			}
 			select {
 			case a.cmsketch <- mergeItem[*statistics.CMSketch]{
 				cmsketch, i,


Test patch:
diff --git a/br/pkg/storage/azblob_test.go b/br/pkg/storage/azblob_test.go
index 75313dd27e..99f5dd4157 100644
--- a/br/pkg/storage/azblob_test.go
+++ b/br/pkg/storage/azblob_test.go
@@ -62,7 +62,7 @@ func TestAzblob(t *testing.T) {
 	builder := &sharedKeyAzuriteClientBuilder{}
 	skip, err := createContainer(ctx, builder, options.Bucket)
 	if skip || err != nil {
-		t.Log("azurite is not running, skip test")
+		t.Skip("azurite is not running, skip test")
 		return
 	}
 	require.NoError(t, err)
@@ -123,23 +123,34 @@ func TestAzblob(t *testing.T) {
 
 	efr, err := azblobStorage.Open(ctx, "key2", nil)
 	require.NoError(t, err)
+	size, err := efr.GetFileSize()
+	require.NoError(t, err)
+	require.EqualValues(t, 33, size)
+
+	realReader := efr.(*azblobObjectReader)
+	require.Nil(t, realReader.reader)
 
 	p := make([]byte, 10)
 	n, err := efr.Read(p)
 	require.NoError(t, err)
 	require.Equal(t, 10, n)
 	require.Equal(t, "data222233", string(p))
+	require.NotNil(t, realReader.reader)
+	oldInnerReader := realReader.reader
 
 	p = make([]byte, 40)
 	n, err = efr.Read(p)
 	require.NoError(t, err)
 	require.Equal(t, 23, n)
 	require.Equal(t, "46757222222222289722222", string(p[:23]))
+	require.Same(t, oldInnerReader, realReader.reader)
 
 	p = make([]byte, 5)
 	offs, err := efr.Seek(3, io.SeekStart)
 	require.NoError(t, err)
 	require.Equal(t, int64(3), offs)
+	// reader reopened
+	require.NotSame(t, oldInnerReader, realReader.reader)
 
 	n, err = efr.Read(p)
 	require.NoError(t, err)
diff --git a/br/tests/br_test_utils.sh b/br/tests/br_test_utils.sh
index 9102415a77..4b63324a18 100644
--- a/br/tests/br_test_utils.sh
+++ b/br/tests/br_test_utils.sh
@@ -25,7 +25,7 @@ wait_log_checkpoint_advance() {
     i=0
     while true; do
         # extract the checkpoint ts of the log backup task. If there is some error, the checkpoint ts should be empty
-        log_backup_status=$(unset BR_LOG_TO_TERM && run_br --skip-goleak --pd $PD_ADDR log status --task-name $task_name --json 2>br.log)
+        log_backup_status=$(unset BR_LOG_TO_TERM && run_br --pd $PD_ADDR log status --task-name $task_name --json 2>br.log)
         echo "log backup status: $log_backup_status"
         local checkpoint_ts=$(echo "$log_backup_status" | head -n 1 | jq 'if .[0].last_errors | length  == 0 then .[0].checkpoint else empty end')
         echo "checkpoint ts: $checkpoint_ts"
diff --git a/pkg/disttask/importinto/planner_test.go b/pkg/disttask/importinto/planner_test.go
index 61ae40a4f1..2688ec62d5 100644
--- a/pkg/disttask/importinto/planner_test.go
+++ b/pkg/disttask/importinto/planner_test.go
@@ -40,7 +40,7 @@ func TestLogicalPlan(t *testing.T) {
 		JobID:             1,
 		Plan:              importer.Plan{},
 		Stmt:              `IMPORT INTO db.tb FROM 'gs://test-load/*.csv?endpoint=xxx'`,
-		EligibleInstances: []*infosync.ServerInfo{{ID: "1"}},
+		EligibleInstances: []*infosync.ServerInfo{{StaticServerInfo: infosync.StaticServerInfo{ID: "1"}}},
 		ChunkMap:          map[int32][]Chunk{1: {{Path: "gs://test-load/1.csv"}}},
 	}
 	bs, err := logicalPlan.ToTaskMeta()
@@ -61,7 +61,7 @@ func TestToPhysicalPlan(t *testing.T) {
 			},
 		},
 		Stmt:              `IMPORT INTO db.tb FROM 'gs://test-load/*.csv?endpoint=xxx'`,
-		EligibleInstances: []*infosync.ServerInfo{{ID: "1"}},
+		EligibleInstances: []*infosync.ServerInfo{{StaticServerInfo: infosync.StaticServerInfo{ID: "1"}}},
 		ChunkMap:          map[int32][]Chunk{chunkID: {{Path: "gs://test-load/1.csv"}}},
 	}
 	planCtx := planner.PlanCtx{
diff --git a/pkg/disttask/importinto/scheduler_testkit_test.go b/pkg/disttask/importinto/scheduler_testkit_test.go
index 5efcfa01e0..7ec4b1084b 100644
--- a/pkg/disttask/importinto/scheduler_testkit_test.go
+++ b/pkg/disttask/importinto/scheduler_testkit_test.go
@@ -66,7 +66,7 @@ func TestSchedulerExtLocalSort(t *testing.T) {
 			DisableTiKVImportMode: true,
 		},
 		Stmt:              `IMPORT INTO db.tb FROM 'gs://test-load/*.csv?endpoint=xxx'`,
-		EligibleInstances: []*infosync.ServerInfo{{ID: "1"}},
+		EligibleInstances: []*infosync.ServerInfo{{StaticServerInfo: infosync.StaticServerInfo{ID: "1"}}},
 		ChunkMap:          map[int32][]importinto.Chunk{1: {{Path: "gs://test-load/1.csv"}}},
 	}
 	bs, err := logicalPlan.ToTaskMeta()
@@ -206,7 +206,7 @@ func TestSchedulerExtGlobalSort(t *testing.T) {
 			InImportInto:          true,
 		},
 		Stmt:              `IMPORT INTO db.tb FROM 'gs://test-load/*.csv?endpoint=xxx'`,
-		EligibleInstances: []*infosync.ServerInfo{{ID: "1"}},
+		EligibleInstances: []*infosync.ServerInfo{{StaticServerInfo: infosync.StaticServerInfo{ID: "1"}}},
 		ChunkMap: map[int32][]importinto.Chunk{
 			1: {{Path: "gs://test-load/1.csv"}},
 			2: {{Path: "gs://test-load/2.csv"}},
diff --git a/pkg/domain/domain_test.go b/pkg/domain/domain_test.go
index d318129cd8..9cb003ed2d 100644
--- a/pkg/domain/domain_test.go
+++ b/pkg/domain/domain_test.go
@@ -271,15 +271,23 @@ func TestClosestReplicaReadChecker(t *testing.T) {
 
 	mockedAllServerInfos := map[string]*infosync.ServerInfo{
 		"s1": {
-			ID: "s1",
-			Labels: map[string]string{
-				"zone": "zone1",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s1",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone1",
+				},
 			},
 		},
 		"s2": {
-			ID: "s2",
-			Labels: map[string]string{
-				"zone": "zone2",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s2",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone2",
+				},
 			},
 		},
 	}
@@ -345,33 +353,53 @@ func TestClosestReplicaReadChecker(t *testing.T) {
 	// partial matches
 	mockedAllServerInfos = map[string]*infosync.ServerInfo{
 		"s1": {
-			ID: "s1",
-			Labels: map[string]string{
-				"zone": "zone1",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s1",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone1",
+				},
 			},
 		},
 		"s2": {
-			ID: "s2",
-			Labels: map[string]string{
-				"zone": "zone2",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s2",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone2",
+				},
 			},
 		},
 		"s22": {
-			ID: "s22",
-			Labels: map[string]string{
-				"zone": "zone2",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s22",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone2",
+				},
 			},
 		},
 		"s3": {
-			ID: "s3",
-			Labels: map[string]string{
-				"zone": "zone3",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s3",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone3",
+				},
 			},
 		},
 		"s4": {
-			ID: "s4",
-			Labels: map[string]string{
-				"zone": "zone4",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID: "s4",
+			},
+			DynamicServerInfo: infosync.DynamicServerInfo{
+				Labels: map[string]string{
+					"zone": "zone4",
+				},
 			},
 		},
 	}
diff --git a/pkg/domain/infosync/info_test.go b/pkg/domain/infosync/info_test.go
index b0f4bca164..eb5dab0685 100644
--- a/pkg/domain/infosync/info_test.go
+++ b/pkg/domain/infosync/info_test.go
@@ -83,10 +83,11 @@ func TestTopology(t *testing.T) {
 	v, ok := topology.Labels["foo"]
 	require.True(t, ok)
 	require.Equal(t, "bar", v)
-	require.Equal(t, info.getTopologyInfo(), *topology)
+	selfInfo := info.getLocalServerInfo()
+	require.Equal(t, selfInfo.asTopologyInfo(), *topology)
 
-	nonTTLKey := fmt.Sprintf("%s/%s:%v/info", TopologyInformationPath, info.info.IP, info.info.Port)
-	ttlKey := fmt.Sprintf("%s/%s:%v/ttl", TopologyInformationPath, info.info.IP, info.info.Port)
+	nonTTLKey := fmt.Sprintf("%s/%s:%v/info", TopologyInformationPath, selfInfo.IP, selfInfo.Port)
+	ttlKey := fmt.Sprintf("%s/%s:%v/ttl", TopologyInformationPath, selfInfo.IP, selfInfo.Port)
 
 	err = util.DeleteKeyFromEtcd(nonTTLKey, client, util2.NewSessionDefaultRetryCnt, time.Second)
 	require.NoError(t, err)
@@ -104,7 +105,7 @@ func TestTopology(t *testing.T) {
 	dir := path.Dir(s)
 	require.Equal(t, dir, topology.DeployPath)
 	require.Equal(t, int64(1282967700), topology.StartTimestamp)
-	require.Equal(t, info.getTopologyInfo(), *topology)
+	require.Equal(t, info.getLocalServerInfo().asTopologyInfo(), *topology)
 
 	// check ttl key
 	ttlExists, err := info.ttlKeyExists(ctx)
@@ -123,7 +124,8 @@ func TestTopology(t *testing.T) {
 }
 
 func (is *InfoSyncer) getTopologyFromEtcd(ctx context.Context) (*TopologyInfo, error) {
-	key := fmt.Sprintf("%s/%s:%v/info", TopologyInformationPath, is.info.IP, is.info.Port)
+	info := is.getLocalServerInfo()
+	key := fmt.Sprintf("%s/%s:%v/info", TopologyInformationPath, info.IP, info.Port)
 	resp, err := is.etcdCli.Get(ctx, key)
 	if err != nil {
 		return nil, err
@@ -143,7 +145,8 @@ func (is *InfoSyncer) getTopologyFromEtcd(ctx context.Context) (*TopologyInfo, e
 }
 
 func (is *InfoSyncer) ttlKeyExists(ctx context.Context) (bool, error) {
-	key := fmt.Sprintf("%s/%s:%v/ttl", TopologyInformationPath, is.info.IP, is.info.Port)
+	info := is.getLocalServerInfo()
+	key := fmt.Sprintf("%s/%s:%v/ttl", TopologyInformationPath, info.IP, info.Port)
 	resp, err := is.etcdCli.Get(ctx, key)
 	if err != nil {
 		return false, err
@@ -280,3 +283,46 @@ func TestTiFlashManager(t *testing.T) {
 
 	CloseTiFlashManager(ctx)
 }
+
+func TestInfoSyncerMarshal(t *testing.T) {
+	info := &ServerInfo{
+		StaticServerInfo: StaticServerInfo{
+			ServerVersionInfo: ServerVersionInfo{
+				Version: "8.8.8",
+				GitHash: "123456",
+			},
+			ID:             "tidb1",
+			IP:             "127.0.0.1",
+			Port:           4000,
+			StatusPort:     10080,
+			Lease:          "1s",
+			StartTimestamp: 10000,
+			ServerIDGetter: func() uint64 { return 0 },
+			JSONServerID:   1,
+		},
+		DynamicServerInfo: DynamicServerInfo{
+			BinlogStatus: "binlog",
+			Labels:       map[string]string{"zone": "ap-northeast-1a"},
+		},
+	}
+	data, err := json.Marshal(info)
+	require.NoError(t, err)
+	require.Equal(t, data, []byte(`{"version":"8.8.8","git_hash":"123456",`+
+		`"ddl_id":"tidb1","ip":"127.0.0.1","listening_port":4000,"status_port":10080,"lease":"1s","start_timestamp":10000,`+
+		`"server_id":1,"binlog_status":"binlog","labels":{"zone":"ap-northeast-1a"}}`))
+	var decodeInfo *ServerInfo
+	err = json.Unmarshal(data, &decodeInfo)
+	require.NoError(t, err)
+	require.Nil(t, decodeInfo.ServerIDGetter)
+	require.Equal(t, info.Version, decodeInfo.Version)
+	require.Equal(t, info.GitHash, decodeInfo.GitHash)
+	require.Equal(t, info.ID, decodeInfo.ID)
+	require.Equal(t, info.IP, decodeInfo.IP)
+	require.Equal(t, info.Port, decodeInfo.Port)
+	require.Equal(t, info.StatusPort, decodeInfo.StatusPort)
+	require.Equal(t, info.Lease, decodeInfo.Lease)
+	require.Equal(t, info.StartTimestamp, decodeInfo.StartTimestamp)
+	require.Equal(t, info.JSONServerID, decodeInfo.JSONServerID)
+	require.Equal(t, info.BinlogStatus, decodeInfo.BinlogStatus)
+	require.Equal(t, info.Labels, decodeInfo.Labels)
+}
diff --git a/pkg/planner/cardinality/selectivity_test.go b/pkg/planner/cardinality/selectivity_test.go
index 8f19734a47..be4687ada3 100644
--- a/pkg/planner/cardinality/selectivity_test.go
+++ b/pkg/planner/cardinality/selectivity_test.go
@@ -1045,7 +1045,7 @@ func TestOrderingIdxSelectivityThreshold(t *testing.T) {
 
 	testKit.MustExec("use test")
 	testKit.MustExec("drop table if exists t")
-	testKit.MustExec("create table t(a int primary key , b int, c int, index ib(b), index ic(c))")
+	testKit.MustExec("create table t(a int primary key , b int, c int, d int, index ib(b), index ic(c))")
 	require.NoError(t, h.HandleDDLEvent(<-h.DDLEventCh()))
 	is := dom.InfoSchema()
 	tb, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t"))
@@ -1055,7 +1055,7 @@ func TestOrderingIdxSelectivityThreshold(t *testing.T) {
 	// Mock the stats:
 	// total row count 100000
 	// column a: PK, from 0 to 100000, NDV 100000
-	// column b, c: from 0 to 10000, each value has 10 rows, NDV 10000
+	// column b, c, d: from 0 to 10000, each value has 10 rows, NDV 10000
 	// indexes are created on (b), (c) respectively
 	mockStatsTbl := mockStatsTable(tblInfo, 100000)
 	pkColValues, err := generateIntDatum(1, 100000)
@@ -1075,7 +1075,7 @@ func TestOrderingIdxSelectivityThreshold(t *testing.T) {
 		idxValues = append(idxValues, types.NewBytesDatum(b))
 	}
 
-	for i := 2; i <= 3; i++ {
+	for i := 2; i <= 4; i++ {
 		mockStatsTbl.Columns[int64(i)] = &statistics.Column{
 			Histogram:         *mockStatsHistogram(int64(i), colValues, 10, types.NewFieldType(mysql.TypeLonglong)),
 			Info:              tblInfo.Columns[i-1],
diff --git a/pkg/planner/cardinality/testdata/cardinality_suite_in.json b/pkg/planner/cardinality/testdata/cardinality_suite_in.json
index 32ad34f22f..f44413a6dd 100644
--- a/pkg/planner/cardinality/testdata/cardinality_suite_in.json
+++ b/pkg/planner/cardinality/testdata/cardinality_suite_in.json
@@ -281,6 +281,8 @@
       "explain format = 'brief' select * from t where a < 9999 order by c limit 1",
       "explain format = 'brief' select * from t where b >= 0 and b <= 100 or c >= 0 and c <= 100 order by c limit 1",
       "explain format = 'brief' select * from t where a < 1000 and c < 100 limit 1",
+      "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+      "explain format = 'brief' select * from t where d < 9950 order by c limit 1",
       "set @@tidb_opt_ordering_index_selectivity_threshold = 0.1",
       "explain format = 'brief' select * from t where b >= 9950 order by c limit 1",
       "explain format = 'brief' select * from t where b >= 9950 order by c desc limit 1",
@@ -293,7 +295,12 @@
       "explain format = 'brief' select * from t where b >= 0 and b <= 50 or c >= 0 and c <= 50 order by c limit 1",
       "explain format = 'brief' select * from t where b >= 9950 and c >= 9950 order by c limit 1",
       "explain format = 'brief' select * from t where b >= 9950 and c >= 9900 order by c limit 1",
-      "explain format = 'brief' select * from t where a < 1000 and c < 100 limit 1"
+      "explain format = 'brief' select * from t where a < 1000 and c < 100 limit 1",
+      "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+      "explain format = 'brief' select * from t where d < 9950 order by c limit 1",
+      "set @@tidb_opt_ordering_index_selectivity_threshold = 1",
+      "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+      "explain format = 'brief' select * from t where d < 9950 order by c limit 1"
     ]
   },
   {
@@ -353,4 +360,4 @@
       "explain format = 'brief' select a, b from t where a = 1 and b = 1"
     ]
   }
-]
\ No newline at end of file
+]
diff --git a/pkg/planner/cardinality/testdata/cardinality_suite_out.json b/pkg/planner/cardinality/testdata/cardinality_suite_out.json
index 86b8d2a56a..113c9d4b51 100644
--- a/pkg/planner/cardinality/testdata/cardinality_suite_out.json
+++ b/pkg/planner/cardinality/testdata/cardinality_suite_out.json
@@ -1071,6 +1071,26 @@
           "      └─TableRangeScan 100.00 cop[tikv] table:t range:[-inf,1000), keep order:false, stats:pseudo"
         ]
       },
+      {
+        "Query": "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+        "Result": [
+          "Limit 1.00 root  offset:0, count:1",
+          "└─IndexLookUp 1.00 root  ",
+          "  ├─IndexFullScan(Build) 200.00 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
+          "  └─Selection(Probe) 1.00 cop[tikv]  ge(test.t.d, 9950)",
+          "    └─TableRowIDScan 200.00 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
+      },
+      {
+        "Query": "explain format = 'brief' select * from t where d < 9950 order by c limit 1",
+        "Result": [
+          "Limit 1.00 root  offset:0, count:1",
+          "└─IndexLookUp 1.00 root  ",
+          "  ├─IndexFullScan(Build) 1.01 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
+          "  └─Selection(Probe) 1.00 cop[tikv]  lt(test.t.d, 9950)",
+          "    └─TableRowIDScan 1.01 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
+      },
       {
         "Query": "set @@tidb_opt_ordering_index_selectivity_threshold = 0.1",
         "Result": null
@@ -1108,11 +1128,11 @@
       {
         "Query": "explain format = 'brief' select * from t where b >= 9000 order by c limit 1",
         "Result": [
-          "Limit 1.00 root  offset:0, count:1",
-          "└─IndexLookUp 1.00 root  ",
-          "  ├─IndexFullScan(Build) 10.00 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
-          "  └─Selection(Probe) 1.00 cop[tikv]  ge(test.t.b, 9000)",
-          "    └─TableRowIDScan 10.00 cop[tikv] table:t keep order:false, stats:pseudo"
+          "TopN 1.00 root  test.t.c, offset:0, count:1",
+          "└─TableReader 1.00 root  data:TopN",
+          "  └─TopN 1.00 cop[tikv]  test.t.c, offset:0, count:1",
+          "    └─Selection 10000.00 cop[tikv]  ge(test.t.b, 9000)",
+          "      └─TableFullScan 100000.00 cop[tikv] table:t keep order:false, stats:pseudo"
         ]
       },
       {
@@ -1138,11 +1158,10 @@
       {
         "Query": "explain format = 'brief' select * from t where a < 10000 order by c limit 1",
         "Result": [
-          "IndexLookUp 1.00 root  limit embedded(offset:0, count:1)",
-          "├─Limit(Build) 1.00 cop[tikv]  offset:0, count:1",
-          "│ └─Selection 1.00 cop[tikv]  lt(test.t.a, 10000)",
-          "│   └─IndexFullScan 10.00 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
-          "└─TableRowIDScan(Probe) 1.00 cop[tikv] table:t keep order:false, stats:pseudo"
+          "TopN 1.00 root  test.t.c, offset:0, count:1",
+          "└─TableReader 1.00 root  data:TopN",
+          "  └─TopN 1.00 cop[tikv]  test.t.c, offset:0, count:1",
+          "    └─TableRangeScan 10000.00 cop[tikv] table:t range:[-inf,10000), keep order:false, stats:pseudo"
         ]
       },
       {
@@ -1195,6 +1214,50 @@
           "    └─Selection 1.00 cop[tikv]  lt(test.t.c, 100)",
           "      └─TableRangeScan 100.00 cop[tikv] table:t range:[-inf,1000), keep order:false, stats:pseudo"
         ]
+      },
+      {
+        "Query": "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+        "Result": [
+          "Limit 1.00 root  offset:0, count:1",
+          "└─IndexLookUp 1.00 root  ",
+          "  ├─IndexFullScan(Build) 200.00 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
+          "  └─Selection(Probe) 1.00 cop[tikv]  ge(test.t.d, 9950)",
+          "    └─TableRowIDScan 200.00 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
+      },
+      {
+        "Query": "explain format = 'brief' select * from t where d < 9950 order by c limit 1",
+        "Result": [
+          "Limit 1.00 root  offset:0, count:1",
+          "└─IndexLookUp 1.00 root  ",
+          "  ├─IndexFullScan(Build) 1.01 cop[tikv] table:t, index:ic(c) keep order:true, stats:pseudo",
+          "  └─Selection(Probe) 1.00 cop[tikv]  lt(test.t.d, 9950)",
+          "    └─TableRowIDScan 1.01 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
+      },
+      {
+        "Query": "set @@tidb_opt_ordering_index_selectivity_threshold = 1",
+        "Result": null
+      },
+      {
+        "Query": "explain format = 'brief' select * from t where d >= 9950 order by c limit 1",
+        "Result": [
+          "TopN 1.00 root  test.t.c, offset:0, count:1",
+          "└─TableReader 1.00 root  data:TopN",
+          "  └─TopN 1.00 cop[tikv]  test.t.c, offset:0, count:1",
+          "    └─Selection 500.00 cop[tikv]  ge(test.t.d, 9950)",
+          "      └─TableFullScan 100000.00 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
+      },
+      {
+        "Query": "explain format = 'brief' select * from t where d < 9950 order by c limit 1",
+        "Result": [
+          "TopN 1.00 root  test.t.c, offset:0, count:1",
+          "└─TableReader 1.00 root  data:TopN",
+          "  └─TopN 1.00 cop[tikv]  test.t.c, offset:0, count:1",
+          "    └─Selection 99500.00 cop[tikv]  lt(test.t.d, 9950)",
+          "      └─TableFullScan 100000.00 cop[tikv] table:t keep order:false, stats:pseudo"
+        ]
       }
     ]
   },
diff --git a/pkg/server/handler/tests/BUILD.bazel b/pkg/server/handler/tests/BUILD.bazel
index 687e94eef2..cd6b30e4b6 100644
--- a/pkg/server/handler/tests/BUILD.bazel
+++ b/pkg/server/handler/tests/BUILD.bazel
@@ -9,7 +9,8 @@ go_test(
         "main_test.go",
     ],
     flaky = True,
-    shard_count = 39,
+    race = "on",
+    shard_count = 40,
     deps = [
         "//pkg/config",
         "//pkg/ddl",
diff --git a/pkg/server/handler/tests/http_handler_test.go b/pkg/server/handler/tests/http_handler_test.go
index ea40ec0968..dc08687efc 100644
--- a/pkg/server/handler/tests/http_handler_test.go
+++ b/pkg/server/handler/tests/http_handler_test.go
@@ -30,6 +30,7 @@ import (
 	"net/http/httptest"
 	"net/http/httputil"
 	"sort"
+	"sync"
 	"testing"
 	"time"
 
@@ -1214,6 +1215,7 @@ func TestSetLabelsWithEtcd(t *testing.T) {
 	ts.startServer(t)
 	defer ts.stopServer(t)
 
+	time.Sleep(time.Second)
 	integration.BeforeTestExternal(t)
 	cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
 	defer cluster.Terminate(t)
@@ -1420,33 +1422,39 @@ func testUpgradeShow(t *testing.T, ts *basicHTTPHandlerTestSuite) {
 	// check the result for upgrade show
 	mockedAllServerInfos := map[string]*infosync.ServerInfo{
 		"s0": {
-			ID:           ddlID,
-			IP:           "127.0.0.1",
-			Port:         4000,
-			JSONServerID: 0,
-			ServerVersionInfo: infosync.ServerVersionInfo{
-				Version: "ver",
-				GitHash: "hash",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID:           ddlID,
+				IP:           "127.0.0.1",
+				Port:         4000,
+				JSONServerID: 0,
+				ServerVersionInfo: infosync.ServerVersionInfo{
+					Version: "ver",
+					GitHash: "hash",
+				},
 			},
 		},
 		"s2": {
-			ID:           "ID2",
-			IP:           "127.0.0.1",
-			Port:         4002,
-			JSONServerID: 2,
-			ServerVersionInfo: infosync.ServerVersionInfo{
-				Version: "ver2",
-				GitHash: "hash2",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID:           "ID2",
+				IP:           "127.0.0.1",
+				Port:         4002,
+				JSONServerID: 2,
+				ServerVersionInfo: infosync.ServerVersionInfo{
+					Version: "ver2",
+					GitHash: "hash2",
+				},
 			},
 		},
 		"s1": {
-			ID:           "ID1",
-			IP:           "127.0.0.1",
-			Port:         4001,
-			JSONServerID: 1,
-			ServerVersionInfo: infosync.ServerVersionInfo{
-				Version: "ver",
-				GitHash: "hash",
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID:           "ID1",
+				IP:           "127.0.0.1",
+				Port:         4001,
+				JSONServerID: 1,
+				ServerVersionInfo: infosync.ServerVersionInfo{
+					Version: "ver",
+					GitHash: "hash",
+				},
 			},
 		},
 	}
@@ -1513,3 +1521,66 @@ func TestIssue52608(t *testing.T) {
 	require.Equal(t, on, true)
 	require.Equal(t, addr[:10], "127.0.0.1:")
 }
+
+func TestSetLabelsConcurrentWithStoreTopology(t *testing.T) {
+	ts := createBasicHTTPHandlerTestSuite()
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	ts.startServer(t)
+	defer ts.stopServer(t)
+
+	time.Sleep(time.Second)
+	integration.BeforeTestExternal(t)
+	cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
+	defer cluster.Terminate(t)
+	client := cluster.RandClient()
+	infosync.SetEtcdClient(client)
+
+	ts.domain.InfoSyncer().Restart(ctx)
+	ts.domain.InfoSyncer().RestartTopology(ctx)
+
+	testUpdateLabels := func() {
+		labels := map[string]string{}
+		labels["zone"] = fmt.Sprintf("z-%v", rand.Intn(100000))
+		buffer := bytes.NewBuffer([]byte{})
+		require.Nil(t, json.NewEncoder(buffer).Encode(labels))
+		resp, err := ts.PostStatus("/labels", "application/json", buffer)
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		defer func() {
+			require.NoError(t, resp.Body.Close())
+		}()
+		require.Equal(t, http.StatusOK, resp.StatusCode)
+		newLabels := config.GetGlobalConfig().Labels
+		require.Equal(t, newLabels, labels)
+	}
+	testStoreTopology := func() {
+		require.NoError(t, ts.domain.InfoSyncer().StoreTopologyInfo(context.Background()))
+	}
+
+	done := make(chan struct{})
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		for {
+			select {
+			case <-done:
+				return
+			default:
+				testStoreTopology()
+			}
+		}
+	}()
+	for i := 0; i < 100; i++ {
+		testUpdateLabels()
+	}
+	close(done)
+	wg.Wait()
+
+	// reset the global variable
+	config.UpdateGlobal(func(conf *config.Config) {
+		conf.Labels = map[string]string{}
+	})
+}
diff --git a/pkg/statistics/handle/autoanalyze/autoanalyze_test.go b/pkg/statistics/handle/autoanalyze/autoanalyze_test.go
index 08de6ff5c8..2f46ce2fb7 100644
--- a/pkg/statistics/handle/autoanalyze/autoanalyze_test.go
+++ b/pkg/statistics/handle/autoanalyze/autoanalyze_test.go
@@ -399,14 +399,18 @@ func makeFailpointRes(t *testing.T, v any) string {
 func getMockedServerInfo() map[string]*infosync.ServerInfo {
 	mockedAllServerInfos := map[string]*infosync.ServerInfo{
 		"s1": {
-			ID:   "s1",
-			IP:   "127.0.0.1",
-			Port: 4000,
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID:   "s1",
+				IP:   "127.0.0.1",
+				Port: 4000,
+			},
 		},
 		"s2": {
-			ID:   "s2",
-			IP:   "127.0.0.2",
-			Port: 4000,
+			StaticServerInfo: infosync.StaticServerInfo{
+				ID:   "s2",
+				IP:   "127.0.0.2",
+				Port: 4000,
+			},
 		},
 	}
 	return mockedAllServerInfos
diff --git a/pkg/statistics/handle/globalstats/global_stats_test.go b/pkg/statistics/handle/globalstats/global_stats_test.go
index 71f3717d48..1f7690a5e1 100644
--- a/pkg/statistics/handle/globalstats/global_stats_test.go
+++ b/pkg/statistics/handle/globalstats/global_stats_test.go
@@ -959,3 +959,23 @@ func TestGlobalStatsAndSQLBindingWithConcurrency(t *testing.T) {
 	tk.MustExec("set global tidb_merge_partition_stats_concurrency=2")
 	testGlobalStatsAndSQLBinding(tk)
 }
+
+func TestMergeGlobalStatsForCMSketch(t *testing.T) {
+	store := testkit.CreateMockStore(t)
+	tk := testkit.NewTestKit(t, store)
+	tk.MustExec("use test")
+	tk.MustExec("drop table if exists t")
+	tk.MustExec(`
+		create table t (a int) partition by range (a) (
+			partition p0 values less than (10),
+			partition p1 values less than (20)
+		)`)
+	tk.MustExec("set @@tidb_analyze_version=1")
+	tk.MustExec("set @@tidb_partition_prune_mode='dynamic'")
+	tk.MustExec("insert into t values (1), (2), (3), (4), (5), (6), (6), (null), (11), (12), (13), (14), (15), (16), (17), (18), (19), (19)")
+	tk.MustExec("analyze table t")
+	tk.MustQuery("explain select * from t where a = 1").Check(
+		testkit.Rows("TableReader_7 1.00 root partition:p0 data:Selection_6",
+			"└─Selection_6 1.00 cop[tikv]  eq(test.t.a, 1)",
+			"  └─TableFullScan_5 18.00 cop[tikv] table:t keep order:false"))
+}
diff --git a/pkg/util/disttask/idservice_test.go b/pkg/util/disttask/idservice_test.go
index d991b6b4b8..bde16de238 100644
--- a/pkg/util/disttask/idservice_test.go
+++ b/pkg/util/disttask/idservice_test.go
@@ -24,15 +24,15 @@ import (
 // This testCase show GenerateExecID only generate string by input parametas
 func TestGenServerID(t *testing.T) {
 	var str string
-	serverIO := GenerateExecID(&infosync.ServerInfo{IP: "", Port: 0})
+	serverIO := GenerateExecID(&infosync.ServerInfo{StaticServerInfo: infosync.StaticServerInfo{IP: "", Port: 0}})
 	require.Equal(t, serverIO, ":0")
-	serverIO = GenerateExecID(&infosync.ServerInfo{IP: "10.124.122.25", Port: 3456})
+	serverIO = GenerateExecID(&infosync.ServerInfo{StaticServerInfo: infosync.StaticServerInfo{IP: "10.124.122.25", Port: 3456}})
 	require.Equal(t, serverIO, "10.124.122.25:3456")
-	serverIO = GenerateExecID(&infosync.ServerInfo{IP: "10.124", Port: 3456})
+	serverIO = GenerateExecID(&infosync.ServerInfo{StaticServerInfo: infosync.StaticServerInfo{IP: "10.124", Port: 3456}})
 	require.Equal(t, serverIO, "10.124:3456")
-	serverIO = GenerateExecID(&infosync.ServerInfo{IP: str, Port: 65537})
+	serverIO = GenerateExecID(&infosync.ServerInfo{StaticServerInfo: infosync.StaticServerInfo{IP: str, Port: 65537}})
 	require.Equal(t, serverIO, ":65537")
 	// IPv6 testcase
-	serverIO = GenerateExecID(&infosync.ServerInfo{IP: "ABCD:EF01:2345:6789:ABCD:EF01:2345:6789", Port: 65537})
+	serverIO = GenerateExecID(&infosync.ServerInfo{StaticServerInfo: infosync.StaticServerInfo{IP: "ABCD:EF01:2345:6789:ABCD:EF01:2345:6789", Port: 65537}})
 	require.Equal(t, serverIO, "[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:65537")
 }