Skip to content

fix: fsync snap directory when saving a received snapshot db - #22314

Merged
fuweid merged 2 commits into
etcd-io:mainfrom
gyuho:fix/snapdb-dir-fsync
Sep 3, 2026
Merged

fix: fsync snap directory when saving a received snapshot db#22314
fuweid merged 2 commits into
etcd-io:mainfrom
gyuho:fix/snapdb-dir-fsync

Conversation

@gyuho

@gyuho gyuho commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

SaveDBFrom synced a received snapshot database before renaming it into place, but did not sync the containing directory. Linux fsync(2) documents that syncing a file does not necessarily persist its directory entry; the directory must also be synced.

The snapshot receiver calls SaveDBFrom before processing the Raft message, which can later sync the WAL snapshot record. Sync the snapshot directory after rename and on the existing-file retry path, and return sync errors so snapshot handling stops before Raft processing.

Add direct unit coverage that both SaveDBFrom paths invoke directory sync and propagate failures. Keep gofail E2E coverage scoped to failure/retry and crash-before-return control flow; remove tests and claims that did not establish directory-entry durability.

@kubernetes-prow

Copy link
Copy Markdown

Hi @gyuho. Thanks for your PR.

I'm waiting for a etcd-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Comment thread server/etcdserver/api/snap/db.go Outdated
@@ -62,6 +73,23 @@ func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) (int64, error) {
return n, err
}

// gofail: var snapDBRenameBeforeDirSync struct{}

// A rename is not durable until the containing directory is fsynced.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify the sequence, 1. etcd receives a snapshot, 2. writes it to a temp file, 3. fsyncs the file, 4. renames it into place as snap/db -- but never fsynced the snap/ directory. A power-off crash in that window could lose the rename, so the freshly received snapshot silently "disappears" on restart.

@ahrtr
ahrtr self-requested a review August 20, 2026 18:58
@gyuho

gyuho commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Was also able to reproduce this using something like GOFAIL_FAILPOINTS=applyBeforeOpenSnapshot=sleep("60s") + echo b > /proc/sysrq-trigger. Here are another sequence that I tested:

  • failpoint on member-3: pause 60 seconds right before it opens a new snap.db.
  • Stop member-3's etcd. Write keys to the cluster and compact the log. Start member-3, to trigger the leader to send it a full snapshot.
  • Member-3 saves snap.db to disk and pauses, as instructed. File exists; folder never fsynced.
  • SIGKILL member-3's etcd process (and stop the systemd unit, so it can't restart mid-surgery).
  • Then, still on member-3 over SSM, run rm -f /var/lib/etcd/member/snap/*.snap.db. The machine never crashed -- deleting the file by hand creates the exact disk state a power cut would leave. The kill has to come first so no running process is holding or rewriting that file.
  • Start member-3. It finds the saved-snapshot record, looks for snap.db, finds nothing, panics

@fuweid

fuweid commented Aug 20, 2026

Copy link
Copy Markdown
Member

it could happen. I run into such kind of issues in production several times for containerd snapshot files. It's easy to reproduce it with https://github.com/etcd-io/bbolt/tree/main/tests/dmflakey for power-off.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.73%. Comparing base (23a4e40) to head (1f9a2d8).
⚠️ Report is 16 commits behind head on main.

Files with missing lines Patch % Lines
server/etcdserver/api/snap/db.go 86.66% 1 Missing and 1 partial ⚠️
server/etcdserver/api/snap/snapshotter.go 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
server/etcdserver/api/snap/db.go 70.96% <86.66%> (+9.26%) ⬆️
server/etcdserver/api/snap/snapshotter.go 66.66% <60.00%> (-0.23%) ⬇️

... and 15 files with indirect coverage changes

@@            Coverage Diff             @@
##             main   #22314      +/-   ##
==========================================
- Coverage   69.76%   69.73%   -0.04%     
==========================================
  Files         448      448              
  Lines       38159    38074      -85     
==========================================
- Hits        26622    26551      -71     
+ Misses      10107    10105       -2     
+ Partials     1430     1418      -12     

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 23a4e40...1f9a2d8. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gyuho

gyuho commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@gyuho

gyuho commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Ran real 3-member etcd clusters on AWS and forced a member to receive a database snapshot: injected failpoints (pausing the code between the snap.db rename and the directory fsync, or making that fsync return an error), sent SIGKILL to the etcd process, deleted the snap.db file after the WAL record was durable, and then injected a real machine crash via echo b > /proc/sysrq-trigger, which reboots the EC2 instance without syncing disks.

To prevent filesystem journaling from masking the bug, also put the member's data on a dedicated non-journaled ext2 EBS volume.

On the unfixed build this reproduced the field failure: after the crash, the member panicked with failed to find database snapshot file.

On the fixed build the snap.db directory entry survived every one of these failures, and the member reopened the snapshot, rejoined the cluster, and converged to the same data as the other two members -- verified by comparing KV hashes across all three.

@ahrtr ahrtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good.

Comment thread server/etcdserver/api/snap/db.go Outdated
@@ -30,8 +30,12 @@ import (

var ErrNoDBSnapshot = errors.New("snap: snapshot file doesn't exist")

// SaveDBFrom saves snapshot of the database from the given reader. It
// guarantees the save operation is atomic.
// SaveDBFrom atomically saves a database snapshot from r. Before returning

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// SaveDBFrom atomically saves a database snapshot from r. Before returning
// SaveDBFrom atomically saves a database snapshot from the reader. Before returning

// every successful return keeps the durability guarantee above.
if err = s.fsyncDir(s.dir); err != nil {
return n, err
}
return n, nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning n when the f is removed is a little weird. It should return the existing file's size. But it isn't introduced in this PR.

It's even nice if we have a verification to ensure the existing file has exactly the same content as the new one.

@ahrtr

ahrtr commented Aug 24, 2026

Copy link
Copy Markdown
Member

/ok-to-test

SaveDBFrom synced a received snapshot database before renaming it into place, but did not sync the containing directory. Linux fsync(2) documents that syncing a file does not necessarily persist its directory entry; the directory must also be synced.

The snapshot receiver calls SaveDBFrom before processing the Raft message, which can later sync the WAL snapshot record. Sync the snapshot directory after rename and on the existing-file retry path, and return sync errors so snapshot handling stops before Raft processing.

Add direct unit coverage that both SaveDBFrom paths invoke directory sync and propagate failures. Keep gofail E2E coverage scoped to failure/retry and crash-before-return control flow; remove tests and claims that did not establish directory-entry durability.

Signed-off-by: Gyuho Lee <gyuhol@nvidia.com>
@gyuho
gyuho force-pushed the fix/snapdb-dir-fsync branch from e82fa68 to cf31e1f Compare August 25, 2026 04:06

@ahrtr ahrtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thx for the fix

cc @fuweid @serathius

@ahrtr

ahrtr commented Aug 26, 2026

Copy link
Copy Markdown
Member

can you raise an issue to track the backport effort? thx

@gyuho

gyuho commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@ahrtr I've created #22340, thanks

@ahrtr

ahrtr commented Aug 31, 2026

Copy link
Copy Markdown
Member

cc @fuweid @ivanvc @serathius

@fuweid fuweid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and left one comment for e2e test

Comment thread tests/e2e/snap_db_dirsync_test.go
Signed-off-by: Gyuho Lee <gyuhol@nvidia.com>
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: ahrtr, fuweid, gyuho

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fuweid
fuweid merged commit e9e5656 into etcd-io:main Sep 3, 2026
33 checks passed
@fuweid

fuweid commented Sep 3, 2026

Copy link
Copy Markdown
Member

/cherry-pick release-3.7
/cherry-pick release-3.6

@k8s-infra-cherrypick-robot

Copy link
Copy Markdown

@fuweid: #22314 failed to apply on top of branch "release-3.6":

Applying: fix: fsync snap directory when saving a received snapshot db
Using index info to reconstruct a base tree...
M	server/etcdserver/api/snap/snapshotter.go
A	tests/robustness/Makefile
Falling back to patching base and 3-way merge...
Auto-merging server/etcdserver/api/snap/snapshotter.go
Auto-merging tests/robustness/makefile.mk
CONFLICT (content): Merge conflict in tests/robustness/makefile.mk
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 fix: fsync snap directory when saving a received snapshot db

Details

In response to this:

/cherry-pick release-3.7
/cherry-pick release-3.6

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-infra-cherrypick-robot

Copy link
Copy Markdown

@fuweid: new pull request created: #22378

Details

In response to this:

/cherry-pick release-3.7
/cherry-pick release-3.6

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@gyuho
gyuho deleted the fix/snapdb-dir-fsync branch September 4, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants