fix(s3): preserve metadata listing extensions (#4261)

* chore(deps): update s3s to 0.14.1

* fix(s3): preserve metadata listing extensions

* fix(swift): make version names monotonic

* fix(s3): preserve v1 list pagination markers
This commit is contained in:
houseme
2026-07-05 05:03:21 +08:00
committed by GitHub
parent d0a965f2ee
commit 9b69c6d14c
12 changed files with 972 additions and 307 deletions
+32 -13
View File
@@ -58,6 +58,7 @@ use super::resolve_swift_object_store_handle;
use super::storage_api::versioning::{ListOperations as _, ObjectOperations as _};
use super::{SwiftError, SwiftResult};
use rustfs_credentials::Credentials;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, error};
@@ -66,6 +67,11 @@ const LOG_SUBSYSTEM_SWIFT_VERSIONING: &str = "swift_versioning";
const EVENT_SWIFT_VERSIONING_ARCHIVE_STATE: &str = "swift_versioning_archive_state";
const EVENT_SWIFT_VERSIONING_RESTORE_STATE: &str = "swift_versioning_restore_state";
const EVENT_SWIFT_VERSIONING_LIST_STATE: &str = "swift_versioning_list_state";
const NANOS_PER_SECOND: u64 = 1_000_000_000;
const VERSION_TIMESTAMP_MAX_SECONDS: u64 = 9_999_999_999;
const VERSION_TIMESTAMP_MAX_NANOS: u64 = VERSION_TIMESTAMP_MAX_SECONDS * NANOS_PER_SECOND + (NANOS_PER_SECOND - 1);
static LAST_VERSION_UNIX_NANOS: AtomicU64 = AtomicU64::new(0);
/// Generate a version name for an archived object
///
@@ -91,22 +97,37 @@ const EVENT_SWIFT_VERSIONING_LIST_STATE: &str = "swift_versioning_list_state";
/// # Returns
/// Versioned object name with inverted timestamp prefix
pub fn generate_version_name(container: &str, object: &str) -> String {
// Get current timestamp
let unix_nanos = next_version_unix_nanos();
let inverted_nanos = VERSION_TIMESTAMP_MAX_NANOS.saturating_sub(unix_nanos);
let inverted_seconds = inverted_nanos / NANOS_PER_SECOND;
let inverted_subsec_nanos = inverted_nanos % NANOS_PER_SECOND;
// Format: {inverted_timestamp}/{container}/{object}
// 9 decimal places = nanosecond precision (prevents collisions up to 1B ops/sec)
format!("{inverted_seconds:010}.{inverted_subsec_nanos:09}/{container}/{object}")
}
fn current_unix_nanos() -> u64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| std::time::Duration::from_secs(0));
let timestamp = now.as_secs_f64();
now.as_secs()
.saturating_mul(NANOS_PER_SECOND)
.saturating_add(u64::from(now.subsec_nanos()))
}
// Invert timestamp so newer versions sort first
// Max reasonable timestamp: 9999999999 (year 2286)
// Using 9 decimal places (nanosecond precision) to prevent collisions
// in high-throughput scenarios where objects are uploaded rapidly
let inverted = 9999999999.999999999 - timestamp;
fn next_version_unix_nanos() -> u64 {
let now = current_unix_nanos();
let mut observed = LAST_VERSION_UNIX_NANOS.load(Ordering::Acquire);
// Format: {inverted_timestamp}/{container}/{object}
// 9 decimal places = nanosecond precision (prevents collisions up to 1B ops/sec)
format!("{:.9}/{}/{}", inverted, container, object)
loop {
let candidate = now.max(observed.saturating_add(1)).min(VERSION_TIMESTAMP_MAX_NANOS);
match LAST_VERSION_UNIX_NANOS.compare_exchange_weak(observed, candidate, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => return candidate,
Err(actual) => observed = actual,
}
}
}
/// Archive the current version of an object before overwriting
@@ -677,8 +698,6 @@ mod tests {
timestamps.insert(ts);
}
// Should have at least some unique timestamps
// (May not be 100 due to system clock granularity)
assert!(timestamps.len() > 1, "Timestamps should be mostly unique");
assert_eq!(timestamps.len(), 100, "Version timestamps should be unique");
}
}
@@ -109,18 +109,9 @@ fn test_version_timestamp_precision() {
std::thread::sleep(std::time::Duration::from_micros(10));
}
// Check uniqueness - allow some collisions on low-precision systems
// Check uniqueness even when the system clock has coarse precision.
let unique_count = versions.iter().collect::<std::collections::HashSet<_>>().len();
let collision_rate = (versions.len() - unique_count) as f64 / versions.len() as f64;
// Allow up to 10% collision rate on low-precision systems
assert!(
collision_rate < 0.1,
"High collision rate: {} collisions out of {} ({}%)",
versions.len() - unique_count,
versions.len(),
collision_rate * 100.0
);
assert_eq!(unique_count, versions.len(), "Version names should be unique");
}
/// Test inverted timestamp calculation
@@ -171,24 +162,10 @@ fn test_version_uniqueness_stress() {
handle.join().unwrap();
}
// Check uniqueness - allow some collisions on low-precision systems
// Check uniqueness even when multiple threads generate versions in the same clock tick.
let versions_vec = versions.lock().unwrap();
let unique_count = versions_vec.iter().collect::<std::collections::HashSet<_>>().len();
let collision_rate = (versions_vec.len() - unique_count) as f64 / versions_vec.len() as f64;
// Allow up to 15% collision rate on low-precision systems with concurrent generation
// This is acceptable because in production:
// 1. Versions are generated with more time between them
// 2. Swift uses additional mechanisms (UUIDs) to ensure uniqueness
// 3. The timestamp is primarily for ordering, not uniqueness
// 4. Concurrent generation from multiple threads on low-precision clocks can cause higher collision rates
assert!(
collision_rate < 0.15,
"High collision rate: {} unique out of {} total ({}% collisions)",
unique_count,
versions_vec.len(),
collision_rate * 100.0
);
assert_eq!(unique_count, versions_vec.len(), "Version names should be unique");
}
/// Test that archive and restore preserve object path structure
@@ -366,18 +343,9 @@ fn test_version_high_count_performance() {
duration.as_millis()
);
// Check uniqueness - allow some collisions on low-precision systems
// Check uniqueness.
let unique_count = versions.iter().collect::<std::collections::HashSet<_>>().len();
let collision_rate = (versions.len() - unique_count) as f64 / versions.len() as f64;
// Allow up to 5% collision rate
assert!(
collision_rate < 0.05,
"High collision rate: {} collisions out of {} ({}%)",
versions.len() - unique_count,
versions.len(),
collision_rate * 100.0
);
assert_eq!(unique_count, versions.len(), "Version names should be unique");
}
/// Test version name format stability