release-nextgen-202603: HA (PD leader partition) still reproducibly freezes changefeed checkpoint after capture replacement
@lidezhu is already working on this.
Since Sep 7, 2026.
- Dominant language
- Go
- Stars
- 56
- Forks
- 63
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 34
Description
Bug Report
Summary
In the TiCDC new architecture, a PD leader ↔ PD followers network partition (HA fault injection) makes a capture exit abnormally (capture suicide + nil-pointer panic during server close). After the new capture takes over, the changefeed checkpoint is permanently frozen: checkpointTs / resolvedTs stop advancing, and the checkpoint lag grows linearly (~3.3h) without ever recovering. The move dispatcher operator from the dead capture to the new capture stays in the running queue for 20+ minutes, and the maintainer keeps reporting checkpointTs can not be advanced, since missing capture heartbeat.
Environment
- TiCDC:
26.3.4-1-g578cb65(branchrelease-nextgen-202603) - Architecture: new architecture
- Upstream: TiDB-X CSE cluster, PD microservices (pd / tso / scheduling split)
- Sink: downstream TiDB (
sinkType=tidb), changefeed configconsistent = "eventual",flush-interval = 500 - Fault injected: network partition between the PD leader and all PD followers, duration 10min (
pdleader_to_pdfollower(all)), with sysbencholtp_read_writerunning on 64 tables upstream.
Timeline (UTC+8)
| Time | Event |
|---|---|
| 11:27:34 | chaos injected: network partition PD leader pd-0 ↔ followers pd-1/pd-2 |
| 11:37:41 | TiCDC etcd-client cannot reach PD follower: RST_STREAM / context deadline exceeded / Auto sync endpoints failed |
| 11:37:54 | tc-ticdc-0 capture exits with [CDC:ErrCaptureSuicide] capture suicide |
| 11:37:55 | capture restarts ("Running TiCDC server in new architecture") |
| 11:39:35 | panic: runtime error: invalid memory address or nil pointer dereference in server.(*server).Close (server.go:566) |
| 11:39:37 | new capture 2d8dffa1 takes over; all 64 table dispatchers re-register (register dispatcher with large startTs lag, lag ≈ 8m10s) |
| 11:39:37~38 | maintainer: checkpointTs can not be advanced, since missing capture heartbeat (checkpointTs/resolvedTs frozen at a constant value) |
| 11:39:37+ | operator is still in running queue: "move dispatcher operator: ... origin:<dead-capture>, dest:<new-capture>" stays in queue 20+ minutes (timeSinceCreated keeps growing) |
| 11:41:50 | checkpoint lag first exceeds threshold: 134.852s > 120s |
| ~11:47 | PD recovers (new scheduling primary elected, etcd leader restored) — but the changefeed does NOT recover |
| 11:57:35 | post-check: waiting for max(ticdc_owner_checkpoint_ts_lag) < 6, 3h timeout |
| 11:57:35 ~ 14:55 | PromQL query result is continuously EMPTY (lag never < 6s) |
| 14:57:21 | max checkpoint lag = 11864.851s (~3.3h) |
| 14:57:36 | 3h timeout exceeded → case FAILED |
What did you expect to see
After the network partition heals and a new capture takes over, the changefeed checkpoint should resume advancing, the lag should drain back to normal (< a few seconds), and the post-failover checkpoint check should pass.
What did you see instead
The changefeed checkpoint is permanently frozen:
- Back-calculating the freeze point from the measured lag is consistent:
11:41:50 − 134.9s ≈ 11:39:35and14:57:21 − 11864.9s ≈ 11:39:36, i.e. the checkpoint stopped advancing at ~11:39:35 and the lag then grew linearly to ~3.3h and never recovered. - The maintainer repeatedly logs
checkpointTs can not be advanced, since missing capture heartbeat, withcheckpointTsandresolvedTsboth frozen at the same constant value. - The
move dispatcher operator(moving a dispatcher from the dead capture to the new capture) stays in the running queue for 20+ minutes;event_broker.go:386 dispatcher not resetis logged repeatedly.
Root cause (appears to be)
The PD leader network partition triggers a capture suicide + a nil-pointer panic in server.Close, so the old capture dies abnormally. During capture replacement in the new architecture, the dispatcher-move operator from the dead capture to the new capture gets stuck in the running queue, the dispatcher cannot complete its reset, and the maintainer therefore cannot advance checkpointTs/resolvedTs — the global checkpoint stays pinned and the lag never drains. This looks like the same family as #5553 / #5710 (changefeed can stall after capture replacement / dispatcher reset in the new architecture).
Evidence (TiCDC component logs)
[2026/09/03 11:37:54.130 +0800] Error: [CDC:ErrCaptureSuicide]capture suicide
[2026/09/03 11:39:35.776 +0800] panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20]
goroutine 860 [running]:
github.com/pingcap/ticdc/server.(*server).Close(...)
github.com/pingcap/ticdc/server/server.go:566
[2026/09/03 11:39:37.x +0800] [maintainer.go:706] ["checkpointTs can not be advanced, since missing capture heartbeat"]
[changefeedID=keyspace_a/ticdc-task] [checkpointTs=...] [resolvedTs=...] (both frozen)
[2026/09/03 11:39:37.x +0800] [operator_controller.go:268] ["operator is still in running queue"]
[operator="move dispatcher operator: ..., origin:<dead-capture>, dest:<new-capture>"]
[timeSinceCreated=16m / 20m ... growing]
[2026/09/03 11:41:50] ChangefeedCheckpointLag check failed: checkPointLag(134.852s) > threshold(120s)
[2026/09/03 14:57:21] max checkPointLag = 11864.851s
Related
- #5553 — Changefeed can stall after CDC failover or dispatcher reset (new architecture)
- #5710 — Changefeed checkpoint can stall after capture replacement when a stale dispatcher reset fails
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.
Assessment
This issue has not been assessed yet.