mirror of
https://github.com/rustfs/rustfs.git
synced 2026-03-17 14:24:08 +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.
|
||||
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,
|
||||
shard_size: usize,
|
||||
checksum_algo: HashAlgorithm,
|
||||
skip_verify: bool,
|
||||
) -> disk::error::Result<Option<BitrotReader<Box<dyn AsyncRead + Send + Sync + Unpin>>>> {
|
||||
// Calculate the total length to read, including the checksum overhead
|
||||
let length = length.div_ceil(shard_size) * checksum_algo.size() + length;
|
||||
@@ -47,13 +48,18 @@ pub async fn create_bitrot_reader(
|
||||
// Use inline data
|
||||
let mut rd = Cursor::new(data.to_vec());
|
||||
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))
|
||||
} else if let Some(disk) = disk {
|
||||
// Read from disk
|
||||
match disk.read_file_stream(bucket, path, offset, length - offset).await {
|
||||
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))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
@@ -116,7 +122,7 @@ mod tests {
|
||||
let checksum_algo = HashAlgorithm::HighwayHash256;
|
||||
|
||||
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.unwrap().is_some());
|
||||
@@ -127,7 +133,8 @@ mod tests {
|
||||
let shard_size = 16;
|
||||
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.unwrap().is_none());
|
||||
|
||||
@@ -28,10 +28,7 @@ pin_project! {
|
||||
shard_size: usize,
|
||||
buf: Vec<u8>,
|
||||
hash_buf: Vec<u8>,
|
||||
// hash_read: usize,
|
||||
// data_buf: Vec<u8>,
|
||||
// data_read: usize,
|
||||
// hash_checked: bool,
|
||||
skip_verify: bool,
|
||||
id: Uuid,
|
||||
}
|
||||
}
|
||||
@@ -41,7 +38,7 @@ where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
/// 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();
|
||||
Self {
|
||||
inner,
|
||||
@@ -49,10 +46,7 @@ where
|
||||
shard_size,
|
||||
buf: Vec::new(),
|
||||
hash_buf: vec![0u8; hash_size],
|
||||
// hash_read: 0,
|
||||
// data_buf: Vec::new(),
|
||||
// data_read: 0,
|
||||
// hash_checked: false,
|
||||
skip_verify,
|
||||
id: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
@@ -90,7 +84,7 @@ where
|
||||
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]);
|
||||
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());
|
||||
@@ -388,7 +382,7 @@ mod tests {
|
||||
// Read
|
||||
let reader = bitrot_writer.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 n = 0;
|
||||
while n < data_size {
|
||||
@@ -420,7 +414,7 @@ mod tests {
|
||||
let pos = written.len() - 1;
|
||||
written[pos] ^= 0xFF;
|
||||
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);
|
||||
|
||||
@@ -464,7 +458,7 @@ mod tests {
|
||||
|
||||
let reader = bitrot_writer.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 n = 0;
|
||||
while n < data_size {
|
||||
|
||||
@@ -454,6 +454,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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 set_index = self.set_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
|
||||
// let _guard_to_hold = _read_lock_guard; // moved into closure below
|
||||
tokio::spawn(async move {
|
||||
@@ -510,6 +511,7 @@ impl ObjectIO for SetDisks {
|
||||
&disks,
|
||||
set_index,
|
||||
pool_index,
|
||||
skip_verify,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -1654,6 +1656,7 @@ impl ObjectOperations for SetDisks {
|
||||
let cloned_fi = fi.clone();
|
||||
let set_index = self.set_index;
|
||||
let pool_index = self.pool_index;
|
||||
let skip_verify = opts.skip_verify_bitrot;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::get_object_with_fileinfo(
|
||||
&cloned_bucket,
|
||||
@@ -1666,6 +1669,7 @@ impl ObjectOperations for SetDisks {
|
||||
&online_disks,
|
||||
set_index,
|
||||
pool_index,
|
||||
skip_verify,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -375,6 +375,7 @@ impl SetDisks {
|
||||
till_offset,
|
||||
erasure.shard_size(),
|
||||
checksum_algo.clone(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -568,6 +568,7 @@ impl SetDisks {
|
||||
disks: &[Option<DiskStore>],
|
||||
set_index: usize,
|
||||
pool_index: usize,
|
||||
skip_verify_bitrot: bool,
|
||||
) -> Result<()>
|
||||
where
|
||||
W: AsyncWrite + Send + Sync + Unpin + 'static,
|
||||
@@ -659,6 +660,7 @@ impl SetDisks {
|
||||
till_offset,
|
||||
erasure.shard_size(),
|
||||
HashAlgorithm::HighwayHash256,
|
||||
skip_verify_bitrot,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -70,6 +70,7 @@ pub struct ObjectOptions {
|
||||
pub eval_metadata: Option<HashMap<String, String>>,
|
||||
|
||||
pub want_checksum: Option<Checksum>,
|
||||
pub skip_verify_bitrot: bool,
|
||||
}
|
||||
|
||||
impl ObjectOptions {
|
||||
|
||||
@@ -164,6 +164,13 @@ pub async fn get_opts(
|
||||
opts.version_suspended = version_suspended;
|
||||
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)?;
|
||||
|
||||
Ok(opts)
|
||||
|
||||
Reference in New Issue
Block a user