Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce memory size constraint during recovery #276

Merged
merged 28 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
020dbba
Initial draft.
Apr 12, 2024
06a5b0a
Additiional changes.
Apr 12, 2024
04fa771
Fixed setting head address.
Apr 15, 2024
f59de06
Cleanup.
Apr 15, 2024
21e14cc
Updates to free previously allocated pages that could violate size co…
Apr 16, 2024
62eaf26
Moved to a batch read approach.
Apr 18, 2024
497a1e7
Added logger to test instance of GarnetServer.
Apr 18, 2024
4d5fbb7
Cleanup.
Apr 18, 2024
7c32494
Fix read page range up to snapshotendpage.
Apr 19, 2024
c6c2d73
Resolved PR comments.
Apr 20, 2024
d555a92
Added benchmark.
Apr 20, 2024
21e6a9f
Initial draft.
Apr 12, 2024
808f879
Additiional changes.
Apr 12, 2024
4881044
Fixed setting head address.
Apr 15, 2024
ae4a213
Cleanup.
Apr 15, 2024
48b986a
Updates to free previously allocated pages that could violate size co…
Apr 16, 2024
7fd86b8
Moved to a batch read approach.
Apr 18, 2024
ed14a56
Added logger to test instance of GarnetServer.
Apr 18, 2024
f3d88d6
Cleanup.
Apr 18, 2024
079f00d
Fix read page range up to snapshotendpage.
Apr 19, 2024
bc39938
Resolved PR comments.
Apr 20, 2024
88ceb33
Added benchmark.
Apr 20, 2024
b42d64f
Merge branch 'yrajas/recoverysizecheck' of https://github.com/yrajas/…
Apr 20, 2024
46968c9
Fixed formatting.
Apr 20, 2024
237c9c2
Merge branch 'main' into yrajas/recoverysizecheck
badrishc Apr 23, 2024
bb4f687
Merge branch 'main' into yrajas/recoverysizecheck
badrishc Apr 24, 2024
c35f629
Merge branch 'main' into yrajas/recoverysizecheck
badrishc Apr 24, 2024
9e9a557
Opt out of test logger by default
badrishc Apr 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions benchmark/BDN.benchmark/RecoveryBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using Embedded.perftest;
using Garnet.server;

namespace BDN.benchmark
{
public class CustomConfig : ManualConfig
{
public CustomConfig()
{
AddColumn(StatisticColumn.Mean);
AddColumn(StatisticColumn.StdDev);
AddColumn(StatisticColumn.Median);
AddColumn(StatisticColumn.P90);
AddColumn(StatisticColumn.P95);
}
}

[Config(typeof(CustomConfig))]
public class RecoveryBenchmark
{
[ParamsSource(nameof(CommandLineArgsProvider))]
public string LogDir { get; set; }

public IEnumerable<string> CommandLineArgsProvider()
{
// Return the command line arguments as an enumerable
return Environment.GetCommandLineArgs().Skip(1);
}

[Params("100m")]
public string MemorySize { get; set; }

EmbeddedRespServer server;

[IterationSetup]
public void Setup()
{
Console.WriteLine($"LogDir: {LogDir}");
server = new EmbeddedRespServer(new GarnetServerOptions()
{
EnableStorageTier = true,
LogDir = LogDir,
CheckpointDir = LogDir,
IndexSize = "1m",
DisableObjects = true,
MemorySize = MemorySize,
PageSize = "32k",
});
}

[IterationCleanup]
public void Cleanup()
{
server.Dispose();
}

[Benchmark]
public void Recover()
{
server.StoreWrapper.RecoverCheckpoint();
}
}
}
21 changes: 21 additions & 0 deletions libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,11 @@ public virtual void Dispose()
/// </summary>
public int AllocatedPageCount;

/// <summary>
/// Max number of pages that have been allocated at any point in time
/// </summary>
public int MaxAllocatedPageCount;

