Skip to content

Commit 7335d93

Browse files
authored
[Breaking change] Simplify object store heap size configuration (#697)
**Breaks the previously available startup options --obj-memory and --obj-total-memory** This change simplifies configuring object store heap size for continuous monitoring. Instead of configuring total object store size, memory constraints for object store can now be configured using the following simple settings. obj-index - Size of Object store index obj-log-memory - Size of object store log that contains references to heap objects (Previously available as --obj-memory) obj-heap-memory - Size of object store heap Previously available --obj-total-memory option is now removed as object store memory can now be controlled using the above options in a fine grained manner. Total object store memory footprint = obj-index + obj-log-memory + obj-heap-memory. No changes to how the main store size is configured. * Simplified object store memory configuration * Added additional details on GC memory to INFO MEMORY command output. This also includes memory that is held by GC not yet released back to the system. * Bug fix to account for null IGarnetObject. * Updated documentation.
1 parent 28328d0 commit 7335d93

File tree

16 files changed

+99
-61
lines changed

16 files changed

+99
-61
lines changed

libs/host/Configuration/Options.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal sealed class Options
5252
public string SegmentSize { get; set; }
5353

5454
[MemorySizeValidation]
55-
[Option('i', "index", Required = false, HelpText = "Size of hash index in bytes (rounds down to power of 2)")]
55+
[Option('i', "index", Required = false, HelpText = "Start size of hash index in bytes (rounds down to power of 2)")]
5656
public string IndexSize { get; set; }
5757

5858
[MemorySizeValidation(false)]
@@ -64,11 +64,11 @@ internal sealed class Options
6464
public int MutablePercent { get; set; }
6565

6666
[MemorySizeValidation(false)]
67-
[Option("obj-total-memory", Required = false, HelpText = "Total object store log memory used including heap memory in bytes")]
68-
public string ObjectStoreTotalMemorySize { get; set; }
67+
[Option("obj-heap-memory", Required = false, HelpText = "Object store heap memory size in bytes (Sum of size taken up by all object instances in the heap)")]
68+
public string ObjectStoreHeapMemorySize { get; set; }
6969

7070
[MemorySizeValidation]
71-
[Option("obj-memory", Required = false, HelpText = "Object store log memory used in bytes excluding heap memory")]
71+
[Option("obj-log-memory", Required = false, HelpText = "Object store log memory used in bytes (Size of only the log with references to heap objects, excludes size of heap memory consumed by the objects themselves referred to from the log)")]
7272
public string ObjectStoreLogMemorySize { get; set; }
7373

7474
[MemorySizeValidation]
@@ -80,7 +80,7 @@ internal sealed class Options
8080
public string ObjectStoreSegmentSize { get; set; }
8181

8282
[MemorySizeValidation]
83-
[Option("obj-index", Required = false, HelpText = "Size of object store hash index in bytes (rounds down to power of 2)")]
83+
[Option("obj-index", Required = false, HelpText = "Start size of object store hash index in bytes (rounds down to power of 2)")]
8484
public string ObjectStoreIndexSize { get; set; }
8585

8686
[MemorySizeValidation(false)]
@@ -575,7 +575,7 @@ public GarnetServerOptions GetServerOptions(ILogger logger = null)
575575
IndexSize = IndexSize,
576576
IndexMaxSize = IndexMaxSize,
577577
MutablePercent = MutablePercent,
578-
ObjectStoreTotalMemorySize = ObjectStoreTotalMemorySize,
578+
ObjectStoreHeapMemorySize = ObjectStoreHeapMemorySize,
579579
ObjectStoreLogMemorySize = ObjectStoreLogMemorySize,
580580
ObjectStorePageSize = ObjectStorePageSize,
581581
ObjectStoreSegmentSize = ObjectStoreSegmentSize,

libs/host/GarnetServer.cs

+11-3
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ private void InitializeServer()
198198

199199
logger?.LogTrace("TLS is {tlsEnabled}", opts.TlsOptions == null ? "disabled" : "enabled");
200200

201+
if (logger != null)
202+
{
203+
var configMemoryLimit = (store.IndexSize * 64) + store.Log.MaxMemorySizeBytes + (store.ReadCache?.MaxMemorySizeBytes ?? 0) + (appendOnlyFile?.MaxMemorySizeBytes ?? 0);
204+
if (objectStore != null)
205+
configMemoryLimit += objectStore.IndexSize * 64 + objectStore.Log.MaxMemorySizeBytes + (objectStore.ReadCache?.MaxMemorySizeBytes ?? 0) + (objectStoreSizeTracker?.TargetSize ?? 0);
206+
logger.LogInformation("Total configured memory limit: {configMemoryLimit}", configMemoryLimit);
207+
}
208+
201209
// Create Garnet TCP server if none was provided.
202210
this.server ??= new GarnetServerTcp(opts.Address, opts.Port, 0, opts.TlsOptions, opts.NetworkSendThrottleMax, logger);
203211

@@ -274,7 +282,7 @@ private void CreateObjectStore(IClusterFactory clusterFactory, CustomCommandMana
274282
if (!opts.DisableObjects)
275283
{
276284
objKvSettings = opts.GetObjectStoreSettings(this.loggerFactory?.CreateLogger("TsavoriteKV [obj]"),
277-
out var objTotalMemorySize);
285+
out var objHeapMemorySize);
278286

279287
// Run checkpoint on its own thread to control p99
280288
objKvSettings.ThrottleCheckpointFlushDelayMs = opts.CheckpointThrottleFlushDelayMs;
@@ -296,8 +304,8 @@ private void CreateObjectStore(IClusterFactory clusterFactory, CustomCommandMana
296304
() => new GarnetObjectSerializer(customCommandManager))
297305
, (allocatorSettings, storeFunctions) => new(allocatorSettings, storeFunctions));
298306

299-
if (objTotalMemorySize > 0)
300-
objectStoreSizeTracker = new CacheSizeTracker(objectStore, objKvSettings, objTotalMemorySize,
307+
if (objHeapMemorySize > 0)
308+
objectStoreSizeTracker = new CacheSizeTracker(objectStore, objKvSettings, objHeapMemorySize,
301309
this.loggerFactory);
302310
}
303311
}

libs/host/defaults.conf

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
/* Size of each log segment in bytes on disk (rounds down to power of 2) */
1919
"SegmentSize" : "1g",
2020

21-
/* Size of hash index in bytes (rounds down to power of 2) */
21+
/* Start size of hash index in bytes (rounds down to power of 2) */
2222
"IndexSize" : "128m",
2323

2424
/* Max size of hash index in bytes (rounds down to power of 2) */
@@ -27,10 +27,10 @@
2727
/* Percentage of log memory that is kept mutable */
2828
"MutablePercent" : 90,
2929

30-
/* Total object store log memory used including heap memory in bytes */
31-
"ObjectStoreTotalMemorySize" : "",
30+
/* Object store heap memory size in bytes (Sum of size taken up by all object instances in the heap) */
31+
"ObjectStoreHeapMemorySize" : "",
3232

33-
/* Object store log memory used in bytes excluding heap memory */
33+
/* Object store log memory used in bytes (Size of only the log with references to heap objects, excludes size of heap memory consumed by the objects themselves referred to from the log) */
3434
"ObjectStoreLogMemorySize" : "32m",
3535

3636
/* Size of each object store page in bytes (rounds down to power of 2) */
@@ -39,7 +39,7 @@
3939
/* Size of each object store log segment in bytes on disk (rounds down to power of 2) */
4040
"ObjectStoreSegmentSize" : "32m",
4141

42-
/* Size of object store hash index in bytes (rounds down to power of 2) */
42+
/* Start size of object store hash index in bytes (rounds down to power of 2) */
4343
"ObjectStoreIndexSize" : "16m",
4444

4545
/* Max size of object store hash index in bytes (rounds down to power of 2) */

libs/server/Metrics/Info/GarnetInfoMetrics.cs

+24-8
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,28 @@ private void PopulateMemoryInfo(StoreWrapper storeWrapper)
6969
var total_main_store_size = main_store_index_size + main_store_log_memory_size + main_store_read_cache_size;
7070

7171
var object_store_index_size = -1L;
72-
var object_store_log_memory_references_size = -1L;
73-
var object_store_read_cache_size = -1L;
72+
var object_store_log_memory_size = -1L;
73+
var object_store_read_cache_log_memory_size = -1L;
74+
var object_store_heap_memory_size = -1L;
75+
var object_store_read_cache_heap_memory_size = -1L;
7476
var total_object_store_size = -1L;
7577
var disableObj = storeWrapper.serverOptions.DisableObjects;
7678

79+
var aof_log_memory_size = storeWrapper.appendOnlyFile?.MemorySizeBytes ?? -1;
80+
7781
if (!disableObj)
7882
{
7983
object_store_index_size = storeWrapper.objectStore.IndexSize * 64;
80-
object_store_log_memory_references_size = storeWrapper.objectStore.Log.MemorySizeBytes;
81-
object_store_read_cache_size = (storeWrapper.objectStore.ReadCache != null ? storeWrapper.objectStore.ReadCache.MemorySizeBytes : 0);
82-
total_object_store_size = object_store_index_size + object_store_log_memory_references_size + object_store_read_cache_size;
84+
object_store_log_memory_size = storeWrapper.objectStore.Log.MemorySizeBytes;
85+
object_store_read_cache_log_memory_size = storeWrapper.objectStore.ReadCache?.MemorySizeBytes ?? 0;
86+
object_store_heap_memory_size = storeWrapper.objectStoreSizeTracker?.mainLogTracker.LogHeapSizeBytes ?? 0;
87+
object_store_read_cache_heap_memory_size = storeWrapper.objectStoreSizeTracker?.readCacheTracker?.LogHeapSizeBytes ?? 0;
88+
total_object_store_size = object_store_index_size + object_store_log_memory_size + object_store_read_cache_log_memory_size + object_store_heap_memory_size + object_store_read_cache_heap_memory_size;
8389
}
8490

91+
var gcMemoryInfo = GC.GetGCMemoryInfo();
92+
var gcAvailableMemory = gcMemoryInfo.TotalCommittedBytes - gcMemoryInfo.HeapSizeBytes;
93+
8594
memoryInfo =
8695
[
8796
new("system_page_size", Environment.SystemPageSize.ToString()),
@@ -105,14 +114,21 @@ private void PopulateMemoryInfo(StoreWrapper storeWrapper)
105114
new("proc_physical_memory_size(MB)", SystemMetrics.GetPhysicalMemoryUsage(1 << 20).ToString()),
106115
new("proc_peak_physical_memory_size", SystemMetrics.GetPeakPhysicalMemoryUsage().ToString()),
107116
new("proc_peak_physical_memory_size(MB)", SystemMetrics.GetPeakPhysicalMemoryUsage(1 << 20).ToString()),
117+
new("gc_committed_bytes", gcMemoryInfo.TotalCommittedBytes.ToString()),
118+
new("gc_heap_bytes", gcMemoryInfo.HeapSizeBytes.ToString()),
119+
new("gc_managed_memory_bytes_excluding_heap", gcAvailableMemory.ToString()),
120+
new("gc_fragmented_bytes", gcMemoryInfo.FragmentedBytes.ToString()),
108121
new("main_store_index_size", main_store_index_size.ToString()),
109122
new("main_store_log_memory_size", main_store_log_memory_size.ToString()),
110123
new("main_store_read_cache_size", main_store_read_cache_size.ToString()),
111124
new("total_main_store_size", total_main_store_size.ToString()),
112125
new("object_store_index_size", object_store_index_size.ToString()),
113-
new("object_store_log_memory_references_size", object_store_log_memory_references_size.ToString()),
114-
new("object_store_read_cache_size", object_store_read_cache_size.ToString()),
115-
new("total_object_store_size", total_object_store_size.ToString())
126+
new("object_store_log_memory_size", object_store_log_memory_size.ToString()),
127+
new("object_store_read_cache_log_memory_size", object_store_read_cache_log_memory_size.ToString()),
128+
new("object_store_heap_memory_size", object_store_heap_memory_size.ToString()),
129+
new("object_store_read_cache_heap_memory_size", object_store_read_cache_heap_memory_size.ToString()),
130+
new("total_object_store_size", total_object_store_size.ToString()),
131+
new("aof_memory_size", aof_log_memory_size.ToString())
116132
];
117133
}
118134

libs/server/Servers/GarnetServerOptions.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ public class GarnetServerOptions : ServerOptions
2222
public bool DisableObjects = false;
2323

2424
/// <summary>
25-
/// Total memory size limit of object store including heap memory of objects.
25+
/// Heap memory size limit of object store.
2626
/// </summary>
27-
public string ObjectStoreTotalMemorySize = "";
27+
public string ObjectStoreHeapMemorySize = "";
2828

2929
/// <summary>
3030
/// Object store log memory used in bytes excluding heap memory.
@@ -528,7 +528,7 @@ public static int MemorySizeBits(string memorySize, string storePageSize, out in
528528
/// <summary>
529529
/// Get KVSettings for the object store log
530530
/// </summary>
531-
public KVSettings<byte[], IGarnetObject> GetObjectStoreSettings(ILogger logger, out long objTotalMemorySize)
531+
public KVSettings<byte[], IGarnetObject> GetObjectStoreSettings(ILogger logger, out long objHeapMemorySize)
532532
{
533533
if (ObjectStoreMutablePercent is < 10 or > 95)
534534
throw new Exception("ObjectStoreMutablePercent must be between 10 and 95");
@@ -576,8 +576,8 @@ public KVSettings<byte[], IGarnetObject> GetObjectStoreSettings(ILogger logger,
576576
}
577577
logger?.LogInformation("[Object Store] Using log mutable percentage of {ObjectStoreMutablePercent}%", ObjectStoreMutablePercent);
578578

579-
objTotalMemorySize = ParseSize(ObjectStoreTotalMemorySize);
580-
logger?.LogInformation("[Object Store] Total memory size including heap objects is {totalMemorySize}", (objTotalMemorySize > 0 ? PrettySize(objTotalMemorySize) : "unlimited"));
579+
objHeapMemorySize = ParseSize(ObjectStoreHeapMemorySize);
580+
logger?.LogInformation("[Object Store] Total memory size including heap objects is {totalMemorySize}", (objHeapMemorySize > 0 ? PrettySize(objHeapMemorySize) : "unlimited"));
581581

582582
if (EnableStorageTier)
583583
{

libs/server/Storage/Functions/ObjectStore/RMWMethods.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ public bool PostCopyUpdater(ref byte[] key, ref ObjectInput input, ref IGarnetOb
171171
{
172172
// We're performing the object update here (and not in CopyUpdater) so that we are guaranteed that
173173
// the record was CASed into the hash chain before it gets modified
174+
var oldValueSize = oldValue.Size;
174175
oldValue.CopyUpdate(ref oldValue, ref value, rmwInfo.RecordInfo.IsInNewVersion);
175176

176177
functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash);
@@ -220,7 +221,9 @@ public bool PostCopyUpdater(ref byte[] key, ref ObjectInput input, ref IGarnetOb
220221
}
221222
}
222223

223-
functionsState.objectStoreSizeTracker?.AddTrackedSize(MemoryUtils.CalculateKeyValueSize(key, value));
224+
// If oldValue has been set to null, subtract it's size from the tracked heap size
225+
var sizeAdjustment = oldValue == null ? value.Size - oldValueSize : value.Size;
226+
functionsState.objectStoreSizeTracker?.AddTrackedSize(sizeAdjustment);
224227

225228
if (functionsState.appendOnlyFile != null)
226229
WriteLogRMW(ref key, ref input, rmwInfo.Version, rmwInfo.SessionID);

libs/server/Storage/SizeTracker/CacheSizeTracker.cs

+5-11
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ public class CacheSizeTracker
2222
{
2323
internal readonly LogSizeTracker<byte[], IGarnetObject, ObjectStoreFunctions, ObjectStoreAllocator, LogSizeCalculator> mainLogTracker;
2424
internal readonly LogSizeTracker<byte[], IGarnetObject, ObjectStoreFunctions, ObjectStoreAllocator, LogSizeCalculator> readCacheTracker;
25-
internal long targetSize;
25+
public long TargetSize;
2626

2727
private const int deltaFraction = 10; // 10% of target size
2828
private TsavoriteKV<byte[], IGarnetObject, ObjectStoreFunctions, ObjectStoreAllocator> store;
2929

30-
internal long IndexSizeBytes => store.IndexSize * 64 + store.OverflowBucketCount * 64;
31-
3230
internal bool Stopped => mainLogTracker.Stopped && (readCacheTracker == null || readCacheTracker.Stopped);
3331

3432
/// <summary>Helps calculate size of a record including heap memory in Object store.</summary>
@@ -44,7 +42,7 @@ public readonly long CalculateRecordSize(RecordInfo recordInfo, byte[] key, IGar
4442
{
4543
long size = Utility.RoundUp(key.Length, IntPtr.Size) + MemoryUtils.ByteArrayOverhead;
4644

47-
if (!recordInfo.Tombstone) // ignore deleted values being evicted (they are accounted for by ConcurrentDeleter)
45+
if (!recordInfo.Tombstone && value != null) // ignore deleted values being evicted (they are accounted for by ConcurrentDeleter)
4846
size += value.Size;
4947

5048
return size;
@@ -64,6 +62,7 @@ public CacheSizeTracker(TsavoriteKV<byte[], IGarnetObject, ObjectStoreFunctions,
6462
Debug.Assert(targetSize > 0);
6563

6664
this.store = store;
65+
this.TargetSize = targetSize;
6766
var logSizeCalculator = new LogSizeCalculator();
6867

6968
var (mainLogTargetSizeBytes, readCacheTargetSizeBytes) = CalculateLogTargetSizeBytes(targetSize);
@@ -96,13 +95,8 @@ public void Start(CancellationToken token)
9695
/// <param name="newTargetSize">Target size</param>
9796
public (long mainLogSizeBytes, long readCacheSizeBytes) CalculateLogTargetSizeBytes(long newTargetSize)
9897
{
99-
long residual = newTargetSize - IndexSizeBytes;
100-
101-
if (residual <= 0)
102-
throw new TsavoriteException($"Target size {newTargetSize} must be larger than index size {IndexSizeBytes}");
103-
104-
var mainLogSizeBytes = this.store.ReadCache == null ? residual : residual / 2;
105-
var readCacheSizeBytes = this.store.ReadCache == null ? 0 : residual / 2;
98+
var mainLogSizeBytes = this.store.ReadCache == null ? newTargetSize : newTargetSize / 2;
99+
var readCacheSizeBytes = this.store.ReadCache == null ? 0 : newTargetSize / 2;
106100

107101
return (mainLogSizeBytes, readCacheSizeBytes);
108102
}

libs/storage/Tsavorite/cs/src/core/Allocator/AllocatorBase.cs

+3
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,9 @@ protected void Initialize(long firstValidAddress)
714714
/// <summary>Minimum number of empty pages in circular buffer to be maintained to account for non-power-of-two size</summary>
715715
public int MinEmptyPageCount;
716716

717+
/// <summary>Maximum memory size in bytes</summary>
718+
public long MaxMemorySizeBytes => (BufferSize - MinEmptyPageCount) * (long)PageSize;
719+
717720
/// <summary>How many pages do we leave empty in the in-memory buffer (between 0 and BufferSize-1)</summary>
718721
public int EmptyPageCount
719722
{

libs/storage/Tsavorite/cs/src/core/Index/Common/LogSizeTracker.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,11 @@ async Task ResizerTask(CancellationToken token)
197197
/// </summary>
198198
private void ResizeIfNeeded(CancellationToken token)
199199
{
200-
// Include memory size from the log (logAccessor.MemorySizeBytes) + heap size (logSize.Total) to check utilization
201-
if (logSize.Total + logAccessor.MemorySizeBytes > highTargetSize)
200+
// Monitor the heap size
201+
if (logSize.Total > highTargetSize)
202202
{
203-
logger?.LogDebug("Heap size {totalLogSize} + log {MemorySizeBytes} > target {highTargetSize}. Alloc: {AllocatedPageCount} EPC: {EmptyPageCount}", logSize.Total, logAccessor.MemorySizeBytes, highTargetSize, logAccessor.AllocatedPageCount, logAccessor.EmptyPageCount);
204-
while (logSize.Total + logAccessor.MemorySizeBytes > highTargetSize &&
203+
logger?.LogDebug("Heap size {totalLogSize} > target {highTargetSize}. Alloc: {AllocatedPageCount} EPC: {EmptyPageCount}", logSize.Total, highTargetSize, logAccessor.AllocatedPageCount, logAccessor.EmptyPageCount);
204+
while (logSize.Total > highTargetSize &&
205205
logAccessor.EmptyPageCount < logAccessor.MaxEmptyPageCount)
206206
{
207207
token.ThrowIfCancellationRequested();
@@ -216,10 +216,10 @@ private void ResizeIfNeeded(CancellationToken token)
216216
logger?.LogDebug("Increasing empty page count to {EmptyPageCount}", logAccessor.EmptyPageCount);
217217
}
218218
}
219-
else if (logSize.Total + logAccessor.MemorySizeBytes < lowTargetSize)
219+
else if (logSize.Total < lowTargetSize)
220220
{
221-
logger?.LogDebug("Heap size {totalLogSize} + log {MemorySizeBytes} < target {lowTargetSize}. Alloc: {AllocatedPageCount} EPC: {EmptyPageCount}", logSize.Total, logAccessor.MemorySizeBytes, lowTargetSize, logAccessor.AllocatedPageCount, logAccessor.EmptyPageCount);
222-
while (logSize.Total + logAccessor.MemorySizeBytes < lowTargetSize &&
221+
logger?.LogDebug("Heap size {totalLogSize} < target {lowTargetSize}. Alloc: {AllocatedPageCount} EPC: {EmptyPageCount}", logSize.Total, lowTargetSize, logAccessor.AllocatedPageCount, logAccessor.EmptyPageCount);
222+
while (logSize.Total < lowTargetSize &&
223223
logAccessor.EmptyPageCount > logAccessor.MinEmptyPageCount)
224224
{
225225
token.ThrowIfCancellationRequested();

libs/storage/Tsavorite/cs/src/core/Index/Tsavorite/LogAccessor.cs

+5
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ public void SetEmptyPageCount(int pageCount, bool wait = false)
107107
/// </summary>
108108
public long MemorySizeBytes => ((long)(allocatorBase.AllocatedPageCount + allocator.OverflowPageCount)) << allocatorBase.LogPageSizeBits;
109109

110+
/// <summary>
111+
/// Maximum memory size in bytes
112+
/// </summary>
113+
public long MaxMemorySizeBytes => allocatorBase.MaxMemorySizeBytes;
114+
110115
/// <summary>
111116
/// Number of pages allocated
112117
/// </summary>

0 commit comments

Comments
 (0)