[volume] preserve volume data mtime across tier moves (#9947)

* fix(tier): preserve volume data modification time

* fix(tier): best-effort restore of data mtime on download

A failed Chtimes should not abort an otherwise complete tier-down; warn
and continue, matching the EC copy path.

* fix(tier): preserve volume data mtime in rust volume server

Mirror the Go fix: store the source .dat mtime on upload instead of the
upload time, and restore it on the downloaded .dat. Without this a
tiered-then-restored volume loads last_modified_ts_seconds from the
upload/download time, extending its TTL across a restart or remount.

* fix(tier): read source mtime via DiskFile.GetStat()

GetStat() is nil-safe when the backend is closed concurrently and skips a
redundant stat syscall; its cached modTime is the on-disk mtime a reload
reads, since every .dat write or Chtimes is followed by a DiskFile (re)open.

* fix(tier): surface mtime-restore failures on rust tier-down

set_file_mtime now returns io::Result; the tier-down path warns on a
failed restore instead of dropping it silently, so a wrong local .dat
mtime (and the TTL drift it causes) is observable. Matches the Go
download. The EC copy path keeps its best-effort silence.
This commit is contained in:
Chris Lu
2026-06-13 15:11:39 -07:00
committed by GitHub
parent f724828bcb
commit aabd44fbb5
4 changed files with 271 additions and 17 deletions
+35 -16
View File
@@ -1247,7 +1247,7 @@ impl VolumeServer for VolumeGrpcService {
.await
.map_err(|e| Status::internal(e))?;
if dat_modified_ts_ns > 0 {
set_file_mtime(&dat_path, dat_modified_ts_ns);
let _ = set_file_mtime(&dat_path, dat_modified_ts_ns);
}
}
@@ -1272,7 +1272,7 @@ impl VolumeServer for VolumeGrpcService {
.await
.map_err(|e| Status::internal(e))?;
if idx_modified_ts_ns > 0 {
set_file_mtime(&idx_path, idx_modified_ts_ns);
let _ = set_file_mtime(&idx_path, idx_modified_ts_ns);
}
// Copy .vif file (ignore if not found on source)
@@ -1296,7 +1296,7 @@ impl VolumeServer for VolumeGrpcService {
.await
.map_err(|e| Status::internal(e))?;
if vif_modified_ts_ns > 0 {
set_file_mtime(&vif_path, vif_modified_ts_ns);
let _ = set_file_mtime(&vif_path, vif_modified_ts_ns);
}
// Remove the .note file
@@ -3087,6 +3087,15 @@ impl VolumeServer for VolumeGrpcService {
dat_path
};
// Store the source .dat mtime, not the upload time, so a reload computes
// TTL from real data age (matches Go VolumeTierMoveDatToRemote).
let dat_modified_secs = std::fs::metadata(&dat_path)
.and_then(|m| m.modified())
.map_err(|e| Status::internal(format!("stat data file {}: {}", dat_path, e)))?
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
// Look up the S3 tier backend
let backend = {
let registry = self.state.s3_tier_registry.read().unwrap();
@@ -3139,18 +3148,13 @@ impl VolumeServer for VolumeGrpcService {
{
let mut store = state.store.write().unwrap();
if let Some((_, vol)) = store.find_volume_mut(vid) {
let now_unix = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
vol.volume_info.files.push(volume_server_pb::RemoteFile {
backend_type: backend_type.clone(),
backend_id: backend_id.clone(),
key,
offset: 0,
file_size: size,
modified_time: now_unix,
modified_time: dat_modified_secs,
extension: ".dat".to_string(),
});
vol.refresh_remote_write_mode();
@@ -3201,7 +3205,7 @@ impl VolumeServer for VolumeGrpcService {
let vid = VolumeId(req.volume_id);
// Validate volume and get remote storage info
let (dat_path, storage_name, storage_key) = {
let (dat_path, storage_name, storage_key, remote_modified_secs) = {
let store = self.state.store.read().unwrap();
let (_, vol) = store
.find_volume(vid)
@@ -3231,7 +3235,14 @@ impl VolumeServer for VolumeGrpcService {
)));
}
(dat_path, storage_name, storage_key)
let remote_modified_secs = vol
.volume_info
.files
.first()
.map(|f| f.modified_time)
.unwrap_or(0);
(dat_path, storage_name, storage_key, remote_modified_secs)
};
// Look up the S3 tier backend
@@ -3279,6 +3290,15 @@ impl VolumeServer for VolumeGrpcService {
))
})?;
// Restore the .dat mtime so a reload computes TTL from real data age,
// not download time (matches Go VolumeTierMoveDatFromRemote).
if remote_modified_secs > 0 {
let modified_ts_ns = (remote_modified_secs as i64).saturating_mul(1_000_000_000);
if let Err(e) = set_file_mtime(&dat_path, modified_ts_ns) {
tracing::warn!("volume {} restore data file {} modified time: {}", vid, dat_path, e);
}
}
if !keep_remote {
// Delete remote file
backend.delete_file(&storage_key).await.map_err(|e| {
@@ -4150,17 +4170,16 @@ async fn ping_filer_target(
use super::grpc_client::parse_grpc_address;
/// Set the modification time of a file from nanoseconds since Unix epoch.
fn set_file_mtime(path: &str, modified_ts_ns: i64) {
fn set_file_mtime(path: &str, modified_ts_ns: i64) -> std::io::Result<()> {
use std::time::{Duration, SystemTime};
let ts = if modified_ts_ns >= 0 {
SystemTime::UNIX_EPOCH + Duration::from_nanos(modified_ts_ns as u64)
} else {
SystemTime::UNIX_EPOCH
};
if let Ok(file) = std::fs::File::open(path) {
let ft = std::fs::FileTimes::new().set_accessed(ts).set_modified(ts);
let _ = file.set_times(ft);
}
let file = std::fs::File::open(path)?;
let ft = std::fs::FileTimes::new().set_accessed(ts).set_modified(ts);
file.set_times(ft)
}
/// Copy a file from a remote volume server via CopyFile streaming RPC.