Skip to content

conn(dm): cap pooled DB connection lifetime and idle time - #12790

Open
andreifedorov-bolt wants to merge 1 commit into
pingcap:masterfrom
andreifedorov-bolt:dm-conn-set-max-lifetime
Open

conn(dm): cap pooled DB connection lifetime and idle time#12790
andreifedorov-bolt wants to merge 1 commit into
pingcap:masterfrom
andreifedorov-bolt:dm-conn-set-max-lifetime

Conversation

@andreifedorov-bolt

@andreifedorov-bolt andreifedorov-bolt commented Aug 4, 2026

Copy link
Copy Markdown

What problem does this PR solve?

Issue Number: close #12789

dm/pkg/conn/basedb.go#Apply only calls db.SetMaxIdleConns; neither
db.SetConnMaxLifetime nor db.SetConnMaxIdleTime is ever set, so a
pooled *sql.DB connection to the upstream MySQL or the downstream
TiDB / MySQL lives for as long as the DM worker process.

When the peer server silently drops such a connection (rolling restart,
wait_timeout expiry, or a stateful middlebox timing out the flow), the
next statement re-using it surfaces as mysql.ErrInvalidConn, which
dm/pkg/retry/errors.go#IsUnretryableConnectionError classifies as
unretryable. The syncer / loader / dumper unit therefore exits with an
error and dm_{syncer,loader,mydumper}_exit_with_error_count{resumable_err="true"}
increments. Task-checker auto-resumes seconds later, so each individual
failure looks spurious, but with many concurrent tasks the aggregate
becomes a steady stream of DM "resumable error" alerts.

What is changed and how it works?

Apply two conservative defaults in Apply so the pool proactively
closes and re-opens a connection before its peer can drop it:

  • SetConnMaxLifetime(30m) — well below the MySQL/TiDB default
    wait_timeout of 28800 s (8 h) and any typical middlebox idle timeout.
    At the default MaxIdleConns this is roughly two reconnects per minute
    per pooled slot — negligible next to steady-state syncer traffic.
  • SetConnMaxIdleTime(5m) — retires connections that are only used
    intermittently before the wall clock does.

Both defaults are also exposed as ConnMaxLifetime / ConnMaxIdleTime
on RawDBConfig (with matching Set… methods for symmetry), so callers
that build a DBConfig programmatically can raise or lower them without
touching package-level defaults. A zero (or negative) RawDBCfg value
keeps the package default, so this is fully backwards-compatible with
every existing caller.

Check List

Tests

  • No code — behaviour of database/sql connection lifetime is
    documented and its handlers are simple setters; there is no additional
    observable behaviour beyond what database/sql already tests. The
    existing dm/pkg/conn test suite (including TestGetBaseConn,
    TestFailDBPing, TestGetBaseConnWontBlock) passes unchanged.

Questions

Will it cause performance regression or break compatibility?

No. Steady-state churn is on the order of MaxIdleConns / 30 min — a
handful of extra sql.Opens per minute per worker per unit — which is
negligible against the normal statement rate. Every caller keeps its
current behaviour unless it explicitly overrides RawDBConfig.

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

No.

Release note

dm: cap pooled DB connection lifetime and idle time so a peer server or middlebox silently dropping an idle TCP connection no longer surfaces as a non-retryable "invalid connection" unit exit.

Summary by CodeRabbit

  • New Features

    • Added configurable database connection lifetime and idle-time limits.
    • Introduced default limits to proactively refresh database connections.
  • Bug Fixes

    • Reduced stale connection errors and improved database connection reliability during retries.

Without SetConnMaxLifetime / SetConnMaxIdleTime the *sql.DB pool holds
its TCP connections indefinitely. When the peer TiDB / MySQL server
silently drops one -- rolling restart, `wait_timeout` expiry, or a
stateful middlebox timing out the flow -- the next statement re-using
that pooled connection surfaces as `mysql.ErrInvalidConn`.

`retry.IsUnretryableConnectionError` classifies `ErrInvalidConn` as
unretryable (because it can't tell whether a preceding write reached
the downstream), so the syncer / loader / dumper unit exits with an
error and `dm_{syncer,loader,mydumper}_exit_with_error_count` fires.
The task-checker auto-resumes seconds later, which makes the failure
look like a spurious blip -- but with enough tasks it turns into
sustained "resumable" error noise on the DM alerts.

Applying two conservative defaults in `Apply` avoids the failure mode
entirely, since the pool never hands out a long-dead connection:

  * ConnMaxLifetime = 30m -- well below the MySQL/TiDB default
    `wait_timeout` of 28800s (8h) and any typical middlebox idle
    timeout, at ~2 reconnects/min per pooled slot.
  * ConnMaxIdleTime = 5m  -- retires connections that are only used
    intermittently before the wall clock does.

