mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 17:28:12 +00:00
perf(ecstore): support per-bucket durability tier overrides (#4407)
perf(ecstore): per-bucket durability tier overrides (HP-5 phase 2) Let a bucket override the process-wide RUSTFS_DURABILITY_MODE with its own strict/relaxed/none tier, stored as a durability.json extension entry in the bucket metadata file and resolved at commit points via effective_durability. System-critical buckets (.rustfs.sys, .minio.sys) can never carry an override and stay pinned to strict; the legacy full-off switch keeps its historical semantics and per-bucket overrides do not apply under it. Overrides are published and cleared through the existing bucket metadata cache-invalidation path, and an admin GET/PUT handler exposes the configuration. Default behavior is unchanged: with no override a bucket follows the global mode, which defaults to strict and stays byte-for-byte identical to before. Refs: https://github.com/rustfs/backlog/issues/938, https://github.com/rustfs/backlog/issues/936 Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -153,6 +153,26 @@ async fn sync_bucket_target_sys(bucket: &str, bm: &BucketMetadata) {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Publish the bucket's durability override (or its absence) to the disk
|
||||
/// layer registry consulted by `effective_durability`.
|
||||
///
|
||||
/// Called from every path that installs a bucket's metadata into the cache
|
||||
/// (initial load, config update, peer reload notification, refresh loop,
|
||||
/// lazy load), so the override propagates with exactly the bucket-metadata
|
||||
/// cache invalidation semantics and never through a channel of its own.
|
||||
fn sync_bucket_durability(bucket: &str, bm: &BucketMetadata) {
|
||||
let mode = bm
|
||||
.durability_config()
|
||||
.and_then(|cfg| cfg.normalized_mode())
|
||||
.and_then(|mode| crate::disk::local::DurabilityMode::parse(&mode));
|
||||
crate::disk::local::bucket_durability::set(bucket, mode);
|
||||
}
|
||||
|
||||
/// Drop a bucket's durability override when its metadata leaves the cache.
|
||||
fn clear_bucket_durability(bucket: &str) {
|
||||
crate::disk::local::bucket_durability::set(bucket, None);
|
||||
}
|
||||
|
||||
pub async fn get(bucket: &str) -> Result<Arc<BucketMetadata>> {
|
||||
let sys = get_bucket_metadata_sys()?;
|
||||
let lock = sys.read().await;
|
||||
@@ -196,6 +216,20 @@ pub async fn get_bucket_acl_config(bucket: &str) -> Result<(String, OffsetDateTi
|
||||
bucket_meta_sys.get_bucket_acl_config(bucket).await
|
||||
}
|
||||
|
||||
/// The bucket's durability override config (if any) with its update time.
|
||||
///
|
||||
/// `Ok((None, ..))` means the bucket has no override and follows the global
|
||||
/// durability mode.
|
||||
pub async fn get_durability_config(
|
||||
bucket: &str,
|
||||
) -> Result<(Option<crate::bucket::durability::BucketDurabilityConfig>, OffsetDateTime)> {
|
||||
let bucket_meta_sys_lock = get_bucket_metadata_sys()?;
|
||||
let bucket_meta_sys = bucket_meta_sys_lock.read().await;
|
||||
|
||||
let (bm, _) = bucket_meta_sys.get_config(bucket).await?;
|
||||
Ok((bm.durability_config(), bm.durability_config_updated_at))
|
||||
}
|
||||
|
||||
pub async fn get_quota_config(bucket: &str) -> Result<(BucketQuota, OffsetDateTime)> {
|
||||
let bucket_meta_sys_lock = get_bucket_metadata_sys()?;
|
||||
let bucket_meta_sys = bucket_meta_sys_lock.read().await;
|
||||
@@ -425,6 +459,7 @@ impl BucketMetadataSys {
|
||||
map.insert(bucket.clone(), bm.clone());
|
||||
drop(map);
|
||||
sync_bucket_target_sys(&bucket, &bm).await;
|
||||
sync_bucket_durability(&bucket, &bm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +475,7 @@ impl BucketMetadataSys {
|
||||
drop(map);
|
||||
if removed {
|
||||
BucketTargetSys::get().delete(bucket).await;
|
||||
clear_bucket_durability(bucket);
|
||||
}
|
||||
removed
|
||||
}
|
||||
@@ -540,6 +576,7 @@ impl BucketMetadataSys {
|
||||
map.insert(bucket.to_string(), bm.clone());
|
||||
drop(map);
|
||||
sync_bucket_target_sys(bucket, &bm).await;
|
||||
sync_bucket_durability(bucket, &bm);
|
||||
|
||||
Ok((bm, true))
|
||||
}
|
||||
@@ -826,6 +863,40 @@ mod tests {
|
||||
target_sys.delete(bucket).await;
|
||||
}
|
||||
|
||||
/// HP-5b (rustfs/backlog#938): installing bucket metadata publishes the
|
||||
/// durability override to the disk-layer registry, and clearing the
|
||||
/// config (or an invalid payload) withdraws it.
|
||||
#[test]
|
||||
fn metadata_sync_publishes_and_clears_durability_override() {
|
||||
use crate::disk::local::{DurabilityMode, bucket_durability};
|
||||
|
||||
let bucket = "metadata-sync-durability";
|
||||
|
||||
let mut bm = BucketMetadata::new(bucket);
|
||||
bm.durability_config_json = br#"{"mode":"relaxed"}"#.to_vec();
|
||||
sync_bucket_durability(bucket, &bm);
|
||||
assert_eq!(bucket_durability::lookup(bucket), Some(DurabilityMode::Relaxed));
|
||||
|
||||
// Metadata without the config entry clears the override.
|
||||
let bm = BucketMetadata::new(bucket);
|
||||
sync_bucket_durability(bucket, &bm);
|
||||
assert_eq!(bucket_durability::lookup(bucket), None);
|
||||
|
||||
// Invalid payloads degrade to "no override", never to a tier.
|
||||
let mut bm = BucketMetadata::new(bucket);
|
||||
bm.durability_config_json = br#"{"mode":"bogus"}"#.to_vec();
|
||||
sync_bucket_durability(bucket, &bm);
|
||||
assert_eq!(bucket_durability::lookup(bucket), None);
|
||||
|
||||
// Cache removal clears the override too.
|
||||
let mut bm = BucketMetadata::new(bucket);
|
||||
bm.durability_config_json = br#"{"mode":"none"}"#.to_vec();
|
||||
sync_bucket_durability(bucket, &bm);
|
||||
assert_eq!(bucket_durability::lookup(bucket), Some(DurabilityMode::None));
|
||||
clear_bucket_durability(bucket);
|
||||
assert_eq!(bucket_durability::lookup(bucket), None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_wait_exits_when_cancelled() {
|
||||
let cancel_token = CancellationToken::new();
|
||||
|
||||
Reference in New Issue
Block a user