pingcap / pingcap/tidb-operator
Whole-cluster suspension gets stuck forever when TiCI is enabled
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 1.3k
- Forks
- 540
- Avg merge
- 3d 2h
- Merged PRs (30d)
- 18
Description
Bug Report
What version of Kubernetes are you using?
N/A (found during code review)
What version of TiDB Operator are you using?
Current release-1.x (0045b1213), any release that includes TiCI support (#6699)
What storage classes exist in the Kubernetes cluster and what are used for PD/TiKV pods?
N/A
What's the status of the TiDB cluster pods?
N/A
What did you do?
Enable TiCI in a TidbCluster, then suspend the whole cluster by setting spec.suspendAction.suspendStatefulSet: true.
What did you expect to see?
All components get suspended in order: TiDB → TiFlash → TiCDC → TiCI meta/worker → TiKV → Pump → PD.
What did you see instead?
The suspension stops at TiCI and never finishes:
- TiDB / TiFlash / TiCDC statefulsets are deleted.
- TiCI meta/worker, TiKV, Pump and PD keep running.
- The operator logs
TiCI is waiting for TiDB cluster runningon every reconcile and requeues forever.
Why this happens
The suspender suspends components one by one in a fixed order, and a component can only start suspending after all components before it are fully suspended (pkg/manager/suspender/suspender.go). Since TiDB is first in the order, by the time it is TiCI's turn, TiDB's statefulset is already deleted and Status.TiDB.Members is cleared, so TiDBAllMembersReady() can never return true again.
The problem is that the TiCI member manager checks PD/TiKV/TiDB availability before calling SuspendComponent() (pkg/manager/member/tici_member_manager.go):
if tc.Spec.TiDB != nil && !tc.TiDBAllMembersReady() {
return controller.RequeueErrorf("TidbCluster: [%s/%s], TiCI is waiting for TiDB cluster running", ns, tcName)
}
// never reached during a whole-cluster suspension
needSuspend, err := m.suspender.SuspendComponent(tc, v1alpha1.TiCIMetaMemberType)
So TiCI never gets its turn to suspend, and nothing after it in the order (TiKV, Pump, PD) starts either.
Every other member manager calls SuspendComponent() at the very beginning of Sync() — the convention since #4640 — so suspension is never blocked by dependency availability. TiCI, introduced in #6699, is the only exception.
The TiFlash member manager (also #6699) has a second instance of the same problem: it checks TiCI readiness before its own suspension check and silently skips syncing when TiCI is not ready. If TiCI happens to be unhealthy when the suspension starts, the whole-cluster suspension gets stuck on TiFlash instead — this one without even an error log.
Note that suspending TiCI alone works fine (the availability checks pass while the rest of the cluster is running), which is probably why this went unnoticed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read pkg/manager/suspender/suspender.go, pkg/manager/member/tici_member_manager.go, and the TiFlash member manager. Start by comparing when each manager calls SuspendComponent() with its dependency checks. Done means whole-cluster suspension proceeds through TiCI, TiKV, Pump, and PD even when earlier components are already suspended or TiCI is unhealthy.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100