mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
fix(tier): replay committed mutation intents (#5107)
Keep recovered tier mutation blocks installed until the local publish transition has atomically established draining for the affected tiers, so old-generation leases cannot slip in after peer commit replay and before local publish. Delay committed intent cleanup until local publish succeeds. If peer replay succeeds but local publish fails, the durable committed intent remains available for retry and the runtime block stays fail-closed. Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -546,7 +546,7 @@ mod tests {
|
||||
runtime::{global::set_object_store_resolver, sources as runtime_sources},
|
||||
services::tier::{
|
||||
test_util::{MockWarmBackend, TransitionCleanupStoreBarrier, register_mock_tier},
|
||||
tier::TierConfigMgr,
|
||||
tier::{TIER_CONFIG_FILE, TierConfigMgr},
|
||||
tier_mutation_intent::{
|
||||
TIER_MUTATION_INTENT_RECORD_PREFIX, TierMutationIntent, TierMutationIntentKind, TierMutationIntentState,
|
||||
TierMutationIntentTarget, advance_tier_mutation_intent_record_idempotent, delete_tier_mutation_intent_record,
|
||||
@@ -1589,6 +1589,47 @@ mod tests {
|
||||
assert_eq!(loaded.state, TierMutationIntentState::Committed);
|
||||
assert_eq!(loaded.committed_config_etag.as_deref(), Some("new-etag"));
|
||||
|
||||
store
|
||||
.tier_config_mgr()
|
||||
.read()
|
||||
.await
|
||||
.save_tiering_config(store.clone())
|
||||
.await
|
||||
.expect("tier config should persist for cleaned intent commit proof");
|
||||
let tier_config_info = store
|
||||
.get_object_info(
|
||||
RUSTFS_META_BUCKET,
|
||||
&format!("{}/{}", com::CONFIG_PREFIX, TIER_CONFIG_FILE),
|
||||
&ObjectOptions::default(),
|
||||
)
|
||||
.await
|
||||
.expect("tier config object info should load");
|
||||
let tier_config_etag = tier_config_info.etag.expect("tier config should carry an ETag");
|
||||
delete_tier_mutation_intent_record(store.clone(), mutation_id)
|
||||
.await
|
||||
.expect("committed peer intent cleanup should persist");
|
||||
let cleaned_commit_retry = handle_tier_mutation_peer_request(
|
||||
store.clone(),
|
||||
TIER_MUTATION_RPC_PROTOCOL_VERSION,
|
||||
TierMutationRpcPhase::Commit,
|
||||
mutation_id,
|
||||
tier_config_etag.as_bytes(),
|
||||
)
|
||||
.await
|
||||
.expect("commit retry after durable cleanup should be idempotently terminal");
|
||||
assert!(!cleaned_commit_retry.applied);
|
||||
assert_eq!(cleaned_commit_retry.state, TierMutationPeerState::Committed);
|
||||
let mismatched_cleaned_commit = handle_tier_mutation_peer_request(
|
||||
store.clone(),
|
||||
TIER_MUTATION_RPC_PROTOCOL_VERSION,
|
||||
TierMutationRpcPhase::Commit,
|
||||
mutation_id,
|
||||
b"not-the-current-etag",
|
||||
)
|
||||
.await
|
||||
.expect_err("missing intent without a matching committed config ETag must fail closed");
|
||||
assert!(matches!(mismatched_cleaned_commit, TierMutationPeerError::Store(Error::ConfigNotFound)));
|
||||
|
||||
let abort_id = uuid::Uuid::new_v4();
|
||||
let abort_intent = tier_mutation_peer_test_intent(abort_id, "COLD-B", [4; 32]);
|
||||
let abort_prepare_payload = abort_intent.encode().expect("abort prepare intent should encode");
|
||||
|
||||
Reference in New Issue
Block a user