Stop refreshable MV gracefully on Keeper without MULTI_READ on ATTACH/restore#108441
Conversation
|
cc @al13n321 @tuanpach could you review? Moves the up-front Keeper feature-flag check in the RefreshTask constructor to cover the ATTACH/restore/existing-replica paths, so a coordinated refreshable MV on a Keeper without MULTI_READ stops gracefully (Disabled) instead of throwing on the scheduling thread and aborting the server (#106737). |
|
Workflow [PR], commit [5aa9c52] Summary: ✅
AI ReviewSummaryThis PR moves the Keeper PR Metadata💡 The
Final VerdictStatus: ✅ Approve No required code changes. |
…/restore A coordinated refreshable materialized view (in a Replicated database) crashed the server when re-attached on a Keeper that lacks the MULTI_READ feature flag, e.g. after a Keeper downgrade. The constructor's up-front MULTI_READ / CREATE_IF_NOT_EXISTS check was gated behind the fresh-CREATE path (!attach && !is_restore_from_backup) inside the !replica_path_existed branch, so ATTACH / restore / existing-replica kept coordination enabled. The scheduling thread then threw NOT_IMPLEMENTED in readZnodesIfNeeded, which the doScheduling catch-all re-raised as a LOGICAL_ERROR, aborting the server (and crash-looping on restart). Move the feature-flag check before the znode-create block so it runs on every coordinated path. Fresh CREATE still rejects with NOT_IMPLEMENTED. On ATTACH / restore / existing-replica the view now enters a permanent, non-resumable "coordination unavailable" state: a new coordination.unavailable flag is set, the task is stopped, and the reason is recorded. doScheduling short-circuits to Disabled before touching Keeper, so readZnodesIfNeeded is never reached and the server stays up. coordination.coordinated is left true on purpose. Clearing it would turn an originally coordinated view into an uncoordinated one, and a later SYSTEM START VIEW or finalizeRestoreFromBackup would then resume it as a local refresh, which breaks the replicated target table of a non-APPEND view in a Replicated database. start() and finalizeRestoreFromBackup() now also check the unavailable flag and refuse to resume, so the view stays Disabled until the table is re-created on a Keeper that supports the required feature flags. Adds an integration test that creates a coordinated refreshable MV with MULTI_READ enabled, restarts on a Keeper with MULTI_READ disabled and asserts the server stays up with the view Disabled, and then asserts SYSTEM START VIEW does not resume an uncoordinated local refresh. Closes: ClickHouse#106737 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98c1022 to
67a1d74
Compare
|
Updated after the review feedback (the fix now uses a non-resumable Pre-PR validation gate (click to expand)
Session id: cron:clickhouse-worker-slot-2:20260625-071500 |
…ti-read test SYSTEM REFRESH VIEW is asynchronous: RefreshTask::run sets the state to Scheduling synchronously and the background scheduler only later reaches the coordination.unavailable branch and switches it back to Disabled. Reading system.view_refreshes right after the refresh request could therefore observe the transient Scheduling state and fail the assertion spuriously. Add SYSTEM WAIT VIEW after the refresh request (and after the downgrade restart, which schedules the same graceful-stop pass asynchronously) so the status read always waits for the scheduled pass. WAIT VIEW returns immediately once the view is Disabled, so it does not block on the coordination-unavailable view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI finish ledger — cdabef4Every failure below has an owner. The single failure is unrelated to this PR's RefreshTask/Keeper diff.
This PR changes only Session id: cron:our-pr-ci-monitor:20260625-130000 |
|
Fixing PR for the Stress test |
alexey-milovidov
left a comment
There was a problem hiding this comment.
This looks reasonable.
However, the case when it happens is very narrow - likely it happens only in tests.
Still good to merge.
|
@groeneai, provide a fix for |
…htask-multiread-attach-106737
|
The It is already fixed by #108489 (merged 2026-06-28), which adds that benign line to the stress harness allowlist in |
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered by tests: 30/32 (93.75%) | Lost baseline coverage: none · Uncovered code |
CI finish ledger — 5aa9c52After merging master to pick up the merged stress-harness fix #108489, CI fully finished green on this head. The only red check is the external private mirror sync. The previously-tracked Session id: cron:our-pr-ci-monitor:20260629-080000 |

Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fixed a server crash (
LOGICAL_ERROR: 'Unexpected exception in refresh scheduling') that could happen on startup when a coordinated refreshable materialized view in aReplicateddatabase was attached on a Keeper that does not support theMULTI_READfeature flag (for example after a Keeper downgrade). The view is now stopped gracefully instead of aborting the server.Description
Closes: #106737 (STID 2508-3e7b)
A coordinated refreshable materialized view used to abort the server when re-attached on a Keeper without
MULTI_READ. The constructor's up-frontMULTI_READ/CREATE_IF_NOT_EXISTScheck was gated behind the fresh-CREATE path (!attach && !is_restore_from_backup) inside the!replica_path_existedbranch, so on ATTACH / restore / existing-replica the view stayedcoordinated = true. The scheduling thread then threwNOT_IMPLEMENTEDinreadZnodesIfNeeded(RefreshTask.cpp:1378), and thedoSchedulingcatch-all re-raised it as aLOGICAL_ERROR(RefreshTask.cpp:936), which aborts the server in debug/sanitizer builds and crash-loops on restart.Inner exception and stack from the reported run (amd_msan):
CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=103786&sha=04c29e862d95e40e250aff7eb504de5d1af13d33&name_0=PR&name_1=Stress%20test%20%28amd_msan%29
Fix
The feature-flag check is moved before the znode-create block, so it runs on every coordinated path:
NOT_IMPLEMENTED(unchanged behavior).readZnodesIfNeededreturns early (!coordinated), so the scheduling thread never reaches the throw, and the view ends upDisabledinstead of crashing the server.This is the approach prescribed by the maintainer in the issue (detect the missing
MULTI_READup front; fresh CREATE keeps rejecting, ATTACH/restore must not throw).Test
Adds an integration test
test_refreshable_mv_no_multi_read: it creates a coordinated refreshable MV in aReplicateddatabase withMULTI_READenabled, then restarts the server on a Keeper withMULTI_READdisabled. Without the fix the server crash-loops with theLOGICAL_ERRORabove; with the fix the server stays up and the view reportsDisabledwith the reason insystem.view_refreshes.exception.Version info
26.7.1.256