From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox-backup v2 06/14] datastore: implement index map for compact chunk usage tracking
Date: Wed, 14 Oct 2026 16:43:49 +0200 [thread overview]
Message-ID: <20261014144357.771051-7-c.ebner@proxmox.com> (raw)
In-Reply-To: <20261014144357.771051-1-c.ebner@proxmox.com>
In order to collect usage information on per-namespace/group/snapshot
level, a compact mapping from the chunks to the individual entities
referencing them needs to be established during phase 1 of garbage
collection in order to allow for reverse lookup and accounting during
phase 2, when the actual chunk raw size information is processed.
This needs to be done while keeping the memory footprint small.
IndexSet and IndexMap are therefore implemented, which store the value
or (key, value) in a compact vector, with fast item access by index,
while keeping a hashtable for fast lookup via key/value. Hash
collisions are taken into account by item comparison. The usable
index is kept generic to allow for different type checked index
implementations.
This follows along the implementation of hashed_index create,
implementing and exposing required functionality only.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
Cargo.toml | 1 +
pbs-datastore/Cargo.toml | 1 +
pbs-datastore/src/index_map.rs | 101 +++++++++++++++++++++++++++++++++
pbs-datastore/src/lib.rs | 1 +
4 files changed, 104 insertions(+)
create mode 100644 pbs-datastore/src/index_map.rs
diff --git a/Cargo.toml b/Cargo.toml
index 6d82326c0..7d838f110 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -136,6 +136,7 @@ foreign-types = "0.3"
futures = "0.3"
h2 = { version = "0.4", features = [ "stream" ] }
handlebars = "3.0"
+hashbrown = "0.14"
hex = "0.4.3"
hickory-resolver = { version = "0.24.1", default-features = false, features = [ "system-config", "tokio-runtime" ] }
http-body-util = "0.1"
diff --git a/pbs-datastore/Cargo.toml b/pbs-datastore/Cargo.toml
index b51438dc6..c18e5b6b0 100644
--- a/pbs-datastore/Cargo.toml
+++ b/pbs-datastore/Cargo.toml
@@ -12,6 +12,7 @@ const_format.workspace = true
crc32fast.workspace = true
endian_trait.workspace = true
futures.workspace = true
+hashbrown.workspace = true
hex = { workspace = true, features = [ "serde" ] }
http-body-util.workspace = true
hyper.workspace = true
diff --git a/pbs-datastore/src/index_map.rs b/pbs-datastore/src/index_map.rs
new file mode 100644
index 000000000..f8affaf76
--- /dev/null
+++ b/pbs-datastore/src/index_map.rs
@@ -0,0 +1,101 @@
+use std::hash::{BuildHasher, Hash};
+
+use hashbrown::HashTable;
+use hashbrown::hash_map::DefaultHashBuilder;
+
+pub struct IndexSet<E, I = u32, H = DefaultHashBuilder> {
+ // Stores the hash of and entry and its index in the entries.
+ // Hash is not guaranteed to be collision free, so entries must be compared.
+ table: HashTable<I>,
+ entries: Vec<E>,
+ hasher: H,
+}
+
+impl<E, I> IndexSet<E, I> {
+ pub fn new() -> Self {
+ Self {
+ table: HashTable::<I>::new(),
+ entries: Vec::new(),
+ hasher: DefaultHashBuilder::default(),
+ }
+ }
+}
+
+impl<E: Hash + Eq, I: Copy + From<usize> + Into<usize>> IndexSet<E, I> {
+ pub fn insert(&mut self, entry: E) -> I {
+ let hash = self.hasher.hash_one(&entry);
+ if let Some(idx) = self
+ .table
+ .find(hash, |idx| self.entries[I::into(*idx)] == entry)
+ {
+ return *idx;
+ }
+ let idx = I::from(self.entries.len());
+ self.entries.push(entry);
+ self.table.insert_unique(hash, idx, |idx| {
+ self.hasher.hash_one(&self.entries[I::into(*idx)])
+ });
+ idx
+ }
+
+ pub fn lookup(&self, idx: I) -> Option<&E> {
+ self.entries.get(I::into(idx))
+ }
+}
+
+pub struct Entry<K, V> {
+ pub key: K,
+ pub value: V,
+}
+
+pub struct IndexMap<K, V, I = u32, H = DefaultHashBuilder> {
+ // Stores the hash of and entry and its index in the entries.
+ // Hash is not guaranteed to be collision free, so entry keys must be compared.
+ table: HashTable<I>,
+ entries: Vec<Entry<K, V>>,
+ hasher: H,
+}
+
+impl<K, V, I> IndexMap<K, V, I> {
+ pub fn new() -> Self {
+ Self {
+ table: HashTable::new(),
+ entries: Vec::new(),
+ hasher: DefaultHashBuilder::default(),
+ }
+ }
+}
+
+impl<K: Hash + Eq, V, I: Copy + From<usize> + Into<usize>> IndexMap<K, V, I> {
+ pub fn insert(&mut self, entry: Entry<K, V>) -> I {
+ let hash = self.hasher.hash_one(&entry.key);
+ if let Some(idx) = self.get_index_of(hash, &entry.key) {
+ self.entries[I::into(idx)].value = entry.value;
+ return idx;
+ }
+ let idx = I::from(self.entries.len());
+ self.entries.push(entry);
+ self.table.insert_unique(hash, idx, |idx| {
+ self.hasher.hash_one(&self.entries[I::into(*idx)].key)
+ });
+ idx
+ }
+
+ pub fn lookup(&self, idx: I) -> Option<&Entry<K, V>> {
+ self.entries.get(I::into(idx))
+ }
+
+ /// Get mutable reference to entry value with given key
+ pub fn get_mut(&mut self, key: &K) -> Option<&mut Entry<K, V>> {
+ let hash = self.hasher.hash_one(key);
+ self.get_index_of(hash, key)
+ .and_then(|idx| self.entries.get_mut(I::into(idx)))
+ }
+
+ /// Find index for entry by its hash, comparig with key to detect hash collisions
+ fn get_index_of(&self, hash: u64, key: &K) -> Option<I> {
+ self.table
+ .find(hash, |idx| &self.entries[I::into(*idx)].key == key)
+ .copied()
+ }
+}
diff --git a/pbs-datastore/src/lib.rs b/pbs-datastore/src/lib.rs
index d9d7a4cb6..4e1222f9d 100644
--- a/pbs-datastore/src/lib.rs
+++ b/pbs-datastore/src/lib.rs
@@ -191,6 +191,7 @@ pub mod data_blob;
pub mod data_blob_reader;
pub mod file_formats;
pub mod index;
+pub(crate) mod index_map;
pub mod manifest;
pub mod move_journal;
pub mod paperkey;
--
2.47.3
next prev parent reply other threads:[~2026-10-14 14:44 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 14:43 [PATCH proxmox{,-backup} v2 00/14] fix #5799: gather datastore usage statistics on per-entity level Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox v2 01/14] pbs-api-types: avoid reimplementation of is_false helper Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox v2 02/14] pbs-api-types: add types for fine-grained datastore usage statistics Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox v2 03/14] pbs-api-types: add schema for datastore usage statistics listing Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox v2 04/14] pbs-api-types: add flag to control usage statistics collection Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 05/14] chunk store: restrict chunk sweep helper method to module parent Christian Ebner
2026-10-14 14:43 ` Christian Ebner [this message]
2026-10-14 14:43 ` [PATCH proxmox-backup v2 07/14] datastore: implement chunk map for collecting chunk usage statistics Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 08/14] datastore: cleanup persisted statistics during datastore destruction Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 09/14] fix #5799: GC: track chunk digests and accumulate statistics Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 10/14] api: config: allow modification of extended-usage-statistics flag Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 11/14] ui: expose extended usage statistics flag in datastore options Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 12/14] api: admin: add endpoint to fetch fine-grained datastore usage stats Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 13/14] bin: manager: allow listing of extended usage statistics via cli Christian Ebner
2026-10-14 14:43 ` [PATCH proxmox-backup v2 14/14] ui: implement statistics tab for datastore panel Christian Ebner
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=20261014144357.771051-7-c.ebner@proxmox.com \
--to=c.ebner@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