Skip to content

*: add changefeed-level performance mode - #5862

Merged
ti-chi-bot[bot] merged 25 commits into
pingcap:masterfrom
asddongmen:agent/changefeed-level-low-latency-mode
Aug 7, 2026
Merged

*: add changefeed-level performance mode#5862
ti-chi-bot[bot] merged 25 commits into
pingcap:masterfrom
asddongmen:agent/changefeed-level-low-latency-mode

Conversation

@asddongmen

@asddongmen asddongmen commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: ref #5705

TiCDC needs a per-changefeed choice between throughput-oriented and
latency-oriented replication so both workloads can coexist on the same
captures. This PR introduces the persisted performance mode and the
control-plane behavior needed by low-latency changefeeds.

What is changed and how it works?

  • Add performance-mode to ReplicaConfig, API/TOML conversion, and
    dispatcher register/reset messages. Throughput remains the default.
  • Use shorter dispatcher heartbeats and event-driven Maintainer watermark
    calculation/reporting only for low-latency changefeeds.
  • Preserve concurrent Maintainer status updates across heartbeat snapshots.
  • Publish LogCoordinator resolved-ts metrics only after complete node-report
    rounds and calculate lag at each node's report time.
  • Leave EventService scan scheduling and mode-isolated EventStore
    subscriptions to Part 2, eventservice: optimize changefeed low-latency scheduling #5900.

Check List

Tests

  • Unit test
  • Manual test (Part 1 + Part 2: three captures, concurrent low-latency and
    throughput changefeeds with 100k tables each, 200k isolated subscriptions,
    about 20 MB/s shared traffic for 30 minutes; both feeds remained normal)

Three-capture, 10-minute results; values are low-latency/throughput/master.
Resolved is maintainer resolved-ts lag; CPU and RSS are aggregate.

Tables (~20MB/s) Checkpoint s Resolved s CPU cores RSS GB Low vs throughput
50k .419/1.394/1.180 .373/.677/.692 14.7/11.0/11.0 18.8/18.8/18.6 checkpoint -69.9%, resolved -44.9%
100k .569/1.496/1.386 .525/.813/.815 24.5/19.2/19.7 20.7/21.0/20.8 checkpoint -62.0%, resolved -35.5%
200k 1.079/1.978/1.880 1.018/1.343/1.318 29.9/24.2/24.2 26.9/26.0/25.9 checkpoint -45.4%, resolved -24.1%
32 tables, 199,936 Regions (~30MB/s) Checkpoint s Resolved s CPU RSS GB
Low latency .584 .485 2.62 11.80
Throughput 1.620 .946 2.54 12.09
Master bootstrap blocked by #5748

This reduces checkpoint/resolved lag by 64.0%/48.7% with +3.0% CPU and -2.4%
RSS. Table-scale latency gains cost 23-34% CPU; RSS stays within 4%.

Questions

Will it cause performance regression or break compatibility?

No compatibility break is expected. Existing configurations default to
throughput mode. Low-latency mode intentionally increases control-plane update
frequency; the mode-specific EventStore behavior is isolated in #5900.

Do you need to update user documentation, design documentation or monitoring documentation?

Yes. The new changefeed performance-mode option needs user documentation.
The owner resolved-lag metric keeps its name and receives clarified semantics.

Release note

Add a changefeed-level performance mode and low-latency control-plane
reporting.

Summary by CodeRabbit

  • New Features

    • Added configurable throughput and low-latency performance modes for changefeeds.
    • Low-latency mode now propagates through dispatcher requests and uses faster heartbeat scheduling.
    • Added API and configuration support for selecting and validating performance modes.
  • Improvements

    • Resolved-timestamp metrics now update after complete reporting rounds and accurately reflect node lag.
    • Low-latency checkpoint and status updates respond more promptly to changes.
    • Improved watermark change detection and notification handling.

Keep the low-latency mode focused on setting the logpuller advance interval to zero. Remove the additional batched heap update path and its helper after the 10k-region E2E test showed comparable mean and p95 latency without it.
Replace the periodic schema-capped scan retry with applied SchemaStore notifications. Serialize dispatcher scan scheduling with a short-lock state machine and coalesce worker continuations without dropping queued work.
Keep no-DML/no-DDL resolved notifications out of the scan worker queue while preserving dispatcher scan ownership. Gate continuation and schema-blocked recovery on low-latency mode, and cover queue-full recovery with a dropped-task metric.
@ti-chi-bot

