Compare commits

...

2 Commits

Author SHA1 Message Date
Erik Grinaker
ab34e34ba9 Merge branch 'main' into erik/durable-rename-fsync-metrics 2024-11-07 16:20:38 +01:00
Erik Grinaker
d6ab04b8e2 safekeeper: record fsync metrics for segment renames 2024-11-07 16:02:06 +01:00
2 changed files with 35 additions and 14 deletions

View File

@@ -1,4 +1,5 @@
use std::os::fd::AsRawFd;
use std::time::Duration;
use std::{
borrow::Cow,
fs::{self, File},
@@ -125,6 +126,7 @@ pub async fn fsync_async_opt(
/// Like postgres' durable_rename, renames file issuing fsyncs do make it
/// durable. After return, file and rename are guaranteed to be persisted.
/// Returns the fsync latencies, for metrics (this is kind of a kludge).
///
/// Unlike postgres, it only does fsyncs to 1) file to be renamed to make
/// contents durable; 2) its directory entry to make rename durable 3) again to
@@ -142,24 +144,35 @@ pub async fn durable_rename(
old_path: impl AsRef<Utf8Path>,
new_path: impl AsRef<Utf8Path>,
do_fsync: bool,
) -> io::Result<()> {
) -> io::Result<[Duration; 3]> {
async fn maybe_fsync_with_latency(path: &Utf8Path, do_fsync: bool) -> io::Result<Duration> {
if !do_fsync {
return Ok(Duration::ZERO);
}
let start = std::time::Instant::now();
fsync_async(path).await?;
Ok(start.elapsed())
}
let mut latency = [Duration::ZERO; 3];
// first fsync the file
fsync_async_opt(old_path.as_ref(), do_fsync).await?;
latency[0] = maybe_fsync_with_latency(old_path.as_ref(), do_fsync).await?;
// Time to do the real deal.
tokio::fs::rename(old_path.as_ref(), new_path.as_ref()).await?;
// Postgres'ish fsync of renamed file.
fsync_async_opt(new_path.as_ref(), do_fsync).await?;
latency[1] = maybe_fsync_with_latency(new_path.as_ref(), do_fsync).await?;
// Now fsync the parent
let parent = match new_path.as_ref().parent() {
Some(p) => p,
None => Utf8Path::new("./"), // assume current dir if there is no parent
};
fsync_async_opt(parent, do_fsync).await?;
latency[2] = maybe_fsync_with_latency(parent, do_fsync).await?;
Ok(())
Ok(latency)
}
/// Writes a file to the specified `final_path` in a crash safe fasion, using [`std::fs`].

View File

@@ -18,6 +18,7 @@ use std::cmp::{max, min};
use std::future::Future;
use std::io::{self, SeekFrom};
use std::pin::Pin;
use std::time::Duration;
use tokio::fs::{self, remove_file, File, OpenOptions};
use tokio::io::{AsyncRead, AsyncWriteExt};
use tokio::io::{AsyncReadExt, AsyncSeekExt};
@@ -274,15 +275,22 @@ impl PhysicalStorage {
});
file.set_len(self.wal_seg_size as u64).await?;
// Note: this doesn't get into observe_flush_seconds metric. But
// segment init should be separate metric, if any.
if let Err(e) = durable_rename(&tmp_path, &wal_file_partial_path, !self.no_sync).await {
// Probably rename succeeded, but fsync of it failed. Remove
// the file then to avoid using it.
remove_file(wal_file_partial_path)
.await
.or_else(utils::fs_ext::ignore_not_found)?;
return Err(e.into());
match durable_rename(&tmp_path, &wal_file_partial_path, !self.no_sync).await {
Ok(fsync_latencies) => {
for latency in fsync_latencies {
if latency != Duration::ZERO {
self.metrics.observe_flush_seconds(latency.as_secs_f64());
}
}
}
Err(e) => {
// Probably rename succeeded, but fsync of it failed. Remove
// the file then to avoid using it.
remove_file(wal_file_partial_path)
.await
.or_else(utils::fs_ext::ignore_not_found)?;
return Err(e.into());
}
}
Ok((file, true))
}