Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
15c9eb7
external workload: wire GCV2 coordination
AmoebaProtozoa Jun 29, 2026
8fa56b2
tidb-server: keep store bootstrap signature unchanged
AmoebaProtozoa Jun 29, 2026
4e58167
gcworker: simplify GCV2 notification flow
AmoebaProtozoa Jun 29, 2026
b08d1c3
external workload: tighten GCV2 wiring
AmoebaProtozoa Jun 29, 2026
caa2a95
external workload: bind manager to storage
AmoebaProtozoa Jun 29, 2026
780583e
external workload: simplify GCV2 startup wiring
AmoebaProtozoa Jun 29, 2026
afdb3e2
external workload: install GCV2 manager before bootstrap
AmoebaProtozoa Jun 29, 2026
a544f8f
external workload: trim GCV2 session test
AmoebaProtozoa Jun 29, 2026
51df1b2
external workload: keep store setter local
AmoebaProtozoa Jun 29, 2026
da94530
external workload: inline keyspace GC checks
AmoebaProtozoa Jun 29, 2026
268aac7
external workload: flatten role helpers
AmoebaProtozoa Jun 29, 2026
75ede41
external workload: store manager in kv options
AmoebaProtozoa Jun 29, 2026
7570947
external workload: preserve role predicate helper
AmoebaProtozoa Jun 29, 2026
d76cf6b
tidb-server: return external workload manager from bootstrap
AmoebaProtozoa Jun 29, 2026
d260bb8
external workload: thread manager explicitly
AmoebaProtozoa Jun 30, 2026
23f740c
external workload: delay manager init until storage validated
AmoebaProtozoa Jun 30, 2026
8fc1a4c
external workload: fix GCV2 fallback ordering
AmoebaProtozoa Jun 30, 2026
8593452
external workload: keep manager on storage
AmoebaProtozoa Jun 30, 2026
2babebc
external workload: simplify GCV2 notification
AmoebaProtozoa Jun 30, 2026
5aa3390
external workload: use explicit GCV2 role checks
AmoebaProtozoa Jun 30, 2026
f60833f
external workload: move GCV2 abort helper
AmoebaProtozoa Jun 30, 2026
890ad29
external workload: tighten GCV2 wiring
AmoebaProtozoa Jun 30, 2026
b677f79
external workload: fold GCV2 abort utility
AmoebaProtozoa Jun 30, 2026
ab0e556
external workload: make GCV2 notification best effort
AmoebaProtozoa Jun 30, 2026
848d589
external workload: address GCV2 review feedback
AmoebaProtozoa Jul 2, 2026
24c71de
external workload: seed GCV2 with effective GC lifetime
AmoebaProtozoa Jul 8, 2026
2e3d8d3
external workload: tighten GCV2 upgrade and lifetime handling
AmoebaProtozoa Jul 14, 2026
c9f91f8
external workload: align GCV2 conversions and exit logging
AmoebaProtozoa Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cmd/tidb-server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ go_library(
"@com_github_tikv_client_go_v2//tikv",
"@com_github_tikv_client_go_v2//tikvrpc",
"@com_github_tikv_client_go_v2//txnkv/transaction",
"@com_github_tikv_pd_client//:client",
"@org_uber_go_automaxprocs//maxprocs",
"@org_uber_go_zap//:zap",
],
Expand Down Expand Up @@ -111,16 +112,20 @@ go_test(
srcs = ["main_test.go"],
embed = [":tidb-server_lib"],
flaky = True,
shard_count = 11,
shard_count = 12,
deps = [
"//pkg/config",
"//pkg/config/deploymode",
"//pkg/config/kerneltype",
"//pkg/extworkload",
"//pkg/parser/mysql",
"//pkg/sessionctx/vardef",
"//pkg/sessionctx/variable",
"//pkg/testkit",
"//pkg/testkit/testsetup",
"@com_github_pingcap_kvproto//pkg/keyspacepb",
"@com_github_stretchr_testify//require",
"@com_github_tikv_pd_client//:client",
"@io_opencensus_go//stats/view",
"@org_uber_go_goleak//:goleak",
],
Expand Down
60 changes: 55 additions & 5 deletions cmd/tidb-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import (
"github.com/tikv/client-go/v2/tikv"
"github.com/tikv/client-go/v2/tikvrpc"
"github.com/tikv/client-go/v2/txnkv/transaction"
pd "github.com/tikv/pd/client"
"go.uber.org/automaxprocs/maxprocs"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -321,33 +322,81 @@ func initDeployMode(cfg *config.Config) error {
}

func initExternalWorkloadManager(ctx context.Context, storage kv.Storage) extworkload.Manager {
if !deploymode.IsStarter() {
return nil
}
cfg := config.GetGlobalConfig().ExternalWorkload
if !cfg.Enable {
return nil
}
// Manager init is non-fatal; TiDB can continue without external workload coordination.
// Non-GCV2 roles can continue without coordination, but a dedicated GCV2
// worker must not run without the controller.
meta := storage.GetCodec().GetKeyspaceMeta()
if meta == nil {
if cfg.Role == config.RoleGCV2Worker {
logutil.BgLogger().Fatal("external workload GCV2 role requires keyspace meta")
}
logutil.BgLogger().Warn("external workload controller enabled but keyspace meta is unavailable; TiDB will continue without external workload coordination")
return nil
}
if cfg.Role == config.RoleGCV2Worker && !pd.IsKeyspaceUsingKeyspaceLevelGC(meta) {
logutil.BgLogger().Fatal("external workload GCV2 role requires keyspace-level GC")
}
mgr, err := extworkload.NewManager(ctx, meta, cfg)
if err != nil {
if cfg.Role == config.RoleGCV2Worker {
logutil.BgLogger().Fatal("failed to initialize external workload manager for GCV2 role", zap.Error(err))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [Major] A temporary PAUSED controller response becomes a fatal worker restart loop

Why
The client exposes ErrControllerPaused as a distinct temporary scheduler outcome, but dedicated GCV2 initialization treats every NewManager error as fatal. A normal PAUSED response from the startup Ping is therefore indistinguishable from a permanent configuration or connectivity failure.

Scope
cmd/tidb-server/main.go:348

Risk if unchanged
A deliberately paused GCV2 pool can make every dedicated worker exit non-zero and be restarted repeatedly by its supervisor, creating unnecessary controller and startup load instead of remaining predictably paused.

Evidence
dialClient returns the annotated Ping error while preserving the typed PAUSED cause, and the dedicated-role branch immediately calls Fatal for any manager-construction error. No caller matches ErrControllerPaused, even though its contract explicitly permits callers to do so.

Change request
Please handle ErrControllerPaused according to an explicit pause contract, such as a clean bounded wait/retry or clean exit, and reserve Fatal for permanent invalid state. Add a startup test for a PAUSED Ping response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I checked the current controller implementation: startup Ping always returns success and PingRequest carries no worker role or paused scheduling state. Pause is enforced by the controller poller, so ErrControllerPaused cannot currently be returned from NewManager on this path. The dedicated role therefore still fails closed for actual configuration, connectivity, or initialization errors; no change was made here.

}
logutil.BgLogger().Warn("failed to initialize external workload manager; TiDB will continue without external workload coordination", zap.Error(err))
return nil
}
extworkload.SetManagerForStore(storage, mgr)
return mgr
}

func closeExternalWorkloadManager(mgr extworkload.Manager) {
func closeExternalWorkloadManager(storage kv.Storage, mgr extworkload.Manager) {
if mgr == nil {
return
}
extworkload.SetManagerForStore(storage, nil)
if err := mgr.Close(); err != nil {
logutil.BgLogger().Warn("failed to close external workload manager", zap.Error(err))
}
}

func loadExternalWorkloadGCLifeTime(ctx context.Context, storage kv.Storage) (time.Duration, error) {
se, err := session.CreateSession(storage)
if err != nil {
return 0, err
}
defer se.Close()
gcLifeTimeVal, err := variable.GetSysVar(vardef.TiDBGCLifetime).GetGlobalFromHook(ctx, se.GetSessionVars())
if err != nil {
return 0, err
}
gcLifeTime, err := time.ParseDuration(gcLifeTimeVal)
if err != nil {
return 0, err
}
return gcLifeTime, nil
}

func initializeExternalWorkloadGCV2(ctx context.Context, storage kv.Storage, mgr extworkload.Manager) {
Comment on lines +367 to +384

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move those logic away into extworkload pkg

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I kept this startup orchestration in cmd/tidb-server because loading the effective global sysvar requires creating a session; moving that into extworkload would introduce a reverse dependency from the coordination package back to session startup. The manager operation and duration conversion remain owned by extworkload.

if !extworkload.IsMaster(mgr) || !pd.IsKeyspaceUsingKeyspaceLevelGC(mgr.Meta()) {
return
}
gcLifeTime, err := loadExternalWorkloadGCLifeTime(ctx, storage)
if err != nil {
logutil.BgLogger().Warn("failed to load GC life time for external workload GCV2 task; TiDB will continue without external workload coordination", zap.Error(err))
closeExternalWorkloadManager(storage, mgr)
return
}
if err := mgr.InitializeGCV2(ctx, gcLifeTime); err != nil {
logutil.BgLogger().Warn("failed to initialize external workload GCV2 task; TiDB will continue without external workload coordination", zap.Error(err))
closeExternalWorkloadManager(storage, mgr)
}
}

func main() {
fset := initFlagSet()
if args := fset.Args(); len(args) != 0 {
Expand Down Expand Up @@ -480,9 +529,8 @@ func main() {
storage, dom, err := createStoreDDLOwnerMgrAndDomain(keyspaceName)
terror.MustNil(err)
repository.SetupRepository(dom)
if deploymode.IsStarter() {
externalWorkloadManager := initExternalWorkloadManager(context.Background(), storage)
defer closeExternalWorkloadManager(externalWorkloadManager)
if externalWorkloadManager := extworkload.GetManagerFromStore(storage); externalWorkloadManager != nil {
defer closeExternalWorkloadManager(storage, externalWorkloadManager)
}
svr := createServer(storage, dom)
if standbyController != nil {
Expand Down Expand Up @@ -631,6 +679,7 @@ func createStoreDDLOwnerMgrAndDomain(keyspaceName string) (kv.Storage, *domain.D
}
}
}
externalWorkloadManager := initExternalWorkloadManager(context.Background(), storage)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [Major] Mixed-version and rollback behavior is not gated or proven

Why
This change starts creating and mutating controller-side GCV2 task state whenever the existing external-workload config is enabled, but there is no TiDB/controller capability gate or rollout epoch. An N-1 TiDB lacks the new initialization, post-GC recycle/register, lifetime-update, and upgrade-abort paths, while controller task state can outlive a rollback.

Scope
cmd/tidb-server/main.go:682; pkg/store/gcworker/gc_worker.go:842; pkg/executor/set.go:195

Risk if unchanged
During a rolling upgrade, controller progress can pause when GC leadership moves to an older node. After downgrade, outstanding tasks created by the newer binary can remain unadvanced or repeatedly schedule workers that the older binary never recycles, without an actionable incompatibility error or cleanup step.

Evidence
The change attaches the manager before bootstrap and activates GCV2 RPCs for eligible roles without adding min-version or capability negotiation, a persisted rollout gate, downgrade cleanup, a mixed-version test, or an N-to-N-1 rollback test. The new tests use one in-process binary and mocked managers.

Change request
Please define and prove the N/N-1 compatibility contract. Add an explicit capability/version gate or implement rollback cleanup, then add a mixed-version test that transfers GC leadership across N and N-1 nodes plus a downgrade test with pre-existing controller tasks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. This integration is opt-in through the existing Starter external-workload configuration. The rollout contract is to keep it disabled during a mixed-version TiDB rollout, enable it only after the supporting fleet is in place, and disable/drain it before rollback. Under that contract an N-1 node never participates in the controller protocol while the feature is active, so I did not add a second binary capability gate in this PR.

copr.GlobalMPPFailedStoreProber.Run()
mppcoordmanager.InstanceMPPCoordinatorManager.Run()
// Bootstrap a session to load information schema.
Expand All @@ -642,6 +691,7 @@ func createStoreDDLOwnerMgrAndDomain(keyspaceName string) (kv.Storage, *domain.D
if err != nil {
return nil, nil, err
}
initializeExternalWorkloadGCV2(context.Background(), storage, externalWorkloadManager)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [Major] GCV2 initialization can be reordered behind the first GC notification

Why
The manager is published on the store before BootstrapSession, but InitializeGCV2 is deferred until after BootstrapSession returns. Bootstrap starts the GC worker, whose first Starter-mode tick can launch a GC job immediately, so a completed GC can recycle and register safe point S before startup later seeds the controller with safe point 0.

Scope
cmd/tidb-server/main.go:694 (cross-function handoff through pkg/session/session.go:4548 and pkg/store/gcworker/gc_worker.go:128,215,454,847)

Risk if unchanged
The controller can observe a non-monotonic S -> 0 transition. Depending on replacement and queue semantics, that can recreate stale work, overwrite current task state, retain obsolete work, or schedule the next round from a stale heartbeat after restart.

Evidence
initExternalWorkloadManager binds the manager before BootstrapSession(storage). During bootstrap, raw.StartGCWorker() starts a first production Starter tick that does not apply gcWaitTime until one job has completed. If that job finishes first, notifyGCV2AfterGC sends safe point S, after which initializeExternalWorkloadGCV2 unconditionally calls RegisterGCV2(ctx, 0, gcLifeTime). The protocol describes this as the safe point of the most recent keyspace-level GC.

Change request
Please establish a monotonic initialization barrier before the GC worker can observe the manager, or seed from current controller/GC state under an ordering mechanism that prevents a later zero registration. Add a focused startup-ordering test in which the first GC notification completes before initialization and assert that the controller never observes S followed by 0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for raising the ordering concern. I traced the current controller and worker behavior before deciding not to add a TiDB-local barrier. The zero value is a startup heartbeat, not an update of the PD GC safe point; controller registrations are stored as task rows and scheduling uses the latest heartbeat, while an activated worker computes GC state from TiDB/PD. The GC path also waits for the GC state-cache synchronization interval before performing GC operations. A local barrier would not provide a cross-node monotonic protocol, so startup ordering remains unchanged.

return storage, dom, nil
}

Expand Down
42 changes: 42 additions & 0 deletions cmd/tidb-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
package main

import (
"context"
"os"
"syscall"
"testing"
"time"

"github.com/pingcap/kvproto/pkg/keyspacepb"
"github.com/pingcap/tidb/pkg/config"
"github.com/pingcap/tidb/pkg/config/deploymode"
"github.com/pingcap/tidb/pkg/config/kerneltype"
"github.com/pingcap/tidb/pkg/extworkload"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/sessionctx/vardef"
"github.com/pingcap/tidb/pkg/sessionctx/variable"
"github.com/pingcap/tidb/pkg/testkit"
"github.com/pingcap/tidb/pkg/testkit/testsetup"
"github.com/stretchr/testify/require"
pd "github.com/tikv/pd/client"
"go.opencensus.io/stats/view"
"go.uber.org/goleak"
)
Expand Down Expand Up @@ -182,6 +188,42 @@ func TestInitDeployMode(t *testing.T) {
})
}

type gcv2InitManager struct {
extworkload.Manager
gcLifeTime time.Duration
initCnt int
}

func (m *gcv2InitManager) Role() config.ExternalWorkloadRole {
return config.RoleMaster
}

func (*gcv2InitManager) Meta() *keyspacepb.KeyspaceMeta {
return &keyspacepb.KeyspaceMeta{
Config: map[string]string{
pd.KeyspaceConfigGCManagementType: pd.KeyspaceConfigGCManagementTypeKeyspaceLevel,
},
}
}

func (m *gcv2InitManager) InitializeGCV2(_ context.Context, gcLifeTime time.Duration) error {
m.initCnt++
m.gcLifeTime = gcLifeTime
return nil
}

func TestInitializeExternalWorkloadGCV2UsesEffectiveGCLifeTime(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set global tidb_gc_life_time = '24h'")

mgr := &gcv2InitManager{}
initializeExternalWorkloadGCV2(context.Background(), store, mgr)

require.Equal(t, 1, mgr.initCnt)
require.Equal(t, 24*time.Hour, mgr.gcLifeTime)
}

func TestCreateMgrClientRequiresPodIdentityInStarter(t *testing.T) {
if kerneltype.IsClassic() {
t.Skip("only for nextgen kernel")
Expand Down
2 changes: 2 additions & 0 deletions pkg/executor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ go_library(
"//pkg/expression/exprctx",
"//pkg/expression/sessionexpr",
"//pkg/extension",
"//pkg/extworkload",
"//pkg/infoschema",
"//pkg/infoschema/context",
"//pkg/keyspace",
Expand Down Expand Up @@ -450,6 +451,7 @@ go_test(
"//pkg/expression/aggregation",
"//pkg/expression/exprstatic",
"//pkg/extension",
"//pkg/extworkload",
"//pkg/infoschema",
"//pkg/keyspace",
"//pkg/kv",
Expand Down
36 changes: 36 additions & 0 deletions pkg/executor/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ package executor
import (
"context"
"strings"
"time"

"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/domain"
"github.com/pingcap/tidb/pkg/dxf/framework/storage"
"github.com/pingcap/tidb/pkg/executor/internal/exec"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/extworkload"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/parser/charset"
"github.com/pingcap/tidb/pkg/parser/mysql"
Expand All @@ -42,6 +44,7 @@ import (
sem "github.com/pingcap/tidb/pkg/util/sem/compat"
semv2 "github.com/pingcap/tidb/pkg/util/sem/v2"
"github.com/tikv/client-go/v2/oracle/oracles"
pd "github.com/tikv/pd/client"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -189,6 +192,9 @@ func (e *SetExecutor) setSysVariable(ctx context.Context, name string, v *expres
logstr = "set instance var"
}
logutil.BgLogger().Info(logstr, zap.Uint64("conn", sessionVars.ConnectionID), zap.String("name", name), zap.String("val", showValStr))
if v.IsGlobal && name == vardef.TiDBGCLifetime {
Comment thread
AmoebaProtozoa marked this conversation as resolved.
notifyExternalWorkloadGCLifeTime(ctx, e.Ctx(), showValStr)
}
if name == vardef.TiDBServiceScope {
dom := domain.GetDomain(e.Ctx())
// SetInstanceSysVar has already updated vardef.ServiceScope in the sysvar hook.
Expand Down Expand Up @@ -275,6 +281,36 @@ func (e *SetExecutor) setSysVariable(ctx context.Context, name string, v *expres
return nil
}

func notifyExternalWorkloadGCLifeTime(ctx context.Context, sctx sessionctx.Context, setValue string) {
mgr := extworkload.GetManagerFromStore(sctx.GetStore())
if !extworkload.IsEnabled(mgr) || !pd.IsKeyspaceUsingKeyspaceLevelGC(mgr.Meta()) {
return
}

gcLifeTimeVal, err := variable.GetSysVar(vardef.TiDBGCLifetime).GetGlobalFromHook(ctx, sctx.GetSessionVars())
if err != nil {
logutil.BgLogger().Warn("failed to load effective external workload GC life time",
zap.String("name", vardef.TiDBGCLifetime),
zap.String("val", setValue),
zap.Error(err))
return
}
gcLifeTime, err := time.ParseDuration(gcLifeTimeVal)
if err != nil {
logutil.BgLogger().Warn("failed to parse effective external workload GC life time",
zap.String("name", vardef.TiDBGCLifetime),
zap.String("val", gcLifeTimeVal),
zap.Error(err))
return
}
if err := mgr.UpdateGCLifeTime(ctx, gcLifeTime); err != nil {
logutil.BgLogger().Warn("failed to update external workload GC life time",
zap.String("name", vardef.TiDBGCLifetime),
zap.String("val", gcLifeTimeVal),
zap.Error(err))
}
}

func (e *SetExecutor) setCharset(cs, co string, isSetName bool) error {
var err error
sessionVars := e.Ctx().GetSessionVars()
Expand Down
62 changes: 62 additions & 0 deletions pkg/executor/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ import (
"net/http"
"strconv"
"testing"
"time"

"github.com/pingcap/failpoint"
"github.com/pingcap/kvproto/pkg/keyspacepb"
"github.com/pingcap/tidb/pkg/config"
"github.com/pingcap/tidb/pkg/errno"
"github.com/pingcap/tidb/pkg/executor"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/extworkload"
"github.com/pingcap/tidb/pkg/infoschema"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/parser/terror"
Expand All @@ -40,8 +43,67 @@ import (
"github.com/pingcap/tidb/pkg/util/mock"
topsqlstate "github.com/pingcap/tidb/pkg/util/topsql/state"
"github.com/stretchr/testify/require"
pd "github.com/tikv/pd/client"
)

type setGCLifeTimeManager struct {
extworkload.Manager
gcLifeTime time.Duration
updateCnt int
role config.ExternalWorkloadRole
meta *keyspacepb.KeyspaceMeta
}

func (m *setGCLifeTimeManager) Role() config.ExternalWorkloadRole {
return m.role
}

func (m *setGCLifeTimeManager) Meta() *keyspacepb.KeyspaceMeta {
return m.meta
}

func (m *setGCLifeTimeManager) UpdateGCLifeTime(_ context.Context, gcLifeTime time.Duration) error {
m.updateCnt++
m.gcLifeTime = gcLifeTime
return nil
}

func TestSetGCLifeTimeNotifiesExternalWorkloadWithEffectiveValue(t *testing.T) {
keyspaceLevelMeta := &keyspacepb.KeyspaceMeta{Config: map[string]string{
pd.KeyspaceConfigGCManagementType: pd.KeyspaceConfigGCManagementTypeKeyspaceLevel,
}}
cases := []struct {
name string
role config.ExternalWorkloadRole
meta *keyspacepb.KeyspaceMeta
setValue string
expectedGlobal time.Duration
expectedNotify time.Duration
expectedUpdate int
}{
{name: "master", role: config.RoleMaster, meta: keyspaceLevelMeta, setValue: "24h", expectedGlobal: 24 * time.Hour, expectedNotify: 24 * time.Hour, expectedUpdate: 1},
{name: "GCV2 worker", role: config.RoleGCV2Worker, meta: keyspaceLevelMeta, setValue: "24h", expectedGlobal: 24 * time.Hour, expectedNotify: 24 * time.Hour, expectedUpdate: 1},
{name: "TTL worker", role: config.RoleTTLTaskWorker, meta: keyspaceLevelMeta, setValue: "24h", expectedGlobal: 24 * time.Hour, expectedNotify: 24 * time.Hour, expectedUpdate: 1},
{name: "auto analyze worker", role: config.RoleAutoAnalyzeWorker, meta: keyspaceLevelMeta, setValue: "24h", expectedGlobal: 24 * time.Hour, expectedNotify: 24 * time.Hour, expectedUpdate: 1},
{name: "minimum value", role: config.RoleMaster, meta: keyspaceLevelMeta, setValue: "1m", expectedGlobal: 10 * time.Minute, expectedNotify: 10 * time.Minute, expectedUpdate: 1},
{name: "unified GC", role: config.RoleMaster, meta: &keyspacepb.KeyspaceMeta{}, setValue: "24h", expectedGlobal: 24 * time.Hour, expectedUpdate: 0},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
store := testkit.CreateMockStore(t)
mgr := &setGCLifeTimeManager{role: tc.role, meta: tc.meta}
extworkload.SetManagerForStore(store, mgr)

tk := testkit.NewTestKit(t, store)
tk.MustExec(fmt.Sprintf("set global tidb_gc_life_time = '%s'", tc.setValue))
tk.MustQuery("select @@global.tidb_gc_life_time").Check(testkit.Rows(tc.expectedGlobal.String()))
require.Equal(t, tc.expectedUpdate, mgr.updateCnt)
require.Equal(t, tc.expectedNotify, mgr.gcLifeTime)
})
}
}

func TestSetVar(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
Expand Down
Loading
Loading