-
Notifications
You must be signed in to change notification settings - Fork 2.2k
fix(buffers): fsync the disk_v2 data directory on file creation and its newly-created ancestors #25779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix(buffers): fsync the disk_v2 data directory on file creation and its newly-created ancestors #25779
Changes from all commits
8ae3854
5e2144a
b557561
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Fixed a durability gap in `disk_v2` buffer creation. On first startup the buffer creates its data directory chain (`<base>/buffer/v2/<id>`) with `create_dir_all`, but that does not make the new directory entries durable in their parents (fsyncing a directory persists its contents, not its own entry). A power loss shortly after startup could therefore drop the freshly created buffer directories -- and an already-acknowledged data file inside them -- even though each data file's own directory is fsynced when the file is created. The buffer now fsyncs every directory it creates, from the buffer directory's parent up to the first pre-existing ancestor, before any write can be acknowledged. | ||
|
|
||
| authors: graphcareful |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Fixed a durability gap in the `disk_v2` buffer that could permanently lose end-to-end acknowledged records across a crash. When the buffer created a new data file it synced the file's contents to disk, but never synced the parent data directory, so the file's directory entry was not made durable. After a crash (for example a `SIGKILL` or power loss) the newly created file could disappear entirely on reopen even though its contents had been fsynced — taking with it records the buffer had already acknowledged to its upstream source, which the source therefore would not retransmit. The buffer now fsyncs the data directory immediately after creating a new data file, so the file is durably present before any records written into it can be acknowledged. | ||
|
|
||
| authors: graphcareful |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1107,9 +1107,12 @@ where | |
| .filesystem() | ||
| .open_file_writable_atomic(&data_file_path) | ||
| .await; | ||
| // The third tuple element records whether we just atomically created this file (and | ||
| // therefore a new directory entry), so we only fsync the directory when there is a new | ||
| // entry to persist -- not when we reopen an existing (possibly empty) file. | ||
| let file = match maybe_data_file { | ||
| // We were able to create the file, so we're good to proceed. | ||
| Ok(data_file) => Some((data_file, 0)), | ||
| Ok(data_file) => Some((data_file, 0, true)), | ||
| // We got back an error trying to open the file: might be that it already exists, | ||
| // might be something else. | ||
| Err(e) => match e.kind() { | ||
|
|
@@ -1135,7 +1138,7 @@ where | |
| // or it's not empty but we're not skipping to the next file, which can | ||
| // only mean that we're still initializing, and so this would be the | ||
| // data file we left off writing to. | ||
| Some((data_file, file_len)) | ||
| Some((data_file, file_len, false)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When rolling to the next file, if Useful? React with 👍 / 👎. |
||
| } else { | ||
| // The file isn't empty, and we're not in initialization anymore, which | ||
| // means this data file is one that the reader still hasn't finished | ||
|
|
@@ -1149,7 +1152,7 @@ where | |
| }, | ||
| }; | ||
|
|
||
| if let Some((data_file, data_file_size)) = file { | ||
| if let Some((data_file, data_file_size, just_created)) = file { | ||
| // We successfully opened the file and it can be written to. | ||
| debug!( | ||
| data_file_path = data_file_path.to_string_lossy().as_ref(), | ||
|
|
@@ -1160,6 +1163,17 @@ where | |
| // Make sure the file is flushed to disk, especially if we just created it. | ||
| data_file.sync_all().await?; | ||
|
|
||
| // Syncing the file's contents above does not make its directory entry durable. | ||
| // Sync the data directory so a newly created file is present on disk before | ||
| // acknowledging anything written into it. Only needed when we actually created a | ||
| // new entry -- reopening an existing (even empty) file needs no directory sync. | ||
| if just_created { | ||
| self.ledger | ||
| .filesystem() | ||
| .sync_directory(self.ledger.data_dir()) | ||
| .await?; | ||
|
Comment on lines
+1171
to
+1174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This directory fsync persists every pending mutation in the data directory, not just the new file entry. In a concurrent run, the reader can have completed Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| self.writer = Some(RecordWriter::new( | ||
| data_file, | ||
| data_file_size, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Unix, a deployment can use a data directory that is writable/searchable but not readable (for example mode
0300); Vector's data-dir validation only requires writability, and creating the known buffer files works in that directory. This new directory fsync path opens the directory read-only before it reaches the unsupported-fsync downgrade, so those previously valid disk buffers now fail withPermissionDeniedduring startup or rotation instead of merely warning that the directory entry cannot be made durable.Useful? React with 👍 / 👎.