Skip to content
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

Change the behavior of persisted index for snapshot #410

Merged
merged 8 commits into from
Dec 18, 2020

Conversation

gengliqi
Copy link
Member

Signed-off-by: gengliqi [email protected]

In previous async ready PR(#403), After getting a snapshot and calling the restore function, the persisted is changed to the index of this snapshot. This makes the persisted index does not mean the data before this index is really persisted, which is not intuitive.
The reason for this behavior is to maintain a invariant that applied <= min(committed, persisted). However, I find it's not needed if the application calls the advance_append or on_persist_ready then calls advance_apply_to. This is intuitive because the snapshot is persisted does not mean it is applied so it should be persisted first, then be applied.

@gengliqi
Copy link
Member Author

@BusyJay @NingLin-P PTAL, thanks.

@BusyJay
Copy link
Member

BusyJay commented Dec 10, 2020

So if the persisted is 2048 and snapshot resets last index back to 2040, then following new committed and not saved entries will be fetched to ready directly?

@gengliqi
Copy link
Member Author

So if the persisted is 2048 and snapshot resets last index back to 2040, then following new committed and not saved entries will be fetched to ready directly?

It won't happen because the persisted should be changed when appending new entries.

It seems it's hard to make the persisted to be really persisted index. Maybe it's no need to change?

@BusyJay
Copy link
Member

BusyJay commented Dec 11, 2020

It won't happen because the persisted should be changed when appending new entries.

It's not true. Entries after snapshot won't cause conflict, so persisted won't be changed.

@gengliqi
Copy link
Member Author

It won't happen because the persisted should be changed when appending new entries.

It's not true. Entries after snapshot won't cause conflict, so persisted won't be changed.

If there is no conflict, the persisted is also be changed.

raft-rs/src/raft_log.rs

Lines 227 to 232 in fc1ef2f

let start = (conflict_idx - (idx + 1)) as usize;
self.append(&ents[start..]);
// persisted should be decreased because entries are changed
if self.persisted > conflict_idx - 1 {
self.persisted = conflict_idx - 1;
}

@BusyJay
Copy link
Member

BusyJay commented Dec 11, 2020

If there is no conflict, conflict_idx will be zero, so the else branch will be skipped.

raft-rs/src/raft_log.rs

Lines 217 to 236 in fc1ef2f

let conflict_idx = self.find_conflict(ents);
if conflict_idx == 0 {
} else if conflict_idx <= self.committed {
fatal!(
self.unstable.logger,
"entry {} conflict with committed entry {}",
conflict_idx,
self.committed
)
} else {
let start = (conflict_idx - (idx + 1)) as usize;
self.append(&ents[start..]);
// persisted should be decreased because entries are changed
if self.persisted > conflict_idx - 1 {
self.persisted = conflict_idx - 1;
}
}
let last_new_index = idx + ents.len() as u64;
self.commit_to(cmp::min(committed, last_new_index));
return Some((conflict_idx, last_new_index));

@gengliqi
Copy link
Member Author

If there is no conflict, conflict_idx will be zero, so the else branch will be skipped.

raft-rs/src/raft_log.rs

Lines 217 to 236 in fc1ef2f

let conflict_idx = self.find_conflict(ents);
if conflict_idx == 0 {
} else if conflict_idx <= self.committed {
fatal!(
self.unstable.logger,
"entry {} conflict with committed entry {}",
conflict_idx,
self.committed
)
} else {
let start = (conflict_idx - (idx + 1)) as usize;
self.append(&ents[start..]);
// persisted should be decreased because entries are changed
if self.persisted > conflict_idx - 1 {
self.persisted = conflict_idx - 1;
}
}
let last_new_index = idx + ents.len() as u64;
self.commit_to(cmp::min(committed, last_new_index));
return Some((conflict_idx, last_new_index));

Hmmm, if it's zero, there is no new entries.

@gengliqi
Copy link
Member Author

By the way, I think we should set persisted to make the logic more intuitive, but set it to index - 1 seems not good enough, I have no better idea.

@BusyJay
Copy link
Member

BusyJay commented Dec 11, 2020

How about setting it to zero, which means unknown? Or use Option<NonZeroU64> and make it None.

Signed-off-by: gengliqi <[email protected]>
@gengliqi gengliqi force-pushed the change-snapshot-persisted branch from bb7ca46 to 9324e81 Compare December 17, 2020 04:44
@gengliqi
Copy link
Member Author

How about setting it to zero, which means unknown? Or use Option<NonZeroU64> and make it None.

It has problems because there is an invariant that applied <= min(committed, persisted).

After thinking for a while, I believe the most suitable method is if self.persisted > self.committed { self.persisted = self.committed; } so we can say the persisted is the really persisted index all the time. You can see the comments in code for details.

Also, I add test for the case mentioned before, which is

  1. Append some entries(index 20) and persist them
  2. Receive a snapshot(index 10) then some new entries(index to 13)
  3. Get a ready then check it(if there is a snapshot and some new entries can be applied, the code should panic in RawNode::ready
assert!(
    !raft
        .raft_log
        .has_next_entries_since(self.commit_since_index),
     "has snapshot but also has committed entries since {}",
     self.commit_since_index
);

@gengliqi
Copy link
Member Author

@BusyJay @NingLin-P PTAL again, thanks.

Signed-off-by: gengliqi <[email protected]>
Copy link
Member

@BusyJay BusyJay left a comment

Choose a reason for hiding this comment

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

Rest LGTM

src/raft.rs Outdated
// the last index of entries from previous leader when it becomes leader
// (see the comments in become_leader), namely, the new persisted entries
// must come from this leader. Here checking the term just for robustness.
if update && self.state == StateRole::Leader && term == self.term {
Copy link
Member

Choose a reason for hiding this comment

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

How about using a log? Even if the term doesn't match self.term in the future adaption, for example introducing paging, it's still safe to enter the if branch.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -565,6 +569,11 @@ impl<T: Storage> RawNode<T> {
}
let mut record = self.records.pop_front().unwrap();

if let Some((i, t)) = record.snapshot {
index = i;
Copy link
Member

Choose a reason for hiding this comment

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

Any case to cover?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added by changing test_async_ready_follower

NingLin-P
NingLin-P previously approved these changes Dec 17, 2020
Copy link
Member

@NingLin-P NingLin-P left a comment

Choose a reason for hiding this comment

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

LGTM

Signed-off-by: gengliqi <[email protected]>
Signed-off-by: gengliqi <[email protected]>
Signed-off-by: gengliqi <[email protected]>
Signed-off-by: gengliqi <[email protected]>
@gengliqi gengliqi force-pushed the change-snapshot-persisted branch from f5e2215 to 147108d Compare December 17, 2020 15:50
@BusyJay BusyJay merged commit a45c4a3 into tikv:master Dec 18, 2020
BusyJay pushed a commit that referenced this pull request Mar 9, 2021
This PR fixes a bug introduced by #410.
Consider the case below
1. A receives a snapshot with index 10
2. A gets a ready and handles it asynchronously
3. A receives a new snapshot with index 20
4. A calls on_persist_ready for ready 1
In step 4, the persisted index can not be updated to 10 because the first_index has changed to 21 so the term check can not be passed. (details in `RaftLog::term`)
I add a `maybe_persist_snap` function to fix this problem. The snapshot does not need to check the term because its data must be committed before and can not be changed in future.

Signed-off-by: gengliqi <[email protected]>
@gengliqi gengliqi mentioned this pull request Jun 16, 2021
shbieng pushed a commit to shbieng/raft-rs that referenced this pull request Sep 19, 2022
This PR fixes a bug introduced by tikv/raft-rs#410.
Consider the case below
1. A receives a snapshot with index 10
2. A gets a ready and handles it asynchronously
3. A receives a new snapshot with index 20
4. A calls on_persist_ready for ready 1
In step 4, the persisted index can not be updated to 10 because the first_index has changed to 21 so the term check can not be passed. (details in `RaftLog::term`)
I add a `maybe_persist_snap` function to fix this problem. The snapshot does not need to check the term because its data must be committed before and can not be changed in future.

Signed-off-by: gengliqi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants