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

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

Summary

  • Total findings: 5
  • Inline comments: 5
  • Summary-only findings (no inline anchor): 0
Findings (highest risk first)

⚠️ [Major] (4)

  1. Connection-lifetime comments promise a guarantee the pool cannot provide (dm/pkg/conn/basedb.go:48 and dm/pkg/conn/basedb.go:225)
  2. Pool limits do not retire DM's long-lived worker connections (dm/pkg/conn/basedb.go:231)
  3. New pool policy lacks testable deterministic coverage (dm/pkg/conn/basedb.go:165 and dm/pkg/conn/basedb.go:231; dm/config/dbconfig/config.go:53)
  4. Connection-lifetime overrides are not available through DM configuration (dm/config/dbconfig/config.go:53 and dm/config/dbconfig/config.go:108; applied in dm/pkg/conn/basedb.go:165)

🟡 [Minor] (1)

  1. Exported setters hide their non-positive-value semantics (dm/config/dbconfig/config.go:53 and dm/config/dbconfig/config.go:83)

Comment thread dm/pkg/conn/basedb.go
// defaultConnMaxLifetime is the default maximum lifetime of a pooled
// database/sql connection. It is set well below any reasonable server-side
// `wait_timeout` (MySQL/TiDB default is 8h) and any typical stateful
// middlebox idle timeout, so the pool never hands out a connection whose

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.

⚠️ [Major] Connection-lifetime comments promise a guarantee the pool cannot provide

Why
The new rationale says the pool closes and reopens connections before any server or middlebox drops them and therefore never returns a connection whose peer has disappeared. The configured limits only retire connections after a fixed age or idle interval; they cannot predict a rolling restart, keepalive failure, or a server or middlebox timeout shorter than those limits, and database/sql opens replacements on demand rather than as part of these setters.

Scope
dm/pkg/conn/basedb.go:48 and dm/pkg/conn/basedb.go:225

Risk if unchanged
Maintainers can treat the invalid-connection failure mode as eliminated and rely on these defaults in deployments where peers disappear sooner, obscuring the remaining operational risk and making later tuning or incident diagnosis misleading.

Evidence
The implementation always uses the hard-coded 30-minute lifetime and 5-minute idle defaults unless a positive override is supplied; it does not read the server wait_timeout, discover a middlebox timeout, or detect restarts. The standard-library contract for both setters states that expired connections may be closed lazily before reuse, which is weaker than the comments' before and never claims.

Change request
Please rewrite both comments to say that the limits reduce the chance of reusing stale connections by retiring sufficiently old or idle entries, remove the universal before/never guarantee and the claim that the setters reopen TCP connections, and state that shorter external timeouts or abrupt peer loss can still produce ErrInvalidConn.

Comment thread dm/pkg/conn/basedb.go
// gone away surfaces as `mysql.ErrInvalidConn` on the next statement,
// which the DM retry classifier treats as unretryable and causes the
// syncer/loader/dumper unit to exit with an error.
db.SetConnMaxLifetime(connMaxLifetime)

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.

⚠️ [Major] Pool limits do not retire DM's long-lived worker connections

Why
SetConnMaxLifetime and SetConnMaxIdleTime are enforced by database/sql when pooled connections are idle or checked out again, but DM reserves a *sql.Conn in each BaseConn and keeps it for the lifetime of the syncer or loader worker. Those connections never become idle in the pool and are not checked out again, so neither new limit rotates the connections that the change is intended to protect.

Scope
dm/pkg/conn/basedb.go:231

Risk if unchanged
After a task has run longer than the configured lifetime, a server restart, wait_timeout, or middlebox drop can still make the next DML on a reserved worker connection return mysql.ErrInvalidConn; DM will continue to classify the ambiguous write as unretryable and exit the unit, leaving the reported production failure mode in place.

Evidence
BaseDB.GetBaseConn obtains d.DB.Conn and stores the resulting fixed connection in BaseConn (dm/pkg/conn/basedb.go:304-319); CreateConns creates these once and retains them in each DBConn (dm/syncer/dbconn/db.go:333-346). The standard pool cleaner only scans idle freeConn entries, while a reserved sql.Conn owns its driver connection until Conn.Close, so the settings added at lines 231-232 cannot expire these worker connections.

Change request
Can we rotate long-lived BaseConn instances at a safe transaction boundary (or stop pinning them and acquire through sql.DB) and add a case that keeps a worker connection past the limit and verifies it is replaced before the next statement?

Comment thread dm/pkg/conn/basedb.go
}

var maxIdleConns int
connMaxLifetime := defaultConnMaxLifetime

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.

⚠️ [Major] New pool policy lacks testable deterministic coverage

Why
The change adds default, non-positive fallback, and positive-override policy directly inside ApplyWithPingTimeout, a method that must open and ping a real MySQL connection, and also changes the lifecycle of every database pool. It adds neither a focused test seam nor unit or integration coverage proving that the selected limits are applied and that an expired connection is retired before reuse.

Scope
dm/pkg/conn/basedb.go:165 and dm/pkg/conn/basedb.go:231; dm/config/dbconfig/config.go:53

Risk if unchanged
A later refactor can silently alter nil-config handling, invert zero or negative fallback semantics, ignore positive overrides, or stop applying the limits across syncer, loader, dumper, checker, and relay. The stale-connection production failure could then return without deterministic regression coverage catching it.

Evidence
Lines 165-180 select two defaults and conditionally replace them for positive RawDBConfig durations, while lines 231-232 apply the selected values. The diff changes only config.go and basedb.go and adds no *_test.go, integration, upgrade, or failure-path coverage for default selection, override selection, or stale-connection replacement.

Change request
Can we extract effective connection-pool option selection into a private pure helper and add table-driven unit cases for nil RawDBCfg, zero or negative values, and positive overrides? Please also add a deterministic lifecycle test using a controllable driver or server timeout and condition-based synchronization rather than an arbitrary time.Sleep, so it verifies that an expired pooled connection is replaced before the next statement.

MaxIdleConns int
ReadTimeout string
WriteTimeout string
// ConnMaxLifetime caps the total lifetime of a pooled connection.

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.

⚠️ [Major] Connection-lifetime overrides are not available through DM configuration

Why
The new defaults are intended to stay below the database or middlebox timeout, but the only overrides live in RawDBConfig, while DBConfig.RawDBCfg is excluded from TOML, JSON, and YAML and SourceConfig.GenerateDBConfig replaces it with an internally constructed value. A DM operator therefore cannot adapt the 30-minute lifetime or 5-minute idle limit to an environment whose wait_timeout or network idle timeout is shorter.

Scope
dm/config/dbconfig/config.go:53 and dm/config/dbconfig/config.go:108; applied in dm/pkg/conn/basedb.go:165

Risk if unchanged
After upgrade, deployments with a timeout below these fixed defaults can continue receiving the same stale pooled connection and unretryable mysql.ErrInvalidConn that this change is meant to prevent, with no supported configuration-based mitigation.

Evidence
ApplyWithPingTimeout only accepts positive overrides from config.RawDBCfg, but the enclosing RawDBCfg field has toml:"-" json:"-" yaml:"-" tags. The normal source path at dm/config/source_config.go:280 always creates DefaultRawDBConfig() and does not carry user-supplied lifetime values.

Change request
Can we expose backward-compatible optional lifetime and idle-time settings in the supported source and downstream configuration path, carry them into RawDBConfig, validate their units and bounds, and document how operators should choose values relative to server and middlebox timeouts?

return c
}

// SetConnMaxLifetime sets the maximum lifetime of a pooled connection.

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.

🟡 [Minor] Exported setters hide their non-positive-value semantics

Why
SetConnMaxLifetime and SetConnMaxIdleTime use names and one-line docs that closely mirror database/sql, where a non-positive duration disables the corresponding limit, but ApplyWithPingTimeout interprets the same values as a request for DM's positive default. The field comments mention this behavior, but callers using the exported fluent setters should not have to inspect a separate field declaration to discover that the familiar sentinel means something different here.

Scope
dm/config/dbconfig/config.go:53 and dm/config/dbconfig/config.go:83

Risk if unchanged
A programmatic caller can pass zero intending to disable connection retirement and silently receive the 30-minute or 5-minute default instead; the phrase process-wide default also overstates the scope because the fallback is local to the default DB provider path.

Evidence
Both setters assign d without validation or explanation, while DefaultDBProviderImpl.ApplyWithPingTimeout accepts an override only when the stored value is greater than zero. This differs from the standard-library methods whose names these APIs reuse.

Change request
Please document on both exported setters that only positive durations override the DM provider defaults and that zero or negative durations do not disable the limits; also replace process-wide default/see basedb.Apply with the precise provider behavior, or expose an explicit way to request the disabled state if that is intended to be supported.

Comment thread dm/pkg/conn/basedb.go
// gone away surfaces as `mysql.ErrInvalidConn` on the next statement,
// which the DM retry classifier treats as unretryable and causes the
// syncer/loader/dumper unit to exit with an error.
db.SetConnMaxLifetime(connMaxLifetime)

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.

These pool settings do not rotate DM’s long-lived worker connections, so the primary syncer failure remains.

SetConnMaxLifetime and SetConnMaxIdleTime are enforced when a database/sql connection is idle in the pool or is checked out/returned. However, BaseDB.GetBaseConn calls DB.Conn, and dm/syncer/dbconn.CreateConns retains the resulting *sql.Conn for the unit lifetime. Operations on an already checked-out *sql.Conn reuse its owned driver connection without evaluating the pool lifetime or idle-time limits.

Concrete failure: if a running syncer receives no events for longer than the downstream wait_timeout, its pinned DML/DDL/checkpoint connection remains checked out and is not retired by either new setting. The next Begin, Exec, or Commit can still encounter the server-closed socket and return mysql.ErrInvalidConn.

Please rotate or validate the pinned BaseConn handles at a safe boundary, or avoid task-lifetime *sql.Conn ownership. A regression test should cover the long-lived BaseConn path; testing ordinary *sql.DB pool reuse would not exercise the affected syncer behavior.

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

3 participants