116 lines
3.4 KiB
C#
116 lines
3.4 KiB
C#
using ZB.MOM.WW.CBDD.Bson;
|
|
using ZB.MOM.WW.CBDD.Core.Storage;
|
|
using ZB.MOM.WW.CBDD.Shared;
|
|
|
|
namespace ZB.MOM.WW.CBDD.Tests;
|
|
|
|
public class CompactionWalCoordinationTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies offline compaction checkpoints and leaves the WAL empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OfflineCompact_ShouldCheckpointAndLeaveWalEmpty()
|
|
{
|
|
var dbPath = NewDbPath();
|
|
var markerPath = $"{dbPath}.compact.state";
|
|
|
|
try
|
|
{
|
|
using var db = new TestDbContext(dbPath);
|
|
for (var i = 0; i < 80; i++)
|
|
{
|
|
db.Users.Insert(new User { Name = $"wal-compact-{i:D3}", Age = i });
|
|
}
|
|
|
|
db.SaveChanges();
|
|
db.Storage.GetWalSize().ShouldBeGreaterThan(0);
|
|
|
|
var stats = db.Compact(new CompactionOptions
|
|
{
|
|
EnableTailTruncation = true,
|
|
NormalizeFreeList = true,
|
|
DefragmentSlottedPages = true
|
|
});
|
|
|
|
stats.OnlineMode.ShouldBeFalse();
|
|
db.Storage.GetWalSize().ShouldBe(0);
|
|
File.Exists(markerPath).ShouldBeFalse();
|
|
|
|
db.Users.Count().ShouldBe(80);
|
|
}
|
|
finally
|
|
{
|
|
CleanupFiles(dbPath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies compaction after WAL recovery preserves durable data.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Compact_AfterWalRecovery_ShouldKeepDataDurable()
|
|
{
|
|
var dbPath = NewDbPath();
|
|
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
|
var expected = new List<(ObjectId Id, string Name)>();
|
|
|
|
try
|
|
{
|
|
using (var writer = new TestDbContext(dbPath))
|
|
{
|
|
for (var i = 0; i < 48; i++)
|
|
{
|
|
var name = $"recoverable-{i:D3}";
|
|
var id = writer.Users.Insert(new User { Name = name, Age = i % 13 });
|
|
expected.Add((id, name));
|
|
}
|
|
|
|
writer.SaveChanges();
|
|
writer.Storage.GetWalSize().ShouldBeGreaterThan(0);
|
|
}
|
|
|
|
new FileInfo(walPath).Length.ShouldBeGreaterThan(0);
|
|
|
|
using (var recovered = new TestDbContext(dbPath))
|
|
{
|
|
recovered.Users.Count().ShouldBe(expected.Count);
|
|
|
|
foreach (var item in expected)
|
|
{
|
|
recovered.Users.FindById(item.Id)!.Name.ShouldBe(item.Name);
|
|
}
|
|
|
|
recovered.SaveChanges();
|
|
recovered.Compact();
|
|
recovered.Storage.GetWalSize().ShouldBe(0);
|
|
}
|
|
|
|
using (var verify = new TestDbContext(dbPath))
|
|
{
|
|
verify.Users.Count().ShouldBe(expected.Count);
|
|
foreach (var item in expected)
|
|
{
|
|
verify.Users.FindById(item.Id)!.Name.ShouldBe(item.Name);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
CleanupFiles(dbPath);
|
|
}
|
|
}
|
|
|
|
private static string NewDbPath()
|
|
=> Path.Combine(Path.GetTempPath(), $"compaction_wal_{Guid.NewGuid():N}.db");
|
|
|
|
private static void CleanupFiles(string dbPath)
|
|
{
|
|
var walPath = Path.ChangeExtension(dbPath, ".wal");
|
|
var markerPath = $"{dbPath}.compact.state";
|
|
if (File.Exists(dbPath)) File.Delete(dbPath);
|
|
if (File.Exists(walPath)) File.Delete(walPath);
|
|
if (File.Exists(markerPath)) File.Delete(markerPath);
|
|
}
|
|
}
|