public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
To: "Proxmox Backup Server development discussion"
	<pbs-devel@lists.proxmox.com>,
	"Fabian Grünbichler" <f.gruenbichler@proxmox.com>
Subject: Re: [pbs-devel] [PATCH proxmox-backup v2 4/4] partial fix #6049: datastore: add TTL fallback to catch manual config edits
Date: Wed, 19 Nov 2025 18:25:02 +0100	[thread overview]
Message-ID: <74251f9a-7a56-4ae3-9807-a1615a349728@proxmox.com> (raw)
In-Reply-To: <1763557376.gpz3an3kl3.astroid@yuna.none>

comments inline

On 11/19/25 2:24 PM, Fabian Grünbichler wrote:
> On November 14, 2025 4:05 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 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 <s.rufinatscha@proxmox.com>
>> ---
>>   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<SectionConfigData>,
>>       // Generation number from ConfigVersionCache
>>       last_generation: usize,
>> +    // Last update time (epoch seconds)
>> +    last_update: i64,
>>   }
>>   
>>   static DATASTORE_CONFIG_CACHE: LazyLock<Mutex<Option<DatastoreConfigCache>>> =
>> @@ -61,6 +63,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
>> @@ -295,16 +299,22 @@ impl DatastoreBackend {
>>   
>>   /// Return the cached datastore SectionConfig and its generation.
>>   fn datastore_section_config_cached() -> Result<(Arc<SectionConfigData>, Option<usize>), 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<SectionConfigData>, Option<u
>>       let (config_raw, _digest) = pbs_config::datastore::config()?;
>>       let config = Arc::new(config_raw);
>>   
>> -    if let Some(gen_val) = gen {
>> +    // Update cache
>> +    let new_gen = if let Some(handle) = version_cache {
>> +        // Bump datastore generation whenever we reload the config.
>> +        // 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 = handle.increase_datastore_generation();
> 
> this could be optimized (further) if we keep the digest when we
> load+parse the config above, because we only need to bump the generation
> if the digest changed. we need to bump the timestamp always of course ;)
> also we only want to bump if we previously had a generation saved, if we
> didn't, then this is the first load and bumping is meaningless anyway..
>

Good point, I think this would be a great optimization - TTL would only 
eventually invalidate cached DataStoreImpls (if the config did change 
manually). Will add!
> but there is another issue here - this is now called in the Drop
> handler, where we don't hold the config lock, so we have no guard
> against a parallel config change API call that also bumps the generation
> between us reloading and us bumping here.. which means we could have a
> mismatch between the value in new_gen and the actual config we loaded..
> 
> I think we need to extend this helper here with a bool flag that
> determines whether we want to reload if the TTL expired, or return
> potentially outdated information? *every* lookup will handle the TTL
> anyway (by setting that parameter), so I think just fetching the
> "freshest" info we can get without reloading (by not setting it) is fine
> for the Drop handler..
>
Good point, will add the flag!


>> +        let new_gen = prev_gen + 1;
>> +
>>           *guard = Some(DatastoreConfigCache {
>>               config: config.clone(),
>> -            last_generation: gen_val,
>> +            last_generation: new_gen,
>> +            last_update: now,
>>           });
>> +
>> +        Some(new_gen)
>>       } else {
>> +        // if the cache was not available, use again the slow path next time
>>           *guard = None;
>> -    }
>> +        None
>> +    };
>>   
>> -    Ok((config, gen))
>> +    Ok((config, new_gen))
>>   }
>>   
>>   impl DataStore {
>> -- 
>> 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
> 
> 



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel

      reply	other threads:[~2025-11-19 17:25 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-14 15:05 [pbs-devel] [PATCH proxmox-backup v2 0/4] datastore: remove config reload on hot path Samuel Rufinatscha
2025-11-14 15:05 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] partial fix #6049: config: enable config version cache for datastore Samuel Rufinatscha
2025-11-14 15:05 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] partial fix #6049: datastore: impl ConfigVersionCache fast path for lookups Samuel Rufinatscha
2025-11-19 13:24   ` Fabian Grünbichler
2025-11-14 15:05 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] partial fix #6049: datastore: use config fast-path in Drop Samuel Rufinatscha
2025-11-14 15:05 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] partial fix #6049: datastore: add TTL fallback to catch manual config edits Samuel Rufinatscha
2025-11-19 13:24   ` Fabian Grünbichler
2025-11-19 17:25     ` Samuel Rufinatscha [this message]

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=74251f9a-7a56-4ae3-9807-a1615a349728@proxmox.com \
    --to=s.rufinatscha@proxmox.com \
    --cc=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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal