mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 05:06:28 +00:00
fix(replication): snapshot existing object admission targets (#5634)
This commit is contained in:
@@ -17,7 +17,7 @@ pub use rustfs_replication::{MrfOpKind, MrfReplicateEntry};
|
||||
pub(crate) use rustfs_replication::{
|
||||
REPLICATE_EXISTING, REPLICATE_EXISTING_DELETE, REPLICATE_HEAL_DELETE, ReplicateTargetDecision, ReplicatedInfos,
|
||||
ReplicatedTargetInfo, ReplicationAction, ReplicationWorkerOperation, ResyncDecision, get_replication_state,
|
||||
parse_replicate_decision, target_reset_header, version_purge_statuses_map,
|
||||
parse_replicate_decision, replicate_decision_for_admitted_targets, target_reset_header, version_purge_statuses_map,
|
||||
};
|
||||
pub use rustfs_replication::{
|
||||
REPLICATE_INCOMING_DELETE, ReplicateDecision, ReplicateObjectInfo, ReplicationState, ReplicationStatusType, ReplicationType,
|
||||
|
||||
@@ -16,8 +16,8 @@ use super::replication_config_store::ReplicationConfigStore;
|
||||
use super::replication_error_boundary::Error as EcstoreError;
|
||||
use super::replication_filemeta_boundary::{
|
||||
MrfOpKind, MrfReplicateEntry, REPLICATE_HEAL_DELETE, ReplicateDecision, ReplicateObjectInfo, ReplicatedTargetInfo,
|
||||
ReplicationStatusType, ReplicationType, ReplicationWorkerOperation, ResyncDecision, replication_statuses_map,
|
||||
version_purge_statuses_map,
|
||||
ReplicationStatusType, ReplicationType, ReplicationWorkerOperation, ResyncDecision, replicate_decision_for_admitted_targets,
|
||||
replication_statuses_map, version_purge_statuses_map,
|
||||
};
|
||||
use super::replication_lock_boundary::ReplicationLockTiming;
|
||||
use super::replication_logging::{EVENT_REPLICATION_CONFIG_LOOKUP_SKIPPED, LOG_COMPONENT_ECSTORE, LOG_SUBSYSTEM_REPLICATION};
|
||||
@@ -990,21 +990,25 @@ impl<S: ReplicationStorage> ReplicationPool<S> {
|
||||
delete_marker: entry.delete_marker,
|
||||
..Default::default()
|
||||
};
|
||||
let dsc = check_replicate_delete(
|
||||
&entry.bucket,
|
||||
&ObjectToDelete {
|
||||
object_name: entry.object.clone(),
|
||||
version_id: entry.version_id,
|
||||
..Default::default()
|
||||
},
|
||||
&oi,
|
||||
&ObjectOptions {
|
||||
versioned,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let dsc = if entry.target_arns.is_empty() {
|
||||
check_replicate_delete(
|
||||
&entry.bucket,
|
||||
&ObjectToDelete {
|
||||
object_name: entry.object.clone(),
|
||||
version_id: entry.version_id,
|
||||
..Default::default()
|
||||
},
|
||||
&oi,
|
||||
&ObjectOptions {
|
||||
versioned,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
replicate_decision_for_admitted_targets(&entry.target_arns)
|
||||
};
|
||||
let mut rstate = oi.replication_state();
|
||||
rstate.replicate_decision_str = dsc.to_string();
|
||||
|
||||
@@ -1036,7 +1040,7 @@ impl<S: ReplicationStorage> ReplicationPool<S> {
|
||||
schedule_replication_delete(dv).await;
|
||||
queued_count += 1;
|
||||
}
|
||||
MrfOpKind::Object => {
|
||||
MrfOpKind::Object | MrfOpKind::Heal | MrfOpKind::ExistingObject => {
|
||||
let opts = ObjectOptions {
|
||||
version_id: entry.version_id.map(|u| u.to_string()),
|
||||
..Default::default()
|
||||
@@ -1055,9 +1059,16 @@ impl<S: ReplicationStorage> ReplicationPool<S> {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// Route through queue_replication_heal so the replication decision (dsc)
|
||||
// is computed from the live config — required for replicate_object.
|
||||
queue_replication_heal(&entry.bucket, oi, entry.retry_count as u32).await;
|
||||
if entry.target_arns.is_empty() {
|
||||
// Legacy entries predate target admission persistence. They cannot
|
||||
// be safely attributed, so retain the old live-config fallback.
|
||||
queue_replication_heal(&entry.bucket, oi, entry.retry_count as u32).await;
|
||||
} else if let Some(pool) = runtime_sources::replication_pool() {
|
||||
let dsc = replicate_decision_for_admitted_targets(&entry.target_arns);
|
||||
let mut roi = replicate_object_info_from_object_info(oi, dsc, entry.op.replication_type());
|
||||
roi.retry_count = entry.retry_count.max(0) as u32;
|
||||
let _ = pool.queue_replica_task(roi).await;
|
||||
}
|
||||
queued_count += 1;
|
||||
}
|
||||
MrfOpKind::Metadata => {
|
||||
|
||||
@@ -792,6 +792,7 @@ impl ReplicationResyncer {
|
||||
let storage = storage.clone();
|
||||
let results_tx = results_tx.clone();
|
||||
let bucket_name = opts.bucket.clone();
|
||||
let target_arn = opts.arn.clone();
|
||||
|
||||
let f = tokio::spawn(async move {
|
||||
while let Some(mut roi) = rx.recv().await {
|
||||
@@ -819,6 +820,7 @@ impl ReplicationResyncer {
|
||||
bucket: roi.bucket.clone(),
|
||||
event_type: REPLICATE_EXISTING_DELETE.to_string(),
|
||||
op_type: ReplicationType::ExistingObject,
|
||||
target_arn: target_arn.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
replicate_delete(doi, storage.clone()).await;
|
||||
@@ -2009,10 +2011,7 @@ pub async fn replicate_object<S: ReplicationStorage>(roi: ReplicateObjectInfo, s
|
||||
let bucket = roi.bucket.clone();
|
||||
let object = roi.name.clone();
|
||||
|
||||
// The admission decision is the target-granular contract. Re-evaluating the
|
||||
// live config here could fan a synchronous request out to targets that were
|
||||
// not admitted, or promote an async target after a mixed-mode split.
|
||||
let tgt_arns = roi.dsc.replicate_target_arns();
|
||||
let tgt_arns = roi.admitted_target_arns();
|
||||
|
||||
// Acquire a per-object namespace lock so that at most one worker (across all cluster
|
||||
// nodes and MRF retry goroutines) replicates this object version at a time.
|
||||
|
||||
Reference in New Issue
Block a user