Skip to content

Fix flaky test_async_load_databases::test_materialized_views_replicated - #107119

Merged
alexey-milovidov merged 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-test-async-load-replicated-flaky
Jun 18, 2026
Merged

Fix flaky test_async_load_databases::test_materialized_views_replicated#107119
alexey-milovidov merged 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-test-async-load-replicated-flaky

Conversation

@groeneai

@groeneai groeneai commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Changelog category (leave one):

  • CI Fix or Improvement (changelog entry is not required)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

...

Description

Fixes flaky test_async_load_databases::test_materialized_views_replicated. 13 distinct PR groups hit it in the last 30 days (most recent 2026-06-10); the dominant signature is an empty test_context_raw (the 900s pytest session timeout), and when the session is killed it takes the sibling test_materialized_views_cascaded_multiple down too (shared module fixture).

Root cause (two cooperating timing assumptions that break on slow sanitizer builds):

  1. disconnect_event.wait(90) ignored its return value. When restart_clickhouse() takes longer than 90s (its stop+start budget alone is ~120s, and startup is much slower under TSan/ASan), the main thread fell through while the server was still restarting.
  2. The INSERT loops used node1.query(...) with no per-query timeout. A query to a half-restarted server (TCP port open but not yet serving) makes clickhouse-client connect and then block on the response. With DEFAULT_QUERY_TIMEOUT=600s, one such query exhausts the 900s session timeout.

Fix (test-only, assertions unchanged):

  • assert disconnect_event.wait(180) so a too-slow restart fails loudly instead of silently racing ahead (180s gives sanitizer builds headroom over the original 90s).
  • timeout=60 on the INSERTs in both loops, so a connection to a not-yet-ready server fails fast and attributably instead of hanging up to 600s.
  • the pre-restart guarded loop also catches QueryTimeoutExceedException (the new fast-fail path).

Reproduced and validated locally (native integration runner, debug build): unmodified test passes on a fast build (race doesn't trigger); with an injected slow restart, an unguarded query(timeout=None) against a half-up server blocks indefinitely while timeout=60 fails in seconds; the fixed test passes 3/3 repeated runs.

Version info

  • Merged into: 26.6.1.955 (included in 26.6 and later)
  • Backported to: 25.8.26.9

The test stops node1, restarts it in a worker thread, and meanwhile issues
INSERTs. Two timing assumptions break on slow (sanitizer) builds:

1. `disconnect_event.wait(90)` ignored its return value. When the restart took
   longer than 90s (plausible under TSan/ASan, where restart's stop+start can
   approach its 120s budget and startup is much slower), the main thread fell
   through while the server was still restarting.

2. The INSERT loops used `node1.query(...)` with no per-query timeout. When a
   query hits a half-restarted server (TCP port open but not yet serving), the
   clickhouse-client connects and then blocks waiting for a response. With the
   default DEFAULT_QUERY_TIMEOUT=600s, a single such query can exhaust pytest's
   900s per-test timeout, which kills the whole session and also fails the
   sibling test_materialized_views_cascaded_multiple sharing the module fixture.

Fix (test-only, behavior preserved):
- assert on `disconnect_event.wait(180)` so a too-slow restart fails loudly with
  a clear message instead of silently racing ahead; 180s gives sanitizer builds
  headroom over the original 90s.
- pass `timeout=60` to the INSERTs in both loops so a connection to a
  not-yet-ready server fails fast and attributably instead of hanging up to 600s.
- the pre-restart guarded loop now also catches QueryTimeoutExceedException
  (the new fast-fail path) alongside QueryRuntimeException.

The assertions the test makes (row-count propagation through the MV) are
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author

Pre-PR validation gate

# Question Answer
a Deterministic repro? Yes. With the real clickhouse-client against a black-hole socket (accepts TCP, never completes the native handshake = a half-restarted server), node1.query(..., timeout=None) stays blocked indefinitely (still alive after 12s, would hit the 600s DEFAULT_QUERY_TIMEOUT); timeout=5 raises QueryTimeoutExceedException in 5.0s. Also reproduced in-context: with an injected slow restart, disconnect_event.wait(N) returns False and the unguarded loop runs against a not-yet-ready server.
b Root cause explained? On slow (sanitizer) builds restart_clickhouse() can exceed the main thread's wait(90). The bare wait(90) ignored its return value, so the main thread proceeded while the server was still restarting. The INSERT loops then used query(timeout=None), so a connection to a half-up server (port open, not serving) blocks up to DEFAULT_QUERY_TIMEOUT=600s, exhausting pytest's 900s session timeout, which also fails the sibling test_materialized_views_cascaded_multiple (shared module fixture). CIDB signature confirms it: empty test_context_raw (session-timeout), 13 distinct PR groups in 30 days, most recent 2026-06-10.
c Fix matches root cause? Yes. assert disconnect_event.wait(180) makes a too-slow restart fail loudly instead of silently racing ahead; timeout=60 on the INSERTs bounds the exact 600s hang that drives the session timeout. No bounds widened, no randomization disabled, no data reduced.
d Test intent preserved / new tests added? Preserved. The test's assertions (row-count propagation test_table_S == test_table_H through the MV) are unchanged. No new test needed (this is a test-reliability fix, not a product-code bug).
e Both directions demonstrated? Yes. Half-up hang: timeout=None blocks (would reach 600s) vs timeout=N fails in N s (proven with the real client + real CommandRequest). Fixed test passes the happy path (67s) and 3/3 repeated runs on a debug build via the native integration runner.
f Fix is general, not a narrow patch? Yes. The same query(timeout=None) hang pattern existed in BOTH insert loops (the guarded L408 loop and the unguarded L422 loop); the fix adds a per-query timeout to both and extends the guarded loop's except to also catch the new QueryTimeoutExceedException fast-fail path. No product code change, so no upstream-origin trace applies.

Note: a separate LOGICAL_ERROR crash ('Mapping for table with UUID ... doesn't exist') also appears in this test, but it is isolated to a single feature PR (#105499, drop_detached_tables) and is that PR's responsibility, not a trunk issue. It is unrelated to this timeout fix.

Session id: cron:clickhouse-worker-slot-30:20260610-235700

@groeneai

Copy link
Copy Markdown
Contributor Author

cc @tavplubix could you review this? It fixes the flaky test_materialized_views_replicated (the dominant CIDB signature is the 900s session timeout, which also takes the sibling cascaded_multiple down). On slow sanitizer builds the restart outlasts the test's wait(90), and the unguarded node1.query(timeout=None) inserts then block up to 600s on a half-restarted server. Fix asserts the restart wait and adds timeout=60 to the insert loops so a hung query fails fast instead of killing the session.

@alexey-milovidov alexey-milovidov added the can be tested Allows running workflows for external contributors label Jun 11, 2026
@clickhouse-gh

clickhouse-gh Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [e5479ff]

Summary:


AI Review

Summary

This PR is a test-only reliability fix for test_async_load_databases::test_materialized_views_replicated: it bounds node1.query calls during restart, catches the resulting QueryTimeoutExceedException in the pre-start loop, and asserts that restart_clickhouse completes instead of silently racing past disconnect_event.wait. The earlier bot finding about per-query timeouts accumulating across the whole loop is addressed in the current code by checking disconnect_event and a phase deadline before each retry. I found no remaining code-review blockers or major issues.

Missing context / blind spots
  • ⚠️ Most GitHub checks for current head e5479ff48697ac4f6a1177fecb7b790c398f26f7 were still queued or pending during this review; the direct Praktika PR report showed no failed tests so far, but completed CI would close the runtime-validation gap.
Final Verdict

Status: ✅ Approve

@clickhouse-gh clickhouse-gh Bot added the pr-ci label Jun 11, 2026
Comment thread tests/integration/test_async_load_databases/test.py
Addresses review feedback on PR ClickHouse#107119: the per-query timeout=60 bounds each
node1.query but not the loop. In the half-restarted state, all 30 attempts can
each block for the full timeout, get swallowed by the except, and accumulate up
to ~30 minutes before reaching disconnect_event.wait(180), still overrunning the
900s pytest session timeout.

Bound the whole pre-start insert phase: break as soon as the restart finishes
(disconnect_event.is_set()) or a 180s phase deadline (the restart budget)
elapses. Worst case is now ~240s (180s deadline + one in-flight 60s query),
well under the 900s session timeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@clickhouse-gh

clickhouse-gh Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 84.60% 84.60% +0.00%
Functions 92.30% 92.30% +0.00%
Branches 77.20% 77.30% +0.10%

Changed lines: No C/C++ source files changed — skipping uncovered code analysis.

Newly covered by added/modified tests: 371 line(s), 22 function(s) across 148 file(s) · Details

Top files
  • src/IO/S3/copyS3File.cpp: 10 line(s)
  • src/Coordination/KeeperServer.cpp: 9 line(s), 1 function(s)
  • src/Storages/StorageReplicatedMergeTree.cpp: 9 line(s)
  • src/AggregateFunctions/AggregateFunctionQuantileExact.cpp: 8 line(s)
  • src/Functions/array/FunctionArrayMapped.h: 8 line(s)

Full report

@groeneai

Copy link
Copy Markdown
Contributor Author

CI finished for HEAD e5479ff48697. No PR-caused failures; all checks green. The only red is CH Inc sync (internal sync check that does not run on fork PRs). Ready for review.

@alexey-milovidov alexey-milovidov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good improvement.

@alexey-milovidov alexey-milovidov self-assigned this Jun 18, 2026
@alexey-milovidov
alexey-milovidov merged commit 6166517 into ClickHouse:master Jun 18, 2026
165 of 166 checks passed
@robot-clickhouse-ci-2 robot-clickhouse-ci-2 added the pr-synced-to-cloud The PR is synced to the cloud repo label Jun 18, 2026
alexey-milovidov added a commit that referenced this pull request Jul 1, 2026
Backport #107119 to 25.8: Fix flaky test_async_load_databases::test_materialized_views_replicated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-ci pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants