Skip to content

Commit 7cad30f

Browse files
Merge pull request cockroachdb#31023 from nvanbenschoten/backport2.0-30990
release-2.0: storage: only track forwarded proposals that were successfully appended
2 parents dfa0c88 + c1aee03 commit 7cad30f

File tree

2 files changed

+65
-32
lines changed

2 files changed

+65
-32
lines changed

pkg/storage/replica.go

+41-21
Original file line numberDiff line numberDiff line change
@@ -3353,12 +3353,17 @@ func (r *Replica) stepRaftGroup(req *RaftMessageRequest) error {
33533353

33543354
// Check if the message is a proposal that should be dropped.
33553355
if r.shouldDropForwardedProposalLocked(req) {
3356-
// If we could signal to the sender that it's proposal was
3357-
// accepted or dropped then we wouldn't need to track anything.
3356+
// If we could signal to the sender that its proposal was accepted
3357+
// or dropped then we wouldn't need to track anything.
33583358
return false /* unquiesceAndWakeLeader */, nil
33593359
}
33603360

33613361
err := raftGroup.Step(req.Message)
3362+
if err == nil {
3363+
// If we stepped successfully and the request is a proposal, consider
3364+
// tracking it so that we can ignore identical proposals in the future.
3365+
r.maybeTrackForwardedProposalLocked(raftGroup, req)
3366+
}
33623367
if err == raft.ErrProposalDropped {
33633368
// A proposal was forwarded to this replica but we couldn't propose it.
33643369
// Swallow the error since we don't have an effective way of signaling
@@ -3377,15 +3382,40 @@ func (r *Replica) shouldDropForwardedProposalLocked(req *RaftMessageRequest) boo
33773382
return false
33783383
}
33793384

3380-
if r.mu.replicaID != r.mu.leaderID {
3381-
// Always continue to forward proposals if we're not the leader.
3382-
return false
3385+
for _, e := range req.Message.Entries {
3386+
switch e.Type {
3387+
case raftpb.EntryNormal:
3388+
cmdID, _ := DecodeRaftCommand(e.Data)
3389+
if _, ok := r.mu.remoteProposals[cmdID]; !ok {
3390+
// Untracked remote proposal. Don't drop.
3391+
return false
3392+
}
3393+
case raftpb.EntryConfChange:
3394+
// Never drop EntryConfChange proposals.
3395+
return false
3396+
default:
3397+
log.Fatalf(context.TODO(), "unexpected Raft entry: %v", e)
3398+
}
33833399
}
3400+
// All entries tracked.
3401+
return true
3402+
}
33843403

3385-
// Record that the proposal was seen and drop the proposal if it was
3386-
// already seen. This prevents duplicate forwarded proposals from each
3387-
// being appended to a leader's raft log.
3388-
drop := true
3404+
func (r *Replica) maybeTrackForwardedProposalLocked(rg *raft.RawNode, req *RaftMessageRequest) {
3405+
if req.Message.Type != raftpb.MsgProp {
3406+
// Not a proposal.
3407+
return
3408+
}
3409+
3410+
if rg.Status().RaftState != raft.StateLeader {
3411+
// We're not the leader. We can't be sure that the proposal made it into
3412+
// the Raft log, so don't track it.
3413+
return
3414+
}
3415+
3416+
// Record that each of the proposal's entries was seen and appended. This
3417+
// allows us to catch duplicate forwarded proposals in the future and
3418+
// prevent them from being repeatedly appended to a leader's raft log.
33893419
for _, e := range req.Message.Entries {
33903420
switch e.Type {
33913421
case raftpb.EntryNormal:
@@ -3394,28 +3424,18 @@ func (r *Replica) shouldDropForwardedProposalLocked(req *RaftMessageRequest) boo
33943424
// An empty command is proposed to unquiesce a range and
33953425
// wake the leader. Don't keep track of these forwarded
33963426
// proposals because they will never be cleaned up.
3397-
drop = false
33983427
} else {
3399-
// Record that the proposal was seen so that we can catch
3400-
// duplicate proposals in the future.
34013428
if r.mu.remoteProposals == nil {
34023429
r.mu.remoteProposals = map[storagebase.CmdIDKey]struct{}{}
34033430
}
3404-
if _, ok := r.mu.remoteProposals[cmdID]; !ok {
3405-
r.mu.remoteProposals[cmdID] = struct{}{}
3406-
drop = false
3407-
}
3431+
r.mu.remoteProposals[cmdID] = struct{}{}
34083432
}
34093433
case raftpb.EntryConfChange:
3410-
// We could peek into the EntryConfChange to find the
3411-
// command ID, but we don't expect follower-initiated
3412-
// conf changes.
3413-
drop = false
3434+
// Don't track EntryConfChanges.
34143435
default:
34153436
log.Fatalf(context.TODO(), "unexpected Raft entry: %v", e)
34163437
}
34173438
}
3418-
return drop
34193439
}
34203440

34213441
type handleRaftReadyStats struct {

pkg/storage/replica_test.go

+24-11
Original file line numberDiff line numberDiff line change
@@ -8052,11 +8052,6 @@ func TestReplicaRefreshPendingCommandsTicks(t *testing.T) {
80528052
func TestReplicaShouldDropForwardedProposal(t *testing.T) {
80538053
defer leaktest.AfterTest(t)()
80548054

8055-
var tc testContext
8056-
stopper := stop.NewStopper()
8057-
defer stopper.Stop(context.TODO())
8058-
tc.Start(t, stopper)
8059-
80608055
cmdSeen, cmdNotSeen := makeIDKey(), makeIDKey()
80618056
data, noData := []byte("data"), []byte("")
80628057

@@ -8152,28 +8147,46 @@ func TestReplicaShouldDropForwardedProposal(t *testing.T) {
81528147
}
81538148
for _, c := range testCases {
81548149
t.Run(c.name, func(t *testing.T) {
8150+
var tc testContext
8151+
stopper := stop.NewStopper()
8152+
defer stopper.Stop(context.TODO())
8153+
tc.Start(t, stopper)
81558154
tc.repl.mu.Lock()
81568155
defer tc.repl.mu.Unlock()
81578156

8157+
rg := tc.repl.mu.internalRaftGroup
81588158
if c.leader {
8159-
// Reset the remoteProposals map to only contain cmdSeen.
8159+
// Set the remoteProposals map to only contain cmdSeen.
81608160
tc.repl.mu.remoteProposals = map[storagebase.CmdIDKey]struct{}{
81618161
cmdSeen: {},
81628162
}
8163+
// Make sure the replica is the leader.
8164+
if s := rg.Status(); s.RaftState != raft.StateLeader {
8165+
t.Errorf("Replica not leader: %v", s)
8166+
}
81638167
} else {
8164-
// Clear the remoteProposals map and set the leader ID to
8165-
// someone else.
8168+
// Clear the remoteProposals map.
81668169
tc.repl.mu.remoteProposals = nil
8167-
tc.repl.mu.leaderID = tc.repl.mu.replicaID + 1
8168-
defer func() { tc.repl.mu.leaderID = tc.repl.mu.replicaID }()
8170+
// Force the replica to step down as the leader by sending it a
8171+
// heartbeat at a high term.
8172+
if err := rg.Step(raftpb.Message{
8173+
Type: raftpb.MsgHeartbeat,
8174+
Term: 999,
8175+
}); err != nil {
8176+
t.Error(err)
8177+
}
8178+
if s := rg.Status(); s.RaftState != raft.StateFollower {
8179+
t.Errorf("Replica not follower: %v", s)
8180+
}
81698181
}
81708182

81718183
req := &RaftMessageRequest{Message: c.msg}
81728184
drop := tc.repl.shouldDropForwardedProposalLocked(req)
8173-
81748185
if c.expDrop != drop {
81758186
t.Errorf("expected drop=%t, found %t", c.expDrop, drop)
81768187
}
8188+
8189+
tc.repl.maybeTrackForwardedProposalLocked(rg, req)
81778190
if l := len(tc.repl.mu.remoteProposals); c.expRemotePropsAfter != l {
81788191
t.Errorf("expected %d tracked remote proposals, found %d", c.expRemotePropsAfter, l)
81798192
}

0 commit comments

Comments
 (0)