[DBMON-6881] Move clickhouse onto the DatabaseCheck job registry and cancellation lifecycle - #24934
Conversation
…lifecycle Register the seven DBM async jobs with the DatabaseCheck registry, replace the run_job_loop fan-out in check() with run_async_jobs(), and drop the cancel() override that blocked the Agent's unschedule thread on all seven job futures. Closing the main client and releasing the shared pool moves to shutdown(), and each job's cancel() override becomes shutdown() so cancel() is signal-only. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
evalya-impact-summaryevalya impact analysis |
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: c31d4f3 | Docs | View more details | Give us feedback! |
Each job's shutdown() now drops its _check reference alongside closing its client, so cancel() leaves nothing pinning the check. ClickhouseMetadata also drops the schema collector and ClickhouseQueryCompletions the explain plans helper; both hold the check, and the latter also holds a bound method of its job. The check's own shutdown() releases the query manager and the health reporter. test_check_gc_after_cancel, ported from postgres, is what found these: with the cycle collector disabled it asserts refcounting alone reclaims the check. Co-authored-by: Cursor <cursoragent@cursor.com>
The query samples, table metrics, and parts and merges jobs never consulted the cancel event, so a cancel landing mid-tick still issued every remaining query in that tick. Each one blocks until the client read_timeout elapses, delaying the Agent's unschedule by a timeout per query. Check the cancel event before issuing each query, matching the convention already used by the query log job and by postgres. In the two samples methods the check sits above the broad except that would otherwise swallow it, so the cancellation aborts the whole collection rather than continuing to the next query. Co-authored-by: Cursor <cursoragent@cursor.com>
Rename _init_dbm_components to _register_async_jobs and adopt the shape the other DBM integrations use: the job attributes default to None in __init__, the shared dbm gate is a single early return instead of a conjunct repeated seven times, and the per-job else branches that only reassigned None are gone. Pure refactor, no change to which jobs are built or to the attributes callers see when a job is disabled. Co-authored-by: Cursor <cursoragent@cursor.com>
Each job keeps a one-line comment naming its feature and source tables. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23071d26be
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
/codex |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48954b49a4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # Run parts and merges monitoring if enabled | ||
| if self.parts_and_merges: | ||
| self.parts_and_merges.run_job_loop(self.tags) | ||
| self.run_async_jobs(self.tags) |
There was a problem hiding this comment.
Stop core queries once the check is cancelled
When cancellation arrives before this line—for example during _query_manager.execute()—run_async_jobs() is the first cancellation-aware operation in check(). The inherited DatabaseCheck.cancel() now intentionally leaves _client open until check() returns, while QueryManager.execute() continues through every configured query, so a cancelled check can start additional core queries and remain alive for multiple read_timeout periods. Poll is_cancelled between the main collection stages or make the query executor abort before starting each subsequent query.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is currently expected
The Agent cancels from another thread, and the inherited lifecycle defers teardown until check() returns so the main client cannot be closed mid-run. Nothing else interrupted the run, so a cancelled check worked through cluster resolution and every configured core query, each bounded only by the client read timeout. Gate the stages that query on is_cancelled. The previous cancel() override aborted these queries by closing the client out from under them, which is the crash the base protocol defers teardown to avoid. Co-authored-by: Cursor <cursoragent@cursor.com>
This reverts commit 3560f5a. The inherited lifecycle already handles this. cancel() sets the flag, signals the jobs and returns without waiting when the check is mid-run, so the Agent's unschedule thread is never blocked by core collection. The deferred _finalize() then runs from run()'s finally on the check's own thread, which is also what keeps the main client open for the rest of the run. That leaves letting the in-flight run finish, which costs at most one collection and yields one complete set of metrics instead of a partial one. If we do want to interrupt core collection, QueryExecutor.execute() in datadog_checks_base is the place, since it loops every compiled query with no cancellation hook and would cover every integration rather than just this one. Co-authored-by: Cursor <cursoragent@cursor.com>
Validation ReportAll 21 validations passed. Show details
|
What does this PR do?
Moves clickhouse's seven
DBMAsyncJobs onto theDatabaseCheckregistry and cancellation lifecycle._register_async_jobs()builds and registers the jobs the config enables, following the postgres and mysql pattern: the attributes default toNonein__init__, and the shareddbmgate is a single early return.check()ends inself.run_async_jobs(self.tags)instead of a per-job fan-out, and no-ops once the check is cancelled.cancel()override is gone. Closing the main client and dropping the shared pool manager moved toshutdown(), which the base calls once, after the loops stop and never whilecheck()is running.cancel()override becameshutdown(), which closes its client and drops_check. The check'sshutdown()releases the query manager and health reporter;ClickhouseMetadataalso drops the schema collector andClickhouseQueryCompletionsthe explain plans helper, both of which hold the check._cancel_eventbefore issuing each query, matching the query log job and postgres.Motivation
The old
cancel()signalled all seven jobs and then called.result()on all seven futures, blocking the Agent's unschedule thread insidecancel()— whichAgentCheck.cancel()explicitly warns against. Inheriting the base protocol moves that wait aftercheck()returns, socancel()signals and gets out of the way.One behavior change: a job's
cancel()used to close its dedicated client immediately, which is what aborted an in-flight query. Teardown now waits for the loop to finish its current iteration, so a query already issued is bounded by the clientread_timeout(10s) rather than cut short. The new cancel-event checks keep a cancelled job from starting further queries, so that wait is one query rather than one per query left in the tick.Tests follow the postgres migration (#24933): the base suite owns the state machine, so per-job tests target
shutdown(), and this adds wiring tests for registry contents (DBM on and off), resource release on cancel, and the cancel-event checks.test_check_gc_after_cancelis ported from postgres and is what found the reference cycles above — with the cycle collector disabled it asserts refcounting alone reclaims the check.Review checklist (to be filled by reviewers)
qa/requiredif this PR needs QA validation, orqa/skip-qaif it does not. Exactly one of the two is required.backport/<branch-name>label to the PR and it will automatically open a backport PR once this one is mergedMade with Cursor