[experimental] CAS (Content-addressed storage) over shared object storage for antalya-26.6 - #2159
Conversation
0ab537f to
9ec80cb
Compare
ReadBufferFromFileView assumed the inner buffer keeps its working buffer across setReadUntilPosition; ReadBufferFromS3 resets it, so getPosition lied and seek logic could re-read a stale decompressed block. Recompute the offset from the inner buffer after every right-bound change. Includes the ReadBufferFromMemory counterpart and gtest batteries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
processException kept retrying transient errors after KILL QUERY; check CurrentThread::get().isQueryCanceled() in the outer retry loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
A borrowed child ThreadGroup parents its trackers at the parent group via raw pointers; background work outliving the query produced a use-after-free in parent counters. The child now holds a shared_ptr to the parent group. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
addPart/dropPart guarded current_writer only with chassert (a release no-op), so a missing writer meant a null dereference; throw LOGICAL_ERROR instead. Also treats a missing logs_dir as normal for storages that do not materialize empty directories (carries a CAS-related hunk; wired later). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
HTTP 412 on a conditional request is deterministic: never retry it (S3Exception::isPreconditionFailed, RetryStrategy). Adds conditional PUT/COPY (If-Match / If-None-Match) through the client, WriteBufferFromS3 and copyS3File, and the token-conditional operations on S3ObjectStorage. Also carries the copyS3File message_format_string fix (PreformattedMessage instead of a preformatted string) and CAS-facing write-settings plumbing (wired later). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
A conditional PUT that is doomed to 412 should not stream its whole body; send Expect: 100-continue above a size threshold and peek the response. Prevents mid-upload connection resets and retry storms on S3-compatible stores. Carries the GCS dialect integration points (wired later). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
GCS XML API needs native GOOG4 signing and x-goog-if-generation-match for generation-safe conditional writes; AWS SigV4 If-Match semantics are not enough, and conditional multipart complete is silently ignored. Adds the signer, the header dialect, and fixed-vector tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Emulate object-store behavior under concurrent removal: a file vanishing between listing and stat is skipped, not a filesystem_error; non-recursive explicit-stack walk with error_code overloads and a symlink guard; fail closed on an embedded-NUL path (AST fuzzer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Projection sub-parts ride the parent whole-part transaction instead of committing early; read-your-writes (in-flight resolve) becomes part of the IDiskTransaction contract so staged state is visible before commit; clone/freeze/restore wrap the whole part in one transaction; staged operation order is explicit. Mixed-file note: these files also carry the content-addressed capability surface and eager-dispatch branches that are wired by the later CAS integration commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Single-table SYSTEM commands (SYNC/RESTORE/RESTART/DROP REPLICA, WAIT LOADING PARTS, PREWARM, ...) cast the storage directly and missed tables behind a proxy; unwrap the proxy before the cast. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
clickhouse-disks --query always exited 0 and reported failures only on
stderr, so scripts, cron jobs and CI could not gate on it at all. Record
each command's error code in processQueryText and return it as the
process exit code for non-interactive runs; interactive REPL sessions
keep exiting 0. Within one semicolon-separated batch a later success does
not clear an earlier failure.
Carries the two test fixes the contract exposed: a 2024 typo in
test_disks_app_func ("d/a" instead of "a/d/a" always failed inside the
tool and was swallowed), and test_replicated_table_structure_alter
reading metadata_path from system.tables after DETACH DATABASE (empty
path, the remove never ran, the recovery scenario was never exercised).
Mixed-file note: DisksApp.cpp also carries the cas-* command registration
and CA pool initialization, wired by the later CAS integration commits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Core value types of the content-addressed storage subsystem: identifiers, hashes, tokens, namespaces. No dependencies on other CAS layers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
On-wire/on-disk encodings: manifests, ref-log records, GC state, seals, codecs. Depends only on Primitives. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Object-storage access layer: the backend interface, the object-storage adapter, the in-memory backend for tests, capability probing and request control. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Pool identity and runtime: server root, mount lifecycle, pool metadata, the ref ledger and ref protocol (publish/confirm, recovery, snapshots), the part-write transaction (dedup gate, conditional create, promote), staging and plain objects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Part-path parsing and the part-folder access facade over manifests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The garbage-collection round: fold, in-degree settlement, lease and heartbeat, prune, baseline rebuild, ack-floor fencing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
fsck (integrity checking), inspect, and pool-member decommission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The content-addressed metadata storage, its disk transaction and part staging (the top-level subsystem glue), registration of the content_addressed metadata storage type, capability predicates on IDisk/DiskObjectStorage/IMetadataStorage, the conditional-object-storage API on IObjectStorage, the FileView read-pipeline stage, write-ETag surfacing, cache-over-CA, and the atomic-file-write short-circuit for txn_version.txt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The CAS system logs (definitions, SystemLog registration, Context getters), the CAS mounts system table, and the per-disk GC-health asynchronous metrics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
SYSTEM CONTENT ADDRESSED GARBAGE COLLECTION / GC REBUILD / DROP POOL MEMBER: grammar, AST, interpreter handlers, access checks, and the parser round-trip test. Includes the magic_enum range widening required once ASTSystemQuery::Type outgrew the default range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
ProfileEvents/CurrentMetrics/AccessType/FailPoint/ServerSettings entries, build wiring, the fetch-by-relink replication protocol extension in DataPartsExchange (same-pool fetch publishes a local ref over shared blobs; gated by pool_uuid, non-CA fetches unchanged), the clickhouse-disks CA commands (inspect, fsck, gc-dryrun, gc-rebuild, drop-member), and server/local entry-point wiring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The full CAS gtest battery: per-layer unit tests, protocol state-machine tests, wiring/assembly tests, and the shared test helpers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Signed-off-by: CarlosFelipeOR <carlosfelipeor@gmail.com>
Signed-off-by: CarlosFelipeOR <carlosfelipeor@gmail.com>
…ection stays cheaper The test did not pin `index_granularity`, so the projection was not reliably cheaper than the base table and the test flapped in CI. CI: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073 PR: #2073 Squashed from: 95ba719 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The "proven absent" presence probe and the cold-reader admission both required two whole-catalog reads to be byte-identical before trusting an observation. The catalog is pool-global, so under a parallel workload the condition almost never holds and both paths starve: in CI, `01069_database_memory` failed 193 of 194 retries with "catalog changed while probing table-root cleanup completeness" on its `deduplication_logs` namespace. Each catalog read is one token-CAS'd full value, so this namespace's row is what the proof actually needs: - presence probe: row absent in BOTH reads => proven absent, linearized at the second read; a row that appears in between answers present; - cold reader: revalidate THIS row by value; unrelated churn admits, an own-row change still forces a fresh observation. The second cut now runs the ambiguity validation explicitly -- an aliasing incarnation admitted between the reads left the target row byte-identical and was only refused by the whole-catalog comparison it replaces. Two tests pinning whole-catalog stillness updated to the per-row contract; regression tests added for unrelated-churn starvation and the aliasing incarnation. PR: #2073 Squashed from: 684161d Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The `encrypted` wrapper over a content-addressed disk fails at the first `INSERT`, not at `CREATE`, so it is recorded as a known limitation rather than as an unsupported configuration. The `cas_log` note mirrors the existing `cas_gc_log` config-enablement wording. Closes: #2213 Squashed from: e1af0b0 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
…uthentication GCS generation handling was wired to authentication rather than to the caller: `ClientFactory::create` forced the GCS conditional dialect for both `gcp_oauth` and `gcs_hmac`, `PocoHTTPClientGCPOAuth::makeRequestInternal` applied the adapter to every OAuth request, and the shared response path substituted `x-goog-generation` into the AWS SDK ETag field whenever the header was present. That is a concrete regression for existing non-CAS `gcp_oauth` users, not a hypothetical risk. `HEAD` and `GET` responses yielded a numeric generation through the SDK ETag field, while `LIST` parses its ETag from the XML response body and kept an ordinary ETag. ClickHouse exposes that value through the `_etag` virtual column and uses it in the filesystem-cache, page-cache and Parquet-metadata-cache keys, so one object acquired inconsistent user-visible `_etag` values and duplicate cache entries depending on whether its metadata came from `LIST` or from `HEAD`. Generation semantics become a property of the request, not of the client. A typed `NativeConditional` field travels on the AWS request wrapper and the Poco HTTP request, re-derived on every SDK attempt, and CAS marks only its own token-producing and token-consuming calls: create-if-absent metadata and control artifacts, conditional replacement, native-token `HEAD`, exact-token `DELETE` and conditional copy. The field cannot become a header, enter a signature or appear on the network, so there is no generation client matrix, no additional OAuth token cache or refresh timeline, and no storage-wide setting. All three authentication paths keep their contracts and their names: ordinary S3 interoperability with `access_key_id`/`secret_access_key` and AWS SigV4, `gcp_oauth`, and `http_client=gcs_hmac`. GOOG4 signing gets an explicit per-operation header allowlist in which every allowed header has a documented disposition -- translate, preserve, or consume -- instead of a generic prefix rename; an unsupported remaining `x-amz-*` header raises an exception rather than being guessed or sent in a mixed-prefix request. Unsupported combinations fail closed at CAS mount or before the mutation, and GCS CAS never falls back from generations to ordinary ETags. The conditional single-`PUT` cap is renamed to `gcs_max_conditional_put_bytes`. Also: transport quoting is stripped when minting a generation token; a token a successful `HEAD` did not carry is refused; a token-dialect flip on disk reload is refused, checked against the effective settings; and S3-native staging is refused on a generation-token backend. Coverage: `test_storage_gcp_auth` proves that a `LIST`-derived and a `HEAD`-derived read of the same object produce the same `_etag` and the same filesystem-cache, page-cache and Parquet-metadata-cache inputs, with the generation in none of them -- written to fail against the old blanket override. `gtest_goog4_signer` and `gtest_aws_s3_client` pin signer and client behavior, `tests/integration/test_cas_gcs` is a deterministic GCS mock that refuses what it does not model, and `tests/integration/test_gcs_live` is an opt-in release gate for the wire shapes unit tests cannot prove. Squashed from: 9601ac3 378472f 5d7f262 9b887ac cf06f81 faab667 b4f34cf 7524e13 4c1916e 1c91a16 8562e4c 10e97f9 c5a0672 66cf66d b85461a debf1d2 b4b27a0 c0517ff 80977a7 b579691 975fe26 07cc447 dabf9f5 af801c3 55f9d5f 576e551 81b7682 2ca5677 1c21fca ea0a051 c375be6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The `FREEZE` shadow namespace was pool-global, so two servers sharing a pool wrote into one another's snapshots and `UNFREEZE` on one could reach the other's. Shadow content is now ordinary server-relative content, scoped under `server_root_id`, at all six sites that construct it. `05024_cas_freeze_two_roots` pins cross-root `UNFREEZE` isolation on a content-addressed disk. Closes: #2212 Squashed from: 8e5ee61 11f5397 7c4d412 080a1a6 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
… the cross-disk path `freezeRemote` had no content-addressed transaction branch, so `ATTACH PARTITION FROM` a local disk into a CAS disk failed. The cross-disk path now clones a part into a content-addressed disk in one transaction. `05025_cas_attach_partition_cross_disk` pins the previously-failing case. Closes: #2173 Squashed from: c5467b8 cfe9a6a f26f322 3a1cb53 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
…le pool A newline in a part-file path made the manifest record line unparseable, and the orphan sweep then refused to make progress -- so a single undecodable manifest wedged reclamation pool-wide. Reproduced. Two halves. The manifest entry path now goes through one escaper in both the record line and the banner, so encoding and decoding are symmetric: the defect was the asymmetry, not the path. And the orphan sweep skips and records an undecodable manifest instead of aborting the round, so one bad object cannot stop the pool. `05026_cas_manifest_path_newline`, plus `gtest_cas_part_manifest_format`, `gtest_cas_orphan_nomination` and `gtest_cas_sweep_deletion_premise`. The documented orphan-sweep safety protocol is corrected to match. Related: #2031 Squashed from: 6333a98 f738450 dbb4e9c (`a628cf09ae3` is the same patch as `6333a986af6` committed on the `cas-unconditional-blob-publication` line and unified by merge `2d39604d584`; it carries no additional content and is deliberately not replayed.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Two motivations: a hard size limit on GCS, and the removal of a conditional lane that turned out to be unnecessary. A blob larger than 1 GiB could not be created on GCS at all. Conditional create was required to return an incarnation token; token-producing writes are marked `NativeConditional`, forced into a single `PUT`, and bounded by the conditional `PUT` cap, because GCS does not enforce preconditions on multipart completion -- so the code failed before multipart rather than silently lose the condition. The cap is not a blob-format limit: raising it only increases the amount each concurrent single-part upload buffers and moves the failure. The same token-producing restriction also reached the otherwise-unconditional resurrect path. The result token was an accident. `BlobDepRecord::token.has_value` only distinguishes an uploaded or observed blob from manifest-trusted evidence; the token value was never a promotion precondition. Dropping it removes, after an exhaustive caller audit, `Backend::putIfAbsentStream`, `Backend::promoteStaged`, `Backend::resurrect`, `CasRequestController::conditionalCreateControlled`, `CasRefLedger::stagingConditionalCreate`, `Pool::stagingConditionalCreate`, `IObjectStorage::copyObjectConditional` with the S3 copy plumbing added solely for it, and the conditional-copy mount probe with its `conditional_copy_supported` state -- shrinking the fork surface in generic object-storage and S3 code. Every CAS writer now uses one protocol, independent of provider and source: make the precommit durable, `HEAD` the blob, adopt a present non-condemned body, otherwise publish the writer's own payload unconditionally under a fresh envelope, reconcile the per-blob freshness metadata to `Clean`, and record an explicit materialization proof. Concurrent writers may both publish the same key after racing `HEAD` misses; this is accepted, because the key fixes the logical payload, durable references name the content hash rather than an incarnation token, and a fresh envelope protects the winner from exact-token deletes already queued for a condemned predecessor. Large blobs stay streaming and multipart-capable. The request-count and latency cost of the mandatory `HEAD` was measured and explicitly accepted. Conditional semantics are unchanged for manifests, ref-log and control objects, leases, checkpoints, the registry, GC state, freshness metadata and exact-token deletion; the cap survives for writes that actually carry a condition. No durable format changes: blob key, envelope, manifest, ref-log and pool formats are untouched. Squashed from: bfb2916 cd4e835 fe80d15 1dcc0f8 7852e64 5147dc4 907c3b5 e6bd0b5 7559364 2f65aaa 57e4a13 12079ee c062fca 02a67bb f8b6e8a ee1d68a ba072e6 2c5a07f 2551ec2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
…ive GC meta-jobs ownership Mount-lease renewal and remount retried asymmetrically: a transient failure could lose the lease with no bounded recovery, and a renewal racing a worker start or a keeper replacement had no single owner. Mount leases gain a required durable `write_attempt_id`. This is a recreate-only format change: the pool format moves to generation 10 and generation-9 pools are rejected at the reader floor, because a missing attempt identity makes ambiguous writes unresolvable. Zero attempt identifiers are rejected. Overwrite retries are bounded by absolute deadlines rather than attempt counts. Renewal ownership is centralized: ownership races are closed, workers terminate on lifecycle loss, terminal publication is serialized and exception-safe, and a refused snapshot publication backs off. Recovery is exposed through `ProfileEvents` and `cas_log`, non-interfering and correctly accounted, with diagnostic failures isolated from the operation they observe. Two further defects, both first-class: `~Gc` did not drain `meta_pool`, so a meta-pool job could outlive the state it referenced. Every meta-pool job now takes shared ownership of what it touches, the pool is drained on every throwing round exit, and that cleanup is nonthrowing while still propagating genuine pool and framework exceptions on the success path. And `MountLeaseKeeper::claim` raised `LOGICAL_ERROR` for ordinary environment changes: claim-time slot races and foreign-owner conflicts now raise `ABORTED`, `MountFencedException` is preserved for GC fencing, and every adoption window is covered. Adds `tests/integration/test_cas_mount_renewal_retry`. Closes: #2244 Squashed from: e8f7ce9 159e1ab bde036b 6323532 b6a0baf ecf3d5d ea81034 8fcd289 948eed7 9b4c4ca 4ee9b69 d27ce8c 2f05d24 71a93fa 90bcbc6 cf1db18 f158974 80601d3 28b3887 9abc55b be5e92b 1ffe590 c1c66cb 7a376f1 145389b e430917 98e4968 57241b3 a77434d 683b686 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Detached CAS background work outlived `Context`, so a shutdown could dereference a released `Context` or leave work running past the release of the pool that owned it. All detached CAS work now goes through one tracked dispatcher. Shutdown stops admission, interrupts ref-table recovery, stops recovery before follow-up requests, and drains before the pool is released. Every teardown phase is fail-soft, so no phase can terminate the process. CAS event sinks resolve a weak `Context` per event and are null-safe. A diagnostic-dispatch failure can no longer replace the caller's fail-closed exception. Verified on the final revision: Debug `CAS*` 2170/2170 and ASan `CAS*` 2169/2169 with zero sanitizer reports, plus a real CAS-backed server shutdown after an approximately 18 MiB insert -- `SIGTERM=0`, wait status 0, no remaining process and no detached background-task timeout or fatal teardown diagnostics. Squashed from: e69b4d3 b0f66ff b565a5d 55d4e39 5bb93cb 6b0db04 4b04b2c c35c400 d7a02b4 e51affc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The RustFS test backend was pinned to a beta image. It moves to rc3. Squashed from: 3c18dcc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
A CAS disk block is shared with the object storage, the generic disk layer and the proxy resolver, and the CAS settings were the only consumer in it that scanned every key and rejected whatever was not in a hand-written skip-list. Any legal key nobody had enumerated failed server startup: all `s3_*` request settings, most S3 client settings, repeated `<header>` elements, `<proxy>`, and every Azure key, so a CAS disk over Azure could not be configured at all. This is also what blocked the `http_keep_alive_timeout` mitigation suggested in #2243. The CAS settings now live under a `cas_` config-key prefix and nothing else in the block is read, so the skip-list is gone with nothing in its place. A mis-spelled CAS setting is still rejected, because that check is now made over a namespace CAS actually owns. The unprefixed spelling is accepted for a bounded period, since configurations using it already exist in external CI/CD scripts; each disk reports its superseded keys once. A key written in both spellings, or twice in one spelling, is rejected rather than resolved silently. `skip_access_check` keeps its bare spelling and stops being a CAS setting: the generic disk layer reads the same key, and one key must not become two. Settings are validated before being applied and are loaded transactionally. `gcs_max_conditional_put_bytes` moves out of CAS into `S3AuthSettings`: it is a property of the GCS conditional-write dialect, not a CAS policy, and what CAS owns -- that such a write must not go multipart -- is already a per-write flag. `WriteSettings::s3_single_part_upload_max_bytes_override` had exactly one producer and disappears with it. Externally configured CAS settings are reported with their `cas_*` names in validation, parser failures, staging capability checks and mount-recovery guidance; the original prefixed spelling is preserved for unknown keys while internal setting names and persisted fields stay unchanged. The integration CAS disk now carries `http_keep_alive_timeout` and five more keys of the same class, so the lane fails if the disk block ever stops accepting them. Closes: #2243 Related: #2031 Squashed from: 1dfd7f6 4810645 3ed7f5f aae83c1 9fad804 71401fc 2671092 07d7416 d1f8fe9 3522179 a923b98 65fcfce 5ac95b8 7eb7a7e 33aa718 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Upstream `7b89d9e0786` (ClickHouse ClickHouse#111483, "Restrict local object access only for user files path") reworked blocks that content-addressed work had commented, and two branches merged it independently four days apart. Both resolutions kept the same code; only the comment text and one `#include` position came out different. Reconciled here so that comparing the two branches stops reporting differences that are not differences. No code changes: `MergeTask.cpp` and `IMergeTreeDataPart.cpp` differ only in the wording of the projection whole-part-transaction comment, `DataPartStorageOnDiskBase.cpp` in one sentence about the arena scope, and `MergeTreeData.cpp` only in where `IO/copyData.h` sits in the include list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
Four independent defects, all of them in test or harness code, none in the product. **The mount-renewal integration test could not start at all.** Its appended compose file bind-mounted the fault-injecting proxy from `utils/ca-soak/proxy/`, a soak-harness path that is deliberately not part of this branch. The mount resolved to nothing, so `python3 /proxy/s3_fault_proxy.py` failed immediately, the `s3proxy` healthcheck never passed, `node` never started behind its `condition: service_healthy`, and `cluster.start()` threw in the module fixture. The test is now self-contained: the proxy script lives beside it and the bind is relative to the test directory. `utils/ca-soak/proxy/` keeps its own copy for the soak harness. **The `FREEZE` and cross-disk `ATTACH PARTITION FROM` tests were not re-runnable.** `05024_cas_freeze_two_roots` and `05025_cas_attach_partition_cross_disk` left state that made a second run in the same server fail, so a retry could not distinguish a real regression from leftovers. **CAS gtest fixtures leaked a parked logger channel.** The fixtures parked a Poco logger channel without holding a real reference to it, so the channel could be released while still installed -- an AutoPtr steal that ASan reports as a use-after-free. Five fixtures now hold a genuine reference. **The exit tests forked a multithreaded sanitizer-instrumented runner.** The tsan unit-test job failed `CASShutdownExitTest.EmitAfterResetSharedContextExitsCleanly`: the default "fast" death-test style forks the test runner, and the exit test then builds a real `LocalObjectStorage`-backed storage inside that child, where a filesystem call failed spuriously (`weakly_canonical: Invalid argument` from the bootstrap's residual LIST) and the fail-closed refusal escaped the death statement. These tests now use the "threadsafe" style, which re-executes the binary so the child never inherits the runner's threads -- the same approach as `gtest_backup_info.cpp`. This removes the whole hazard class rather than one symptom: a forked child of a threaded process can also hang outright if it inherits a held lock, which is what a `std::exit` in such a child did before `d7a02b45f23` replaced it with `std::_Exit`. `makeLocalObjectStorageForTest` also stops discarding the error code when it cannot clear or create its scratch root, so a broken fixture says so instead of letting the storage layer report something bewildering later. Verified under ASan: the `CAS*` battery passes (2186 tests, exit 0) with the exit tests going through the re-exec path. The tsan lane was not reproduced locally (no fresh tsan build); the next CI push measures it. CI: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=5828352eddfa33a42915e26314999c358b84db95&name_0=PR PR: #2073 Squashed from: 0e623d2 d2a0599 81642c8 (plus the compose bind-mount repair, authored here: the soak-harness path it referenced does not exist on this branch.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
The renewal worker's admission gate checked only the driver state (explicit park, keeper in `RenewalTerminal`), never that the keeper it was about to drive was `Active`. After an external lease loss the remount path replaces the keeper, and a worker that re-admitted around that window called `renew` on a keeper outside `Active`. `MountLeaseKeeper::renew` classifies that as `LOGICAL_ERROR`, the worker call path had no handler, and an exception escaping a thread terminates the process: `CASPoolRemount.ExternalLossDuringRenewalUsesOneRecoveryGeneration` dumped core twice in a row in the Debug gate with exactly this stack (`renewalLoop` -> `renewKeeperOnce` -> `renew`, "renew is allowed only in Active state"). The worker now admits a renewal only when `renewalWorkerMayRenew` holds under `driver_mutex`: it exclusively owns the driver (`WorkerIdle`), a keeper is installed, and that keeper is `Active`. The park test and the wake predicate share this one definition, so they cannot drift apart. The check is sound against concurrent transitions because every keeper transition is mutually exclusive with an admitted worker call: `installKeeper`, `keeperReset` and `startKeeper` require `Dormant` or `Parked` driver ownership under the same mutex, and `release` runs only after `stopBackgroundWorkers` has joined the worker. Parking instead of the old fall-through also removes a latent null dereference on `mount_keeper` when no keeper is installed. Wake-up is guaranteed: the only path from `New` to `Active` is the startup renewal, whose `DriverLease::finish` notifies `driver_cv`. If `renew` still reports a state violation, the worker no longer takes the process down: it trips the mount fence (write admission is bounded by the fence deadline, not by this thread's existence), logs, asserts in debug builds, and exits the loop. Both `LOGICAL_ERROR` guards in `MountLeaseKeeper` now print the observed keeper state. Verification: Debug gate `CAS*` 2187/2187 with the fix, including the previously crashing test; the `CASPoolRemount` suite repeated 207 times in one process with no failure. The only observable side effect is one extra idle global-pool thread in the unit-test binary (a CAS worker lifetime now overlaps one more concurrent thread before its join), which shifts the pre-existing gtest fork-with-threads warning from 20 to 21 threads; thread names in /proc confirm no thread stays parked in `renewalLoop`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 6d9709ea3f6d5ad6fc706c8589fc714b72fd0267) Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
CI triage for
|
The failures in It was fixed in clickhouse-regression@6948a42: This explanation does not cover One harness issue also remains:
This is a very high-contention shard. It executes many feature groups concurrently and creates substantial namespace churn in Most of the later failures are consistent with However, not every failure should be classified as a scalability issue yet. The This requires follow-up but does not have to block the PR. Additionally (from PR #2073 run).
This is a real shutdown-liveness defect. The CI job timed out and initiated shutdown, but ClickHouse did not terminate within the harness deadline while a GC round was in flight. The actionable issue is that shutdown can wait indefinitely, or for too long, for an in-flight GC operation. It can be tracked as a known issue for now.
For This is an observability/compatibility gap. We should clarify the expected |
What this is
A new opt-in metadata storage type
casfor object-storage disks: MergeTree parts are storedas content-addressed blobs in a shared S3/GCS bucket pool, deduplicated by content hash, with
part manifests and named refs on top — "git for MergeTree". Multiple servers mount the same
pool and share identical data blocks without re-uploading them; same-pool replication fetches
publish a local ref over the shared blobs instead of copying bytes (fetch-by-relink). Garbage
collection runs as a lease-coordinated background round with full audit trail; integrity is
checkable online with
SYSTEM CAS FSCKand theclickhouse-diskscas-*applets.User-facing surface: the
casdisk/metadata-storage type,SYSTEM CAS GC RUN / GC REBUILD / FSCK / DROP POOL MEMBER, system tablessystem.cas_log,system.cas_gc_log,system.cas_mounts, and theclickhouse-diskscommandscas-inspect,cas-fsck,cas-gc-dryrun,cas-gc-rebuild,cas-drop-member. Full documentation ships in this PR underdocs/en/antalya/cas/(architecture and operations), plus the system-table pages.How the series is structured
29 commits, ordered for review; the subsystem lands dark and is switched on by one registration
line.
reviewable on its own:
ReadBufferFromFileViewposition tracking,ReadBufferFromS3retry-after-cancel,
ThreadGroupparent lifetime,MergeTreeDeduplicationLogfail-closednull-writer, S3 conditional writes + 412 no-retry policy,
Expect: 100-continuefor largeconditional uploads, GCS conditional-write dialect and GOOG4 signer,
LocalObjectStoragesnapshot listing semantics, the disk-transaction contract (one logical part = one
transaction, read-your-writes), SYSTEM-on-proxy unwrap, and
clickhouse-disksnon-zero exitcode for failed non-interactive commands.
src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/, bottom-up:Primitives → Formats → Backend → Pool → Parts → Gc → Tools. No layer is reachable untilthe wiring lands.
predicates, the registration line, system logs and tables, SYSTEM commands, registries and
entry points, fetch-by-relink in
DataPartsExchange.on CAS, and the documentation set.
Behavior changes to shared code (reviewers, look here)
Everything CAS-specific is gated behind the
casdisk type; a server without acasdisk getsidentical behavior except for these deliberate fixes:
clickhouse-disks --querynow exits non-zero when a command fails (previously always 0);interactive sessions unaffected. This exposed and fixes two latent test defects
(
test_disks_app_funcpath typo,test_replicated_databasereadingmetadata_pathafterDETACH).pre-existing Iceberg conditional writes).
whole-part transaction and makes staged state readable before commit — behavior-preserving on
local disks, stricter on object-storage metadata.
ReadBufferFromFileViewposition fix also affects the pre-existing packed skip-index reader.Verification
src/Disks/tests/, 134 files) plus stateless and integrationsuites; CI runs a CAS-default-disk lane over the general stateless set.
invariants) has been green on this tree; the harness itself is intentionally not part of this
PR.
and the development branch, restricted to shipped paths, is empty by construction.
Developed with AI assistance (Claude); every commit carries
Co-Authored-ByandSigned-off-by.Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Added experimental content-addressed storage (CAS) for MergeTree: a new
casmetadata storagetype for object-storage disks that stores parts as content-hash-deduplicated blobs in a shared
S3/GCS pool with manifests and refs, allowing multiple servers to share identical data without
re-uploading it, replicate same-pool parts without transferring bytes, and reclaim unreferenced
data with lease-coordinated garbage collection. Includes
SYSTEM CAS GC RUN / GC REBUILD / FSCK / DROP POOL MEMBER, system tablessystem.cas_log,system.cas_gc_log,system.cas_mounts, andclickhouse-diskscommandscas-inspect,cas-fsck,cas-gc-dryrun,cas-gc-rebuild,cas-drop-member.Documentation entry for user-facing changes
Documentation is included in this pull request:
docs/en/antalya/cas/(architecture andoperations),
docs/en/operations/storing-data.md,docs/en/sql-reference/statements/system.md,and the three system-table pages.
CI/CD Options
Exclude tests:
Regression jobs to run: