Skip to content

Commit 9e7454c

Browse files
badrishcCopilot
andcommitted
Replace SubscribeEvictions with IRecordTriggers.OnEvict for heap-size tracking
Garnet's `CacheSizeTracker` used `LogAccessor.SubscribeEvictions` to drive a `LogSizeTracker.OnNext` observer that, on every page eviction, allocated a scan iterator and walked the page to sum `MemoryUtils.CalculateHeapMemorySize` over each non-null / non-closed record. This is heavyweight for a hot path (buffer-pool allocation, iterator bookkeeping, epoch resume/suspend, and a virtual dispatch per page). This PR migrates the per-page heap-size decrement onto the per-record `IRecordTriggers.OnEvict` hook introduced in #1695, which the object allocator already walks during `EvictRecordsInRange`. That collapses the "scan iterator + observer + sum" path into a single per-record callback directly on the record we would have visited anyway. ### Tsavorite changes - Add `EvictionSource { MainLog, ReadCache }` and thread it through `IRecordTriggers.OnEvict`, `IStoreFunctions.OnEvict`, and `IAllocator.EvictRecordsInRange`. Garnet uses this to route decrements to the correct counter (`AddHeapSize` vs `AddReadCacheHeapSize`). - Change `IRecordTriggers.CallOnEvict` / `IStoreFunctions.CallOnEvict` from a property to `CallOnEvict(EvictionSource)` so the allocator can skip the per-record eviction walk entirely when the application has no work on that side (e.g. a read-cache-only budget should not force walks of the main-log allocator). - Add `AllocatorSettings.IsReadCache` and `AllocatorBase.IsReadCache`, set to `true` by `Tsavorite.cs` when constructing the read-cache allocator. This is the cleanest way to distinguish the two allocators at `OnPagesClosedWorker` time without relying on the `evictCallback` sentinel, and is used to pass the correct `EvictionSource` to `CallOnEvict`. - `AllocatorBase.EvictPageForRecovery` routes through the per-record `EvictRecordsInRange` when `storeFunctions.CallOnEvict(source)` is set. The legacy `MemoryPageScan(observer)` path is preserved as a fallback for consumers that still use `SubscribeEvictions`. - Collapse `AllocatorBase`'s constructor to accept `AllocatorSettings` directly instead of unpacking individual fields at each concrete allocator (`ObjectAllocatorImpl`, `SpanByteAllocatorImpl`, `TsavoriteLogAllocatorImpl`). - Tighten `ObjectAllocatorImpl.EvictRecordsInRange` to match `ObjectScanIterator`'s single-page invariant: clip `stopAddress` to the start page, bail on `offset == 0`, and document that both callers (`OnPagesClosedWorker`, `EvictPageForRecovery`) hand single-page ranges. ### Garnet changes - `GarnetRecordTriggers.CallOnEvict(EvictionSource)` returns true only for the sides that are actually configured (`mainLogTracker` and/or `readCacheTracker` non-null), avoiding pointless per-record walks on the untracked allocator. - `GarnetRecordTriggers.OnEvict(ref LogRecord, EvictionSource)` computes `MemoryUtils.CalculateHeapMemorySize(in logRecord)` and dispatches to `AddHeapSize(-size)` or `AddReadCacheHeapSize(-size)` based on the source. This goes through the standard asserting path — the counter must never undershoot zero. - `CacheSizeTracker.Initialize` replaces the two `SubscribeEvictions` calls with `SetLogSizeTracker` calls so the fast-path size tracking during `TryCopyToTail`, `TryCopyToReadCache`, and object-page growth (`UpdateSize`, `IncrementSize`) continues to work unchanged. ### MainStore heap-tracking fix (root cause of negative counter) Routing the eviction decrement through the asserting `AddHeapSize` path surfaced a pre-existing gap: `MainStore` record-creation paths never emitted a positive heap-size bump when a record's key or value spilled to overflow (large inline field backed by a heap-allocated byte array). The legacy observer path masked this silently because `OnNext` did a raw decrement with no assertion — the counter quietly went negative on every HLL sparse→dense transition (≈-12 KB per key) and accumulated drift elsewhere. `MainStore` now emits balanced heap accounting at all create/update/delete entry points that go through a typed function callback rather than the shared `SessionFunctionsUtils` writer (which already tracks deltas): - `RMWMethods.PostInitialUpdater`: `+logRecord.CalculateHeapMemorySize()` for the freshly-created record. - `RMWMethods.PostCopyUpdater`: `+dstLogRecord.CalculateHeapMemorySize()` for the new record only. The source is not subtracted — the `TSourceLogRecord` may be an in-memory main-log record (whose heap is tracked in `mainLogTracker` and will leak upward by a bounded sealed- source amount, parity with `ObjectStore`'s `ClearSource=false` branch and the legacy observer path), a read-cache record (heap lives in `readCacheTracker`, unrelated to this write), or a pending-IO `DiskLogRecord` (heap never counted in any tracker). Subtracting unconditionally would undercount in the last two cases and drive the counter negative. - `RMWMethods.InPlaceUpdater`: `GetValueHeapMemorySize()` pre/post delta on `Succeeded`, so value-heap changes (e.g. APPEND, SETRANGE, or any path that triggers `ReallocateValueOverflow` / `ConvertInlineToOverflow` / `ConvertOverflowToInline`) are tracked. - `UpsertMethods.PostInitialWriter` (all three overloads): `+logRecord.CalculateHeapMemorySize()` for SET-style inserts that create a new record through the Upsert path. - `DeleteMethods.InPlaceDeleter`: `-logRecord.CalculateHeapMemorySize()` before the wrapper sets Tombstone. After tombstone is set, `CalculateHeapMemorySize` short- circuits to zero and `EvictRecordsInRange` skips the record, so without this decrement the creation-side increments would leak for every DEL of an overflow record. The `HyperLogLogPFADD_LTM` suite is the regression that first surfaced this — HLL sparse→dense goes through CopyUpdater and allocates ≈12 KB of overflow for the dense representation. With the fix the counter stays balanced and the assertion `heapSize.Total >= 0` holds throughout. ### Parity Behavior at every record state encountered during page eviction is bit-for-bit identical to the prior `SubscribeEvictions` path: | Record state | Old path (iterator) | New path (per-record) | Delta | | ------------------------------------------------ | ---------------------------------- | ---------------------------- | ----- | | Valid, !Sealed, !Tombstone, !IsNull | yielded → -CalculateHeapMemorySize | OnEvict → -Calculate... | same | | `IsNull` | skipped by iterator | skipped by filter | 0 | | `SkipOnScan` (Invalid or Sealed) | skipped by iterator | skipped by filter | 0 | | `Tombstone` (post-delete, already ValueIsInline) | yielded → 0 via `!Info.Tombstone` | skipped by filter → 0 | 0 | ### Hot-path cost Fast paths (SET / GET / INCR on inline-sized values) pay only a few aggressive-inlined inline-bit checks; `GetValueHeapMemorySize` on an inline record early-returns 0 before any tracker call, and a `heap != 0` guard skips the `AddHeapSize` dispatch entirely. Tracker work occurs only when a record genuinely created, resized, or freed an overflow/object allocation — which was already a heavyweight event. ### Testing All on net10.0 Debug: - `HyperLogLogTests` (incl. `HyperLogLogPFADD_LTM{32,4096}`, `HyperLogLogTestPFMERGE_LTM_*`) ✓ - `CacheSizeTrackerTests` ✓ - `RespListTests` (incl. `ListPushPopStressTest` ×10), `RespHashTests`, `RespSetTests`, `RespSortedSetTests`, `RespEtagTests`, `RespBitmapTests`, `RespTests.Set*`, `RespTests.Del*`: 517/517 ✓ - Clean build on both `Garnet.slnx` and `Tsavorite.slnx` (0 warnings, 0 errors), `dotnet format --verify-no-changes` clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0740ff9 commit 9e7454c

19 files changed

+183
-46
lines changed

libs/server/Storage/Functions/GarnetRecordTriggers.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ namespace Garnet.server
77
{
88
/// <summary>
99
/// Record lifecycle triggers for Garnet's unified store. Handles per-record cleanup
10-
/// on delete via <see cref="IRecordTriggers.OnDispose"/>.
10+
/// on delete via <see cref="IRecordTriggers.OnDispose"/> and per-record heap-size
11+
/// accounting on page eviction via <see cref="IRecordTriggers.OnEvict"/>.
1112
/// </summary>
1213
public readonly struct GarnetRecordTriggers : IRecordTriggers
1314
{
1415
/// <summary>
15-
/// Cache size tracker for heap size accounting on delete.
16+
/// Cache size tracker for heap size accounting on delete and eviction.
1617
/// Created before the store and initialized after via <see cref="CacheSizeTracker.Initialize"/>.
1718
/// </summary>
1819
internal readonly CacheSizeTracker cacheSizeTracker;
@@ -29,7 +30,19 @@ public GarnetRecordTriggers(CacheSizeTracker cacheSizeTracker)
2930
public bool CallOnFlush => false;
3031

3132
/// <inheritdoc/>
32-
public bool CallOnEvict => false;
33+
// Drives per-record heap-size decrement on page eviction. Mirrors the work the
34+
// legacy SubscribeEvictions → LogSizeTracker.OnNext observer path used to perform
35+
// (see CacheSizeTracker.Initialize for the wiring change). Gated per source so
36+
// that enabling only a main-log or only a read-cache memory budget does not
37+
// force a per-record eviction walk on the other allocator.
38+
public bool CallOnEvict(EvictionSource source)
39+
{
40+
if (cacheSizeTracker is null)
41+
return false;
42+
return source == EvictionSource.ReadCache
43+
? cacheSizeTracker.readCacheTracker is not null
44+
: cacheSizeTracker.mainLogTracker is not null;
45+
}
3346

3447
/// <inheritdoc/>
3548
public bool CallOnDiskRead => false;
@@ -49,5 +62,26 @@ public void OnDispose(ref LogRecord logRecord, DisposeReason reason)
4962
cacheSizeTracker?.AddHeapSize(-logRecord.ValueObject.HeapMemorySize);
5063
}
5164
}
65+
66+
/// <inheritdoc/>
67+
public void OnEvict(ref LogRecord logRecord, EvictionSource source)
68+
{
69+
if (cacheSizeTracker is null)
70+
return;
71+
72+
// Decrement heap size by this record's heap contribution. Uses the same sizing
73+
// helper that LogSizeTracker.OnNext used to sum over an evicted iterator. Routes
74+
// through the standard AddHeapSize/AddReadCacheHeapSize path so the assertion
75+
// guarding against negative totals remains in force; creation sites on the main
76+
// log (RMW PostInitialUpdater/PostCopyUpdater and in-place grow/shrink) must emit
77+
// a matching positive bump so the account stays balanced.
78+
var size = MemoryUtils.CalculateHeapMemorySize(in logRecord);
79+
if (size == 0)
80+
return;
81+
if (source == EvictionSource.ReadCache)
82+
cacheSizeTracker.AddReadCacheHeapSize(-size);
83+
else
84+
cacheSizeTracker.AddHeapSize(-size);
85+
}
5286
}
5387
}

libs/server/Storage/Functions/MainStore/DeleteMethods.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public void PostInitialDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInf
2828
/// <inheritdoc />
2929
public bool InPlaceDeleter(ref LogRecord logRecord, ref DeleteInfo deleteInfo)
3030
{
31+
// Decrement here — before the wrapper sets Tombstone. After Tombstone is set,
32+
// CalculateHeapMemorySize short-circuits to zero and EvictRecordsInRange skips
33+
// the record, so any matching decrement would be lost and the creation-side
34+
// increments in PostInitialUpdater/PostInitialWriter/InPlaceUpdater would leak.
35+
var heap = logRecord.CalculateHeapMemorySize();
36+
if (heap != 0)
37+
functionsState.cacheSizeTracker?.AddHeapSize(-heap);
38+
3139
logRecord.ClearOptionals();
3240
if (!logRecord.Info.Modified)
3341
functionsState.watchVersionMap.IncrementVersion(deleteInfo.KeyHash);

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,12 @@ public readonly void PostInitialUpdater(ref LogRecord logRecord, in RecordSizeIn
398398
input.header.SetExpiredFlag();
399399
rmwInfo.UserData |= NeedAofLog; // Mark that we need to write to AOF
400400
}
401+
402+
// Account for any heap contribution (key/value overflow) of the newly created record;
403+
// balances the decrement emitted by GarnetRecordTriggers.OnEvict on page eviction.
404+
var heap = logRecord.CalculateHeapMemorySize();
405+
if (heap != 0)
406+
functionsState.cacheSizeTracker?.AddHeapSize(heap);
401407
}
402408

