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 32AFF1FF184 for ; Thu, 20 Nov 2025 14:04:17 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6C7277E51; Thu, 20 Nov 2025 14:04:23 +0100 (CET) From: Samuel Rufinatscha To: pbs-devel@lists.proxmox.com Date: Thu, 20 Nov 2025 14:03:40 +0100 Message-ID: <20251120130342.248815-5-s.rufinatscha@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251120130342.248815-1-s.rufinatscha@proxmox.com> References: <20251120130342.248815-1-s.rufinatscha@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1763643796811 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.239 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 SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup v3 4/6] 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 Fixes: #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 1494521c..1711c753 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 @@ -297,16 +301,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), + )); } } @@ -314,16 +324,28 @@ fn datastore_section_config_cached() -> Result<(Arc, Option