/// <summary>
/// Maximum possible number of empty pages in circular buffer
/// </summary>
Expand Down Expand Up @@ -1116,6 +1121,22 @@ public int EmptyPageCount
/// </summary>
internal abstract void DeleteFromMemory();

/// <summary>
/// Increments AllocatedPageCount
/// Update MaxAllocatedPageCount, if a higher number of pages have been allocated.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void IncrementAllocatedPageCount()
{
var newAllocatedPageCount = Interlocked.Increment(ref AllocatedPageCount);
var currMaxAllocatedPageCount = MaxAllocatedPageCount;
while (currMaxAllocatedPageCount < newAllocatedPageCount)
{
if (Interlocked.CompareExchange(ref MaxAllocatedPageCount, newAllocatedPageCount, currMaxAllocatedPageCount) == currMaxAllocatedPageCount)
return;
currMaxAllocatedPageCount = MaxAllocatedPageCount;
}
}

yrajas marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// Segment size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public override void Dispose()
/// <param name="index"></param>
internal override void AllocatePage(int index)
{
Interlocked.Increment(ref AllocatedPageCount);
IncrementAllocatedPageCount();

if (overflowPagePool.TryGet(out var item))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ internal override void AllocatePage(int index)

internal Record<Key, Value>[] AllocatePage()
{
Interlocked.Increment(ref AllocatedPageCount);
IncrementAllocatedPageCount();

if (overflowPagePool.TryGet(out var item))
return item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public override void Dispose()
/// <param name="index"></param>
internal override void AllocatePage(int index)
{
Interlocked.Increment(ref AllocatedPageCount);
IncrementAllocatedPageCount();

if (overflowPagePool.TryGet(out var item))
{
Expand Down
207 changes: 127 additions & 80 deletions libs/storage/Tsavorite/cs/src/core/Index/Recovery/Recovery.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public partial class TsavoriteKV<Key, Value> : TsavoriteBase, IDisposable
/// </summary>
public long EntryCount => GetEntryCount();

/// <summary>
/// Maximum number of memory pages ever allocated
/// </summary>
public long MaxAllocatedPageCount => hlog.MaxAllocatedPageCount;

/// <summary>
/// Size of index in #cache lines (64 bytes each)
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions playground/Embedded.perftest/EmbeddedRespServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public EmbeddedRespServer(GarnetServerOptions opts, ILoggerFactory loggerFactory
/// </summary>
public new void Dispose() => base.Dispose();

public StoreWrapper StoreWrapper => storeWrapper;

/// <summary>
/// Return a RESP session to this server
/// </summary>
Expand Down
114 changes: 114 additions & 0 deletions test/Garnet.test/RespAdminCommandsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,120 @@ public void SeSaveRecoverObjectTest()
Assert.AreEqual(ldata, returnedData);
}
}
[Test]
[TestCase(63, 15, 1)]
[TestCase(63, 1, 1)]
[TestCase(16, 16, 1)]
[TestCase(5, 64, 1)]
public void SeSaveRecoverMultipleObjectsTest(int memorySize, int recoveryMemorySize, int pageSize)
{
string sizeToString(int size) => size + "k";

server.Dispose();
server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, lowMemory: true, MemorySize: sizeToString(memorySize), PageSize: sizeToString(pageSize));
server.Start();

var ldata = new RedisValue[] { "a", "b", "c", "d" };
var ldataArr = ldata.Select(x => x).Reverse().ToArray();
using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)))
{
var db = redis.GetDatabase(0);
for (int i = 0; i < 3000; i++)
{
var key = $"SeSaveRecoverTestKey{i:0000}";
db.ListLeftPush(key, ldata);
var retval = db.ListRange(key);
Assert.AreEqual(ldataArr, retval, $"key {key}");
}

// Issue and wait for DB save
var server = redis.GetServer($"{TestUtils.Address}:{TestUtils.Port}");
server.Save(SaveType.BackgroundSave);
while (server.LastSave().Ticks == DateTimeOffset.FromUnixTimeSeconds(0).Ticks) Thread.Sleep(10);
}

