[pbs-devel] [PATCH proxmox-backup 5/6] api: admin: datastore: optimize `groups` api call
Thomas Lamprecht
t.lamprecht at proxmox.com
Fri Oct 3 12:18:38 CEST 2025
Am 03.10.25 um 10:50 schrieb Dominik Csapak:
> Currently we always touch all files for each snapshot in a group when
> listing them, even though we don't need all that info.
>
> We're only interested in getting either the last finished snapshot
> information, or the last unfinished one (which must the only one in
> normal use, we can't have multiple unfinished snapshots usually)
>
> Instead of getting all the information upgfront, use the snapshot
s/upgfront/upfront/
> iterator of the group to get only the id, sort them by time, and
> use the first we're interested in, getting the snapshot specific info
> only for those we want to check.
>
> In my (admittedly extreme) setup with ~600 groups with ~1000 snapshots
> each, this changes the time this api call needs from ~40s to <1s.
> (on a relatively fast disk).
nice! In the UI we mainly (only?) uses this for gathering the notes of
a group, FWICT?
>
> While at it, lift the restriction of only returning groups with
> snapshots in them, now returning also empty ones.
>
> To keep api compatibility, use a timestamp of 0 for those.
> (no valid backup could have been made at that time anyway)
lazy question: Is this dependent on the previous patches? From a quick
check, it looks like it might be rather independent and one could apply
this one already.
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
> src/api2/admin/datastore.rs | 45 +++++++++++++++++++++++--------------
> 1 file changed, 28 insertions(+), 17 deletions(-)
>
> diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs
> index 0b133d166..2252dcfa4 100644
> --- a/src/api2/admin/datastore.rs
> +++ b/src/api2/admin/datastore.rs
> @@ -166,36 +166,47 @@ fn backup_group_to_group_list_item(
> return None;
> }
>
> - let snapshots = match group.list_backups() {
> - Ok(snapshots) => snapshots,
> + let mut snapshots: Vec<_> = match group.iter_snapshots() {
> + Ok(snapshots) => snapshots.collect::<Result<Vec<_>, Error>>().ok()?,
> Err(_) => return None,
> };
>
> let backup_count: u64 = snapshots.len() as u64;
> - if backup_count == 0 {
> - return None;
> - }
> + let last = if backup_count == 1 {
> + // we may have only one unfinished snapshot
> + snapshots.pop().and_then(|dir| BackupInfo::new(dir).ok())
> + } else {
> + // we either have no snapshots, or at least one finished one, since we cannot have
> + // multiple unfinished ones
> + snapshots.sort_by_key(|b| std::cmp::Reverse(b.backup_time()));
> + snapshots
> + .iter()
> + .filter_map(|backup| match BackupInfo::new(backup.clone()) {
> + Ok(info) => {
> + if info.is_finished() {
> + Some(info)
> + } else {
> + None
> + }
> + }
> + Err(_) => None,
nit: Could be written as a bit more concise way:
snapshots
.iter()
.filter_map(|backup| BackupInfo::new(backup.clone()).ok())
.filter(|info| info.is_finished())
.next()
With splitting the conversion to BackupInfo and filtering for finished
backups into two makes it a bit easier (or rather quicker) to follow to me.
> + })
> + .next()
> + };
>
> - let last_backup = snapshots
> - .iter()
> - .fold(&snapshots[0], |a, b| {
> - if a.is_finished() && a.backup_dir.backup_time() > b.backup_dir.backup_time() {
> - a
> - } else {
> - b
> - }
> - })
> - .to_owned();
> + let (last_backup, files) = last
> + .map(|info| (info.backup_dir.backup_time(), info.files))
> + .unwrap_or((0, Vec::new()));
>
> let notes_path = datastore.group_notes_path(ns, group.as_ref());
> let comment = file_read_firstline(notes_path).ok();
>
> let item = GroupListItem {
> backup: group.into(),
> - last_backup: last_backup.backup_dir.backup_time(),
> + last_backup,
> owner: Some(owner),
> backup_count,
> - files: last_backup.files,
> + files,
> comment,
> };
>
More information about the pbs-devel
mailing list