Skip to content

logpuller: extract region request scheduler from subscription client - #5665

Open
lidezhu wants to merge 18 commits into
ldz/refactor-puller04from
ldz/refactor-puller05
Open

logpuller: extract region request scheduler from subscription client#5665
lidezhu wants to merge 18 commits into
ldz/refactor-puller04from
ldz/refactor-puller05

Conversation

@lidezhu

@lidezhu lidezhu commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: close #5871

What is changed and how it works?

  1. Extracted region request scheduling logic from subscriptionClient into a standalone regionRequestScheduler.
  2. Introduced a per-store request management layer to own region request workers and route region tasks to the correct TiKV store.
  3. Reorganized the region request pipeline around clearer responsibilities, including task submission, deregistration, and stream failure recovery.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Questions

Will it cause performance regression or break compatibility?
Do you need to update user documentation, design documentation or monitoring documentation?

Release note

Please refer to [Release Notes Language Style Guide](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/release-notes-style-guide.html) to write a quality release note.

If you don't think this PR needs a release note then fill it with `None`.

Summary by CodeRabbit

  • Performance and Reliability

    • Improved region request scheduling with prioritized processing, per-store worker distribution, retry handling, and graceful shutdown.
    • Enhanced recovery from region and stream failures, including safer event delivery and deregistration handling.
    • Improved request tracking and metrics for clearer region-request state reporting.
  • Tests

    • Expanded coverage for scheduling, worker distribution, retries, failure recovery, event delivery, and shutdown behavior.

@ti-chi-bot

ti-chi-bot Bot commented Jul 17, 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 do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Jul 17, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign 3aceshowhand 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

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The log puller extracts region request scheduling into dedicated scheduler, store, and worker components. It rewires subscription lifecycle and failure recovery around shared upstream dependencies, updates tests and mocks, and removes obsolete client-local request management.

Changes

Log puller scheduling

Layer / File(s) Summary
Region request scheduler
logservice/logpuller/region_request_scheduler.go, logservice/logpuller/region_request_store.go, logservice/logpuller/*scheduler_test.go, logservice/logpuller/*store_test.go
Adds prioritized scheduling, lazy per-store workers, RPC-context resolution, deregistration broadcasts, failure reporting, shutdown handling, and aggregate in-flight metrics.
Per-store worker execution
logservice/logpuller/region_request_worker.go, logservice/logpuller/region_request_worker_test.go, logservice/logpuller/mock_upstream_test.go
Rewires workers to direct upstream, event sink, failure handler, and store address dependencies. Tests cover stream recovery, event delivery, aborted requests, and deregistration headers.
Region failure recovery
logservice/logpuller/region_failure_handler.go, logservice/logpuller/region_failure_handler_test.go
Uses direct region-cache and scheduling callbacks for region and range recovery. Scan priority is resolved through the subscribed span policy.
Subscription client integration
logservice/logpuller/subscription_client.go, logservice/logpuller/subscription_client_test.go, logservice/logpuller/scan_priority_test.go, pkg/metrics/log_puller.go, server/server.go
Centralizes upstream metadata, starts and closes the scheduler, forwards complete range tasks, updates metrics text, and removes the obsolete constructor configuration.

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

Sequence Diagram(s)

sequenceDiagram
  participant SubscriptionClient
  participant RegionRequestScheduler
  participant RegionRequestStore
  participant RegionRequestWorker
  participant RegionFailureHandler
  SubscriptionClient->>RegionRequestScheduler: submit region or range task
  RegionRequestScheduler->>RegionRequestStore: route task by store address
  RegionRequestStore->>RegionRequestWorker: admit and send request
  RegionRequestWorker->>SubscriptionClient: push region or timestamp event
  RegionRequestWorker->>RegionFailureHandler: report stream or request failure
  RegionFailureHandler->>RegionRequestScheduler: schedule recovery task
Loading

Possibly related PRs

  • pingcap/ticdc#5603: Extracts related region failure handling and subscription client responsibilities.
  • pingcap/ticdc#5619: Refactors region scheduling, worker lifecycle, admission, and scan-priority handling in the same components.
  • pingcap/ticdc#5657: Modifies related log puller region and span initialization and event processing.

Suggested reviewers: asddongmen, hongyunyan

Poem

A rabbit queues regions in line,
With workers hopping store to store.
Events stream through neat new paths,
Failures find their tasks once more.
The scheduler closes softly at night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.26% 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 The changes extract scheduling into dedicated components while retaining subscription responsibilities and covering retry and failure behavior for issue [#5871].
Out of Scope Changes check ✅ Passed The implementation and tests support the scheduler extraction, including lifecycle integration, metrics wording, and constructor updates.
Title check ✅ Passed The title clearly and concisely describes the primary change: extracting the region request scheduler from subscriptionClient.
Description check ✅ Passed The description includes the issue number and explains the main changes, with unit tests indicated in the checklist.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ldz/refactor-puller05

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 17, 2026
@lidezhu
lidezhu marked this pull request as ready for review July 17, 2026 01:58

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the region request scheduling logic in the logpuller package by introducing a dedicated regionRequestScheduler and requestedStore to manage workers connected to TiKV stores, decoupling this responsibility from subscriptionClient. The review feedback suggests adding defensive nil checks for worker.admission in the submit, close, and inflightCount methods of requestedStore to prevent potential nil pointer dereferences during testing or partial initialization.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread logservice/logpuller/requested_store.go Outdated
Comment thread logservice/logpuller/requested_store.go Outdated
Comment thread logservice/logpuller/requested_store.go Outdated
@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 17, 2026
@lidezhu
lidezhu force-pushed the ldz/refactor-puller05 branch 2 times, most recently from f6cc71c to 4d3a6b4 Compare August 3, 2026 16:28
@lidezhu
lidezhu force-pushed the ldz/refactor-puller05 branch from 33e46fc to 7e3df1d Compare August 4, 2026 08:00
@lidezhu

lidezhu commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@lidezhu

lidezhu commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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/logpuller/mock_upstream_test.go (1)

71-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not call require from the serving goroutine.

require.Nil calls t.FailNow, which the testing package allows only from the goroutine that runs the test. From this goroutine the failure can be lost, and the test does not stop where expected. grpc.Server.Serve also returns grpc.ErrServerStopped when Stop runs before Serve starts, so this assertion can fail during normal teardown.

Log the error instead, or ignore grpc.ErrServerStopped and record the error for the test goroutine to assert.

♻️ Proposed change
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
-		err := grpcServer.Serve(lis)
-		require.Nil(t, err)
+		if err := grpcServer.Serve(lis); err != nil && !errors.Is(err, grpc.ErrServerStopped) {
+			log.Warn("mock grpc server exited with error", zap.Error(err))
+		}
 	}()

Add the errors import for this change.

As per coding guidelines: "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/logpuller/mock_upstream_test.go` around lines 71 - 76, Update the
grpcServer.Serve goroutine to remove require.Nil, since test failures must be
reported from the test goroutine. Handle the expected grpc.ErrServerStopped
teardown result, and log or record any other Serve error for the main test
goroutine to assert; add the errors import if needed.

Source: Coding guidelines

logservice/logpuller/region_request_scheduler_test.go (1)

93-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared scheduler fixture.

Both tests repeat the same setup: mock TiKV cluster, mockPDClient, region cache, backoffer, raw span, subscribedSpan, and the LockRange assertion. Extract one helper that returns the region cache, the located region, and the locked subscribedSpan. The tests then differ only in the stopped flag and the store wiring.

Also applies to: 173-202

🤖 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/logpuller/region_request_scheduler_test.go` around lines 93 - 122,
The repeated mock-cluster and locked-span setup in the affected scheduler tests
should be extracted into a shared test helper. Add a helper that creates the
mock TiKV cluster and mockPDClient, initializes the region cache and backoffer,
builds the raw span and subscribedSpan, performs and asserts the successful
LockRange call, and returns the region cache, located region, and locked
subscribedSpan; update both tests to use it so only stopped-state and
store-wiring setup remain test-specific.
🤖 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/logpuller/subscription_client_test.go`:
- Around line 426-441: Update the two t.Fatal failure messages in
TestRegionEventSinkPushUnblocksOnClientClose to reference regionEventSink.Push
instead of the removed pushRegionEventToDS method, while leaving the test
behavior unchanged.

---

Nitpick comments:
In `@logservice/logpuller/mock_upstream_test.go`:
- Around line 71-76: Update the grpcServer.Serve goroutine to remove
require.Nil, since test failures must be reported from the test goroutine.
Handle the expected grpc.ErrServerStopped teardown result, and log or record any
other Serve error for the main test goroutine to assert; add the errors import
if needed.

In `@logservice/logpuller/region_request_scheduler_test.go`:
- Around line 93-122: The repeated mock-cluster and locked-span setup in the
affected scheduler tests should be extracted into a shared test helper. Add a
helper that creates the mock TiKV cluster and mockPDClient, initializes the
region cache and backoffer, builds the raw span and subscribedSpan, performs and
asserts the successful LockRange call, and returns the region cache, located
region, and locked subscribedSpan; update both tests to use it so only
stopped-state and store-wiring setup remain test-specific.
🪄 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: 4386cd10-de97-4565-bf5a-ff652d3b174a

📥 Commits

Reviewing files that changed from the base of the PR and between 3311a11 and 7e3df1d.

📒 Files selected for processing (14)
  • logservice/logpuller/mock_upstream_test.go
  • logservice/logpuller/region_failure_handler.go
  • logservice/logpuller/region_failure_handler_test.go
  • logservice/logpuller/region_request_scheduler.go
  • logservice/logpuller/region_request_scheduler_test.go
  • logservice/logpuller/region_request_store.go
  • logservice/logpuller/region_request_store_test.go
  • logservice/logpuller/region_request_worker.go
  • logservice/logpuller/region_request_worker_test.go
  • logservice/logpuller/scan_priority_test.go
  • logservice/logpuller/subscription_client.go
  • logservice/logpuller/subscription_client_test.go
  • pkg/metrics/log_puller.go
  • server/server.go

Comment on lines +426 to +441
func TestRegionEventSinkPushUnblocksOnClientClose(t *testing.T) {
sink := &regionEventSink{
ds: &mockDynamicStream{},
}
return rawSpan, span
}

func newScanPriorityTestRegion(span *subscribedSpan) regionInfo {
return newRegionInfo(tikv.NewRegionVerID(1, 1, 1), span.span, nil, span, false)
}

func newTestScanPriorityPolicy() scanPriorityPolicy {
return newScanPriorityPolicy(pdutil.NewClock4Test(), 30*time.Minute)
}

func TestPushRegionEventToDSUnblocksOnClose(t *testing.T) {
sink := newTestRegionEventSink(&mockDynamicStream{})
client := &subscriptionClient{
eventSink: sink,
regionTaskQueue: priorityqueue.New[*regionPriorityTask](),
sink.cond = sync.NewCond(&sink.mu)
client := &subscriptionClient{eventSink: sink}
client.regionScheduler = &regionRequestScheduler{
taskQueue: priorityqueue.New[*regionPriorityTask](),
}
client.ctx, client.cancel = context.WithCancel(context.Background())

sink.paused.Store(true)

done := make(chan struct{})
go func() {
client.pushRegionEventToDS(SubscriptionID(1), regionEvent{})
sink.Push(SubscriptionID(1), regionEvent{})

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale failure messages in this test.

The test now exercises regionEventSink.Push. The two t.Fatal messages at Lines 447 and 456 still name the removed pushRegionEventToDS method. Rename them so a failure points to the current API.

💚 Proposed fix
 	select {
 	case <-done:
-		t.Fatal("pushRegionEventToDS should block when paused")
+		t.Fatal("regionEventSink.Push should block when paused")
 	case <-time.After(100 * time.Millisecond):
 	}
 
 	require.NoError(t, client.Close(context.Background()))
 
 	select {
 	case <-done:
 	case <-time.After(5 * time.Second):
-		t.Fatal("pushRegionEventToDS should be unblocked by Close")
+		t.Fatal("regionEventSink.Push should be unblocked by Close")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestRegionEventSinkPushUnblocksOnClientClose(t *testing.T) {
sink := &regionEventSink{
ds: &mockDynamicStream{},
}
return rawSpan, span
}
func newScanPriorityTestRegion(span *subscribedSpan) regionInfo {
return newRegionInfo(tikv.NewRegionVerID(1, 1, 1), span.span, nil, span, false)
}
func newTestScanPriorityPolicy() scanPriorityPolicy {
return newScanPriorityPolicy(pdutil.NewClock4Test(), 30*time.Minute)
}
func TestPushRegionEventToDSUnblocksOnClose(t *testing.T) {
sink := newTestRegionEventSink(&mockDynamicStream{})
client := &subscriptionClient{
eventSink: sink,
regionTaskQueue: priorityqueue.New[*regionPriorityTask](),
sink.cond = sync.NewCond(&sink.mu)
client := &subscriptionClient{eventSink: sink}
client.regionScheduler = &regionRequestScheduler{
taskQueue: priorityqueue.New[*regionPriorityTask](),
}
client.ctx, client.cancel = context.WithCancel(context.Background())
sink.paused.Store(true)
done := make(chan struct{})
go func() {
client.pushRegionEventToDS(SubscriptionID(1), regionEvent{})
sink.Push(SubscriptionID(1), regionEvent{})
select {
case <-done:
t.Fatal("regionEventSink.Push should block when paused")
case <-time.After(100 * time.Millisecond):
}
require.NoError(t, client.Close(context.Background()))
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("regionEventSink.Push should be unblocked by Close")
}
🤖 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/logpuller/subscription_client_test.go` around lines 426 - 441,
Update the two t.Fatal failure messages in
TestRegionEventSinkPushUnblocksOnClientClose to reference regionEventSink.Push
instead of the removed pushRegionEventToDS method, while leaving the test
behavior unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

1 participant