feat: implement jetstream governance runtime parity semantics

This commit is contained in:
Joseph Doherty
2026-02-23 14:54:30 -05:00
parent 0413ff6ae9
commit 148ff9ebb6
4 changed files with 56 additions and 2 deletions

View File

@@ -7,6 +7,8 @@ public sealed class JetStreamMetaGroup
{ {
private readonly int _nodes; private readonly int _nodes;
private readonly ConcurrentDictionary<string, byte> _streams = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, byte> _streams = new(StringComparer.Ordinal);
private int _leaderIndex = 1;
private long _leadershipVersion = 1;
public JetStreamMetaGroup(int nodes) public JetStreamMetaGroup(int nodes)
{ {
@@ -25,13 +27,18 @@ public sealed class JetStreamMetaGroup
{ {
Streams = _streams.Keys.OrderBy(x => x, StringComparer.Ordinal).ToArray(), Streams = _streams.Keys.OrderBy(x => x, StringComparer.Ordinal).ToArray(),
ClusterSize = _nodes, ClusterSize = _nodes,
LeaderId = $"meta-{_leaderIndex}",
LeadershipVersion = _leadershipVersion,
}; };
} }
public void StepDown() public void StepDown()
{ {
// Placeholder for parity API behavior; current in-memory meta group _leaderIndex++;
// does not track explicit leader state. if (_leaderIndex > Math.Max(_nodes, 1))
_leaderIndex = 1;
Interlocked.Increment(ref _leadershipVersion);
} }
} }
@@ -39,4 +46,6 @@ public sealed class MetaGroupState
{ {
public IReadOnlyList<string> Streams { get; init; } = []; public IReadOnlyList<string> Streams { get; init; } = [];
public int ClusterSize { get; init; } public int ClusterSize { get; init; }
public string LeaderId { get; init; } = string.Empty;
public long LeadershipVersion { get; init; }
} }

View File

@@ -34,6 +34,7 @@ public sealed class StreamManager
} }
public IReadOnlyCollection<string> StreamNames => _streams.Keys.ToArray(); public IReadOnlyCollection<string> StreamNames => _streams.Keys.ToArray();
public MetaGroupState? GetMetaState() => _metaGroup?.GetState();
public IReadOnlyList<string> ListNames() public IReadOnlyList<string> ListNames()
=> [.. _streams.Keys.OrderBy(x => x, StringComparer.Ordinal)]; => [.. _streams.Keys.OrderBy(x => x, StringComparer.Ordinal)];

View File

@@ -0,0 +1,25 @@
using NATS.Server.JetStream.Cluster;
namespace NATS.Server.Tests.JetStream;
public class JetStreamMetaGovernanceStrictParityTests
{
[Fact]
public async Task Meta_and_replica_governance_actions_reflect_committed_state_transitions()
{
var meta = new JetStreamMetaGroup(3);
var before = meta.GetState();
await meta.ProposeCreateStreamAsync(new NATS.Server.JetStream.Models.StreamConfig
{
Name = "ORDERS_GOV",
Subjects = ["orders.gov"],
}, default);
meta.StepDown();
var after = meta.GetState();
after.Streams.ShouldContain("ORDERS_GOV");
after.LeaderId.ShouldNotBe(before.LeaderId);
after.LeadershipVersion.ShouldBe(before.LeadershipVersion + 1);
}
}

View File

@@ -0,0 +1,19 @@
using NATS.Server.JetStream.Cluster;
namespace NATS.Server.Tests.JetStream;
public class JetStreamReplicaGovernanceStrictParityTests
{
[Fact]
public async Task Meta_and_replica_governance_actions_reflect_committed_state_transitions()
{
var group = new StreamReplicaGroup("ORDERS_GOV", replicas: 3);
var beforeLeader = group.Leader.Id;
await group.StepDownAsync(default);
group.Leader.Id.ShouldNotBe(beforeLeader);
var index = await group.ProposeAsync("set placement", default);
index.ShouldBeGreaterThan(0);
}
}