ti-chi-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@ti-chi-bot ti-chi-bot Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds validated throughput and low-latency performance modes. It propagates the mode through API, dispatcher, and event requests. It adjusts heartbeat and maintainer notifications. Coordinator metrics now publish after complete reporting rounds.

Changes

Performance mode configuration

Layer / File(s) Summary
Configuration and API propagation
pkg/config/..., api/v2/...
Replica and changefeed configurations support throughput and low-latency modes. Validation, conversion, serialization, and round-trip tests preserve the selected mode.

Dispatcher and maintainer behavior

Layer / File(s) Summary
Dispatcher propagation and heartbeat scheduling
downstreamadapter/dispatcher/..., downstreamadapter/dispatchermanager/..., downstreamadapter/eventcollector/..., eventpb/event.proto, pkg/messaging/message.go
Dispatchers expose low-latency mode. Heartbeat timing changes by mode. Register and reset requests carry the mode.
Maintainer notifications and heartbeat propagation
maintainer/...
Maintainers use buffered notifications for watermark and status changes. Periodic heartbeat processing remains active. Watermark changes are detected and reported selectively.

Coordinator metrics

Layer / File(s) Summary
Complete reporting-round metrics
logservice/coordinator/..., pkg/metrics/log_coordinator.go
The coordinator tracks reporting nodes and report timestamps. It publishes resolved-timestamp and lag metrics after complete reporting rounds. Tests cover partial, duplicate, complete, and removal cases.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DispatcherManager
  participant BasicDispatcher
  participant DispatcherSession
  participant DispatcherRequest
  DispatcherManager->>BasicDispatcher: initialize low-latency mode
  BasicDispatcher->>DispatcherSession: expose low-latency mode
  DispatcherSession->>DispatcherRequest: set low-latency mode on register or reset
Loading
sequenceDiagram
  participant Maintainer
  participant MaintainerManager
  participant Coordinator
  Maintainer->>Maintainer: accept newer watermark
  Maintainer->>MaintainerManager: send coalesced heartbeat notification
  MaintainerManager->>Maintainer: process maintainer heartbeat
  Coordinator->>Coordinator: publish metrics after complete reporting round
Loading

Possibly related PRs

  • pingcap/ticdc#5749: Extends the same low-latency performance-mode implementation across dispatcher, maintainer, event, and configuration paths.
  • pingcap/ticdc#5826: Overlaps with dispatcher heartbeats, maintainer notifications, and coordinator metric updates.
  • pingcap/ticdc#4030: Contains related dispatcher scan-window behavior affected by low-latency timing.

Suggested labels: lgtm, approved

Suggested reviewers: lidezhu, wk989898

Poem

A rabbit taps a quicker beat,
Low-latency hops on nimble feet.
Dispatchers carry the mode along,
Maintainers wake when changes belong.
Complete rounds make metrics clear—
“Review this burrow!” says the hare.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: adding changefeed-level performance mode.
Description check ✅ Passed The description covers the issue, implementation, tests, compatibility, documentation, and release note requirements.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ti-chi-bot ti-chi-bot Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Aug 3, 2026
@asddongmen
asddongmen marked this pull request as ready for review August 5, 2026 13:55
@ti-chi-bot ti-chi-bot Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 5, 2026
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🧹 Nitpick comments (3)
pkg/eventservice/event_broker.go (2)

1182-1199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the keyspace key construction.

getSchemaBlockedDispatcherBucket and removeSchemaBlockedDispatcher build the same common.KeyspaceMeta from the same two sources. getScanTaskRequestResult (Lines 457-460) and getSchemaBlockedDispatcherBucket also repeat it. A single helper on dispatcherStat would keep the key definition in one place.

♻️ Sketch: one keyspace-key helper
+func (a *dispatcherStat) keyspaceMeta() common.KeyspaceMeta {
+	return common.KeyspaceMeta{
+		ID:   a.info.GetTableSpan().KeyspaceID,
+		Name: a.changefeedStat.changefeedID.Keyspace(),
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/eventservice/event_broker.go` around lines 1182 - 1199, Extract the
repeated common.KeyspaceMeta construction into a helper method on
dispatcherStat, using d.info.GetTableSpan().KeyspaceID and
d.changefeedStat.changefeedID.Keyspace(). Update
getSchemaBlockedDispatcherBucket, removeSchemaBlockedDispatcher, and
getScanTaskRequestResult to call this helper instead of constructing the key
inline.

1225-1242: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a dependency note for the schema frontier bucket.

GetTableDDLEventState returns store.resolvedTs.Load() for the whole keyspace and only uses tableID for MaxEventCommitTs. If ResolvedTs is later changed to table-scoped, this bucket will compare every dispatcher in the keyspace against the first dispatcher’s table frontier; add a short comment to preserve this invariant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/eventservice/event_broker.go` around lines 1225 - 1242, Add a concise
comment beside the GetTableDDLEventState call in the
firstSchemaBlockedDispatcher bucket flow documenting that ResolvedTs is
keyspace-scoped, while tableID only affects MaxEventCommitTs; preserve this
invariant so the bucket does not compare dispatchers against a table-scoped
frontier.
maintainer/maintainer_manager.go (1)

136-151: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a floor interval for prompt heartbeats.

m.heartbeatCh has capacity 1, so it coalesces only one pending signal. Every markStatusChanged call from a low-latency maintainer can therefore trigger one extra sendHeartbeat round. Under frequent status changes across many maintainers, this loop can send coordinator heartbeats far more often than the 200 ms ticker.

A minimum spacing between prompt heartbeats would bound the control-message rate while keeping the latency benefit.

♻️ Sketch: gate prompt heartbeats by a floor interval
 func (m *Manager) Run(ctx context.Context) error {
 	ticker := time.NewTicker(defaultManagerHeartbeatInterval)
 	defer ticker.Stop()
+	var lastPromptHeartbeat time.Time
 	for {
 		select {
 		case <-ctx.Done():
 			return ctx.Err()
 		case msg := <-m.msgCh:
 			m.handleMessage(msg)
 		case <-m.heartbeatCh:
-			m.sendHeartbeat()
+			if time.Since(lastPromptHeartbeat) >= minPromptHeartbeatInterval {
+				lastPromptHeartbeat = time.Now()
+				m.sendHeartbeat()
+			}
 		case <-ticker.C:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@maintainer/maintainer_manager.go` around lines 136 - 151, Update the
heartbeat handling in the manager loop around m.heartbeatCh and sendHeartbeat to
enforce a minimum spacing between prompt heartbeats, using the existing 200 ms
heartbeat interval or an appropriate floor constant. Track the last prompt
heartbeat time, only send when the floor has elapsed, and preserve ticker-driven
heartbeats and channel coalescing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@logservice/coordinator/coordinator.go`:
- Around line 321-349: The updateChangefeedMetrics flow must not recompute or
publish the minimum during an incomplete reporting round. Gate minimum
calculation and updates to minLogServiceResolvedTs and resolvedTsGauge on
nodesReportedSinceLastUpdate reaching a complete round; otherwise, retain the
last published minimum and refresh only resolvedTsLagGauge from it. Add a test
covering reportChangefeedMetrics between partial node reports.

In `@maintainer/maintainer_manager_maintainers.go`:
- Around line 219-223: Update the newMaintainer factory and NewMaintainer
initialization so managerHeartbeatCh is assigned before NewMaintainer starts any
goroutines or installs callbacks that may invoke markStatusChanged. Prefer
passing p.heartbeatCh into NewMaintainer and setting the field during
construction, preserving the existing channel used by the manager.

In `@pkg/eventservice/event_broker_test.go`:
- Around line 76-86: Update the goroutine setup in the test’s WaitGroup flow to
use wg.Go instead of manually calling wg.Add(1), launching a goroutine, and
deferring wg.Done(). Preserve the existing start-channel synchronization and
broker.requestScan(disp) invocation.

In `@pkg/eventservice/event_broker.go`:
- Around line 1106-1108: Update the blocking enqueue in onNotify to select
between sending to c.taskChan[d.scanWorkerIndex] and broker-context
cancellation. On cancellation, call finishScan with the interrupted-shutdown
outcome so the dispatcher cannot remain in dispatcherScanQueued after close;
preserve the existing send behavior while the broker is active.

---

Nitpick comments:
In `@maintainer/maintainer_manager.go`:
- Around line 136-151: Update the heartbeat handling in the manager loop around
m.heartbeatCh and sendHeartbeat to enforce a minimum spacing between prompt
heartbeats, using the existing 200 ms heartbeat interval or an appropriate floor
constant. Track the last prompt heartbeat time, only send when the floor has
elapsed, and preserve ticker-driven heartbeats and channel coalescing behavior.

In `@pkg/eventservice/event_broker.go`:
- Around line 1182-1199: Extract the repeated common.KeyspaceMeta construction
into a helper method on dispatcherStat, using d.info.GetTableSpan().KeyspaceID
and d.changefeedStat.changefeedID.Keyspace(). Update
getSchemaBlockedDispatcherBucket, removeSchemaBlockedDispatcher, and
getScanTaskRequestResult to call this helper instead of constructing the key
inline.
- Around line 1225-1242: Add a concise comment beside the GetTableDDLEventState
call in the firstSchemaBlockedDispatcher bucket flow documenting that ResolvedTs
is keyspace-scoped, while tableID only affects MaxEventCommitTs; preserve this
invariant so the bucket does not compare dispatchers against a table-scoped
frontier.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 25b39436-5b08-4719-9c94-ea9eb8a7c330

📥 Commits

Reviewing files that changed from the base of the PR and between af33cc1 and e599c77.

⛔ Files ignored due to path filters (1)
  • eventpb/event.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (35)
  • api/v2/changefeed_toml_test.go
  • api/v2/model.go
  • api/v2/model_test.go
  • downstreamadapter/dispatcher/basic_dispatcher.go
  • downstreamadapter/dispatcher/basic_dispatcher_info.go
  • downstreamadapter/dispatcher/event_dispatcher_test.go
  • downstreamadapter/dispatchermanager/dispatcher_manager.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_test.go
  • downstreamadapter/dispatchermanager/task.go
  • downstreamadapter/dispatchermanager/task_test.go
  • downstreamadapter/eventcollector/dispatcher_session.go
  • downstreamadapter/eventcollector/dispatcher_stat_test.go
  • downstreamadapter/eventcollector/event_collector_test.go
  • eventpb/event.proto
  • logservice/coordinator/coordinator.go
  • logservice/coordinator/coordinator_test.go
  • logservice/eventstore/event_store.go
  • logservice/eventstore/event_store_test.go
  • maintainer/maintainer.go
  • maintainer/maintainer_manager.go
  • maintainer/maintainer_manager_maintainers.go
  • maintainer/maintainer_manager_test.go
  • maintainer/maintainer_test.go
  • pkg/config/changefeed.go
  • pkg/config/changefeed_test.go
  • pkg/config/replica_config.go
  • pkg/config/replica_config_test.go
  • pkg/eventservice/dispatcher_stat.go
  • pkg/eventservice/event_broker.go
  • pkg/eventservice/event_broker_test.go
  • pkg/eventservice/event_service.go
  • pkg/eventservice/event_service_test.go
  • pkg/eventservice/metrics_collector.go
  • pkg/messaging/message.go
  • pkg/metrics/event_service.go

Comment thread logservice/coordinator/coordinator.go Outdated
Comment thread maintainer/maintainer_manager_maintainers.go
Comment thread pkg/eventservice/event_broker_test.go Outdated
Comment thread pkg/eventservice/event_broker.go Outdated
Publish LogCoordinator resolved lag only from complete node-report rounds and calculate it at each node's report time. Preserve Maintainer watermark ordering, expose checkpoint/resolved lag atomically, and update dashboards to use the paired metrics.
Restore the unrelated Maintainer metric collector, watermark state, and dashboard changes. Keep this follow-up scoped to LogCoordinator owner resolved-ts lag calculation and its tests.
Keep the first changefeed-level low-latency PR free of pkg/eventservice changes. Move the coupled EventStore subscription isolation and EventService dropped-task metric into the follow-up part as well so both trees compile independently.
@asddongmen asddongmen changed the title *: make low-latency mode changefeed-scoped *: add changefeed-level performance mode Aug 6, 2026
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Aug 7, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: 3AceShowHand, lidezhu

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added lgtm approved and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Aug 7, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-08-07 02:54:10.114974796 +0000 UTC m=+2755836.151069852: ☑️ agreed by 3AceShowHand.
  • 2026-08-07 03:35:25.030770612 +0000 UTC m=+2758311.066865668: ☑️ agreed by lidezhu.

@ti-chi-bot
ti-chi-bot Bot merged commit 167f740 into pingcap:master Aug 7, 2026
22 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants