From 8907e0cb2f2be688743e9e004ccdbad3a613498c Mon Sep 17 00:00:00 2001 From: Nick Liu Date: Fri, 28 Apr 2023 00:43:47 -0500 Subject: [PATCH] feat(syncoid): Sort snapshots by `createtxg` if possible It is possible for `creation` of a subsequent snapshot to be in the past compared to the current snapshot due to system clock discrepancies, which leads to earlier snapshots not being replicated in the initial syncoid sync. Also, `syncoid --no-sync-snap` might not pick up the most recently taken snapshot if the clock moved backwards before taking that snapshot. Sorting snapshots by the `createtxg` value is reliable and documented in `man 8 zfsprops` as the proper way to order snapshots, but it was not available in ZFS versions before 0.7. To maintain backwards compatibility, the sorting falls back to sorting by the `creation` property, which was the old behavior. Fixes: https://github.com/jimsalterjrs/sanoid/issues/815 --- syncoid | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/syncoid b/syncoid index 3a5a9e4..44e8486 100755 --- a/syncoid +++ b/syncoid @@ -862,9 +862,9 @@ sub syncdataset { if (defined $args{'delete-target-snapshots'}) { # Find the snapshots that exist on the target, filter with # those that exist on the source. Remaining are the snapshots - # that are only on the target. Then sort by creation date, as - # to remove the oldest snapshots first. - my @to_delete = sort { $snaps{'target'}{$a}{'creation'}<=>$snaps{'target'}{$b}{'creation'} } grep {!exists $snaps{'source'}{$_}} keys %{ $snaps{'target'} }; + # that are only on the target. Then sort to remove the oldest + # snapshots first. + my @to_delete = sort { sortsnapshots(\%snaps, $a, $b) } grep {!exists $snaps{'source'}{$_}} keys %{ $snaps{'target'} }; while (@to_delete) { # Create batch of snapshots to remove my $snaps = join ',', splice(@to_delete, 0, 50); @@ -1480,9 +1480,17 @@ sub readablebytes { return $disp; } +sub sortsnapshots { + my ($snaps, $left, $right) = @_; + if (defined $snaps->{'source'}{$left}{'createtxg'} && defined $snaps->{'source'}{$right}{'createtxg'}) { + return $snaps->{'source'}{$left}{'createtxg'} <=> $snaps->{'source'}{$right}{'createtxg'}; + } + return $snaps->{'source'}{$left}{'creation'} <=> $snaps->{'source'}{$right}{'creation'}; +} + sub getoldestsnapshot { my $snaps = shift; - foreach my $snap ( sort { $snaps{'source'}{$a}{'creation'}<=>$snaps{'source'}{$b}{'creation'} } keys %{ $snaps{'source'} }) { + foreach my $snap (sort { sortsnapshots($snaps, $a, $b) } keys %{ $snaps{'source'} }) { # return on first snap found - it's the oldest return $snap; } @@ -1496,7 +1504,7 @@ sub getoldestsnapshot { sub getnewestsnapshot { my $snaps = shift; - foreach my $snap ( sort { $snaps{'source'}{$b}{'creation'}<=>$snaps{'source'}{$a}{'creation'} } keys %{ $snaps{'source'} }) { + foreach my $snap (sort { sortsnapshots($snaps, $b, $a) } keys %{ $snaps{'source'} }) { # return on first snap found - it's the newest writelog('INFO', "NEWEST SNAPSHOT: $snap"); return $snap; @@ -1675,7 +1683,7 @@ sub pruneoldsyncsnaps { sub getmatchingsnapshot { my ($sourcefs, $targetfs, $snaps) = @_; - foreach my $snap ( sort { $snaps{'source'}{$b}{'creation'}<=>$snaps{'source'}{$a}{'creation'} } keys %{ $snaps{'source'} }) { + foreach my $snap ( sort { sortsnapshots($snaps, $b, $a) } keys %{ $snaps{'source'} }) { if (defined $snaps{'target'}{$snap}) { if ($snaps{'source'}{$snap}{'guid'} == $snaps{'target'}{$snap}{'guid'}) { return $snap;