feat: implement raft snapshot catchup

This commit is contained in:
Joseph Doherty
2026-02-23 06:13:08 -05:00
parent ecc4752c07
commit 005600b9b8
6 changed files with 93 additions and 1 deletions

View File

@@ -18,10 +18,14 @@ public class RaftElectionTests
internal sealed class RaftTestCluster
{
public List<RaftNode> Nodes { get; }
public RaftNode Leader { get; private set; }
public RaftNode LaggingFollower { get; private set; }
private RaftTestCluster(List<RaftNode> nodes)
{
Nodes = nodes;
Leader = nodes[0];
LaggingFollower = nodes[^1];
}
public static RaftTestCluster Create(int nodes)
@@ -40,6 +44,7 @@ internal sealed class RaftTestCluster
foreach (var voter in Nodes.Skip(1))
candidate.ReceiveVote(voter.GrantVote(candidate.Term));
Leader = candidate;
return Task.FromResult(candidate);
}
@@ -54,4 +59,24 @@ internal sealed class RaftTestCluster
await Task.Delay(20, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
}
}
public async Task GenerateCommittedEntriesAsync(int count)
{
var leader = await ElectLeaderAsync();
for (int i = 0; i < count; i++)
_ = await leader.ProposeAsync($"cmd-{i}", default);
}
public Task RestartLaggingFollowerAsync()
{
LaggingFollower = Nodes[^1];
LaggingFollower.AppliedIndex = 0;
return Task.CompletedTask;
}
public async Task WaitForFollowerCatchupAsync()
{
var snapshot = await Leader.CreateSnapshotAsync(default);
await LaggingFollower.InstallSnapshotAsync(snapshot, default);
}
}

View File

@@ -0,0 +1,16 @@
namespace NATS.Server.Tests;
public class RaftSnapshotCatchupTests
{
[Fact]
public async Task Lagging_follower_catches_up_via_snapshot()
{
var cluster = RaftTestCluster.Create(3);
await cluster.GenerateCommittedEntriesAsync(500);
await cluster.RestartLaggingFollowerAsync();
await cluster.WaitForFollowerCatchupAsync();
cluster.LaggingFollower.AppliedIndex.ShouldBe(cluster.Leader.AppliedIndex);
}
}