mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
feat(perf): Add configurable bitrot skip for reads (#2110)
Co-authored-by: houseme <housemecn@gmail.com> Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
@@ -167,3 +167,16 @@ pub const DEFAULT_OBJECT_CACHE_TTI_SECS: u64 = 120;
|
|||||||
///
|
///
|
||||||
/// Default is set to 5 hits.
|
/// Default is set to 5 hits.
|
||||||
pub const DEFAULT_OBJECT_HOT_MIN_HITS_TO_EXTEND: usize = 5;
|
pub const DEFAULT_OBJECT_HOT_MIN_HITS_TO_EXTEND: usize = 5;
|
||||||
|
|
||||||
|
/// Skip bitrot hash verification on GetObject reads.
|
||||||
|
///
|
||||||
|
/// When enabled, GetObject reads skip the per-shard hash
|
||||||
|
/// computation and comparison, reducing CPU usage on the read path.
|
||||||
|
/// The background scanner still performs full integrity verification.
|
||||||
|
/// Does not affect writes, heals, or scanner operations.
|
||||||
|
///
|
||||||
|
/// Default is false (verify on every read, matching pre-existing behavior).
|
||||||
|
pub const ENV_OBJECT_GET_SKIP_BITROT_VERIFY: &str = "RUSTFS_OBJECT_GET_SKIP_BITROT_VERIFY";
|
||||||
|
|
||||||
|
/// Default: bitrot verification is enabled on GetObject reads (do not skip).
|
||||||
|
pub const DEFAULT_OBJECT_GET_SKIP_BITROT_VERIFY: bool = false;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub async fn create_bitrot_reader(
|
|||||||
length: usize,
|
length: usize,
|
||||||
shard_size: usize,
|
shard_size: usize,
|
||||||
checksum_algo: HashAlgorithm,
|
checksum_algo: HashAlgorithm,
|
||||||
|
skip_verify: bool,
|
||||||
) -> disk::error::Result<Option<BitrotReader<Box<dyn AsyncRead + Send + Sync + Unpin>>>> {
|
) -> disk::error::Result<Option<BitrotReader<Box<dyn AsyncRead + Send + Sync + Unpin>>>> {
|
||||||
// Calculate the total length to read, including the checksum overhead
|
// Calculate the total length to read, including the checksum overhead
|
||||||
let length = length.div_ceil(shard_size) * checksum_algo.size() + length;
|
let length = length.div_ceil(shard_size) * checksum_algo.size() + length;
|
||||||
@@ -47,13 +48,18 @@ pub async fn create_bitrot_reader(
|
|||||||
// Use inline data
|
// Use inline data
|
||||||
let mut rd = Cursor::new(data.to_vec());
|
let mut rd = Cursor::new(data.to_vec());
|
||||||
rd.set_position(offset as u64);
|
rd.set_position(offset as u64);
|
||||||
let reader = BitrotReader::new(Box::new(rd) as Box<dyn AsyncRead + Send + Sync + Unpin>, shard_size, checksum_algo);
|
let reader = BitrotReader::new(
|
||||||
|
Box::new(rd) as Box<dyn AsyncRead + Send + Sync + Unpin>,
|
||||||
|
shard_size,
|
||||||
|
checksum_algo,
|
||||||
|
skip_verify,
|
||||||
|
);
|
||||||
Ok(Some(reader))
|
Ok(Some(reader))
|
||||||
} else if let Some(disk) = disk {
|
} else if let Some(disk) = disk {
|
||||||
// Read from disk
|
// Read from disk
|
||||||
match disk.read_file_stream(bucket, path, offset, length - offset).await {
|
match disk.read_file_stream(bucket, path, offset, length - offset).await {
|
||||||
Ok(rd) => {
|
Ok(rd) => {
|
||||||
let reader = BitrotReader::new(rd, shard_size, checksum_algo);
|
let reader = BitrotReader::new(rd, shard_size, checksum_algo, skip_verify);
|
||||||
Ok(Some(reader))
|
Ok(Some(reader))
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@@ -116,7 +122,7 @@ mod tests {
|
|||||||
let checksum_algo = HashAlgorithm::HighwayHash256;
|
let checksum_algo = HashAlgorithm::HighwayHash256;
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
create_bitrot_reader(Some(test_data), None, "test-bucket", "test-path", 0, 0, shard_size, checksum_algo).await;
|
create_bitrot_reader(Some(test_data), None, "test-bucket", "test-path", 0, 0, shard_size, checksum_algo, false).await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert!(result.unwrap().is_some());
|
assert!(result.unwrap().is_some());
|
||||||
@@ -127,7 +133,8 @@ mod tests {
|
|||||||
let shard_size = 16;
|
let shard_size = 16;
|
||||||
let checksum_algo = HashAlgorithm::HighwayHash256;
|
let checksum_algo = HashAlgorithm::HighwayHash256;
|
||||||
|
|
||||||
let result = create_bitrot_reader(None, None, "test-bucket", "test-path", 0, 1024, shard_size, checksum_algo).await;
|
let result =
|
||||||
|
create_bitrot_reader(None, None, "test-bucket", "test-path", 0, 1024, shard_size, checksum_algo, false).await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert!(result.unwrap().is_none());
|
assert!(result.unwrap().is_none());
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ pin_project! {
|
|||||||
shard_size: usize,
|
shard_size: usize,
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
hash_buf: Vec<u8>,
|
hash_buf: Vec<u8>,
|
||||||
// hash_read: usize,
|
skip_verify: bool,
|
||||||
// data_buf: Vec<u8>,
|
|
||||||
// data_read: usize,
|
|
||||||
// hash_checked: bool,
|
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +38,7 @@ where
|
|||||||
R: AsyncRead + Unpin + Send + Sync,
|
R: AsyncRead + Unpin + Send + Sync,
|
||||||
{
|
{
|
||||||
/// Create a new BitrotReader.
|
/// Create a new BitrotReader.
|
||||||
pub fn new(inner: R, shard_size: usize, algo: HashAlgorithm) -> Self {
|
pub fn new(inner: R, shard_size: usize, algo: HashAlgorithm, skip_verify: bool) -> Self {
|
||||||
let hash_size = algo.size();
|
let hash_size = algo.size();
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
@@ -49,10 +46,7 @@ where
|
|||||||
shard_size,
|
shard_size,
|
||||||
buf: Vec::new(),
|
buf: Vec::new(),
|
||||||
hash_buf: vec![0u8; hash_size],
|
hash_buf: vec![0u8; hash_size],
|
||||||
// hash_read: 0,
|
skip_verify,
|
||||||
// data_buf: Vec::new(),
|
|
||||||
// data_read: 0,
|
|
||||||
// hash_checked: false,
|
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +84,7 @@ where
|
|||||||
data_len += n;
|
data_len += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
if hash_size > 0 {
|
if hash_size > 0 && !self.skip_verify {
|
||||||
let actual_hash = self.hash_algo.hash_encode(&out[..data_len]);
|
let actual_hash = self.hash_algo.hash_encode(&out[..data_len]);
|
||||||
if actual_hash.as_ref() != self.hash_buf.as_slice() {
|
if actual_hash.as_ref() != self.hash_buf.as_slice() {
|
||||||
error!("bitrot reader hash mismatch, id={} data_len={}, out_len={}", self.id, data_len, out.len());
|
error!("bitrot reader hash mismatch, id={} data_len={}, out_len={}", self.id, data_len, out.len());
|
||||||
@@ -388,7 +382,7 @@ mod tests {
|
|||||||
// Read
|
// Read
|
||||||
let reader = bitrot_writer.into_inner();
|
let reader = bitrot_writer.into_inner();
|
||||||
let reader = Cursor::new(reader.into_inner());
|
let reader = Cursor::new(reader.into_inner());
|
||||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256);
|
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256, false);
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
while n < data_size {
|
while n < data_size {
|
||||||
@@ -420,7 +414,7 @@ mod tests {
|
|||||||
let pos = written.len() - 1;
|
let pos = written.len() - 1;
|
||||||
written[pos] ^= 0xFF;
|
written[pos] ^= 0xFF;
|
||||||
let reader = Cursor::new(written);
|
let reader = Cursor::new(written);
|
||||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256);
|
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256, false);
|
||||||
|
|
||||||
let count = data_size.div_ceil(shard_size);
|
let count = data_size.div_ceil(shard_size);
|
||||||
|
|
||||||
@@ -464,7 +458,7 @@ mod tests {
|
|||||||
|
|
||||||
let reader = bitrot_writer.into_inner();
|
let reader = bitrot_writer.into_inner();
|
||||||
let reader = Cursor::new(reader.into_inner());
|
let reader = Cursor::new(reader.into_inner());
|
||||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::None);
|
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::None, false);
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
while n < data_size {
|
while n < data_size {
|
||||||
|
|||||||
@@ -454,6 +454,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reader_cursor = Cursor::new(buf);
|
let reader_cursor = Cursor::new(buf);
|
||||||
BitrotReader::new(reader_cursor, shard_size, hash_algo.clone())
|
BitrotReader::new(reader_cursor, shard_size, hash_algo.clone(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,6 +494,7 @@ impl ObjectIO for SetDisks {
|
|||||||
let object = object.to_owned();
|
let object = object.to_owned();
|
||||||
let set_index = self.set_index;
|
let set_index = self.set_index;
|
||||||
let pool_index = self.pool_index;
|
let pool_index = self.pool_index;
|
||||||
|
let skip_verify = opts.skip_verify_bitrot;
|
||||||
// Move the read-lock guard into the task so it lives for the duration of the read
|
// Move the read-lock guard into the task so it lives for the duration of the read
|
||||||
// let _guard_to_hold = _read_lock_guard; // moved into closure below
|
// let _guard_to_hold = _read_lock_guard; // moved into closure below
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@@ -510,6 +511,7 @@ impl ObjectIO for SetDisks {
|
|||||||
&disks,
|
&disks,
|
||||||
set_index,
|
set_index,
|
||||||
pool_index,
|
pool_index,
|
||||||
|
skip_verify,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -1654,6 +1656,7 @@ impl ObjectOperations for SetDisks {
|
|||||||
let cloned_fi = fi.clone();
|
let cloned_fi = fi.clone();
|
||||||
let set_index = self.set_index;
|
let set_index = self.set_index;
|
||||||
let pool_index = self.pool_index;
|
let pool_index = self.pool_index;
|
||||||
|
let skip_verify = opts.skip_verify_bitrot;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = Self::get_object_with_fileinfo(
|
if let Err(e) = Self::get_object_with_fileinfo(
|
||||||
&cloned_bucket,
|
&cloned_bucket,
|
||||||
@@ -1666,6 +1669,7 @@ impl ObjectOperations for SetDisks {
|
|||||||
&online_disks,
|
&online_disks,
|
||||||
set_index,
|
set_index,
|
||||||
pool_index,
|
pool_index,
|
||||||
|
skip_verify,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ impl SetDisks {
|
|||||||
till_offset,
|
till_offset,
|
||||||
erasure.shard_size(),
|
erasure.shard_size(),
|
||||||
checksum_algo.clone(),
|
checksum_algo.clone(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -568,6 +568,7 @@ impl SetDisks {
|
|||||||
disks: &[Option<DiskStore>],
|
disks: &[Option<DiskStore>],
|
||||||
set_index: usize,
|
set_index: usize,
|
||||||
pool_index: usize,
|
pool_index: usize,
|
||||||
|
skip_verify_bitrot: bool,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
W: AsyncWrite + Send + Sync + Unpin + 'static,
|
W: AsyncWrite + Send + Sync + Unpin + 'static,
|
||||||
@@ -659,6 +660,7 @@ impl SetDisks {
|
|||||||
till_offset,
|
till_offset,
|
||||||
erasure.shard_size(),
|
erasure.shard_size(),
|
||||||
HashAlgorithm::HighwayHash256,
|
HashAlgorithm::HighwayHash256,
|
||||||
|
skip_verify_bitrot,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ pub struct ObjectOptions {
|
|||||||
pub eval_metadata: Option<HashMap<String, String>>,
|
pub eval_metadata: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
pub want_checksum: Option<Checksum>,
|
pub want_checksum: Option<Checksum>,
|
||||||
|
pub skip_verify_bitrot: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectOptions {
|
impl ObjectOptions {
|
||||||
|
|||||||
@@ -164,6 +164,13 @@ pub async fn get_opts(
|
|||||||
opts.version_suspended = version_suspended;
|
opts.version_suspended = version_suspended;
|
||||||
opts.versioned = versioned;
|
opts.versioned = versioned;
|
||||||
|
|
||||||
|
// Optionally skip per-shard bitrot hash verification on reads to save CPU.
|
||||||
|
// Background scanner still performs full integrity checks asynchronously.
|
||||||
|
opts.skip_verify_bitrot = rustfs_utils::get_env_bool(
|
||||||
|
rustfs_config::ENV_OBJECT_GET_SKIP_BITROT_VERIFY,
|
||||||
|
rustfs_config::DEFAULT_OBJECT_GET_SKIP_BITROT_VERIFY,
|
||||||
|
);
|
||||||
|
|
||||||
fill_conditional_writes_opts_from_header(headers, &mut opts)?;
|
fill_conditional_writes_opts_from_header(headers, &mut opts)?;
|
||||||
|
|
||||||
Ok(opts)
|
Ok(opts)
|
||||||
|
|||||||
Reference in New Issue
Block a user