Both are also exposed on `RawDBConfig` so callers that already
programmatically build a config can override them without touching
package defaults.
@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/needs-triage-completed area/dm Issues or PRs related to DM. contribution This PR is from a community contributor. first-time-contributor Indicates that the PR was contributed by an external member and is a first-time contributor. needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. labels Aug 4, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hi @andreifedorov-bolt. Thanks for your PR.

I'm waiting for a pingcap member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot

ti-chi-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Welcome @andreifedorov-bolt!

It looks like this is your first PR to pingcap/tiflow 🎉.

I'm the bot to help you request reviewers, add labels and more, See available commands.

We want to make sure your contribution gets all the attention it needs!



Thank you, and welcome to pingcap/tiflow. 😃

@pingcap-cla-assistant

pingcap-cla-assistant Bot commented Aug 4, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@ti-chi-bot ti-chi-bot Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Aug 4, 2026
@andreifedorov-bolt

Copy link
Copy Markdown
Author

/label needs-cherry-pick-release-8.5

Requesting a backport to release-8.5 (aimed at v8.5.6+) since the failure mode this fixes exists in that line as well. Happy to open the manual cherry-pick PR if the bot is not enabled for external contributors.

@ti-chi-bot ti-chi-bot Bot added the needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

RawDBConfig gets two new duration fields, ConnMaxLifetime and ConnMaxIdleTime, with chainable setters. ApplyWithPingTimeout in basedb.go applies default values (30 minutes lifetime, 5 minutes idle) or configured overrides to the database connection pool via SetConnMaxLifetime and SetConnMaxIdleTime.

Changes

Connection pool lifetime management

Layer / File(s) Summary
RawDBConfig lifetime fields and setters
dm/config/dbconfig/config.go
Adds time import. Adds ConnMaxLifetime and ConnMaxIdleTime fields to RawDBConfig. Adds SetConnMaxLifetime and SetConnMaxIdleTime chainable setter methods.
Apply default and configured limits to connection pool
dm/pkg/conn/basedb.go
Defines defaultConnMaxLifetime (30 minutes) and defaultConnMaxIdleTime (5 minutes) constants. ApplyWithPingTimeout initializes local variables from these defaults, overrides them when rawCfg.ConnMaxLifetime or rawCfg.ConnMaxIdleTime is greater than zero, and calls db.SetConnMaxLifetime and db.SetConnMaxIdleTime on the connection pool.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ApplyWithPingTimeout
  participant RawDBConfig
  participant sqlDB as "sql.DB"

  Caller->>ApplyWithPingTimeout: call with rawCfg
  ApplyWithPingTimeout->>ApplyWithPingTimeout: init connMaxLifetime=defaultConnMaxLifetime, connMaxIdleTime=defaultConnMaxIdleTime
  ApplyWithPingTimeout->>RawDBConfig: read ConnMaxLifetime, ConnMaxIdleTime
  RawDBConfig-->>ApplyWithPingTimeout: return configured values if greater than zero
  ApplyWithPingTimeout->>sqlDB: SetConnMaxLifetime(connMaxLifetime)
  ApplyWithPingTimeout->>sqlDB: SetConnMaxIdleTime(connMaxIdleTime)
Loading

Poem

A rabbit hops through pools of code,
No stale connections down this road. 🐇
Thirty minutes, five for rest,
Fresh connections serve us best.
Hop, sync, dump — no more surprise exits today!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: limiting pooled database connection lifetime and idle time.
Description check ✅ Passed The description covers the issue, implementation, tests, compatibility impact, documentation needs, and release note.
Linked Issues check ✅ Passed The changes implement issue #12789 by adding connection lifetime and idle-time defaults plus configurable RawDBConfig overrides.
Out of Scope Changes check ✅ Passed All changes are directly related to preventing stale database connections and configuring their pool lifetime and idle time.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dm/pkg/conn/basedb.go (1)

165-181: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add unit tests for the RawDBCfg pool-limit selection.

Test nil RawDBCfg, zero/negative ConnMaxLifetime, zero/negative ConnMaxIdleTime, positive overrides for each field, and both overrides together. Cover ApplyWithPingTimeout with sqlmock; if the chosen pool settings are not directly observable, extract the default/override selection into a pure helper and test that helper instead.

🤖 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 `@dm/pkg/conn/basedb.go` around lines 165 - 181, Add unit tests covering
RawDBCfg pool-limit selection in the connection setup around
ApplyWithPingTimeout: verify nil configuration, zero or negative ConnMaxLifetime
and ConnMaxIdleTime, positive individual overrides, and both overrides together.
Use sqlmock to exercise ApplyWithPingTimeout; if pool settings cannot be
observed directly, extract the default/override selection into a pure helper and
test that helper.

Source: Coding guidelines

🧹 Nitpick comments (1)
dm/pkg/conn/basedb.go (1)

