From 572c10a99bcae99149bd04a1daa52b9369a41182 Mon Sep 17 00:00:00 2001 From: Konstantin Morozov Date: Wed, 2 Sep 2026 18:10:40 +0200 Subject: [PATCH 1/2] add settings Signed-off-by: Konstantin Morozov --- docs/en/antalya/cas/architecture/mounts-and-leases.md | 4 ++-- docs/en/antalya/cas/configuration.md | 2 ++ .../ContentAddressed/ContentAddressedMetadataStorage.cpp | 6 ++++++ .../ContentAddressed/ContentAddressedMetadataStorage.h | 2 ++ .../ContentAddressed/ContentAddressedSettings.cpp | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/en/antalya/cas/architecture/mounts-and-leases.md b/docs/en/antalya/cas/architecture/mounts-and-leases.md index d778756ce323..71abeb9c749b 100644 --- a/docs/en/antalya/cas/architecture/mounts-and-leases.md +++ b/docs/en/antalya/cas/architecture/mounts-and-leases.md @@ -85,8 +85,8 @@ watermark — there is no separate watermark object. `MountLease` fields: `serve controller checks that one configured attempt still fits before each backend `PUT` or resolving `GET`, after each interruptible backoff, and before accepting success. A retry, `GET`, response timestamp, or wall-clock step never extends authority. -- **Cadence.** The runtime normally starts a logical renewal every `mount_renew_period` (default - 10 s), with TTL `mount_lease_ttl_ms` (default 30 s, TTL/3 renewal ratio). The next beat is anchored +- **Cadence.** The runtime normally starts a logical renewal every `cas_mount_renew_period_ms` (default + 10 s), with TTL `cas_mount_lease_ttl_ms` (default 30 s, TTL/3 renewal ratio). The next beat is anchored at the committed body's pre-I/O BOOTTIME start. A slow recovery therefore causes an immediate catch-up beat when the nominal cadence has elapsed; it does not wait a fresh full period after the response. diff --git a/docs/en/antalya/cas/configuration.md b/docs/en/antalya/cas/configuration.md index d7ee12130993..7d52106692cd 100644 --- a/docs/en/antalya/cas/configuration.md +++ b/docs/en/antalya/cas/configuration.md @@ -92,6 +92,8 @@ entirely before release. Treat this table as a snapshot of the current build, no | `cas_blob_hash` | `cityhash128` | Pool blob content-hash function (`cityhash128` \| `xxh3-128` \| `sha256`). Recorded in the pool at creation; a mismatching config is refused at mount | | `cas_blob_hash_allow_new` | `false` | Explicit opt-in to admit a new hash algorithm into an existing pool. One-way: once admitted, the pool carries both algorithms permanently | | `skip_access_check` | `false` | Skip the boot-time capability probe (start now, fix later). Only the preflight probe is skipped — the conditional-write correctness check still runs on every writable mount. **Not available on a writable generation-token (GCS) disk**, which refuses to mount with it: there, the probe battery is the only proof that a token-exact delete carries its generation precondition. Mount such a disk read-only if you need to defer the check | +| `cas_mount_lease_ttl_ms` | `30000` | Milliseconds for which a mount lease remains valid after a successful claim or renewal. Lower values shorten stale-mount recovery but reduce tolerance for object-storage and scheduling delays | +| `cas_mount_renew_period_ms` | `10000` | Milliseconds between background mount-lease renewals. It must leave enough time for one request attempt and the lease safety margin before the TTL expires | | `cas_gc_snapshot_generations_to_keep` | `3` | GC snapshot generations retained | | `cas_gc_shards` | `1` | Blob-hash-prefix reducer shards (≥ 1). Recorded in the pool at creation; a mismatching config is refused at mount | | `gcs_max_conditional_put_bytes` | 1 GiB | Largest conditional non-blob `PUT` on a generation-token store, including create-if-absent metadata/control artifacts and conditional replacements. Blob publication is unconditional, uses ordinary multipart, and is not subject to this cap | diff --git a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.cpp b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.cpp index c9cf2b166389..b585b3b3e6dd 100644 --- a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.cpp +++ b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.cpp @@ -77,6 +77,8 @@ namespace ContentAddressedSetting extern const ContentAddressedSettingsUInt64 gc_round_prefix_wholesale_budget; extern const ContentAddressedSettingsUInt64 gc_round_handoff_prefix_wholesale_budget; extern const ContentAddressedSettingsUInt64 gc_round_outcome_entry_budget; + extern const ContentAddressedSettingsUInt64 mount_lease_ttl_ms; + extern const ContentAddressedSettingsUInt64 mount_renew_period_ms; extern const ContentAddressedSettingsUInt64 part_folder_cache_bytes; extern const ContentAddressedSettingsUInt64 part_folder_cache_max_entries; extern const ContentAddressedSettingsUInt64 part_folder_cache_max_entry_bytes; @@ -294,6 +296,8 @@ ContentAddressedMetadataStorage::ContentAddressedMetadataStorage( , gc_round_prefix_wholesale_budget(settings_[ContentAddressedSetting::gc_round_prefix_wholesale_budget].value) , gc_round_handoff_prefix_wholesale_budget(settings_[ContentAddressedSetting::gc_round_handoff_prefix_wholesale_budget].value) , gc_round_outcome_entry_budget(settings_[ContentAddressedSetting::gc_round_outcome_entry_budget].value) + , mount_lease_ttl(std::chrono::milliseconds(settings_[ContentAddressedSetting::mount_lease_ttl_ms].value)) + , mount_renew_period(std::chrono::milliseconds(settings_[ContentAddressedSetting::mount_renew_period_ms].value)) , cas_part_folder_cache_bytes(settings_[ContentAddressedSetting::part_folder_cache_bytes].value) , cas_part_folder_cache_max_entries(settings_[ContentAddressedSetting::part_folder_cache_max_entries].value) , cas_part_folder_cache_max_entry_bytes(settings_[ContentAddressedSetting::part_folder_cache_max_entry_bytes].value) @@ -790,6 +794,8 @@ ContentAddressedMetadataStorage::PoolView ContentAddressedMetadataStorage::openP pool_config.gc_round_handoff_prefix_wholesale_budget = gc_round_handoff_prefix_wholesale_budget; pool_config.gc_round_outcome_entry_budget = gc_round_outcome_entry_budget; pool_config.gc_meta_pool_size = gc_meta_pool_size; + pool_config.mount_lease_ttl_ms = mount_lease_ttl; + pool_config.mount_renew_period = mount_renew_period; pool_config.event_sink = makeCasEventSink(); PoolView view; diff --git a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.h b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.h index 043ce8f9280b..b1d30b6724d9 100644 --- a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.h +++ b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedMetadataStorage.h @@ -611,6 +611,8 @@ class ContentAddressedMetadataStorage final : public IMetadataStorage, public IC const uint64_t gc_round_prefix_wholesale_budget; const uint64_t gc_round_handoff_prefix_wholesale_budget; const uint64_t gc_round_outcome_entry_budget; + const std::chrono::milliseconds mount_lease_ttl; + const std::chrono::milliseconds mount_renew_period; /// Part-folder view cache settings. `cas_part_folder_cache_bytes == 0` disables retention. const uint64_t cas_part_folder_cache_bytes; const uint64_t cas_part_folder_cache_max_entries; diff --git a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp index bf6ab3b90e33..4a8b7a82d04f 100644 --- a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp +++ b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp @@ -70,6 +70,8 @@ constexpr std::string_view CAS_KEY_PREFIX = "cas_"; DECLARE(UInt64, gc_round_prefix_wholesale_budget, 20000, "Generation-prefix wholesale delete (prune only) object cap per round (0 = unbounded)", 0) \ DECLARE(UInt64, gc_round_handoff_prefix_wholesale_budget, 5000, "Post-CAS hand-off generation-prefix reclaim object cap per round, reserved separately from gc_round_prefix_wholesale_budget so a prune-heavy round cannot starve the one-shot hand-off (0 = unbounded)", 0) \ DECLARE(UInt64, gc_round_outcome_entry_budget, 5000, "GcOutcomes per-round entry cap across the redelete/spared audit log (0 = unbounded)", 0) \ + DECLARE(UInt64, mount_lease_ttl_ms, 30000, "Mount lease validity after a successful claim or renewal, in milliseconds", 0) \ + DECLARE(UInt64, mount_renew_period_ms, 10000, "Interval between background mount lease renewals, in milliseconds", 0) \ DECLARE(String, server_root_id, "", "REQUIRED explicit layout subtree identity; macros expand as in the s3 endpoint", 0) \ DECLARE(UInt64, part_folder_cache_bytes, 64ULL << 20, "Part-folder view cache byte budget (0 disables retention)", 0) \ DECLARE(UInt64, part_folder_cache_max_entries, 10000, "Part-folder view cache entry cap", 0) \ From e4240830ccc91a4d30012e416b31a91789c8e967 Mon Sep 17 00:00:00 2001 From: Konstantin Morozov Date: Wed, 2 Sep 2026 18:37:04 +0200 Subject: [PATCH 2/2] add validation Signed-off-by: Konstantin Morozov --- docs/en/antalya/cas/configuration.md | 4 ++-- .../ContentAddressed/ContentAddressedSettings.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/en/antalya/cas/configuration.md b/docs/en/antalya/cas/configuration.md index 7d52106692cd..8770000811ba 100644 --- a/docs/en/antalya/cas/configuration.md +++ b/docs/en/antalya/cas/configuration.md @@ -92,8 +92,8 @@ entirely before release. Treat this table as a snapshot of the current build, no | `cas_blob_hash` | `cityhash128` | Pool blob content-hash function (`cityhash128` \| `xxh3-128` \| `sha256`). Recorded in the pool at creation; a mismatching config is refused at mount | | `cas_blob_hash_allow_new` | `false` | Explicit opt-in to admit a new hash algorithm into an existing pool. One-way: once admitted, the pool carries both algorithms permanently | | `skip_access_check` | `false` | Skip the boot-time capability probe (start now, fix later). Only the preflight probe is skipped — the conditional-write correctness check still runs on every writable mount. **Not available on a writable generation-token (GCS) disk**, which refuses to mount with it: there, the probe battery is the only proof that a token-exact delete carries its generation precondition. Mount such a disk read-only if you need to defer the check | -| `cas_mount_lease_ttl_ms` | `30000` | Milliseconds for which a mount lease remains valid after a successful claim or renewal. Lower values shorten stale-mount recovery but reduce tolerance for object-storage and scheduling delays | -| `cas_mount_renew_period_ms` | `10000` | Milliseconds between background mount-lease renewals. It must leave enough time for one request attempt and the lease safety margin before the TTL expires | +| `cas_mount_lease_ttl_ms` | `30000` | Milliseconds for which a mount lease remains valid after a successful claim or renewal (≥ 1). Lower values shorten stale-mount recovery but reduce tolerance for object-storage and scheduling delays | +| `cas_mount_renew_period_ms` | `10000` | Milliseconds between background mount-lease renewals (≥ 1). It must leave enough time for one request attempt and the lease safety margin before the TTL expires | | `cas_gc_snapshot_generations_to_keep` | `3` | GC snapshot generations retained | | `cas_gc_shards` | `1` | Blob-hash-prefix reducer shards (≥ 1). Recorded in the pool at creation; a mismatching config is refused at mount | | `gcs_max_conditional_put_bytes` | 1 GiB | Largest conditional non-blob `PUT` on a generation-token store, including create-if-absent metadata/control artifacts and conditional replacements. Blob publication is unconditional, uses ordinary multipart, and is not subject to this cap | diff --git a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp index 4a8b7a82d04f..6fed1a6a99f0 100644 --- a/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp +++ b/src/Disks/DiskObjectStorage/MetadataStorages/ContentAddressed/ContentAddressedSettings.cpp @@ -230,6 +230,16 @@ void ContentAddressedSettings::validate() "content_addressed disk: cas_gc_interval_sec and cas_gc_shards must be >= 1 (got {}, {})", settings[ContentAddressedSetting::gc_interval_sec].value, settings[ContentAddressedSetting::gc_shards].value); + if (settings[ContentAddressedSetting::mount_lease_ttl_ms] == 0) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "content_addressed disk: cas_mount_lease_ttl_ms must be >= 1 (got {})", + settings[ContentAddressedSetting::mount_lease_ttl_ms].value); + + if (settings[ContentAddressedSetting::mount_renew_period_ms] == 0) + throw Exception(ErrorCodes::BAD_ARGUMENTS, + "content_addressed disk: cas_mount_renew_period_ms must be >= 1 (got {})", + settings[ContentAddressedSetting::mount_renew_period_ms].value); + /// The layout subtree identity is explicit and REQUIRED — no default, so an ABSENT key throws a /// typed `NO_ELEMENTS_IN_CONFIG` (mirroring the `metadata_type` check in `MetadataStorageFactory`), /// distinct from a PRESENT-but-invalid value, which falls through to `validateServerRootId`'s