Skip to content

*: add low-latency watermark propagation - #5826

Open
asddongmen wants to merge 8 commits into
pingcap:masterfrom
asddongmen:agent/low-latency-non-eventservice
Open

*: add low-latency watermark propagation#5826
asddongmen wants to merge 8 commits into
pingcap:masterfrom
asddongmen:agent/low-latency-non-eventservice

Conversation

@asddongmen

@asddongmen asddongmen commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: ref #5705

This is PR2, the non-EventService subset split from PR1 #5749 so the low-latency propagation changes can be reviewed independently.

PR2 intentionally contains no pkg/eventservice code or EventService metric changes; those changes remain in PR1.

What is changed and how it works?

  • Add opt-in performance-mode = "low-latency" while keeping throughput mode as the default and rejecting unknown values.
  • Advance LogPuller resolved-ts without timestamp batching when low-latency mode is enabled.
  • Start dispatcher-manager heartbeats immediately and reduce their interval from 200 ms to 50 ms in low-latency mode.
  • Wake maintainer checkpoint calculation as soon as an accepted dispatcher watermark changes in low-latency mode.
  • Report maintainer watermarks every 50 ms and promptly mark changed watermarks for coordinator reporting in low-latency mode.
  • Refresh log-coordinator resolved-ts metrics after a complete node reporting round and report the maximum per-node lag.

Check List

Tests

  • Unit test (GOFLAGS=-mod=mod make unit_test_pkg PKG='<five owning package scopes>'; 459 tests passed).
  • Manual test (PR1 contains the three-capture, 10-minute table and Region benchmark matrices; this split did not rerun them).

Questions

Will it cause performance regression or break compatibility?

Throughput mode remains the default with unchanged behavior, while low-latency mode intentionally increases heartbeat frequency. Enable it only after all TiCDC nodes are upgraded, and remove the option before downgrading.

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

Yes, the performance mode and its resource trade-off need user documentation, which remains tracked by PR1 and is not duplicated in this split.

Release note

Add an opt-in low-latency mode for faster LogPuller, dispatcher heartbeat, maintainer checkpoint, and log-coordinator metric propagation.

Summary by CodeRabbit

  • New Features
    • Added configurable performance modes: throughput (default) and low-latency.
    • Low-latency mode provides faster heartbeats, immediate resolved-timestamp advancement, and more responsive checkpoint updates.
  • Improvements
    • Improved changefeed metrics and status reporting, including more timely updates as nodes report progress.
    • Enhanced watermark and resolved-timestamp tracking to better reflect current system state.
  • Validation
    • Added configuration validation to reject unsupported performance mode values.

@ti-chi-bot

ti-chi-bot Bot commented Jul 31, 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 Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@asddongmen, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75415b8e-349c-430e-8775-3d873f8036ea

📥 Commits

Reviewing files that changed from the base of the PR and between f9a099d and e245a95.

📒 Files selected for processing (2)
  • logservice/coordinator/coordinator.go
  • logservice/coordinator/coordinator_test.go
📝 Walkthrough

Walkthrough

The PR adds throughput and low-latency server modes. Low-latency mode accelerates heartbeats, subscriptions, checkpoint updates, and metric refreshes. Coordinator state now tracks reporting rounds and node timestamps.

Changes

Performance mode and reporting flow

Layer / File(s) Summary
Performance mode configuration
pkg/config/server.go, pkg/config/server_config_test.go
ServerConfig now supports throughput and low-latency modes, validation, defaults, serialization, and IsLowLatencyMode().
Maintainer checkpoint notifications
maintainer/maintainer.go, maintainer/maintainer_manager.go, maintainer/*_test.go
Maintainer checkpoint calculation responds to coalesced watermark notifications. Manager heartbeats use mode-specific intervals.
Coordinator metric refresh
logservice/coordinator/coordinator.go, logservice/coordinator/coordinator_test.go
Coordinator state tracks node reporting rounds and timestamps. Metric updates occur immediately or after a complete reporting round, as applicable.
Heartbeat and subscription timing
downstreamadapter/dispatchermanager/task.go, downstreamadapter/dispatchermanager/task_test.go, logservice/eventstore/event_store.go
Heartbeat intervals and initial delays use the selected mode. Low-latency subscriptions use a zero resolved-timestamp advance interval.

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

Sequence Diagram(s)

sequenceDiagram
  participant ServerConfig
  participant MaintainerManager
  participant Maintainer
  participant DispatcherManager
  ServerConfig->>MaintainerManager: select heartbeat interval
  ServerConfig->>DispatcherManager: select heartbeat interval and initial delay
  MaintainerManager->>Maintainer: process heartbeat
  Maintainer->>Maintainer: detect watermark change
  Maintainer->>Maintainer: notify checkpoint calculation
  DispatcherManager->>DispatcherManager: run heartbeat task
Loading

Possibly related PRs

  • pingcap/ticdc#5749: Implements the same low-latency performance mode changes across the same files and functions.

Suggested reviewers: hongyunyan, wk989898, wlwilliamx

Poem

A rabbit hops on timers bright,
Fifty milliseconds through the night.
Watermarks wake and metrics run,
Reports align when rounds are done.
Throughput rests at two hundred beats—
Low latency dances on quick feet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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
Title check ✅ Passed The title clearly identifies the main change: adding low-latency watermark propagation.
Description check ✅ Passed The description covers the required problem, issue reference, changes, tests, compatibility, documentation, and release note sections.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 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/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Jul 31, 2026
@asddongmen
asddongmen marked this pull request as ready for review July 31, 2026 04:14
@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 Jul 31, 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: 1

🧹 Nitpick comments (2)
logservice/coordinator/coordinator.go (1)

291-319: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the affected-changefeed loop.

The len(affectedGIDs) > 0 guard at line 306 is redundant. A range over an empty map performs no iterations. Removing the guard reduces one nesting level.

♻️ Proposed simplification
-	if len(affectedGIDs) > 0 {
-		for gid := range affectedGIDs {
-			if state, ok := c.changefeedStates.m[gid]; ok {
-				if len(state.nodeStates) == 0 ||
-					len(state.nodesReportedSinceLastUpdate) != len(state.nodeStates) {
-					continue
-				}
-				if c.updateChangefeedMetrics(state, pdPhyTs, false) {
-					state.metricsUpdatedSinceLastTick = true
-				}
-				clear(state.nodesReportedSinceLastUpdate)
-			}
-		}
-	}
+	for gid := range affectedGIDs {
+		state, ok := c.changefeedStates.m[gid]
+		if !ok {
+			continue
+		}
+		if len(state.nodeStates) == 0 ||
+			len(state.nodesReportedSinceLastUpdate) != len(state.nodeStates) {
+			continue
+		}
+		if c.updateChangefeedMetrics(state, pdPhyTs, false) {
+			state.metricsUpdatedSinceLastTick = true
+		}
+		clear(state.nodesReportedSinceLastUpdate)
+	}
🤖 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 `@logservice/coordinator/coordinator.go` around lines 291 - 319, Remove the
redundant len(affectedGIDs) > 0 guard and place the existing range over
affectedGIDs directly around the changefeed-state processing, preserving all
current checks and updates inside the loop.
logservice/coordinator/coordinator_test.go (1)

408-409: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify the repeated report from node-1.

Line 409 repeats the exact call on line 408 with the same node, time, and lag. The round set already contains node-1, so the call changes no state. If the intent is to prove that a repeated report does not complete the round, add a comment and an assertion. Otherwise remove the line.

♻️ Proposed cleanup
 	newResolvedTs := report("node-1", pdTime, 50*time.Millisecond)
-	report("node-1", pdTime, 50*time.Millisecond)
+	// A repeated report from the same node must not complete the round.
+	report("node-1", pdTime, 50*time.Millisecond)
+	require.Len(t, state.nodesReportedSinceLastUpdate, 1)
 	report("node-2", pdTime.Add(300*time.Millisecond), 180*time.Millisecond)

As per path instructions: "Prefer focused deterministic tests".

🤖 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 `@logservice/coordinator/coordinator_test.go` around lines 408 - 409, Remove
the redundant second report call in the test around newResolvedTs, unless the
test intentionally verifies duplicate-report behavior. If retaining it, add a
focused comment explaining the scenario and an assertion proving the repeated
node-1 report does not complete or otherwise change the round state.

Source: Coding guidelines

🤖 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 344-378: Update the lag calculation in the changefeed metrics
update flow to always set resolvedTsLagGauge from pdPhyTs minus the physical
timestamp of the global minResolvedTs. Remove the maxNodeLag/hasNodeLag tracking
and the conditional override, while preserving the existing force and metrics
initialization behavior.

---

Nitpick comments:
In `@logservice/coordinator/coordinator_test.go`:
- Around line 408-409: Remove the redundant second report call in the test
around newResolvedTs, unless the test intentionally verifies duplicate-report
behavior. If retaining it, add a focused comment explaining the scenario and an
assertion proving the repeated node-1 report does not complete or otherwise
change the round state.

In `@logservice/coordinator/coordinator.go`:
- Around line 291-319: Remove the redundant len(affectedGIDs) > 0 guard and
place the existing range over affectedGIDs directly around the changefeed-state
processing, preserving all current checks and updates inside the loop.
🪄 Autofix (Beta)

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: 945b8b8e-ddf8-4092-a95a-19e229879415

📥 Commits

Reviewing files that changed from the base of the PR and between 84654e2 and f9a099d.

📒 Files selected for processing (10)
  • downstreamadapter/dispatchermanager/task.go
  • downstreamadapter/dispatchermanager/task_test.go
  • logservice/coordinator/coordinator.go
  • logservice/coordinator/coordinator_test.go
  • logservice/eventstore/event_store.go
  • maintainer/maintainer.go
  • maintainer/maintainer_manager.go
  • maintainer/maintainer_test.go
  • pkg/config/server.go
  • pkg/config/server_config_test.go

Comment thread logservice/coordinator/coordinator.go
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@ti-chi-bot

ti-chi-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 3AceShowHand
Once this PR has been reviewed and has the lgtm label, please assign lidezhu for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found 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 the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Jul 31, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-07-31 07:38:34.465269507 +0000 UTC m=+2168100.501364553: ☑️ agreed by 3AceShowHand.


func heartbeatInterval() time.Duration {
if config.GetGlobalServerConfig().IsLowLatencyMode() {
return lowLatencyHeartbeatInterval

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can the HeartbeatInterval and HeartbeatInitialDelay parameters be set by the user?

serverConfig := config.GetGlobalServerConfig()
resolvedTsAdvanceInterval := int64(serverConfig.KVClient.AdvanceIntervalInMs)
if serverConfig.IsLowLatencyMode() {
resolvedTsAdvanceInterval = 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ditto.

Comment thread maintainer/maintainer.go
@@ -699,28 +701,33 @@ func (m *Maintainer) calCheckpointTs(ctx context.Context) {
case <-ctx.Done():
return
case <-ticker.C:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should it be removed?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

And it looks like ticdc can't advance checkpointTs in no Low Latency Mode

@asddongmen asddongmen added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. needs-1-more-lgtm Indicates a PR needs 1 more LGTM. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants