You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On a quiet local MinIO, with no injected fault, concurrent SELECT … FINAL (joins / LIMIT / LIMIT BY / PREWHERE) drives the CAS pool from Live to TransientNotLive. Every subsequent read on cas_disk is refused with NETWORK_ERROR (210) until the disk self-remounts.
This is not the #2243 port-exhaustion signature. That issue was closed after <http_keep_alive_timeout>60</http_keep_alive_timeout> was added to the --cas disk config. This run's clickhouse-server.log has zeroEADDRNOTAVAIL / Cannot assign requested address / CasMountLeaseKeeper / background renewal failed lines. The client still gets the lease-not-held refusal; the server never logs why the lease was dropped.
This issue:
Takes cas_disk offline for reads for ~35 s in the middle of /selects/final/force/concurrent (02:20:05–02:20:39 UTC)
Recovers: later scenarios in the same feature (PREWHERE with DML, WHERE) pass, so the pool did return to Live
Is CAS-specific: non-CAS selects on the same run is green; cas_s3_cache_selects (cache in front of CAS) is green
Is not deterministic across arch: AArch64 cas_selects on the same SHA passed
Does not crash the server (no <Fatal>, no sanitizer, no core)
Related: #2243 (closed) — same suite, same error string, but that report was driven by ephemeral-port exhaustion (errno 99) and CasMountLeaseKeeper give-up. Comment on that issue (2026-09-09) already notes cas_selects still failing 15 of 47 executions after the keep-alive workaround, most of them with mount lease not held and 0EADDRNOTAVAIL. This run is another of those.
SELECTcount()
FROM ReplacingMergeTree_table_<uid>_core FINAL
INNER JOIN MergeTree_table_<uid>_core
ON ReplacingMergeTree_table_<uid>_core.id= MergeTree_table_<uid>_core.id
FORMAT TabSeparated
SETTINGS final =1
While it runs, poll system.cas_mounts (lifecycle / lease holder) on clickhouse1. The expected transition is Live → TransientNotLive → Live with no object-store fault.
Expected behavior
Concurrent SELECT … FINAL on a healthy CAS disk must keep the mount lease. Reads should return rows. A lease blip is the designed response to backing unavailability — it must not happen when MinIO is up and no fault was injected.
Actual behavior
On this release build (amd)
The test harness sees a server exception (not a result mismatch):
Received exception from server (version 26.6.2):
Code: 210. DB::Exception: Received from localhost:9000. DB::Exception:
content-addressed disk 'cas_disk' -- mount lease not held; backing may be
temporarily unreachable; the operation is admitted again once the disk
recovers to Live; TRANSIENT unavailability, not damage:
(while reading from part data/store/34c/34c354f8-e80c-4280-9bb9-0ecf0d6dda6e/1_123_123_1/
in table default.ReplacingMergeTree_table_2fb28bf9_abf3_11f1_990f_92000882c33e_core
(34c354f8-e80c-4280-9bb9-0ecf0d6dda6e) located on disk cas_disk of type s3,
from mark 0 with max_rows_to_read = 1, offset = 0):
While reading part 1_123_123_1:
While executing MergeTreeSelect(pool: ReadPoolInOrder, algorithm: InOrder).
(NETWORK_ERROR)
(query: SELECT count() FROM ReplacingMergeTree_table_2fb28bf9_abf3_11f1_990f_92000882c33e_core FINAL INNER JOIN MergeTree_table_2f30564a_abf3_11f1_89c5_92000882c33e_core on ReplacingMergeTree_table_2fb28bf9_abf3_11f1_990f_92000882c33e_core.id = MergeTree_table_2f30564a_abf3_11f1_89c5_92000882c33e_core.id FORMAT TabSeparated
)
Call site in the suite: selects/tests/steps/join.py:67 → helpers/cluster.py:1289 (assert False on Exception: in the client output).
Failing tests (leaf + parents)
From the job log, 02:20:05–02:21:25 UTC (container clock 04:20–04:21 CEST):
The 3 failed features / 1 unsatisfied requirement are this concurrent subtree plus its ancestors. The other /selects/final work (general, alias, user rights) passed.
What the server log does not contain
clickhouse1clickhouse-server.err.log (607 lines) is CollapsingMergeTree sign-balance warnings and the usual startup cert/cas_ prefix warnings. No lease, fence, or remount line.
0 hits on all of those (except a startup stack frame through claimMountAwaitingExpiry at 04:10:11, disk bring-up). The only <Error> lines after startup are expected ACCESS_DENIED from user_rights at 04:22:18, after the concurrent feature had already recovered.
So: the client was refused because the pool was TransientNotLive, and the server wrote no Error/Warning that names the lease loss.
Same-run controls
Job
Arch
Result
RegressionTestsRelease / CAS (selects) / cas_selects
amd
failed (this report)
RegressionTestsAarch64 / CAS (selects) / cas_selects
if (lc == Cas::PoolLifecycle::Live)
return CasOpAdmission::Proceed;
if (lc == Cas::PoolLifecycle::TransientNotLive)
Cas::throwCasTransientUnavailable(
fmt::format("content-addressed disk '{}'", disk_name),
"mount lease not held; backing may be temporarily unreachable; the operation is admitted ""again once the disk recovers to Live");
throwCasTransientUnavailable (CasRequestControl.cpp:203-214) mints ErrorCodes::NETWORK_ERROR so ReplicatedMergeTreePartCheckThread will not detach the part.
Live → TransientNotLive is CasMountRuntime::noteLeaseLost() (called from the mount-fence trip). Recovery is TransientNotLive → Live after self-remount. The ~35 s outage length matches the constructed remount wait described on #2243 (~1.2× mount_lease_ttl_ms).
What is not explained by this run:whynoteLeaseLost() fired. The #2243 path (lease renewal failing on EADDRNOTAVAIL) is absent from these logs. Candidates that still fit a silent drop:
Lease renewal failed as a transient the keeper did not log at Error/Warning.
The known renewOnce / onRenewCommitted exception-safety hole (audit bc3-7): a successful renew can still call onRenewFailed() and fence the mount.
scheduleRemount latching remount_running before the thread starts (audit bc3-8), stretching the TransientNotLive window.
The soak harness already classifies this exact message as mount_fenced and retries it (cas/soak/tests/test_mount_fence_retry.py, cas/soak/soak/cluster.py). That is correct for chaos. It is not correct as the only answer for a green-path cas_selects run: the selects suite must not xfail or retry this, or the availability hole disappears from CI.
Version attr recorded by the job: 26.6.2.20001.altinityantalya.
Startup (clickhouse1, not causal)
2026.09.09 04:10:10 <Warning> ContentAddressedSettings: content_addressed disk
`storage_configuration.disks.cas_disk`: server_root_id use the superseded
unprefixed spelling and are applied for now; write them with the `cas_` prefix.
2026.09.09 04:10:10 <Information> DiskObjectStorage(cas_disk): Starting up disk cas_disk
2026.09.09 04:10:11 <Information> DiskObjectStorage(cas_disk): Disk cas_disk started up
S3 client for cas_disk initialized with s3_retry_attempts: 500 (then a second client with 0 — the CAS metadata path).
Related
#2243 — port exhaustion fencing the lease; closed; keep-alive workaround in helpers/cas_storage.py (CAS_S3_KEEP_ALIVE_TIMEOUT = 60, CAS_S3_KEEP_ALIVE_MAX_REQUESTS = 1000)
#2233 — different mechanism (relink NETWORK_ERROR storm on soak)
Do not xfail /selects/final/force/concurrent — that hides the availability hole. cas_s3_cache_selects staying green is a control, not a fix.
Suggested next facts
Rerun amd cas_selects on this SHA (n=1 here; arm already passed).
On a repro, dump system.cas_mounts and system.events (DiskConnectionsCreated/Reused/Expired/Reset) for the 02:20 window, and raise the CAS mount-lease logger so a silent noteLeaseLost is visible.
Describe the situation
On a quiet local MinIO, with no injected fault, concurrent
SELECT … FINAL(joins / LIMIT / LIMIT BY / PREWHERE) drives the CAS pool fromLivetoTransientNotLive. Every subsequent read oncas_diskis refused withNETWORK_ERROR(210) until the disk self-remounts.Found in RegressionTestsRelease / CAS (selects) / cas_selects on PR #2300 (
CAS improvements), commitd876cda683f06972f54470910c0366a93002671d.This is not the #2243 port-exhaustion signature. That issue was closed after
<http_keep_alive_timeout>60</http_keep_alive_timeout>was added to the--casdisk config. This run'sclickhouse-server.loghas zeroEADDRNOTAVAIL/Cannot assign requested address/CasMountLeaseKeeper/background renewal failedlines. The client still gets the lease-not-held refusal; the server never logs why the lease was dropped.This issue:
cas_diskoffline for reads for ~35 s in the middle of/selects/final/force/concurrent(02:20:05–02:20:39 UTC)PREWHEREwith DML,WHERE) pass, so the pool did return toLiveselectson the same run is green;cas_s3_cache_selects(cache in front of CAS) is greencas_selectson the same SHA passed<Fatal>, no sanitizer, no core)Related: #2243 (closed) — same suite, same error string, but that report was driven by ephemeral-port exhaustion (
errno 99) andCasMountLeaseKeepergive-up. Comment on that issue (2026-09-09) already notescas_selectsstill failing 15 of 47 executions after the keep-alive workaround, most of them withmount lease not heldand 0EADDRNOTAVAIL. This run is another of those.How to reproduce the behavior
Environment
26.6.2.20001.altinityantalyabuild_amd_binary)d876cda683f06972f54470910c0366a93002671dhttps://altinity-build-artifacts.s3.amazonaws.com/PRs/2300/d876cda683f06972f54470910c0366a93002671d/build_amd_binary/clickhousehttp://minio:9001/cas/data/), single containercas_diskoverriding thedefaultpolicy (selects/cas_mode.py), no filesystem cache in front--with-analyzerSteps
/selects/final/*subtree to accumulate parts):python3 -u selects/regression.py \ --clickhouse https://altinity-build-artifacts.s3.amazonaws.com/PRs/2300/d876cda683f06972f54470910c0366a93002671d/build_amd_binary/clickhouse \ --cas --local --with-analyzer --test-to-end \ --only '/selects/final/*' \ -l test.logsystem.cas_mounts(lifecycle / lease holder) onclickhouse1. The expected transition isLive → TransientNotLive → Livewith no object-store fault.Expected behavior
Concurrent
SELECT … FINALon a healthy CAS disk must keep the mount lease. Reads should return rows. A lease blip is the designed response to backing unavailability — it must not happen when MinIO is up and no fault was injected.Actual behavior
On this release build (amd)
The test harness sees a server exception (not a result mismatch):
Call site in the suite:
selects/tests/steps/join.py:67→helpers/cluster.py:1289(assert FalseonException:in the client output).Failing tests (leaf + parents)
From the job log, 02:20:05–02:21:25 UTC (container clock 04:20–04:21 CEST):
/selects/final/force/concurrent/joins/ReplacingMergeTree_table_core/selects/final/force/concurrent/joins/selects/final/force/concurrent/SELECT LIMIT BY parallel/MergeTree_table_…_core/selects/final/force/concurrent/SELECT LIMIT BY parallel/ReplacingMergeTree_table_…_core/selects/final/force/concurrent/SELECT LIMIT BY parallel/selects/final/force/concurrent/SELECT LIMIT BY parallel inserts, deletes, updates/selects/final/force/concurrent/SELECT LIMIT parallel/MergeTree_table_…_core/selects/final/force/concurrent/SELECT LIMIT parallel/ReplacingMergeTree_table_…_core/selects/final/force/concurrent/SELECT LIMIT parallel/selects/final/force/concurrent/SELECT LIMIT parallel inserts, deletes, updates/selects/final/force/concurrent/SELECT PREWHERE parallel/MergeTree_table_…_core/selects/final/force/concurrent/SELECT PREWHERE parallel/ReplacingMergeTree_table_…_core/selects/final/force/concurrent/SELECT PREWHERE parallel/selects/final/force/concurrentParents
/selects/final/force,/selects/final,/selectsfail only because of the above.Suite totals from the same job:
The 3 failed features / 1 unsatisfied requirement are this concurrent subtree plus its ancestors. The other
/selects/finalwork (general, alias, user rights) passed.What the server log does not contain
clickhouse1clickhouse-server.err.log(607 lines) is CollapsingMergeTree sign-balance warnings and the usual startup cert/cas_prefix warnings. No lease, fence, or remount line.clickhouse-server.log(~220 MB), streamed for:mount lease not heldTRANSIENT unavailabilityCode: 210CasMountLeaseKeeperbackground renewal failedCannot assign requested address/e.code() = 99/EADDRNOTAVAILself-remount/PartWriteTxn/superseded mount0 hits on all of those (except a startup stack frame through
claimMountAwaitingExpiryat 04:10:11, disk bring-up). The only<Error>lines after startup are expectedACCESS_DENIEDfromuser_rightsat 04:22:18, after the concurrent feature had already recovered.So: the client was refused because the pool was
TransientNotLive, and the server wrote no Error/Warning that names the lease loss.Same-run controls
RegressionTestsRelease / CAS (selects) / cas_selectsRegressionTestsAarch64 / CAS (selects) / cas_selectsRegressionTestsRelease / CASS3Cache (selects) / cas_s3_cache_selectsRegressionTestsRelease / Common (selects) / selectsRoot cause analysis
The refusal itself is the designed
TransientNotLivegate, not a bad exception class.ContentAddressedMetadataStorage::checkOpAdmitted(ContentAddressedMetadataStorage.cpp:1138-1154):throwCasTransientUnavailable(CasRequestControl.cpp:203-214) mintsErrorCodes::NETWORK_ERRORsoReplicatedMergeTreePartCheckThreadwill not detach the part.Live → TransientNotLiveisCasMountRuntime::noteLeaseLost()(called from the mount-fence trip). Recovery isTransientNotLive → Liveafter self-remount. The ~35 s outage length matches the constructed remount wait described on #2243 (~1.2×mount_lease_ttl_ms).What is not explained by this run: why
noteLeaseLost()fired. The #2243 path (lease renewal failing onEADDRNOTAVAIL) is absent from these logs. Candidates that still fit a silent drop:renewOnce/onRenewCommittedexception-safety hole (audit bc3-7): a successful renew can still callonRenewFailed()and fence the mount.scheduleRemountlatchingremount_runningbefore the thread starts (audit bc3-8), stretching theTransientNotLivewindow.FINALprefetch with no cache (same pressure as CAS: object-store connections exhaust the container's ephemeral ports, fencing out the mount lease #2243, different failure mode now that keep-alive is 60 s).The soak harness already classifies this exact message as
mount_fencedand retries it (cas/soak/tests/test_mount_fence_retry.py,cas/soak/soak/cluster.py). That is correct for chaos. It is not correct as the only answer for a green-pathcas_selectsrun: theselectssuite must not xfail or retry this, or the availability hole disappears from CI.Additional context
CI failure
CAS improvements(feature/antalya-26.6/CAS-improvements→antalya-26.6)d876cda683f06972f54470910c0366a93002671dhttps://altinity-build-artifacts.s3.amazonaws.com/PRs/2300/d876cda683f06972f54470910c0366a93002671d/build_amd_binary/clickhouse10085730770)Command line from the job
Version attr recorded by the job:
26.6.2.20001.altinityantalya.Startup (clickhouse1, not causal)
S3 client for
cas_diskinitialized withs3_retry_attempts: 500(then a second client with0— the CAS metadata path).Related
helpers/cas_storage.py(CAS_S3_KEEP_ALIVE_TIMEOUT = 60,CAS_S3_KEEP_ALIVE_MAX_REQUESTS = 1000)NETWORK_ERRORstorm on soak)/selects/final/force/concurrent— that hides the availability hole.cas_s3_cache_selectsstaying green is a control, not a fix.Suggested next facts
cas_selectson this SHA (n=1 here; arm already passed).system.cas_mountsandsystem.events(DiskConnectionsCreated/Reused/Expired/Reset) for the 02:20 window, and raise the CAS mount-lease logger so a silentnoteLeaseLostis visible.