π€ Generated with AI assistance β Claude Opus 5 (claude-opus-5[1m]) β from the CI logs and artifacts linked below, reviewed before posting.
β
I checked the Altinity Stable Builds lifecycle table, and the Altinity Stable Build version I'm using is still supported.
Type of problem
Bug report β availability failure under read-heavy concurrency on a CAS default policy.
Describe the situation
With cas_disk as the default storage policy, a read-heavy concurrent phase drives the ClickHouse container to exhaust its local ephemeral ports against the object store. Every CAS request then fails at connect() with errno 99:
Poco::Exception. Code: 1000, e.code() = 99,
Net Exception: Cannot assign requested address: 172.18.0.2:9001
Nothing on the object-store side is down β the port pool on the ClickHouse side is empty. Because the mount-lease renewal is a conditional write over the same path, the outage takes down the lease, and the whole disk stops answering reads and writes until a self-remount.
Found in RegressionTestsRelease / CAS (selects) / cas_selects, test /selects/final/force/concurrent/SELECT WHERE parallel inserts, deletes, updates. Both retries of the job failed, at the same phase, from the same cause β this is not flaky.
This issue:
- Turns a client-side resource exhaustion into a full read+write outage of
cas_disk (1330 queries refused over ~42 s in retry 2)
- Fences out the mount lease and forces a self-remount, discarding an in-flight
PartWriteTxn
- Is not caused by the store: RustFS
1.0.0-beta.12 honours HTTP keep-alive (verified β 3 requests, 1 connect, no Connection: close), so the connection churn is client-side
- Is not bounded by any existing limit:
disk_connections_store_limit=10000 / disk_connections_hard_limit=200000 against 28 232 ephemeral ports in the container netns
How to reproduce the behavior
Environment
- Version:
26.6.2.20000.altinityantalya
- Build type: release, amd (
build_amd_release)
- Package: PR #2159
7b274f0a461d67fff65a56aafb0865ba0e1af4c8
https://altinity-build-artifacts.s3.amazonaws.com/PRs/2159/7b274f0a461d67fff65a56aafb0865ba0e1af4c8/build_amd_release/clickhouse
- Store: RustFS
1.0.0-beta.12, single container (selects/selects_env/rustfs-service.yml)
- Disk:
cas_disk overriding the default policy (selects/cas_mode.py), no filesystem cache in front of it
- Container netns:
ip_local_port_range = 32768 60999 (28 232 ports), tcp_fin_timeout = 60
Steps
python3 -u selects/regression.py --clickhouse https://altinity-build-artifacts.s3.amazonaws.com/PRs/2159/7b274f0a461d67fff65a56aafb0865ba0e1af4c8/build_amd_release/clickhouse --cas --local --clickhouse-version 26.6.2.20000.altinityantalya --log log.log --only '/selects/final/*'
While it runs, watch the port pressure inside the server container:
docker exec <clickhouse1> grep -c ' 06 ' /proc/net/tcp # TIME_WAIT
Scope matters. Running only the failing scenario (--only '/selects/final/force/concurrent/SELECT WHERE parallel inserts, deletes, updates/*') passes β 280 s, 3837 OK, 0 fail, peak TIME_WAIT 1051 of 28 232. Exhaustion is cumulative: it needs the whole final subtree, a store with accumulated manifests (GC sweep running), and enough parts for FINAL to re-read.
Expected behavior
Concurrent SELECT ... FINAL load should not be able to drain the port pool the mount lease depends on. If the data plane does saturate, the lease should survive and the failure should stay scoped to the affected queries β not take the disk offline for reads.
Actual behavior
Same root cause, two blast radii across the two retries of the same job:
Retry 1 β lease survived; the write budget did not:
05:33:32 <Warning> CasWriteRetryLater: CAS write could not be committed
(stageManifest: part-manifest PUT at 'data/cas/manifests/selects-cas-clickhouse1/store/6c6/
6c641193-...@cas@/0000000000000001-0000000000000a6e/000001.zst' is UNCERTAIN
(retry budget exhausted) β nothing conclusive was named); retrying later
Surfaces to the client as the INSERT failing:
Code: 210. DB::Exception: CAS write could not be committed (...) ; retrying later:
while pushing to view default.MergeTree_table_..._core_mview:
While executing WaitForAsyncInsert. (NETWORK_ERROR)
16 attempts / 90 s operation deadline (CasRequestControl.h) against ~2.5 min of unavailable ports.
Retry 2 β the lease was lost:
12:18:01 <Error> CasMountLeaseKeeper: background renewal failed transiently, retrying while the lease is still valid: ... e.code() = 99 ...
12:19:41 <Error> CasMountLeaseKeeper: (same)
12:19:51 <Error> CasMountLeaseKeeper: (same)
12:20:01 <Error> CasMountLeaseKeeper: background renewal failed, the mount-lease stops advancing
The pool goes TransientNotLive and every query on the disk is refused for ~42 s:
Code: 210. DB::Exception: content-addressed disk 'cas_disk' -- mount lease not held;
backing may be temporarily unreachable; ... TRANSIENT unavailability, not damage
β 1330 occurrences in the TestFlows log, including plain SELECTs and the async-insert queue.
Then the self-remount invalidates work already in flight:
12:23:16 <Warning> CasWriteRetryLater: PartWriteTxn (writer_epoch 1) belongs to a superseded
mount incarnation (live epoch 2) β the mount was fenced out and self-remounted; restart the build
Root cause analysis
The trigger is measurable in the server log. MergeTreePrefetchedReadPool activity jumps ~25Γ in exactly the minute the ports run out β cas_disk has no cache, so each of the 4 parallel SELECT ... FINAL variants re-reads every part from the store:
|
prefetch baseline |
prefetch peak |
first errno 99 |
total errno 99 |
| retry 1 |
~1 000/min |
22 973/min (05:31) |
05:31:24 |
7 362 (until 05:33:46) |
| retry 2 |
~1 000/min |
25 818/min (12:17) |
12:17:32 |
8 722 (until 12:20:06) |
All 8 722 target 172.18.0.2:9001 β the object store, nothing else.
~430 GETs/s with tcp_fin_timeout=60 puts on the order of 26 000 sockets in TIME_WAIT, against 28 232 available ports. The retry loops then make it worse rather than better: errno 99 is local resource exhaustion, but it is classified as an ordinary retryable network error, so 16 CAS attempts (and the base client's s3_retry_attempts=500) keep opening more sockets. The lease renewal shares that path and has only 3 chances (mount_lease_ttl_ms=30000 / mount_renew_period=10000).
Suggested fix
- Bound port usage.
disk_connections_store_limit=10000 / disk_connections_hard_limit=200000 are both far above the 28 232 ports a container has. Cap disk connections for CAS disks below the ephemeral range (and/or investigate why the pool churns connections when the store honours keep-alive).
- Do not let the data plane fence out the lease. Give the mount-lease renewal reserved capacity, or widen the margin β 30 s TTL / 10 s renew is 3 attempts.
- Classify
EADDRNOTAVAIL as local exhaustion, not as a retryable remote error: back off hard instead of spending 16 attempts opening further sockets.
- CI mitigation (separate PR): put a filesystem cache in front of
cas_disk in selects/cas_mode.py, and/or raise ip_local_port_range / lower tcp_fin_timeout in the CAS test envs.
Additional context
-
Job: RegressionTestsRelease / CAS (selects) / cas_selects β failed on both retries
-
Server logs (cas_selects-artifacts-release_zookeeper, path inside the zip: selects/_instances/clickhouse1/logs/clickhouse-server.log) β every excerpt above comes from these:
-
Possibly related: #2233 (replica HTTP dies under a CAS NETWORK_ERROR storm on soak) β worth checking whether port exhaustion is the shared mechanism there too
π€ Generated with AI assistance β Claude Opus 5 (
claude-opus-5[1m]) β from the CI logs and artifacts linked below, reviewed before posting.β I checked the Altinity Stable Builds lifecycle table, and the Altinity Stable Build version I'm using is still supported.
Type of problem
Bug report β availability failure under read-heavy concurrency on a CAS default policy.
Describe the situation
With
cas_diskas thedefaultstorage policy, a read-heavy concurrent phase drives the ClickHouse container to exhaust its local ephemeral ports against the object store. Every CAS request then fails atconnect()witherrno 99:Nothing on the object-store side is down β the port pool on the ClickHouse side is empty. Because the mount-lease renewal is a conditional write over the same path, the outage takes down the lease, and the whole disk stops answering reads and writes until a self-remount.
Found in
RegressionTestsRelease / CAS (selects) / cas_selects, test/selects/final/force/concurrent/SELECT WHERE parallel inserts, deletes, updates. Both retries of the job failed, at the same phase, from the same cause β this is not flaky.This issue:
cas_disk(1330 queries refused over ~42 s in retry 2)PartWriteTxn1.0.0-beta.12honours HTTP keep-alive (verified β 3 requests, 1connect, noConnection: close), so the connection churn is client-sidedisk_connections_store_limit=10000/disk_connections_hard_limit=200000against 28 232 ephemeral ports in the container netnsHow to reproduce the behavior
Environment
26.6.2.20000.altinityantalyabuild_amd_release)7b274f0a461d67fff65a56aafb0865ba0e1af4c8https://altinity-build-artifacts.s3.amazonaws.com/PRs/2159/7b274f0a461d67fff65a56aafb0865ba0e1af4c8/build_amd_release/clickhouse1.0.0-beta.12, single container (selects/selects_env/rustfs-service.yml)cas_diskoverriding thedefaultpolicy (selects/cas_mode.py), no filesystem cache in front of itip_local_port_range = 32768 60999(28 232 ports),tcp_fin_timeout = 60Steps
python3 -u selects/regression.py --clickhouse https://altinity-build-artifacts.s3.amazonaws.com/PRs/2159/7b274f0a461d67fff65a56aafb0865ba0e1af4c8/build_amd_release/clickhouse --cas --local --clickhouse-version 26.6.2.20000.altinityantalya --log log.log --only '/selects/final/*'While it runs, watch the port pressure inside the server container:
Scope matters. Running only the failing scenario (
--only '/selects/final/force/concurrent/SELECT WHERE parallel inserts, deletes, updates/*') passes β 280 s, 3837 OK, 0 fail, peakTIME_WAIT1051 of 28 232. Exhaustion is cumulative: it needs the wholefinalsubtree, a store with accumulated manifests (GC sweep running), and enough parts forFINALto re-read.Expected behavior
Concurrent
SELECT ... FINALload should not be able to drain the port pool the mount lease depends on. If the data plane does saturate, the lease should survive and the failure should stay scoped to the affected queries β not take the disk offline for reads.Actual behavior
Same root cause, two blast radii across the two retries of the same job:
Retry 1 β lease survived; the write budget did not:
Surfaces to the client as the INSERT failing:
16 attempts / 90 s operation deadline (
CasRequestControl.h) against ~2.5 min of unavailable ports.Retry 2 β the lease was lost:
The pool goes
TransientNotLiveand every query on the disk is refused for ~42 s:β 1330 occurrences in the TestFlows log, including plain
SELECTs and the async-insert queue.Then the self-remount invalidates work already in flight:
Root cause analysis
The trigger is measurable in the server log.
MergeTreePrefetchedReadPoolactivity jumps ~25Γ in exactly the minute the ports run out βcas_diskhas no cache, so each of the 4 parallelSELECT ... FINALvariants re-reads every part from the store:errno 99errno 99All 8 722 target
172.18.0.2:9001β the object store, nothing else.~430 GETs/s with
tcp_fin_timeout=60puts on the order of 26 000 sockets inTIME_WAIT, against 28 232 available ports. The retry loops then make it worse rather than better:errno 99is local resource exhaustion, but it is classified as an ordinary retryable network error, so 16 CAS attempts (and the base client'ss3_retry_attempts=500) keep opening more sockets. The lease renewal shares that path and has only 3 chances (mount_lease_ttl_ms=30000/mount_renew_period=10000).Suggested fix
disk_connections_store_limit=10000/disk_connections_hard_limit=200000are both far above the 28 232 ports a container has. Cap disk connections for CAS disks below the ephemeral range (and/or investigate why the pool churns connections when the store honours keep-alive).EADDRNOTAVAILas local exhaustion, not as a retryable remote error: back off hard instead of spending 16 attempts opening further sockets.cas_diskinselects/cas_mode.py, and/or raiseip_local_port_range/ lowertcp_fin_timeoutin the CAS test envs.Additional context
Job: RegressionTestsRelease / CAS (selects) / cas_selects β failed on both retries
Server logs (
cas_selects-artifacts-release_zookeeper, path inside the zip:selects/_instances/clickhouse1/logs/clickhouse-server.log) β every excerpt above comes from these:raw.logis in the same zipPossibly related: #2233 (replica HTTP dies under a CAS
NETWORK_ERRORstorm on soak) β worth checking whether port exhaustion is the shared mechanism there too