From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: Proxmox Backup Server development discussion
<pbs-devel@lists.proxmox.com>
Subject: Re: [pbs-devel] [PATCH proxmox-backup v5 4/4] partial fix #6049: datastore: add TTL fallback to catch manual config edits
Date: Wed, 26 Nov 2025 16:15:48 +0100 [thread overview]
Message-ID: <1764168037.5k45i6iik1.astroid@yuna.none> (raw)
In-Reply-To: <20251124170423.303300-5-s.rufinatscha@proxmox.com>
nit for the subject: this doesn't fix the reported issue, it just
improves the fix further, so please drop that part and maybe instead add
"lookup" somewhere..
On November 24, 2025 6:04 pm, Samuel Rufinatscha wrote:
> 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 entry. As an optimization, a check to
> catch manual edits was added (if the digest changed but generation
> stayed the same), so that the generation is only bumped when needed.
>
> Links
>
> [1] cargo-flamegraph: https://github.com/flamegraph-rs/flamegraph
>
> Fixes: #6049
> Signed-off-by: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
one style nit below, otherwise:
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
> ---
> Changes:
>
> From v1 → v2
> - Store last_update timestamp in DatastoreConfigCache type.
>
> From v2 → v3
> No changes
>
> From v3 → v4
> - Fix digest generation bump logic in update_cache, thanks @Fabian.
>
> From v4 → v5
> - Rebased only, no changes
>
> pbs-datastore/src/datastore.rs | 53 ++++++++++++++++++++++++----------
> 1 file changed, 38 insertions(+), 15 deletions(-)
>
> diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
> index 7638a899..0fc3fbf2 100644
> --- a/pbs-datastore/src/datastore.rs
> +++ b/pbs-datastore/src/datastore.rs
> @@ -53,8 +53,12 @@ use crate::{DataBlob, LocalDatastoreLruCache};
> struct DatastoreConfigCache {
> // Parsed datastore.cfg file
> config: Arc<SectionConfigData>,
> + // Digest of the datastore.cfg file
> + digest: [u8; 32],
> // Generation number from ConfigVersionCache
> last_generation: usize,
> + // Last update time (epoch seconds)
> + last_update: i64,
> }
>
> static DATASTORE_CONFIG_CACHE: LazyLock<Mutex<Option<DatastoreConfigCache>>> =
> @@ -63,6 +67,8 @@ static DATASTORE_CONFIG_CACHE: LazyLock<Mutex<Option<DatastoreConfigCache>>> =
> static DATASTORE_MAP: LazyLock<Mutex<HashMap<String, Arc<DataStoreImpl>>>> =
> 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
> @@ -329,13 +335,14 @@ impl DatastoreThreadSettings {
> /// generation.
> ///
> /// Uses `ConfigVersionCache` to detect stale entries:
> -/// - If the cached generation matches the current generation, the
> -/// cached config is returned.
> +/// - If the cached generation matches the current generation and TTL is
> +/// OK, the cached config is returned.
> /// - Otherwise the config is re-read from disk. If `update_cache` is
> -/// `true`, the new config and bumped generation are stored in the
> -/// cache. Callers that set `update_cache = true` must hold the
> -/// datastore config lock to avoid racing with concurrent config
> -/// changes.
> +/// `true` and a previous cached entry exists with the same generation
> +/// but a different digest, this indicates the config has changed
> +/// (e.g. manual edit) and the generation must be bumped. Callers
> +/// that set `update_cache = true` must hold the datastore config lock
> +/// to avoid racing with concurrent config changes.
> /// - If `update_cache` is `false`, the freshly read config is returned
> /// but the cache and generation are left unchanged.
> ///
> @@ -347,30 +354,46 @@ fn datastore_section_config_cached(
> let mut config_cache = DATASTORE_CONFIG_CACHE.lock().unwrap();
>
> if let Ok(version_cache) = ConfigVersionCache::new() {
> + let now = epoch_i64();
> let current_gen = version_cache.datastore_generation();
> if let Some(cached) = config_cache.as_ref() {
> - // Fast path: re-use cached datastore.cfg
> - if cached.last_generation == current_gen {
> + // Fast path: re-use cached datastore.cfg if generation matches and TTL not expired
> + if cached.last_generation == current_gen
> + && now - cached.last_update < DATASTORE_CONFIG_CACHE_TTL_SECS
> + {
> return Ok((cached.config.clone(), Some(cached.last_generation)));
> }
> }
> // Slow path: re-read datastore.cfg
> - let (config_raw, _digest) = pbs_config::datastore::config()?;
> + let (config_raw, digest) = pbs_config::datastore::config()?;
> let config = Arc::new(config_raw);
>
> let mut effective_gen = current_gen;
> if update_cache {
> - // Bump the generation. This ensures that Drop
> - // handlers will detect that a newer config exists
> - // and will not rely on a stale cached entry for
> - // maintenance mandate.
> - let prev_gen = version_cache.increase_datastore_generation();
> - effective_gen = prev_gen + 1;
> + // Bump the generation if the config has been changed manually.
> + // This ensures that Drop handlers will detect that a newer config exists
> + // and will not rely on a stale cached entry for maintenance mandate.
> + let (prev_gen, prev_digest) = config_cache
> + .as_ref()
> + .map(|c| (Some(c.last_generation), Some(c.digest)))
> + .unwrap_or((None, None));
so here we map an option to a tuple of options and unwrap it
> +
> + let manual_edit = match (prev_gen, prev_digest) {
only to then match and convert it to a boolean again here
> + (Some(prev_g), Some(prev_d)) => prev_g == current_gen && prev_d != digest,
> + _ => false,
> + };
> +
> + if manual_edit {
> + let prev_gen = version_cache.increase_datastore_generation();
> + effective_gen = prev_gen + 1;
to then do some code here, if the boolean is true ;)
this can all just be a single block of code instead:
if let Some(cached) = config_cache.as_ref() {
if cached.last_generation == current_gen && cached.digest != digest {
effective_gen = version_cache.increase_datastore_generation() + 1;
}
}
which also matches the first block higher up in the helper..
> + }
>
> // Persist
> *config_cache = Some(DatastoreConfigCache {
> config: config.clone(),
> + digest,
> last_generation: effective_gen,
> + last_update: now,
> });
> }
>
> --
> 2.47.3
>
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
next prev parent reply other threads:[~2025-11-26 15:16 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-24 17:04 [pbs-devel] [PATCH proxmox-backup v5 0/4] datastore: remove config reload on hot path Samuel Rufinatscha
2025-11-24 17:04 ` [pbs-devel] [PATCH proxmox-backup v5 1/4] partial fix #6049: config: enable config version cache for datastore Samuel Rufinatscha
2025-11-26 15:15 ` Fabian Grünbichler
2025-11-24 17:04 ` [pbs-devel] [PATCH proxmox-backup v5 2/4] partial fix #6049: datastore: impl ConfigVersionCache fast path for lookups Samuel Rufinatscha
2025-11-26 15:15 ` Fabian Grünbichler
2025-11-26 17:21 ` Samuel Rufinatscha
2025-11-24 17:04 ` [pbs-devel] [PATCH proxmox-backup v5 3/4] partial fix #6049: datastore: use config fast-path in Drop Samuel Rufinatscha
2025-11-26 15:15 ` Fabian Grünbichler
2025-11-28 9:03 ` Samuel Rufinatscha
2025-11-28 10:46 ` Fabian Grünbichler
2025-11-28 11:10 ` Samuel Rufinatscha
2025-11-24 17:04 ` [pbs-devel] [PATCH proxmox-backup v5 4/4] partial fix #6049: datastore: add TTL fallback to catch manual config edits Samuel Rufinatscha
2025-11-26 15:15 ` Fabian Grünbichler [this message]
2025-11-26 15:16 ` [pbs-devel] [PATCH proxmox-backup v5 0/4] datastore: remove config reload on hot path Fabian Grünbichler
2025-11-26 16:10 ` Samuel Rufinatscha
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1764168037.5k45i6iik1.astroid@yuna.none \
--to=f.gruenbichler@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox