mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 15:16:56 +00:00
Expose target-scoped durable MRF backlog metrics (#5584)
* feat(replication): expose target durable mrf backlog Add target ARN attribution to durable MRF entries and surface target-scoped durable backlog metrics without changing existing bucket-only metric labels. Keep legacy MRF files bucket-only by defaulting missing targetARNs to an empty list, and expose target snapshots through an additive API so existing DurableMrfBacklogSummary callers remain source-compatible. Co-Authored-By: heihutu <heihutu@gmail.com> * feat(replication): expose runtime target backlog (#5586) Track runtime replication backlog by target ARN for regular, large, delete, and MRF admission paths while preserving the existing bucket-level backlog semantics. Add target-scoped current backlog metrics and merge them with durable target backlog snapshots for observability. Co-authored-by: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -49,6 +49,11 @@ impl ReplicationWorkerOperation for DeletedObjectReplicationInfo {
|
||||
.delete_object
|
||||
.delete_marker_mtime
|
||||
.and_then(|t| i64::try_from(t.unix_timestamp_nanos()).ok()),
|
||||
target_arns: if self.target_arn.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![self.target_arn.clone()]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +116,7 @@ mod tests {
|
||||
delete_marker_mtime: Some(mtime),
|
||||
..Default::default()
|
||||
},
|
||||
target_arn: "arn:target-a".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -129,6 +135,7 @@ mod tests {
|
||||
Some(mtime.unix_timestamp_nanos() as i64),
|
||||
"delete-marker mtime must be persisted in the MRF entry"
|
||||
);
|
||||
assert_eq!(entry.target_arns, vec!["arn:target-a".to_string()]);
|
||||
assert_eq!(info.get_object(), "object");
|
||||
}
|
||||
|
||||
@@ -148,6 +155,7 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(info.to_mrf_entry().delete_marker_mtime, None);
|
||||
assert!(info.to_mrf_entry().target_arns.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -593,6 +593,9 @@ pub struct MrfReplicateEntry {
|
||||
// to preserve pre-existing behaviour (backlog#867).
|
||||
#[serde(rename = "deleteMarkerMtime", skip_serializing_if = "Option::is_none", default)]
|
||||
pub delete_marker_mtime: Option<i64>,
|
||||
|
||||
#[serde(rename = "targetARNs", skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub target_arns: Vec<String>,
|
||||
}
|
||||
|
||||
fn retry_count_to_mrf(retry_count: u32) -> i32 {
|
||||
@@ -672,6 +675,18 @@ impl ReplicateDecision {
|
||||
}
|
||||
if result.is_empty() { None } else { Some(result) }
|
||||
}
|
||||
|
||||
pub fn replicate_target_arns(&self) -> Vec<String> {
|
||||
let mut arns = self
|
||||
.targets_map
|
||||
.values()
|
||||
.filter(|target| target.replicate && !target.arn.is_empty())
|
||||
.map(|target| target.arn.clone())
|
||||
.collect::<Vec<_>>();
|
||||
arns.sort();
|
||||
arns.dedup();
|
||||
arns
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicateDecision {
|
||||
@@ -799,6 +814,7 @@ impl ReplicationWorkerOperation for ReplicateObjectInfo {
|
||||
delete_marker_version_id: None,
|
||||
delete_marker: false,
|
||||
delete_marker_mtime: None,
|
||||
target_arns: self.dsc.replicate_target_arns(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,6 +869,7 @@ impl ReplicateObjectInfo {
|
||||
delete_marker_version_id: None,
|
||||
delete_marker: false,
|
||||
delete_marker_mtime: None,
|
||||
target_arns: self.dsc.replicate_target_arns(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -994,6 +1011,62 @@ impl Default for ResyncDecision {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn replicate_decision_returns_sorted_unique_replicating_target_arns() {
|
||||
let mut decision = ReplicateDecision::new();
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: "arn:target-b".to_string(),
|
||||
replicate: true,
|
||||
..Default::default()
|
||||
});
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: "arn:target-a".to_string(),
|
||||
replicate: true,
|
||||
..Default::default()
|
||||
});
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: "arn:target-c".to_string(),
|
||||
replicate: false,
|
||||
..Default::default()
|
||||
});
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: String::new(),
|
||||
replicate: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
decision.replicate_target_arns(),
|
||||
vec!["arn:target-a".to_string(), "arn:target-b".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replicate_object_info_mrf_entry_carries_replicating_targets() {
|
||||
let mut decision = ReplicateDecision::new();
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: "arn:target-a".to_string(),
|
||||
replicate: true,
|
||||
..Default::default()
|
||||
});
|
||||
decision.set(ReplicateTargetDecision {
|
||||
arn: "arn:target-b".to_string(),
|
||||
replicate: false,
|
||||
..Default::default()
|
||||
});
|
||||
let info = ReplicateObjectInfo {
|
||||
bucket: "bucket".to_string(),
|
||||
name: "object".to_string(),
|
||||
size: 42,
|
||||
dsc: decision,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let entry = info.to_mrf_entry();
|
||||
|
||||
assert_eq!(entry.target_arns, vec!["arn:target-a".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_state_reads_resync_timestamp_from_target_reset_header_key() {
|
||||
let arn = "arn:rustfs:replication:us-east-1:target:bucket";
|
||||
|
||||
@@ -71,6 +71,7 @@ mod tests {
|
||||
delete_marker_version_id: None,
|
||||
delete_marker: false,
|
||||
delete_marker_mtime: None,
|
||||
target_arns: vec!["arn:target-a".to_string(), "arn:target-b".to_string()],
|
||||
},
|
||||
MrfReplicateEntry {
|
||||
bucket: "bucket-a".to_string(),
|
||||
@@ -82,6 +83,7 @@ mod tests {
|
||||
delete_marker_version_id: Some(del_vid),
|
||||
delete_marker: true,
|
||||
delete_marker_mtime: Some(1_705_312_200_123_456_789),
|
||||
target_arns: vec!["arn:target-a".to_string()],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -91,9 +93,11 @@ mod tests {
|
||||
assert_eq!(decoded.len(), 2);
|
||||
assert_eq!(decoded[0].version_id, Some(obj_vid));
|
||||
assert_eq!(decoded[0].op, MrfOpKind::Object);
|
||||
assert_eq!(decoded[0].target_arns, vec!["arn:target-a".to_string(), "arn:target-b".to_string()]);
|
||||
assert_eq!(decoded[0].delete_marker_mtime, None);
|
||||
assert_eq!(decoded[1].delete_marker_version_id, Some(del_vid));
|
||||
assert_eq!(decoded[1].op, MrfOpKind::Delete);
|
||||
assert_eq!(decoded[1].target_arns, vec!["arn:target-a".to_string()]);
|
||||
assert!(decoded[1].delete_marker);
|
||||
assert_eq!(
|
||||
decoded[1].delete_marker_mtime,
|
||||
@@ -129,6 +133,7 @@ mod tests {
|
||||
assert_eq!(decoded[0].retry_count, 2);
|
||||
assert_eq!(decoded[0].size, 100);
|
||||
assert_eq!(decoded[0].op, MrfOpKind::Object);
|
||||
assert!(decoded[0].target_arns.is_empty());
|
||||
// Old files lack the deleteMarkerMtime key; it must default to None so replay keeps the
|
||||
// pre-#867 fallback to the current time.
|
||||
assert_eq!(decoded[0].delete_marker_mtime, None);
|
||||
|
||||
Reference in New Issue
Block a user