datastore: add tuning option for chunk order
currently, we sort chunks by inode when verifying or backing up to tape. we get the inode# by stat'ing each chunk, which may be more expensive than the gains of reading the chunks in order Since that is highly dependent on the underlying storage of the datastore, introduce a tuning option so that the admin can tune that behaviour for each datastore. The default stays the same (sorting by inode) Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
		
				
					committed by
					
						 Dietmar Maurer
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						parent
						
							118f8589a9
						
					
				
				
					commit
					fef61684b4
				
			| @ -167,6 +167,38 @@ pub struct PruneOptions { | ||||
|     pub keep_yearly: Option<u64>, | ||||
| } | ||||
|  | ||||
| #[api] | ||||
| #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| /// The order to sort chunks by | ||||
| pub enum ChunkOrder { | ||||
|     /// Iterate chunks in the index order | ||||
|     None, | ||||
|     /// Iterate chunks in inode order | ||||
|     Inode, | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     properties: { | ||||
|         "chunk-order": { | ||||
|             type: ChunkOrder, | ||||
|             optional: true, | ||||
|         }, | ||||
|     }, | ||||
| )] | ||||
| #[derive(Serialize, Deserialize, Default)] | ||||
| #[serde(rename_all = "kebab-case")] | ||||
| /// Datastore tuning options | ||||
| pub struct DatastoreTuning { | ||||
|     /// Iterate chunks in this order | ||||
|     pub chunk_order: Option<ChunkOrder>, | ||||
| } | ||||
|  | ||||
| pub const DATASTORE_TUNING_STRING_SCHEMA: Schema = StringSchema::new( | ||||
|     "Datastore tuning options") | ||||
|     .format(&ApiStringFormat::PropertyString(&DatastoreTuning::API_SCHEMA)) | ||||
|     .schema(); | ||||
|  | ||||
| #[api( | ||||
|     properties: { | ||||
|         name: { | ||||
| @ -224,6 +256,10 @@ pub struct PruneOptions { | ||||
|             optional: true, | ||||
|             type: bool, | ||||
|         }, | ||||
|         tuning: { | ||||
|             optional: true, | ||||
|             schema: DATASTORE_TUNING_STRING_SCHEMA, | ||||
|         }, | ||||
|     } | ||||
| )] | ||||
| #[derive(Serialize,Deserialize,Updater)] | ||||
| @ -261,6 +297,9 @@ pub struct DataStoreConfig { | ||||
|     /// Send notification only for job errors | ||||
|     #[serde(skip_serializing_if="Option::is_none")] | ||||
|     pub notify: Option<String>, | ||||
|     /// Datastore tuning options | ||||
|     #[serde(skip_serializing_if="Option::is_none")] | ||||
|     pub tuning: Option<String>, | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|  | ||||
| @ -9,13 +9,18 @@ use std::time::Duration; | ||||
| use anyhow::{bail, format_err, Error}; | ||||
| use lazy_static::lazy_static; | ||||
|  | ||||
| use proxmox_schema::ApiType; | ||||
|  | ||||
| use proxmox_sys::fs::{replace_file, file_read_optional_string, CreateOptions}; | ||||
| use proxmox_sys::process_locker::ProcessLockSharedGuard; | ||||
| use proxmox_sys::WorkerTaskContext; | ||||
| use proxmox_sys::{task_log, task_warn}; | ||||
| use proxmox_sys::fs::{lock_dir_noblock, DirLockGuard}; | ||||
|  | ||||
| use pbs_api_types::{UPID, DataStoreConfig, Authid, GarbageCollectionStatus, HumanByte}; | ||||
| use pbs_api_types::{ | ||||
|     UPID, DataStoreConfig, Authid, GarbageCollectionStatus, HumanByte, | ||||
|     ChunkOrder, DatastoreTuning, | ||||
| }; | ||||
| use pbs_config::{open_backup_lockfile, BackupLockGuard}; | ||||
|  | ||||
| use crate::DataBlob; | ||||
| @ -57,12 +62,11 @@ pub struct DataStore { | ||||
|     gc_mutex: Mutex<()>, | ||||
|     last_gc_status: Mutex<GarbageCollectionStatus>, | ||||
|     verify_new: bool, | ||||
|     chunk_order: ChunkOrder, | ||||
| } | ||||
|  | ||||
| impl DataStore { | ||||
|  | ||||
|     pub fn lookup_datastore(name: &str) -> Result<Arc<DataStore>, Error> { | ||||
|  | ||||
|         let (config, _digest) = pbs_config::datastore::config()?; | ||||
|         let config: DataStoreConfig = config.lookup("datastore", name)?; | ||||
|         let path = PathBuf::from(&config.path); | ||||
| @ -116,11 +120,17 @@ impl DataStore { | ||||
|             GarbageCollectionStatus::default() | ||||
|         }; | ||||
|  | ||||
|         let tuning: DatastoreTuning = serde_json::from_value( | ||||
|             DatastoreTuning::API_SCHEMA.parse_property_string(config.tuning.as_deref().unwrap_or(""))? | ||||
|         )?; | ||||
|         let chunk_order = tuning.chunk_order.unwrap_or(ChunkOrder::Inode); | ||||
|  | ||||
|         Ok(Self { | ||||
|             chunk_store: Arc::new(chunk_store), | ||||
|             gc_mutex: Mutex::new(()), | ||||
|             last_gc_status: Mutex::new(gc_status), | ||||
|             verify_new: config.verify_new.unwrap_or(false), | ||||
|             chunk_order, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -907,16 +917,26 @@ impl DataStore { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             let ino = match self.stat_chunk(&info.digest) { | ||||
|             let ino = match self.chunk_order { | ||||
|                 ChunkOrder::Inode => { | ||||
|                     match self.stat_chunk(&info.digest) { | ||||
|                         Err(_) => u64::MAX, // could not stat, move to end of list | ||||
|                         Ok(metadata) => metadata.ino(), | ||||
|                     } | ||||
|                 } | ||||
|                 ChunkOrder::None => 0, | ||||
|             }; | ||||
|  | ||||
|             chunk_list.push((pos, ino)); | ||||
|         } | ||||
|  | ||||
|         match self.chunk_order { | ||||
|             // sorting by inode improves data locality, which makes it lots faster on spinners | ||||
|         chunk_list.sort_unstable_by(|(_, ino_a), (_, ino_b)| ino_a.cmp(ino_b)); | ||||
|             ChunkOrder::Inode => { | ||||
|                 chunk_list.sort_unstable_by(|(_, ino_a), (_, ino_b)| ino_a.cmp(ino_b)) | ||||
|             } | ||||
|             ChunkOrder::None => {} | ||||
|         } | ||||
|  | ||||
|         Ok(chunk_list) | ||||
|     } | ||||
|  | ||||
| @ -184,6 +184,8 @@ pub enum DeletableProperty { | ||||
|     notify_user, | ||||
|     /// Delete the notify property | ||||
|     notify, | ||||
|     /// Delete the tuning property | ||||
|     tuning, | ||||
| } | ||||
|  | ||||
| #[api( | ||||
| @ -250,6 +252,7 @@ pub fn update_datastore( | ||||
|                 DeletableProperty::verify_new => { data.verify_new = None; }, | ||||
|                 DeletableProperty::notify => { data.notify = None; }, | ||||
|                 DeletableProperty::notify_user => { data.notify_user = None; }, | ||||
|                 DeletableProperty::tuning => { data.tuning = None; }, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -295,6 +298,8 @@ pub fn update_datastore( | ||||
|  | ||||
|     if update.notify_user.is_some() { data.notify_user = update.notify_user; } | ||||
|  | ||||
|     if update.tuning.is_some() { data.tuning = update.tuning; } | ||||
|  | ||||
|     config.set_data(&name, "datastore", &data)?; | ||||
|  | ||||
|     pbs_config::datastore::save_config(&config)?; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user