Repository navigation
Conversation
`SYSTEM CAS DROP POOL MEMBER 'a'` and the staging sweep select objects by key prefix, so a member `a/b` was drained together with `a`. The first claim of an id (owner absent) now refuses an id containing `/`; `validateServerRootId` keeps accepting one so members that already own a nested id keep mounting. The adopt-from-lease decommission fallback goes through the same claim and is refused for a nested id before any object is touched. Tests: `CASServerRootId.NewIdWithSlashIsRefused`, `CASDecommission.LeaseAdoptFallbackRefusesANestedIdWithoutOwner` (red on the base: the owner was created), guard `CASServerRootId.ExistingIdWithSlashStillMounts`. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
An aborted transaction left its S3 staging object until the next mount of the same `server_root_id`; the comment calling it a re-readable publication source was wrong, nothing reads `pending_blobs` after the cleanup. Commit and abort now delete it alike, best-effort. Incomplete multipart uploads are invisible to object LIST, so the docs state the bucket lifecycle rule `AbortIncompleteMultipartUpload`. Tests: `CASS3Staging.AbortDeletesS3Staging` (red on the base: one object remains), guard `CASS3Staging.AbortAfterStorageShutdownDoesNotThrow`. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drain lists `<pool_prefix>/staging/<srid>/` as a logical key and the backend applies the local root itself, while the writer mints keys through `physicalKey`. Two guard tests stage a blob through a real transaction, model a lost abort delete, and decommission the member over an EmulatedSingleProcess (root-prefixed) and a Native (verbatim) backend. A third pins that the next mount's sweep reclaims the same object. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The foreign-owner refusal advised a hand delete that the next start refuses over existing data; it now names `SYSTEM CAS DROP POOL MEMBER` for a dead owner and the hand delete last. The retired-owner refusal advised clearing the tombstone, which would let writer epochs repeat; it now says to configure a new `cas_server_root_id`. The `NoWait` refusal said "wait for its lease to lapse", but the claim trusts no wall clock: it now names the terminal states (`terminated`, `fenced`) and how GC reaches `fenced` for a hard-killed member. Tests: `CASServerRootClaim.RefusalsNameTheSupportedRecovery`, `CASDecommission.NoWaitRefusalNamesTheTerminalStates` (red on the base: substrings absent). The substrings the integration test matches on (`alive or contended`, `explicitly decommissioned`) are kept. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fencing happens only in a round run by the disk's GC leader, after it has seen the mount token unchanged for about one lease TTL plus one renew period. The refusal text and the docs said any live member's next round. They now name the leader and `is_leader` in `system.cas_mounts`. The test also pins `alive or contended` and `state = 'terminated'`. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three CASServerRootSafety owner-claim expectations now assert code and message, so they pass only through the emptiness check that runs before the nesting rule. The post-shutdown abort test makes the delete throw so it reaches the catch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
…ifecycle PR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
40 of 82 tasks
The GC leader fences an expired mount lease only at the start of a GC round, and under load a round took 183-245 s. The decommission refusal and the docs now say so. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k-morozov
reviewed
Oct 16, 2026
| CasOperation & op, const Layout & l, const String & srid, const RefCatalog & catalog_observation) | ||
| { | ||
| const String owned_prefix = srid + "/"; | ||
| for (const CatalogEntry & entry : catalog_observation.entries) |
There was a problem hiding this comment.
Seems there is a corner case here.
As I can see above here we decided support old server_root with '`/'.
We have entry in ref_catalog only after inserting data (precommit stage). So we could have server_rrot_id a/b without entry in ref_catalog and serverRootSubtreeEmpty returns true instead false.
This repoduces the same problem.
Member
Author
There was a problem hiding this comment.
Agreed. / is now refused for every cas_server_root_id, including ids that already own their owner object, so a nested id can no longer mount or be decommissioned and the subtree check does not have to reason about one. An operator recreates such a member under a new id without /. Fixed in 52cb4b5.
Decommission and the staging sweep select a member's objects by key prefix, so a member `a/b` would be drained by a decommission of `a`. An owner-claim check covered only new ids; a nested id with no ref catalog entry yet still let `a` claim over it. `validateServerRootId` now refuses '/' for every id, so the disk config, `Pool::open` and `SYSTEM CAS DROP POOL MEMBER` all refuse it, and the claim-time check is gone. Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
cas_server_root_idmay no longer contain/, for new and existing members alike. Decommission, staging and the GC floor lookup select a member's objects by path prefix, so decommissioningaalso removes the namespaces, staging and mountpoint objects of a membera/b. A server whose id contains/refuses to open the disk and must be recreated under a new id.Changes
validateServerRootIdrefuses/withBAD_ARGUMENTS. It runs at disk config read, inPool::openand inSYSTEM CAS DROP POOL MEMBER/clickhouse-disks cas-drop-member, so a nested id can neither mount nor be decommissioned. The message tells the operator to recreate the server under a new<cas_server_root_id>without/.server_root_id.AbortIncompleteMultipartUploadlifecycle rule: an upload killed mid-way is invisible to object LIST and is never swept.cas_server_root_id; do not clear the tombstone;terminatedorfenced;SYSTEM CAS DROP POOL MEMBERon a hard-killed member: wait until the disk's GC leader fences it, about one lease TTL plus one renew period after it first saw the lease;SYSTEM CAS GC RUNon the leader helps, but two back-to-back runs do not fence.Risks / notes
cas_server_root_idcontains/(for example from a{shard}/{replica}template) refuses to open the disk withBAD_ARGUMENTS. Recreate it under a new id without/, such as{shard}-{replica}. Data stored under the old id is not visible under the new id; the old id's objects stay in the pool and keep their blobs referenced, andSYSTEM CAS DROP POOL MEMBERrefuses the old id, so it cannot reclaim them.astill selects the objects of a legacya/bleft in the pool by an earlier build. Such a member can no longer mount.Cleaning up a legacy id with
/(example ida/b)Before upgrading, on 26.6.4 (preferred):
a/b. If it must stay in the cluster, first start a replacement under an id without/and let it replicate.SYSTEM CAS DROP POOL MEMBER 'a/b' FROM DISK '<disk>'. 26.6.4 accepts/in the id. A live lease is refused; a hard-killed member must be fenced first, as described above.slot_removed = trueand emptywarnings. The warningnamespace(s) are still owned by this membermeans GC rounds must run first.Dropping
a/bselects keys undera/b/only, nevera/bcora, but it does select a deepera/b/c. Never drop a parentawhilea/bmust survive: both versions selecta/b's objects fora.After upgrading, if the steps above were missed:
cas/ref_catalogis one object for the whole pool, updated by compare-and-swap by writers and GC. Itsa/b/...rows cannot be removed by hand.cas/ns/stream/<life>/andcas/ns/state/<life>/are keyed by life id, not byserver_root_id. Only the catalog maps them toa/b.gc/server-roots/a/b/(owner,epoch,mount) is the slot that decommission claims. Withoutownerandmountit refuses withunknown pool member. The manifest sweep reads its floor frommount.staging/a/b/,roots/a/b/and eligiblecas/manifests/a/b/debris are drained bycas-drop-memberitself.blobs/: GC reclaims a blob once no namespace references it.a/bserver stopped. Run the 26.6.4 offline tool against the pool from a config whose CAS disk points at the same pool with<readonly>true</readonly>:clickhouse-disks -C config.xml --disk cas cas-drop-member 'a/b'slot_removed=trueand nowarning=lines. The upgraded servers' GC deletes the removed catalog rows and then the unreferenced blobs.Not verified: that a 26.6.4 binary reads every object this build writes. No test runs a 26.6.4
cas-drop-memberagainst a pool written by this build.Testing
system.cas_mountsstill names a legacy nested slot whole; abort after storage shutdown does not throw; the decommission staging drain removes what the writer staged under the local and native key prefixes; the next mount sweeps a staging object whose abort delete was lost.CAS*unit tests green in release and ASan;test_cas_drop_pool_memberpasses, including a new case:SYSTEM CAS DROP POOL MEMBER 'node2/nested'is refused withBAD_ARGUMENTS.Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
CAS:
cas_server_root_idmay not contain/. A server whose id contains/(for example from a{shard}/{replica}template) refuses to start the disk and must be recreated under a new id without/; data stored under the old id is not visible under the new one. An aborted transaction deletes its S3 staging object.SYSTEM CAS DROP POOL MEMBERand the owner refusals name the state or command that resolves them.Documentation entry for user-facing changes
CI/CD Options
Exclude tests:
Regression jobs to run: