feat(batch14): complete group1 write path and wave1 tests
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
@@ -134,6 +135,47 @@ public sealed partial class ConcurrencyTests1
|
||||
}
|
||||
}
|
||||
|
||||
[Fact] // T:2441
|
||||
public void NoRaceJetStreamFileStoreLargeKVAccessTiming_ShouldSucceed()
|
||||
{
|
||||
var value = Enumerable.Repeat((byte)'Z', 256).ToArray();
|
||||
const int keyCount = 5_000;
|
||||
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
for (var i = 1; i <= keyCount; i++)
|
||||
{
|
||||
fs.StoreMsg($"KV.STREAM_NAME.{i}", null, value, 0).Seq.ShouldBeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var last = fs.LoadLastMsg($"KV.STREAM_NAME.{keyCount}", null);
|
||||
sw.Stop();
|
||||
var lastLookup = sw.Elapsed;
|
||||
|
||||
last.ShouldNotBeNull();
|
||||
last!.Msg.ShouldBe(value);
|
||||
|
||||
sw.Restart();
|
||||
var first = fs.LoadLastMsg("KV.STREAM_NAME.1", null);
|
||||
sw.Stop();
|
||||
var firstLookup = sw.Elapsed;
|
||||
|
||||
first.ShouldNotBeNull();
|
||||
first!.Msg.ShouldBe(value);
|
||||
|
||||
// Keep generous bounds to avoid machine-specific flakiness while still
|
||||
// asserting access stays fast under a large key set.
|
||||
lastLookup.ShouldBeLessThan(TimeSpan.FromMilliseconds(250));
|
||||
firstLookup.ShouldBeLessThan(TimeSpan.FromMilliseconds(350));
|
||||
|
||||
var firstState = fs.FilteredState(1, "KV.STREAM_NAME.1");
|
||||
var lastState = fs.FilteredState(1, $"KV.STREAM_NAME.{keyCount}");
|
||||
firstState.First.ShouldBeGreaterThan(0UL);
|
||||
lastState.First.ShouldBeGreaterThan(0UL);
|
||||
}, DefaultStreamConfig());
|
||||
}
|
||||
|
||||
private static void WithStore(Action<JetStreamFileStore, string> action, StreamConfig? cfg = null)
|
||||
{
|
||||
var root = NewRoot();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;
|
||||
|
||||
public sealed class JetStreamClusterLongTests
|
||||
{
|
||||
[Fact] // T:1219
|
||||
public void LongFileStoreEnforceMsgPerSubjectLimit_ShouldSucceed()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"impl-fs-long-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
JetStreamFileStore? fs = null;
|
||||
try
|
||||
{
|
||||
fs = JetStreamFileStore.NewFileStore(
|
||||
new FileStoreConfig { StoreDir = root, BlockSize = 1024 },
|
||||
new StreamConfig
|
||||
{
|
||||
Name = "zzz",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["test.>"],
|
||||
MaxMsgsPer = 1,
|
||||
MaxMsgs = -1,
|
||||
MaxBytes = -1,
|
||||
Discard = DiscardPolicy.DiscardOld,
|
||||
Retention = RetentionPolicy.LimitsPolicy,
|
||||
});
|
||||
|
||||
const int keys = 4_000;
|
||||
const int rewriteCount = 30_000;
|
||||
|
||||
for (var i = 0; i < keys; i++)
|
||||
{
|
||||
fs.StoreMsg($"test.{i:000000}", null, "seed"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
for (var i = 0; i < rewriteCount; i++)
|
||||
{
|
||||
var n = Random.Shared.Next(keys);
|
||||
fs.StoreMsg($"test.{n:000000}", null, "rewrite"u8.ToArray(), 0).Seq.ShouldBeGreaterThan(0UL);
|
||||
}
|
||||
sw.Stop();
|
||||
|
||||
var totals = fs.SubjectsTotals("test.*");
|
||||
totals.Count.ShouldBe(keys);
|
||||
totals.Values.All(v => v <= 1UL).ShouldBeTrue();
|
||||
sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
finally
|
||||
{
|
||||
fs?.Stop();
|
||||
if (Directory.Exists(root))
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,6 +203,177 @@ public sealed partial class JetStreamFileStoreTests
|
||||
[Fact] // T:592
|
||||
public void FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed() => RunWaveBScenario(nameof(FileStoreTrailingSkipMsgsFromStreamStateFile_ShouldSucceed));
|
||||
|
||||
[Fact] // T:549
|
||||
public void FileStoreTruncateRemovedBlock_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL);
|
||||
fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL);
|
||||
fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL);
|
||||
|
||||
var before = fs.State();
|
||||
before.Msgs.ShouldBe(3UL);
|
||||
before.FirstSeq.ShouldBe(1UL);
|
||||
before.LastSeq.ShouldBe(3UL);
|
||||
before.NumDeleted.ShouldBe(0);
|
||||
|
||||
fs.RemoveMsg(2).Removed.ShouldBeTrue();
|
||||
var mid = fs.State();
|
||||
mid.Msgs.ShouldBe(2UL);
|
||||
mid.FirstSeq.ShouldBe(1UL);
|
||||
mid.LastSeq.ShouldBe(3UL);
|
||||
mid.NumDeleted.ShouldBe(1);
|
||||
|
||||
fs.Truncate(2);
|
||||
var after = fs.State();
|
||||
after.Msgs.ShouldBe(1UL);
|
||||
after.FirstSeq.ShouldBe(1UL);
|
||||
after.LastSeq.ShouldBe(2UL);
|
||||
after.NumDeleted.ShouldBe(1);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // T:551
|
||||
public void FileStoreRemoveBlockWithStaleStreamState_ShouldSucceed()
|
||||
{
|
||||
var root = NewRoot();
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
JetStreamFileStore? fs = null;
|
||||
JetStreamFileStore? reopened = null;
|
||||
try
|
||||
{
|
||||
fs = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
|
||||
var blk1 = CreateBlock(root, 1, "blk1-data"u8.ToArray());
|
||||
var blk2 = CreateBlock(root, 2, "blk2-data"u8.ToArray());
|
||||
var blk3 = CreateBlock(root, 3, "blk3-data"u8.ToArray());
|
||||
|
||||
fs.RecoverMsgBlock(1).Index.ShouldBe(1u);
|
||||
fs.RecoverMsgBlock(2).Index.ShouldBe(2u);
|
||||
fs.RecoverMsgBlock(3).Index.ShouldBe(3u);
|
||||
fs.Stop();
|
||||
fs = null;
|
||||
|
||||
File.Delete(blk2);
|
||||
|
||||
reopened = JetStreamFileStore.NewFileStore(new FileStoreConfig { StoreDir = root }, DefaultStreamConfig());
|
||||
reopened.RecoverMsgBlock(1).Index.ShouldBe(1u);
|
||||
reopened.RecoverMsgBlock(3).Index.ShouldBe(3u);
|
||||
Should.Throw<FileNotFoundException>(() => reopened.RecoverMsgBlock(2));
|
||||
}
|
||||
finally
|
||||
{
|
||||
reopened?.Stop();
|
||||
fs?.Stop();
|
||||
if (Directory.Exists(root))
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact] // T:561
|
||||
public void FileStoreTombstonesSelectNextFirstCleanup_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
fs.StoreMsg("foo", null, "x"u8.ToArray(), 0);
|
||||
|
||||
for (ulong seq = 2; seq <= 100; seq++)
|
||||
fs.RemoveMsg(seq).Removed.ShouldBeTrue();
|
||||
|
||||
var before = fs.State();
|
||||
before.Msgs.ShouldBe(1UL);
|
||||
before.FirstSeq.ShouldBe(1UL);
|
||||
before.LastSeq.ShouldBe(100UL);
|
||||
|
||||
fs.RemoveMsg(1).Removed.ShouldBeTrue();
|
||||
var after = fs.State();
|
||||
after.Msgs.ShouldBe(0UL);
|
||||
after.FirstSeq.ShouldBe(101UL);
|
||||
after.LastSeq.ShouldBe(100UL);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // T:572
|
||||
public void FileStoreEmptyBlockContainsPriorTombstones_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
fs.StoreMsg("foo", null, "1"u8.ToArray(), 0).Seq.ShouldBe(1UL);
|
||||
fs.StoreMsg("foo", null, "2"u8.ToArray(), 0).Seq.ShouldBe(2UL);
|
||||
fs.StoreMsg("foo", null, "3"u8.ToArray(), 0).Seq.ShouldBe(3UL);
|
||||
|
||||
fs.RemoveMsg(2).Removed.ShouldBeTrue();
|
||||
fs.RemoveMsg(3).Removed.ShouldBeTrue();
|
||||
|
||||
var mid = fs.State();
|
||||
mid.Msgs.ShouldBe(1UL);
|
||||
mid.FirstSeq.ShouldBe(1UL);
|
||||
mid.LastSeq.ShouldBe(3UL);
|
||||
mid.Deleted.ShouldContain(2UL);
|
||||
mid.Deleted.ShouldContain(3UL);
|
||||
|
||||
fs.StoreMsg("foo", null, "4"u8.ToArray(), 0).Seq.ShouldBe(4UL);
|
||||
fs.RemoveMsg(1).Removed.ShouldBeTrue();
|
||||
|
||||
var after = fs.State();
|
||||
after.Msgs.ShouldBe(1UL);
|
||||
after.FirstSeq.ShouldBe(4UL);
|
||||
after.LastSeq.ShouldBe(4UL);
|
||||
(after.Deleted ?? Array.Empty<ulong>()).ShouldBeEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // T:586
|
||||
public void FileStoreLoadNextMsgSkipAhead_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var subj = i % 2 == 0 ? "a" : "c";
|
||||
fs.StoreMsg(subj, null, "x"u8.ToArray(), 0);
|
||||
}
|
||||
|
||||
fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL);
|
||||
|
||||
var (headSm, headSeq) = fs.LoadNextMsg("b", false, 0, null);
|
||||
headSm.ShouldNotBeNull();
|
||||
headSm!.Subject.ShouldBe("b");
|
||||
headSeq.ShouldBe(11UL);
|
||||
|
||||
var (interiorSm, interiorSeq) = fs.LoadNextMsg("b", false, 3, null);
|
||||
interiorSm.ShouldNotBeNull();
|
||||
interiorSm!.Subject.ShouldBe("b");
|
||||
interiorSeq.ShouldBe(11UL);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // T:587
|
||||
public void FileStoreLoadNextMsgMultiSkipAhead_ShouldSucceed()
|
||||
{
|
||||
WithStore((fs, _) =>
|
||||
{
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var subj = i % 2 == 0 ? "a" : "c";
|
||||
fs.StoreMsg(subj, null, "x"u8.ToArray(), 0);
|
||||
}
|
||||
|
||||
fs.StoreMsg("b", null, "target"u8.ToArray(), 0).Seq.ShouldBe(11UL);
|
||||
|
||||
var (headSm, headSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 11, null);
|
||||
headSm.ShouldNotBeNull();
|
||||
headSm!.Subject.ShouldBe("b");
|
||||
headSeq.ShouldBe(11UL);
|
||||
|
||||
var (interiorSm, interiorSeq) = fs.LoadNextMsgMulti(new[] { "b", "d" }, 3, null);
|
||||
interiorSm.ShouldNotBeNull();
|
||||
interiorSeq.ShouldBeGreaterThanOrEqualTo(3UL);
|
||||
});
|
||||
}
|
||||
|
||||
private static void RunWaveBScenario(string scenario)
|
||||
{
|
||||
switch (scenario)
|
||||
|
||||
Reference in New Issue
Block a user