From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 000691FF15C for ; Fri, 14 Nov 2025 16:05:40 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7D40516A6F; Fri, 14 Nov 2025 16:06:36 +0100 (CET) From: Samuel Rufinatscha To: pbs-devel@lists.proxmox.com Date: Fri, 14 Nov 2025 16:05:44 +0100 Message-ID: <20251114150544.224839-5-s.rufinatscha@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251114150544.224839-1-s.rufinatscha@proxmox.com> References: <20251114150544.224839-1-s.rufinatscha@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1763132735352 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.179 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [datastore.rs] Subject: [pbs-devel] [PATCH proxmox-backup v2 4/4] partial fix #6049: datastore: add TTL fallback to catch manual config edits X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" The lookup fast path reacts to API-driven config changes because save_config() bumps the generation. Manual edits of datastore.cfg do not bump the counter. To keep the system robust against such edits without reintroducing config reading and hashing on the hot path, this patch adds a TTL to the cache entry. If the cached config is older than DATASTORE_CONFIG_CACHE_TTL_SECS (set to 60s), the next lookup takes the slow path and refreshes the cached entry. Within the TTL window, unchanged generations still use the fast path. Links [1] cargo-flamegraph: https://github.com/flamegraph-rs/flamegraph Refs: #6049 Signed-off-by: Samuel Rufinatscha --- pbs-datastore/src/datastore.rs | 46 +++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs index 0fabf592..7a18435c 100644 --- a/pbs-datastore/src/datastore.rs +++ b/pbs-datastore/src/datastore.rs @@ -22,7 +22,7 @@ use proxmox_sys::error::SysError; use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions}; use proxmox_sys::linux::procfs::MountInfo; use proxmox_sys::process_locker::{ProcessLockExclusiveGuard, ProcessLockSharedGuard}; -use proxmox_time::TimeSpan; +use proxmox_time::{epoch_i64, TimeSpan}; use proxmox_worker_task::WorkerTaskContext; use pbs_api_types::{ @@ -53,6 +53,8 @@ struct DatastoreConfigCache { config: Arc, // Generation number from ConfigVersionCache last_generation: usize, + // Last update time (epoch seconds) + last_update: i64, } static DATASTORE_CONFIG_CACHE: LazyLock>> = @@ -61,6 +63,8 @@ static DATASTORE_CONFIG_CACHE: LazyLock>> = static DATASTORE_MAP: LazyLock>>> = LazyLock::new(|| Mutex::new(HashMap::new())); +/// Max age in seconds to reuse the cached datastore config. +const DATASTORE_CONFIG_CACHE_TTL_SECS: i64 = 60; /// Filename to store backup group notes pub const GROUP_NOTES_FILE_NAME: &str = "notes"; /// Filename to store backup group owner @@ -295,16 +299,22 @@ impl DatastoreBackend { /// Return the cached datastore SectionConfig and its generation. fn datastore_section_config_cached() -> Result<(Arc, Option), Error> { - let gen = ConfigVersionCache::new() - .ok() - .map(|c| c.datastore_generation()); + let now = epoch_i64(); + let version_cache = ConfigVersionCache::new().ok(); + let current_gen = version_cache.as_ref().map(|c| c.datastore_generation()); let mut guard = DATASTORE_CONFIG_CACHE.lock().unwrap(); - // Fast path: re-use cached datastore.cfg - if let (Some(gen), Some(cache)) = (gen, guard.as_ref()) { - if cache.last_generation == gen { - return Ok((cache.config.clone(), Some(gen))); + // Fast path: re-use cached datastore.cfg if cache is available, generation matches and TTL not expired + if let (Some(current_gen), Some(config_cache)) = (current_gen, guard.as_ref()) { + let gen_matches = config_cache.last_generation == current_gen; + let ttl_ok = (now - config_cache.last_update) < DATASTORE_CONFIG_CACHE_TTL_SECS; + + if gen_matches && ttl_ok { + return Ok(( + config_cache.config.clone(), + Some(config_cache.last_generation), + )); } } @@ -312,16 +322,28 @@ fn datastore_section_config_cached() -> Result<(Arc, Option