Skip to content

internal/locate: probe follower once when the cached leader keeps rejecting leader reads with ServerIsBusy(0) (#2041) - #2044

Merged
ti-chi-bot[bot] merged 5 commits into
tikv:tidb-8.5from
ti-chi-bot:cherry-pick-2041-to-tidb-8.5
Aug 6, 2026
Merged

internal/locate: probe follower once when the cached leader keeps rejecting leader reads with ServerIsBusy(0) (#2041)#2044
ti-chi-bot[bot] merged 5 commits into
tikv:tidb-8.5from
ti-chi-bot:cherry-pick-2041-to-tidb-8.5

Conversation

@ti-chi-bot

Copy link
Copy Markdown
Member

This is an automated cherry-pick of #2041

Close #2028

Problem

When a TiKV store becomes half-dead (e.g. unified read pool wedged, tikv/tikv#18491),
leader reads are rejected at the read-pool entrance with ServerIsBusy and
EstimatedWaitMs == 0, before the request reaches the raft layer. Even after PD has
moved the leader away, the store keeps answering gRPC with ServerIsBusy(0) instead of
NotLeader. The client only marks the store slow and backs off, retrying the same
cached leader indefinitely — a production incident lasted ~25 minutes and ended only
when the store became fully unreachable.

Fix

"Sleep first, probe once":

  • The 1st ServerIsBusy(0) on the cached leader behaves exactly as today (mark slow +
    backoff + retry leader) — transient busyness costs nothing.
  • On the 2nd ServerIsBusy(0) from the same cached leader within one selector,
    mark the leader replica with a new dedicated suspectNotLeaderFlag (at most once
    per selector; the count restarts only when the cached leader changes).
  • The next attempt skips the flagged leader, so the existing mixed strategy picks a
    follower with unchanged request semantics (req.ReplicaRead stays false). A follower
    can only reject a leader read with NotLeader + leader hint — never serve it — so
    there is no stale-read risk.
  • The hint drives the existing onNotLeaderupdateLeader path, healing the shared
    region cache for all subsequent requests. onUpdateLeader clears the flag, so a
    misjudgment (the store is still the leader) costs exactly one rejected RPC and the
    request reverts to today's behavior.
  • If the followers yield no hint (election in progress, or followers also
    busy/exhausted), the selector restores the cached leader when the mixed strategy
    finds no candidate, instead of invalidating the region: it falls back to the plain
    backoff-and-retry loop, keeping essentially today's retry shape in an all-busy
    meltdown and adding no PD traffic. Region invalidation / PD reload still happen only
    through the pre-existing paths (e.g. after the leader exhausts its attempts).

No read/write semantics change, no extra PD traffic on the busy path, no new state
machines/timers/configs. Bounded extra cost: the probe fires at most once per
selector — when it succeeds (or is a misjudgment) it costs one rejected RPC; in the
worst case (all followers also busy) each follower is contacted once before the leader
is restored. Applies to reads and writes alike — a write rejected by a follower gets
the same NotLeader treatment.

What it does NOT cover

The window where the wedged store is still the leader (before PD eviction): the hint
points back to the same store and behavior degrades to status quo; that window is
covered by PD slow-store eviction. Complementary to the server-side fix
tikv/tikv#19932 (return NotLeader at the rejection gate): that one heals old clients
on new TiKV, this one heals old TiKV on new clients.

The forwarding (proxy) path is out of scope: EnableForwarding defaults to false, and
this change is designed and tested for the default selection path only — its
interaction with ReplicaSelectLeaderWithProxyStrategy is not covered.

A compound failure — wedged old leader AND all followers also rejecting with
ServerIsBusy(0) AND PD has already moved the leader — is intentionally not cured
eagerly: the probe cannot obtain a hint, and invalidating/reloading would tax PD in
the (much more common) case where the cache is actually correct. The request falls
back to today's behavior; the cure arrives once any follower can answer again.

Tests

TestReplicaSelectorLeaderBusyProbe: no probe on a single busy; probe on the 2nd busy
with leader-read semantics kept; cache healed via hint and a new selector goes
straight to the new leader; misjudgment restores the leader with no further probing;
no-hint goes through the region-scheduling backoff path; stale/follower/leaderOnly
reads never probe; a single-replica region (no probe target) restores the leader
without invalidating; the busy count restarts when the cached leader changes.

Two existing access-path tests were updated: their expectations encoded the old
behavior this PR removes (leader hammered until exhausted before trying a follower).
The all-busy meltdown case now ends with the leader restored — twelve accesses ending
in success with the region cache intact, essentially the same retry shape as before
this PR, with only one early fruitless probe of each follower.

Summary by CodeRabbit

  • Bug Fixes
    • Improved request routing when a cached leader repeatedly reports that it is busy.
    • Temporarily probes an available follower and preserves leader-read behavior where appropriate.
    • Automatically recovers from stale or incorrect leader information.
    • Prevents unnecessary probing for stale reads, follower-only requests, leader-only requests, and single-replica configurations.
    • Improves handling of transient busy responses while avoiding false leader suspicions.

ekexium added 4 commits August 6, 2026 02:01
…ecting leader reads with ServerIsBusy(0)

ref tikv#2028

When a TiKV store's unified read pool is wedged, it rejects leader reads
with ServerIsBusy(EstimatedWaitMs=0) at the pool entrance, so the request
never reaches the raft layer and no NotLeader error is returned even if PD
has already moved the leader away. The replica selector then retries the
cached leader forever and hammers the half-dead store.

After 2 consecutive such rejections on the cached leader within one
selector, mark the leader replica with a new suspectNotLeaderFlag so that
the next attempt skips it in the leader strategy and probes a follower via
the mixed strategy with the leader-read semantics of the request
unchanged. The follower replies NotLeader with the real leader hint,
which heals the shared region cache through the existing
onNotLeader/updateLeader path. The probe fires at most once per selector;
if the store is still the leader, the hint points back to it,
onUpdateLeader clears the flag, and the only cost is one rejected RPC.

Signed-off-by: Ziqian Qin <eke@fastmail.com>
…der when no probe target, tie busy count to the cached leader

Address review on tikv#2041, ref tikv#2028.

- When the mixed strategy finds no candidate while the leader is skipped
  only due to suspectNotLeaderFlag (single-replica region, or all
  followers unreachable/stale/exhausted, or the probe turns out
  fruitless), restore the leader and fall back to the plain backoff-retry
  behavior instead of invalidating the region and reloading it from PD
  for nothing.
- Tie leaderBusyCount to the new leaderBusyPeerID: whenever the cached
  leader changes (e.g. switched by a NotLeader hint), restart the count
  so the new leader won't be marked after inheriting the old leader's
  count.

Signed-off-by: Ziqian Qin <eke@fastmail.com>
The trigger is the 2nd ServerIsBusy(0) from the same cached leader within
one selector, not necessarily on consecutive attempts; the count restarts
only when the cached leader changes. Comment-only change.

Signed-off-by: Ziqian Qin <eke@fastmail.com>
…hreshold

Address review on tikv#2041, ref tikv#2028.

- Hoist the GetLeaderPeerID read out of the probe trigger condition so it
  is read once and shared by the condition and the count-reset logic,
  closing a TOCTOU window against concurrent cached-leader changes
  (double-read review comment by zyguan).
- Name the probe trigger threshold as leaderBusyProbeThreshold instead of
  a literal 2.
- Add contract tests: the busy count is cumulative per cached leader
  (busy(0) interleaved with other errors still counts), and when both
  probed followers reply NotLeader without a hint, the leader is restored
  with the region kept valid instead of being eagerly invalidated.

Signed-off-by: Ziqian Qin <eke@fastmail.com>
@ti-chi-bot ti-chi-bot added dco-signoff: yes Indicates the PR's author has signed the dco. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. type/cherry-pick-for-tidb-8.5 labels Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fbcd878f-c4f0-4185-ba3a-f486dae8e0c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

tidb-8.5 does not have the config.NextGen build constant, so the stale
read / follower read cases in TestReplicaSelectorLeaderBusyProbe must run
unconditionally. Also adjust the stale-read access path expectation:
tidb-8.5 falls back to replica read after the first ServerIsBusy(0),
unlike master which keeps the stale-read semantics.

Signed-off-by: Ziqian Qin <eke@fastmail.com>
@ti-chi-bot ti-chi-bot Bot added needs-1-more-lgtm Indicates a PR needs 1 more LGTM. approved labels Aug 6, 2026
@ti-chi-bot ti-chi-bot Bot added the lgtm label Aug 6, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cfzjywxk, zyguan

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 removed the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Aug 6, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-08-06 02:21:19.470101154 +0000 UTC m=+2667465.506196210: ☑️ agreed by zyguan.
  • 2026-08-06 04:15:41.296161822 +0000 UTC m=+2674327.332256878: ☑️ agreed by cfzjywxk.

@ti-chi-bot
ti-chi-bot Bot merged commit 494edbd into tikv:tidb-8.5 Aug 6, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved dco-signoff: yes Indicates the PR's author has signed the dco. lgtm size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. type/cherry-pick-for-tidb-8.5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants