From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 0242369BCD; Wed, 3 Mar 2021 10:57:55 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E966934705; Wed, 3 Mar 2021 10:57:24 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 5E4A4346E2; Wed, 3 Mar 2021 10:57:23 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 21C9945859; Wed, 3 Mar 2021 10:57:23 +0100 (CET) From: Stefan Reiter To: pve-devel@lists.proxmox.com, pbs-devel@lists.proxmox.com Date: Wed, 3 Mar 2021 10:56:05 +0100 Message-Id: <20210303095612.7475-5-s.reiter@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210303095612.7475-1-s.reiter@proxmox.com> References: <20210303095612.7475-1-s.reiter@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.025 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mount.rs] Subject: [pbs-devel] [PATCH v2 proxmox-backup 04/11] RemoteChunkReader: add LRU cached variant 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: , X-List-Received-Date: Wed, 03 Mar 2021 09:57:55 -0000 Retain the old constructor for compatibility, most use cases don't need an LRU cache anyway. For now convert the 'mount' API to use the new variant, as the same set of chunks might be accessed multiple times in a random pattern there. Signed-off-by: Stefan Reiter --- v2: * unchanged src/bin/proxmox_backup_client/mount.rs | 4 +- src/client/remote_chunk_reader.rs | 77 ++++++++++++++++++++------ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs index 24100752..f80869b1 100644 --- a/src/bin/proxmox_backup_client/mount.rs +++ b/src/bin/proxmox_backup_client/mount.rs @@ -252,7 +252,7 @@ async fn mount_do(param: Value, pipe: Option) -> Result { if server_archive_name.ends_with(".didx") { let index = client.download_dynamic_index(&manifest, &server_archive_name).await?; let most_used = index.find_most_used_chunks(8); - let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used); + let chunk_reader = RemoteChunkReader::new_lru_cached(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used, 16); let reader = BufferedDynamicReader::new(index, chunk_reader); let archive_size = reader.archive_size(); let reader: proxmox_backup::pxar::fuse::Reader = @@ -278,7 +278,7 @@ async fn mount_do(param: Value, pipe: Option) -> Result { } else if server_archive_name.ends_with(".fidx") { let index = client.download_fixed_index(&manifest, &server_archive_name).await?; let size = index.index_bytes(); - let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), HashMap::new()); + let chunk_reader = RemoteChunkReader::new_lru_cached(client.clone(), crypt_config, file_info.chunk_crypt_mode(), HashMap::new(), 16); let reader = AsyncIndexReader::new(index, chunk_reader); let name = &format!("{}:{}/{}", repo.to_string(), path, archive_name); diff --git a/src/client/remote_chunk_reader.rs b/src/client/remote_chunk_reader.rs index 06f693a2..1314bcdc 100644 --- a/src/client/remote_chunk_reader.rs +++ b/src/client/remote_chunk_reader.rs @@ -8,6 +8,13 @@ use anyhow::{bail, Error}; use super::BackupReader; use crate::backup::{AsyncReadChunk, CryptConfig, CryptMode, DataBlob, ReadChunk}; use crate::tools::runtime::block_on; +use crate::tools::lru_cache::LruCache; + +struct Cache { + cache_hint: HashMap<[u8; 32], usize>, + hinted: HashMap<[u8; 32], Vec>, + lru: Option>>, +} /// Read chunks from remote host using ``BackupReader`` #[derive(Clone)] @@ -15,8 +22,7 @@ pub struct RemoteChunkReader { client: Arc, crypt_config: Option>, crypt_mode: CryptMode, - cache_hint: Arc>, - cache: Arc>>>, + cache: Arc>, } impl RemoteChunkReader { @@ -28,13 +34,30 @@ impl RemoteChunkReader { crypt_config: Option>, crypt_mode: CryptMode, cache_hint: HashMap<[u8; 32], usize>, + ) -> Self { + Self::new_lru_cached(client, crypt_config, crypt_mode, cache_hint, 0) + } + + /// Create a new instance. + /// + /// Chunks listed in ``cache_hint`` are cached and kept in RAM, as well as the last + /// 'cache_last' accessed chunks. + pub fn new_lru_cached( + client: Arc, + crypt_config: Option>, + crypt_mode: CryptMode, + cache_hint: HashMap<[u8; 32], usize>, + cache_last: usize, ) -> Self { Self { client, crypt_config, crypt_mode, - cache_hint: Arc::new(cache_hint), - cache: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(Mutex::new(Cache { + hinted: HashMap::with_capacity(cache_hint.len()), + lru: if cache_last == 0 { None } else { Some(LruCache::new(cache_last)) }, + cache_hint, + })), } } @@ -64,6 +87,34 @@ impl RemoteChunkReader { }, } } + + fn cache_get(&self, digest: &[u8; 32]) -> Option> { + let cache = &mut *self.cache.lock().unwrap(); + if let Some(data) = cache.hinted.get(digest) { + return Some(data.to_vec()); + } + + cache + .lru + .as_mut() + .map(|lru| lru.get_mut(*digest).map(|x| x.to_vec())) + .flatten() + } + + fn cache_insert(&self, digest: &[u8; 32], raw_data: &Vec) { + let cache = &mut *self.cache.lock().unwrap(); + + // if hinted, always cache given digest + if cache.cache_hint.contains_key(digest) { + cache.hinted.insert(*digest, raw_data.to_vec()); + return; + } + + // otherwise put in LRU + if let Some(ref mut lru) = cache.lru { + lru.insert(*digest, raw_data.to_vec()); + } + } } impl ReadChunk for RemoteChunkReader { @@ -72,18 +123,14 @@ impl ReadChunk for RemoteChunkReader { } fn read_chunk(&self, digest: &[u8; 32]) -> Result, Error> { - if let Some(raw_data) = (*self.cache.lock().unwrap()).get(digest) { - return Ok(raw_data.to_vec()); + if let Some(raw_data) = self.cache_get(digest) { + return Ok(raw_data); } let chunk = ReadChunk::read_raw_chunk(self, digest)?; let raw_data = chunk.decode(self.crypt_config.as_ref().map(Arc::as_ref), Some(digest))?; - - let use_cache = self.cache_hint.contains_key(digest); - if use_cache { - (*self.cache.lock().unwrap()).insert(*digest, raw_data.to_vec()); - } + self.cache_insert(digest, &raw_data); Ok(raw_data) } @@ -102,18 +149,14 @@ impl AsyncReadChunk for RemoteChunkReader { digest: &'a [u8; 32], ) -> Pin, Error>> + Send + 'a>> { Box::pin(async move { - if let Some(raw_data) = (*self.cache.lock().unwrap()).get(digest) { + if let Some(raw_data) = self.cache_get(digest) { return Ok(raw_data.to_vec()); } let chunk = Self::read_raw_chunk(self, digest).await?; let raw_data = chunk.decode(self.crypt_config.as_ref().map(Arc::as_ref), Some(digest))?; - - let use_cache = self.cache_hint.contains_key(digest); - if use_cache { - (*self.cache.lock().unwrap()).insert(*digest, raw_data.to_vec()); - } + self.cache_insert(digest, &raw_data); Ok(raw_data) }) -- 2.20.1