server.Dispose(false);
server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, tryRecover: true, lowMemory: true, MemorySize: sizeToString(recoveryMemorySize), PageSize: sizeToString(pageSize));
server.Start();

Assert.LessOrEqual(server.Provider.StoreWrapper.objectStore.MaxAllocatedPageCount, (recoveryMemorySize / pageSize) + 1);
using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)))
{
var db = redis.GetDatabase(0);
for (int i = 0; i < 3000; i++)
{
var key = $"SeSaveRecoverTestKey{i:0000}";
var returnedData = db.ListRange(key);
Assert.AreEqual(ldataArr, returnedData, $"key {key}");
}
}
}

[Test]
[TestCase("63k", "15k")]
[TestCase("63k", "3k")]
[TestCase("63k", "1k")]
[TestCase("8k", "5k")]
[TestCase("16k", "16k")]
[TestCase("5k", "8k")]
[TestCase("5k", "64k")]
public void SeSaveRecoverMultipleKeysTest(string memorySize, string recoveryMemorySize)
{
bool disableObj = true;

server.Dispose();
server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, DisableObjects: disableObj, lowMemory: true, MemorySize: memorySize, PageSize: "1k", enableAOF: true);
server.Start();

using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)))
{
var db = redis.GetDatabase(0);
for (int i = 0; i < 1000; i++)
{
db.StringSet($"SeSaveRecoverTestKey{i:0000}", $"SeSaveRecoverTestValue");
}

for (int i = 0; i < 1000; i++)
{
var recoveredValue = db.StringGet($"SeSaveRecoverTestKey{i:0000}");
Assert.AreEqual("SeSaveRecoverTestValue", recoveredValue.ToString());
}

var inforesult = db.Execute("INFO");

// Issue and wait for DB save
var server = redis.GetServer($"{TestUtils.Address}:{TestUtils.Port}");
server.Save(SaveType.BackgroundSave);
while (server.LastSave().Ticks == DateTimeOffset.FromUnixTimeSeconds(0).Ticks) Thread.Sleep(10);

for (int i = 1000; i < 2000; i++)
{
db.StringSet($"SeSaveRecoverTestKey{i:0000}", $"SeSaveRecoverTestValue");
}

for (int i = 1000; i < 2000; i++)
{
var recoveredValue = db.StringGet($"SeSaveRecoverTestKey{i:0000}");
Assert.AreEqual("SeSaveRecoverTestValue", recoveredValue.ToString());
}

db.Execute("COMMITAOF");
}

server.Dispose(false);
server = TestUtils.CreateGarnetServer(TestUtils.MethodTestDir, DisableObjects: disableObj, tryRecover: true, lowMemory: true, MemorySize: recoveryMemorySize, PageSize: "1k", enableAOF: true);
server.Start();

using (var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(allowAdmin: true)))
{
var db = redis.GetDatabase(0);
for (int i = 0; i < 2000; i++)
{
var recoveredValue = db.StringGet($"SeSaveRecoverTestKey{i:0000}");
Assert.AreEqual("SeSaveRecoverTestValue", recoveredValue.ToString(), $"Key SeSaveRecoverTestKey{i:0000}");
}
}
}

[Test]
public void SeAofRecoverTest()
Expand Down
8 changes: 7 additions & 1 deletion test/Garnet.test/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,13 @@ public static GarnetServer CreateGarnetServer(
opts.PageSize = opts.ObjectStorePageSize = PageSize == default ? "512" : PageSize;
}

return new GarnetServer(opts);
var loggerFactory = LoggerFactory.Create(builder =>
badrishc marked this conversation as resolved.
Show resolved Hide resolved
{
builder.AddProvider(new NUnitLoggerProvider(TestContext.Progress, "GarnetServer", null, false, false, LogLevel.Trace));
builder.SetMinimumLevel(LogLevel.Trace);
});

return new GarnetServer(opts, loggerFactory);
}

/// <summary>
Expand Down