403409
/// <inheritdoc />
@@ -409,6 +415,7 @@ public readonly bool InPlaceUpdater(ref LogRecord logRecord, ref StringInput inp
409415
return false;
410416
}
411417

418+
var preHeap = logRecord.GetValueHeapMemorySize();
412419
var ipuResult = InPlaceUpdaterWorker(ref logRecord, ref input, ref output, ref rmwInfo);
413420
switch (ipuResult)
414421
{
@@ -419,6 +426,9 @@ public readonly bool InPlaceUpdater(ref LogRecord logRecord, ref StringInput inp
419426
functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash);
420427
if (functionsState.appendOnlyFile != null)
421428
rmwInfo.UserData |= NeedAofLog; // Mark that we need to write to AOF
429+
var delta = logRecord.GetValueHeapMemorySize() - preHeap;
430+
if (delta != 0)
431+
functionsState.cacheSizeTracker?.AddHeapSize(delta);
422432
return true;
423433
case IPUResult.NotUpdated:
424434
default:
@@ -1701,6 +1711,18 @@ public readonly bool PostCopyUpdater<TSourceLogRecord>(in TSourceLogRecord srcLo
17011711
functionsState.watchVersionMap.IncrementVersion(rmwInfo.KeyHash);
17021712
if (functionsState.appendOnlyFile != null)
17031713
rmwInfo.UserData |= NeedAofLog; // Mark that we need to write to AOF
1714+
1715+
// Account for the new record's heap contribution so that the matching OnEvict decrement
1716+
// balances. We do NOT subtract srcLogRecord's heap: the source may be a main-log record
1717+
// (whose heap is tracked in mainLogTracker and will leak upward only by a bounded sealed-
1718+
// source amount — parity with ObjectStore's ClearSource=false branch and the legacy
1719+
// SubscribeEvictions path), a read-cache record (heap lives in readCacheTracker, unrelated
1720+
// to this write), or a pending-IO DiskLogRecord (heap never counted in any tracker).
1721+
// Subtracting unconditionally would undercount in the last two cases and could drive the
1722+
// counter negative.
1723+
var newHeap = dstLogRecord.CalculateHeapMemorySize();
1724+
if (newHeap != 0)
1725+
functionsState.cacheSizeTracker?.AddHeapSize(newHeap);
17041726
return true;
17051727
}
17061728

libs/server/Storage/Functions/MainStore/UpsertMethods.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ public void PostInitialWriter(ref LogRecord logRecord, in RecordSizeInfo sizeInf
4545
functionsState.watchVersionMap.IncrementVersion(upsertInfo.KeyHash);
4646
if (functionsState.appendOnlyFile != null)
4747
upsertInfo.UserData |= NeedAofLog; // Mark that we need to write to AOF
48+
49+
// Account for overflow key/value heap on the freshly-inserted record so the matching
50+
// OnEvict decrement balances.
51+
var heap = logRecord.CalculateHeapMemorySize();
52+
if (heap != 0)
53+
functionsState.cacheSizeTracker?.AddHeapSize(heap);
4854
}
4955

5056
/// <inheritdoc />
@@ -61,6 +67,10 @@ public void PostInitialWriter<TSourceLogRecord>(ref LogRecord logRecord, in Reco
6167
Debug.Assert(!inputLogRecord.Info.ValueIsObject, "String store should not be called with IHeapObject");
6268
upsertInfo.UserData |= NeedAofLog; // Mark that we need to write to AOF
6369
}
70+
71+
var heap = logRecord.CalculateHeapMemorySize();
72+
if (heap != 0)
73+
functionsState.cacheSizeTracker?.AddHeapSize(heap);
6474
}
6575

6676
/// <inheritdoc />

libs/server/Storage/SizeTracker/CacheSizeTracker.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ public CacheSizeTracker(TsavoriteKV<StoreFunctions, StoreAllocator> store, long
6565
=> Initialize(store, targetSize, readCacheTargetSize, loggerFactory);
6666

6767
/// <summary>
68-
/// Initialize the tracker with a store. Subscribes to eviction notifications.
69-
/// Called after store creation when using the parameterless constructor.
68+
/// Initialize the tracker with a store. Wires the <see cref="LogSizeTracker"/> as the fast-path
69+
/// size tracker for copy-to-tail / copy-to-readcache. Per-record heap-size decrement on page
70+
/// eviction is driven by <see cref="GarnetRecordTriggers.OnEvict"/>.
7071
/// </summary>
7172
public void Initialize(TsavoriteKV<StoreFunctions, StoreAllocator> store, long targetSize, long readCacheTargetSize, ILoggerFactory loggerFactory = null)
7273
{
@@ -77,14 +78,14 @@ public void Initialize(TsavoriteKV<StoreFunctions, StoreAllocator> store, long t
7778
{
7879
mainLogTracker = new LogSizeTracker<StoreFunctions, StoreAllocator>(store.Log, targetSize,
7980
targetSize / HighTargetSizeDeltaFraction, targetSize / LowTargetSizeDeltaFraction, loggerFactory?.CreateLogger("MainLogSizeTracker"));
80-
store.Log.SubscribeEvictions(mainLogTracker);
81+
store.Log.SetLogSizeTracker(mainLogTracker);
8182
}
8283

8384
if (store.ReadCache != null && readCacheTargetSize > 0)
8485
{
8586
readCacheTracker = new LogSizeTracker<StoreFunctions, StoreAllocator>(store.ReadCache, readCacheTargetSize,
8687
readCacheTargetSize / HighTargetSizeDeltaFraction, readCacheTargetSize / LowTargetSizeDeltaFraction, loggerFactory?.CreateLogger("ReadCacheSizeTracker"));
87-
store.ReadCache.SubscribeEvictions(readCacheTracker);
88+
store.ReadCache.SetLogSizeTracker(readCacheTracker);
8889
}
8990
}
9091

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ protected string BaseToString(string fuaDetails = "")
205205
/// <summary>Observer for records getting evicted from memory (page closed). May be the same object as <see cref="logSizeTracker"/>.</summary>
206206
internal IObserver<ITsavoriteScanIterator> onEvictionObserver;
207207

208+
/// <summary>
209+
/// Whether this allocator is the read cache (as opposed to the main hybrid log).
210+
/// Set once at construction from <see cref="AllocatorSettings.IsReadCache"/>.
211+
/// </summary>
212+
internal readonly bool IsReadCache;
213+
208214
/// <summary>Log size tracker; called when an operation at the Tsavorite-internal level adds or removes heap memory size
209215
/// (e.g. copying to log tail or read cache, which do not call <see cref="ISessionFunctions{TInputOutput, TContext}"/>).
210216
/// May be the same object as <see cref="onEvictionObserver"/>.</summary>
@@ -553,9 +559,15 @@ internal void WriteInlinePageAsync<TContext>(IntPtr alignedSourceAddress, ulong
553559

554560
/// <summary>Instantiate base allocator implementation</summary>
555561
[MethodImpl(MethodImplOptions.NoInlining)]
556-
private protected AllocatorBase(LogSettings logSettings, TStoreFunctions storeFunctions, Func<object, TAllocator> wrapperCreator, Action<long, long> evictCallback,
557-
LightEpoch epoch, Action<CommitInfo> flushCallback, ILogger logger = null, ObjectIdMap transientObjectIdMap = null)
562+
private protected AllocatorBase(AllocatorSettings allocatorSettings, TStoreFunctions storeFunctions, Func<object, TAllocator> wrapperCreator,
563+
ILogger logger = null, ObjectIdMap transientObjectIdMap = null)
558564
{
565+
var logSettings = allocatorSettings.LogSettings;
566+
var evictCallback = allocatorSettings.evictCallback;
567+
var epoch = allocatorSettings.epoch;
568+
var flushCallback = allocatorSettings.flushCallback;
569+
IsReadCache = allocatorSettings.IsReadCache;
570+
559571
this.storeFunctions = storeFunctions;
560572
_wrapper = wrapperCreator(this);
561573

@@ -1403,10 +1415,20 @@ public void ShiftBeginAddress(long newBeginAddress, bool truncateLog, bool noFlu
14031415
/// <summary>Invokes eviction observer if set and then frees the page.</summary>
14041416
internal void EvictPageForRecovery(long page)
14051417
{
1406-
if (logSizeTracker is not null)
1418+
var start = GetLogicalAddressOfStartOfPage(page);
1419+
var end = GetLogicalAddressOfStartOfPage(page + 1);
1420+
1421+
var source = IsReadCache ? EvictionSource.ReadCache : EvictionSource.MainLog;
1422+
if (storeFunctions.CallOnEvict(source))
1423+
{
1424+
// New per-record path: OnEvict handles heap-size decrement per record. Parity
1425+
// with the runtime eviction path in OnPagesClosedWorker.
1426+
_wrapper.EvictRecordsInRange(start, end, source);
1427+
}
1428+
else if (logSizeTracker is not null)
14071429
{
1408-
var start = GetLogicalAddressOfStartOfPage(page);
1409-
var end = GetLogicalAddressOfStartOfPage(page + 1);
1430+
// Legacy observer path: materialize an iterator and push heap-size decrement to
1431+
// the LogSizeTracker.OnNext observer (kept for consumers still using SubscribeEvictions).
14101432
MemoryPageScan(start, end, logSizeTracker);
14111433
}
14121434

@@ -1489,8 +1511,9 @@ private void OnPagesClosedWorker()
14891511
MemoryPageScan(start, end, onEvictionObserver);
14901512

14911513
// Notify application of records being evicted — allows cleanup of external resources.
1492-
if (storeFunctions.CallOnEvict)
1493-
_wrapper.EvictRecordsInRange(start, end);
1514+
var evictSource = IsReadCache ? EvictionSource.ReadCache : EvictionSource.MainLog;
1515+
if (storeFunctions.CallOnEvict(evictSource))
1516+
_wrapper.EvictRecordsInRange(start, end, evictSource);
14941517

14951518
// If we are using a null storage device, we must also shift BeginAddress (leave it in-memory)
14961519
if (IsNullDevice)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ public struct AllocatorSettings
2323
/// <summary>The action to call on page eviction; used only for readcache</summary>
2424
internal Action<long, long> evictCallback;
2525

26+
/// <summary>
27+
/// Whether this allocator is the read cache (as opposed to the main hybrid log).
28+
/// Used to tag per-record eviction callbacks so applications can distinguish the source.
29+
/// </summary>
30+
internal bool IsReadCache;
31+
2632
/// <summary>The action to execute on flush completion; used only for <see cref="TsavoriteLog"/></summary>
2733
internal Action<CommitInfo> flushCallback;
2834

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ RecordSizeInfo GetDeleteRecordSize<TKey>(TKey key)
117117
/// <see cref="IRecordTriggers.OnEvict"/> hook for each valid, non-tombstoned record.
118118
/// Used during page eviction to allow cleanup of external resources.
119119
/// </summary>
120-
void EvictRecordsInRange(long startAddress, long endAddress);
120+
/// <param name="startAddress">Start logical address of the range.</param>
121+
/// <param name="endAddress">End logical address of the range (exclusive).</param>
122+
/// <param name="source">Identifies whether this eviction is from the main log or the read cache.</param>
123+
void EvictRecordsInRange(long startAddress, long endAddress, EvictionSource source);
121124
}
122125
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,6 @@ public readonly RecordSizeInfo GetDeleteRecordSize<TKey>(TKey key)
146146
public readonly void OnDispose(ref DiskLogRecord logRecord, DisposeReason disposeReason) => _this.OnDispose(ref logRecord, disposeReason);
147147

148148
/// <inheritdoc/>
149-
public readonly void EvictRecordsInRange(long startAddress, long endAddress) => _this.EvictRecordsInRange(startAddress, endAddress);
149+
public readonly void EvictRecordsInRange(long startAddress, long endAddress, EvictionSource source) => _this.EvictRecordsInRange(startAddress, endAddress, source);
150150
}
151151
}

0 commit comments

Comments
 (0)