mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-02 02:08:41 +00:00
perf(filemeta): phase-1~3 rename_data metadata optimization (#3011)
* chore(perf): harden amd64 profiling benchmark flow * fix(profiling): isolate bench buckets and map protobuf conflict * perf: avoid blocking owned local writes * style: format profile admin handler * docs: clarify observability trace validation * perf: reduce mkdir overhead on local writes * perf: add rename_data meta microbenchmark * perf(filemeta): fast-path data_dir decode in version meta * perf(filemeta): collapse data-dir lookup into one scan * perf(filemeta): reduce scan allocs and refresh meta bench * perf(ecstore): skip mkdir path on read-only open * perf(filemeta): single-pass unshared data-dir scan * perf(filemeta): add two-key inline remove fast path * perf(filemeta): compare remove-two keys by bytes first * bench(ecstore): add remove_two-only micro benchmark * bench(ecstore): stabilize rename_data meta benchmark timing * bench(ecstore): align rename_data path with remove_two * perf(filemeta): avoid uuid string alloc in remove_two * perf(filemeta): add fast-path for empty inline data * perf(filemeta): streamline add_version match branch * perf(filemeta): fast-return remove_key on miss * perf(filemeta): speed up add_version insertion lookup * style(ecstore): normalize formatting in perf-tuning files * refactor(filemeta): unify inline data removal paths
This commit is contained in:
@@ -13,8 +13,48 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
impl FileMeta {
|
||||
pub fn find_unshared_data_dir_for_version(&self, version_id: Option<Uuid>) -> Option<Uuid> {
|
||||
let vid = version_id.unwrap_or_default();
|
||||
let mut target_data_dir = None;
|
||||
let mut target_selected = false;
|
||||
let mut other_data_dirs = HashSet::new();
|
||||
|
||||
for version in self
|
||||
.versions
|
||||
.iter()
|
||||
.filter(|v| v.header.version_type == VersionType::Object && v.header.uses_data_dir())
|
||||
{
|
||||
let is_target_version = version.header.version_id.unwrap_or_default() == vid;
|
||||
if is_target_version {
|
||||
if target_selected {
|
||||
continue;
|
||||
}
|
||||
|
||||
target_selected = true;
|
||||
target_data_dir = FileMetaVersion::decode_data_dir_from_meta(&version.meta).unwrap_or_default();
|
||||
if let Some(dir) = target_data_dir
|
||||
&& other_data_dirs.contains(&dir)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let dir = FileMetaVersion::decode_data_dir_from_meta(&version.meta).unwrap_or_default();
|
||||
if let Some(dir) = dir {
|
||||
if target_data_dir == Some(dir) {
|
||||
return None;
|
||||
}
|
||||
other_data_dirs.insert(dir);
|
||||
}
|
||||
}
|
||||
|
||||
target_data_dir
|
||||
}
|
||||
|
||||
pub fn shard_data_dir_count(&self, vid: &Option<Uuid>, data_dir: &Option<Uuid>) -> usize {
|
||||
let vid = vid.unwrap_or_default();
|
||||
self.versions
|
||||
@@ -58,3 +98,70 @@ impl FileMeta {
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use s3s::header::X_AMZ_RESTORE;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
fn make_file_info(version_id: Uuid, data_dir: Uuid) -> FileInfo {
|
||||
let restore_header = format!(
|
||||
"ongoing-request=\"false\", expiry-date=\"{}\"",
|
||||
(OffsetDateTime::now_utc() + Duration::days(1))
|
||||
.format(&Rfc3339)
|
||||
.expect("format restore expiry"),
|
||||
);
|
||||
FileInfo {
|
||||
version_id: Some(version_id),
|
||||
data_dir: Some(data_dir),
|
||||
size: 64 * 1024,
|
||||
mod_time: Some(OffsetDateTime::now_utc()),
|
||||
metadata: [
|
||||
("etag".to_string(), format!("etag-{version_id}")),
|
||||
(X_AMZ_RESTORE.as_str().to_string(), restore_header),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
erasure: ErasureInfo {
|
||||
algorithm: ErasureAlgo::ReedSolomon.to_string(),
|
||||
data_blocks: 4,
|
||||
parity_blocks: 2,
|
||||
block_size: 1024 * 1024,
|
||||
index: 1,
|
||||
distribution: vec![1, 2, 3, 4, 5, 6],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_unshared_data_dir_for_version_returns_data_dir_when_unique() {
|
||||
let target_version = Uuid::new_v4();
|
||||
let target_data_dir = Uuid::new_v4();
|
||||
let mut meta = FileMeta::new();
|
||||
meta.add_version(make_file_info(target_version, target_data_dir))
|
||||
.expect("seed target version");
|
||||
meta.add_version(make_file_info(Uuid::new_v4(), Uuid::new_v4()))
|
||||
.expect("seed non-shared version");
|
||||
|
||||
let got = meta.find_unshared_data_dir_for_version(Some(target_version));
|
||||
assert_eq!(got, Some(target_data_dir));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_unshared_data_dir_for_version_returns_none_when_shared() {
|
||||
let target_version = Uuid::new_v4();
|
||||
let shared_data_dir = Uuid::new_v4();
|
||||
let mut meta = FileMeta::new();
|
||||
meta.add_version(make_file_info(target_version, shared_data_dir))
|
||||
.expect("seed target version");
|
||||
meta.add_version(make_file_info(Uuid::new_v4(), shared_data_dir))
|
||||
.expect("seed shared version");
|
||||
|
||||
let got = meta.find_unshared_data_dir_for_version(Some(target_version));
|
||||
assert_eq!(got, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +312,77 @@ pub struct FileMetaVersion {
|
||||
}
|
||||
|
||||
impl FileMetaVersion {
|
||||
fn decode_data_dir_from_v2_object(buf: &[u8]) -> Result<Option<Uuid>> {
|
||||
let mut cur = std::io::Cursor::new(buf);
|
||||
let mut fields = rmp::decode::read_map_len(&mut cur)?;
|
||||
let mut version_type = VersionType::Invalid;
|
||||
|
||||
while fields > 0 {
|
||||
fields -= 1;
|
||||
|
||||
let key_len = rmp::decode::read_str_len(&mut cur)? as usize;
|
||||
let mut key_buf = vec![0u8; key_len];
|
||||
cur.read_exact(&mut key_buf)?;
|
||||
let key = String::from_utf8(key_buf)?;
|
||||
|
||||
match key.as_str() {
|
||||
"Type" => {
|
||||
let v: i64 = rmp::decode::read_int(&mut cur)?;
|
||||
version_type = VersionType::from_u8(v as u8);
|
||||
}
|
||||
"V2Obj" => {
|
||||
if version_type != VersionType::Object {
|
||||
skip_msgp_value(&mut cur)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut first = [0u8; 1];
|
||||
cur.read_exact(&mut first)?;
|
||||
if first[0] == 0xc0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut prepend = PrependByteReader {
|
||||
byte: Some(first[0]),
|
||||
inner: &mut cur,
|
||||
};
|
||||
let mut obj_fields = rmp::decode::read_map_len(&mut prepend)?;
|
||||
let mut data_dir: Option<Uuid> = None;
|
||||
|
||||
while obj_fields > 0 {
|
||||
obj_fields -= 1;
|
||||
|
||||
let obj_key_len = rmp::decode::read_str_len(&mut prepend)? as usize;
|
||||
let mut obj_key_buf = vec![0u8; obj_key_len];
|
||||
prepend.read_exact(&mut obj_key_buf)?;
|
||||
let obj_key = String::from_utf8(obj_key_buf)?;
|
||||
|
||||
if obj_key == "DDir" {
|
||||
let bin_len = rmp::decode::read_bin_len(&mut prepend)? as usize;
|
||||
if bin_len != 16 {
|
||||
return Err(Error::other(format!("DDir must be 16 bytes, got {bin_len}")));
|
||||
}
|
||||
let mut raw = [0u8; 16];
|
||||
prepend.read_exact(&mut raw)?;
|
||||
let id = Uuid::from_bytes(raw);
|
||||
data_dir = if id.is_nil() { None } else { Some(id) };
|
||||
break;
|
||||
}
|
||||
|
||||
skip_msgp_value(&mut prepend)?;
|
||||
}
|
||||
|
||||
return Ok(data_dir);
|
||||
}
|
||||
_ => {
|
||||
skip_msgp_value(&mut cur)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn valid(&self) -> bool {
|
||||
if !self.version_type.valid() {
|
||||
return false;
|
||||
@@ -367,6 +438,9 @@ impl FileMetaVersion {
|
||||
|
||||
// decode_data_dir_from_meta reads data_dir from meta TODO: directly parse only data_dir from meta buf, msg.skip
|
||||
pub fn decode_data_dir_from_meta(buf: &[u8]) -> Result<Option<Uuid>> {
|
||||
if let Ok(data_dir) = Self::decode_data_dir_from_v2_object(buf) {
|
||||
return Ok(data_dir);
|
||||
}
|
||||
Ok(Self::try_from(buf)?.get_data_dir())
|
||||
}
|
||||
|
||||
@@ -3213,4 +3287,31 @@ mod tests {
|
||||
assert_eq!(fi.mod_time, Some(sample_mod_time()));
|
||||
assert_eq!(fi.metadata.get("x-rustfs-test").map(String::as_str), Some("gone"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_data_dir_from_meta_extracts_v2_object_fast_path() {
|
||||
let data_dir = Uuid::new_v4();
|
||||
let version = FileMetaVersion {
|
||||
version_type: VersionType::Object,
|
||||
object: Some(MetaObject {
|
||||
version_id: Some(Uuid::new_v4()),
|
||||
data_dir: Some(data_dir),
|
||||
erasure_algorithm: ErasureAlgo::ReedSolomon,
|
||||
erasure_m: 2,
|
||||
erasure_n: 4,
|
||||
erasure_block_size: 1024 * 1024,
|
||||
erasure_index: 1,
|
||||
erasure_dist: vec![1, 2, 3, 4, 5, 6],
|
||||
bitrot_checksum_algo: ChecksumAlgo::HighwayHash,
|
||||
size: 64 * 1024,
|
||||
mod_time: Some(OffsetDateTime::now_utc()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded = version.marshal_msg().expect("marshal");
|
||||
let decoded = FileMetaVersion::decode_data_dir_from_meta(&encoded).expect("decode data_dir");
|
||||
assert_eq!(decoded, Some(data_dir));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user