48-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid an absolute stale-connection guarantee.

SetConnMaxLifetime and SetConnMaxIdleTime bound connection age and idle time. They do not prove that the peer is reachable. A peer can drop a connection before either limit expires. Replace “never” and “before the server or any middlebox drops them” with wording that says these limits reduce stale-connection reuse.

Please verify the final wording against the supported Go version’s database/sql documentation.

Also applies to: 225-230

🤖 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 `@dm/pkg/conn/basedb.go` around lines 48 - 53, The comments describing
defaultConnMaxLifetime and the related connection lifetime/idle-time settings
overstate their guarantees. Revise the wording to say SetConnMaxLifetime and
SetConnMaxIdleTime reduce the likelihood of reusing stale connections, without
claiming they prove peer reachability or prevent all server/middlebox
disconnects; verify the terminology against the supported Go version’s
database/sql documentation.
🤖 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.

Outside diff comments:
In `@dm/pkg/conn/basedb.go`:
- Around line 165-181: Add unit tests covering RawDBCfg pool-limit selection in
the connection setup around ApplyWithPingTimeout: verify nil configuration, zero
or negative ConnMaxLifetime and ConnMaxIdleTime, positive individual overrides,
and both overrides together. Use sqlmock to exercise ApplyWithPingTimeout; if
pool settings cannot be observed directly, extract the default/override
selection into a pure helper and test that helper.

---

Nitpick comments:
In `@dm/pkg/conn/basedb.go`:
- Around line 48-53: The comments describing defaultConnMaxLifetime and the
related connection lifetime/idle-time settings overstate their guarantees.
Revise the wording to say SetConnMaxLifetime and SetConnMaxIdleTime reduce the
likelihood of reusing stale connections, without claiming they prove peer
reachability or prevent all server/middlebox disconnects; verify the terminology
against the supported Go version’s database/sql documentation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3636b8d6-6307-44d8-88ac-9a00c69f0230

📥 Commits

Reviewing files that changed from the base of the PR and between 6463f89 and a603f49.

📒 Files selected for processing (2)
  • dm/config/dbconfig/config.go
  • dm/pkg/conn/basedb.go

@dveeden

dveeden commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/check-issue-triage-complete

1 similar comment
@dveeden

dveeden commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/check-issue-triage-complete

@dveeden

dveeden commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/ok-to-test

@ti-chi-bot ti-chi-bot Bot added ok-to-test Indicates a PR is ready to be tested. and removed needs-ok-to-test Indicates a PR created by contributors and need ORG member send '/ok-to-test' to start testing. labels Aug 6, 2026
}

// SetConnMaxLifetime sets the maximum lifetime of a pooled connection.
func (c *RawDBConfig) SetConnMaxLifetime(d time.Duration) *RawDBConfig {

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.

Do we really need SetConnMaxLifetime() if ConnMaxLifetime isn't private?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Kept for symmetry with the existing SetReadTimeout / SetWriteTimeout / SetMaxIdleConns setters on the same struct — DM's RawDBConfig is used almost exclusively via the chained fluent-builder pattern, e.g.

dbCfg.RawDBCfg = dbconfig.DefaultRawDBConfig().
    SetReadTimeout(maxDMLConnectionTimeout).
    SetMaxIdleConns(s.cfg.WorkerCount)

14 non-test call sites use it that way (e.g. dm/syncer/syncer.go:3173,3216,3226, dm/syncer/data_validator.go:287,295,1326,1355,1395, dm/syncer/checkpoint.go:484, dm/syncer/sharding_group.go:457, dm/syncer/online-ddl-tools/online_ddl.go:136, dm/checker/checker.go:587,599, dm/config/source_config.go:280). Only one non-test caller assigns the field directly (dm/relay/relay.go:1099) and it does so on an already-built config, not in a builder chain.

Without SetConnMaxLifetime / SetConnMaxIdleTime a caller wanting to override the new fields in a chain would have to break out to a temporary and mutate the public field, which reads worse and diverges from the convention. Happy to drop them if you'd prefer to move the codebase away from the fluent pattern going forward — the fields are public either way, so it's purely stylistic.

@dveeden

dveeden commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

/cc @D3Hunter

@ti-chi-bot
ti-chi-bot Bot requested a review from D3Hunter August 6, 2026 06:16
@ti-chi-bot

ti-chi-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@dveeden: adding LGTM is restricted to approvers and reviewers in OWNERS files.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot

ti-chi-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

Labels

area/dm Issues or PRs related to DM. contribution This PR is from a community contributor. first-time-contributor Indicates that the PR was contributed by an external member and is a first-time contributor. needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. ok-to-test Indicates a PR is ready to be tested. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dm: pooled DB connections live forever, causing spurious invalid connection unit exits

2 participants