Skip to content

Commit

Permalink
RavenDB-22986 - use an extended in memory info in order to avoid disk…
Browse files Browse the repository at this point in the history
… lookups
  • Loading branch information
grisha-kotler committed Nov 11, 2024
1 parent 2429eda commit 11c6fcd
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 48 deletions.
112 changes: 77 additions & 35 deletions src/Voron/Impl/FreeSpace/FreeSpaceHandling.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Sparrow.Json.Parsing;
using Sparrow.Server;
using Voron.Data.Fixed;
Expand All @@ -14,7 +15,15 @@ public sealed class FreeSpaceHandling : IFreeSpaceHandling

private readonly FreeSpaceRecursiveCallGuard _guard;

private readonly Dictionary<long, int> _maxConsecutiveRangePerSection = new();
private readonly Dictionary<long, SectionMetadata> _maxConsecutiveRangePerSection = new();

[StructLayout(LayoutKind.Sequential, Size = 6)]
public struct SectionMetadata()
{
public ushort Max = NumberOfPagesInSection;
public ushort StartBits = NumberOfPagesInSection;
public ushort EndBits = NumberOfPagesInSection;
}

static FreeSpaceHandling()
{
Expand Down Expand Up @@ -187,34 +196,44 @@ public void Clear()
}
}


private long? TryFindSmallValue(LowLevelTransaction tx, FixedSizeTree freeSpaceTree, FixedSizeTree.IFixedSizeIterator it, int num)
{
do
{
var current = new StreamBitArray(it.CreateReaderForCurrent());

long currentSectionId = it.CurrentKey;
long? page;
if (_maxConsecutiveRangePerSection.TryGetValue(currentSectionId, out var knownMax) && knownMax <= num)

if (_maxConsecutiveRangePerSection.TryGetValue(currentSectionId, out var known) && known.Max <= num)
{
if (_maxConsecutiveRangePerSection.TryGetValue(currentSectionId + 1, out var knownNext))
{
if (known.EndBits + knownNext.StartBits < num)
continue;
}

// current section's maximum continuous range is insufficient for the requested size.
// while we can skip searching within this section alone, we still need to check
// if merging with the following section could provide enough space
if (TryFindSmallValueMergingTwoSections(tx, freeSpaceTree, currentSectionId, num, current, out page))
var currentBitArray = new StreamBitArray(it.CreateReaderForCurrent());
if (TryFindSmallValueMergingTwoSections(tx, freeSpaceTree, currentSectionId, num, currentBitArray, out page))
return page;

continue;
}

var current = new StreamBitArray(it.CreateReaderForCurrent());
if (current.SetCount >= num
&& TryFindContinuousRange(tx, freeSpaceTree, it, num, current, currentSectionId, out page))
&& TryFindContinuousRange(tx, freeSpaceTree, num, current, currentSectionId, out page))
return page;

if (knownMax == 0 || num < knownMax)
ref var metadata = ref CollectionsMarshal.GetValueRefOrAddDefault(_maxConsecutiveRangePerSection, currentSectionId, out var exists);
if (exists == false)
metadata = new SectionMetadata();

if (num < metadata.Max)
{
// here we _know_ it can't fit anything larger, so we mark it for the next time
_maxConsecutiveRangePerSection[currentSectionId] = num;
metadata.Max = (ushort)Math.Min(num, current.SetCount);
}

// could not find a continuous so trying to merge two consecutive sections
Expand All @@ -226,8 +245,8 @@ public void Clear()
return null;
}

private bool TryFindContinuousRange(LowLevelTransaction tx, FixedSizeTree freeSpaceTree, FixedSizeTree.IFixedSizeIterator it, int num,
StreamBitArray current, long currentSectionId, out long? page)
private bool TryFindContinuousRange(LowLevelTransaction tx, FixedSizeTree freeSpaceTree,
int num, StreamBitArray current, long currentSectionId, out long? page)
{
page = -1;

Expand All @@ -239,7 +258,7 @@ private bool TryFindContinuousRange(LowLevelTransaction tx, FixedSizeTree freeSp

if (current.SetCount == num)
{
freeSpaceTree.Delete(it.CurrentKey);
freeSpaceTree.Delete(currentSectionId);
}
else
{
Expand All @@ -250,39 +269,61 @@ private bool TryFindContinuousRange(LowLevelTransaction tx, FixedSizeTree freeSp

Slice val;
using (current.ToSlice(tx.Allocator, out val))
freeSpaceTree.Add(it.CurrentKey, val);
freeSpaceTree.Add(currentSectionId, val);
}

return true;
}

private static bool TryFindSmallValueMergingTwoSections(LowLevelTransaction tx, FixedSizeTree freeSpacetree, long currentSectionId, int num,
StreamBitArray current, out long? result)
private bool TryFindSmallValueMergingTwoSections(LowLevelTransaction tx, FixedSizeTree freeSpaceTree,
long currentSectionId, int num, StreamBitArray current, out long? result)
{
result = -1;
var currentEndRange = current.GetEndRangeCount();
if (currentEndRange == 0)
return false;

var nextSectionId = currentSectionId + 1;
StreamBitArray next = null;
int nextRangeStart = 0;
using (freeSpaceTree.Read(nextSectionId, out Slice read))
{
if (read.HasValue)
{
next = new StreamBitArray(read.CreateReader());
nextRangeStart = next.GetStartRangeCount();
}
}

StreamBitArray next;
Slice read;
using (freeSpacetree.Read(nextSectionId, out read))
if (currentEndRange == 0 || next == null || currentEndRange + nextRangeStart < num)
{
if (!read.HasValue)
return false;
// we update the cache in any of the following cases:
// - the current section has no more available space at its end
// - the next section does not exist
// - even when combining the current and next sections, the total space is insufficient

ref var metadata = ref CollectionsMarshal.GetValueRefOrAddDefault(
_maxConsecutiveRangePerSection, currentSectionId, out var exists);

if (exists == false)
metadata = new SectionMetadata();

metadata.EndBits = (ushort)currentEndRange;

ref var nextMetadata = ref CollectionsMarshal.GetValueRefOrAddDefault(
_maxConsecutiveRangePerSection, nextSectionId, out exists);

next = new StreamBitArray(read.CreateReader());
if (exists == false)
nextMetadata = new SectionMetadata();

nextMetadata.StartBits = (ushort)nextRangeStart;

return false;
}

var nextRange = num - currentEndRange;
if (next.HasStartRangeCount(nextRange) == false)
return false;

if (next.SetCount == nextRange)
{
freeSpacetree.Delete(nextSectionId);
freeSpaceTree.Delete(nextSectionId);
}
else
{
Expand All @@ -292,12 +333,12 @@ private static bool TryFindSmallValueMergingTwoSections(LowLevelTransaction tx,
}
Slice val;
using (next.ToSlice(tx.Allocator, out val))
freeSpacetree.Add(nextSectionId, val);
freeSpaceTree.Add(nextSectionId, val);
}

if (current.SetCount == currentEndRange)
{
freeSpacetree.Delete(currentSectionId);
freeSpaceTree.Delete(currentSectionId);
}
else
{
Expand All @@ -307,7 +348,7 @@ private static bool TryFindSmallValueMergingTwoSections(LowLevelTransaction tx,
}
Slice val;
using (current.ToSlice(tx.Allocator, out val))
freeSpacetree.Add(currentSectionId, val);
freeSpaceTree.Add(currentSectionId, val);
}


Expand Down Expand Up @@ -338,13 +379,14 @@ public List<long> AllPages(LowLevelTransaction tx)
for (var i = 0; i < NumberOfPagesInSection; i++)
{
if (current.Get(i))
freePages.Add(currentSectionId*NumberOfPagesInSection + i);
freePages.Add(currentSectionId * NumberOfPagesInSection + i);
}
} while (it.MoveNext());

return freePages;
}
}
}

public List<DynamicJsonValue> FreeSpaceSnapshot(LowLevelTransaction tx, bool hex)
{
var freeSpaceTree = GetFreeSpaceTree(tx);
Expand All @@ -367,7 +409,7 @@ public List<DynamicJsonValue> FreeSpaceSnapshot(LowLevelTransaction tx, bool hex

return freeSpace;
}
}
}

public void FreePage(LowLevelTransaction tx, long pageNumber)
{
Expand Down Expand Up @@ -414,12 +456,12 @@ public IEnumerable<long> GetFreePagesOverheadPages(LowLevelTransaction tx)
}
}

public Dictionary<long, int> GetMaxConsecutiveRangePerSection(LowLevelTransaction tx)
public Dictionary<long, SectionMetadata> GetMaxConsecutiveRangePerSection(LowLevelTransaction tx)
{
if (tx.Transaction.IsWriteTransaction == false)
throw new InvalidOperationException($"{nameof(GetMaxConsecutiveRangePerSection)} must be called with an open write transaction");

return new Dictionary<long, int>(_maxConsecutiveRangePerSection);
return new Dictionary<long, SectionMetadata>(_maxConsecutiveRangePerSection);
}

public void OnRollback() => _maxConsecutiveRangePerSection.Clear();
Expand Down
2 changes: 1 addition & 1 deletion src/Voron/Impl/FreeSpace/IFreeSpaceHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface IFreeSpaceHandling
void FreePage(LowLevelTransaction tx, long pageNumber);
long GetFreePagesOverhead(LowLevelTransaction tx);
IEnumerable<long> GetFreePagesOverheadPages(LowLevelTransaction tx);
Dictionary<long, int> GetMaxConsecutiveRangePerSection(LowLevelTransaction tx);
Dictionary<long, FreeSpaceHandling.SectionMetadata> GetMaxConsecutiveRangePerSection(LowLevelTransaction tx);
void OnRollback();
FreeSpaceHandlingDisabler Disable();
}
Expand Down
63 changes: 51 additions & 12 deletions test/FastTests/Voron/Trees/FreeSpaceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,40 +287,78 @@ public void CanUpdateMaxConsecutiveRanges()
{
tx.LowLevelTransaction.AllocatePage(3);

var max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);
Assert.Equal(5, max.Count);

for (var i = 0; i < totalSections - 1; i++)
{
Assert.Equal(1, max[i].Max);
Assert.Equal(0, max[i].EndBits);
}

Assert.Equal(2, max[3].Max);
Assert.Equal(1, max[3].StartBits);
Assert.Equal(1, max[3].EndBits);

Assert.Equal(0, max[4].StartBits);

// releasing 2 pages in section 4 to make 3 consecutive pages between two sections
Env.FreeSpaceHandling.FreePage(tx.LowLevelTransaction, totalPages);
Env.FreeSpaceHandling.FreePage(tx.LowLevelTransaction, totalPages + 1);
tx.Commit();

var max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);
max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);
Assert.Equal(4, max.Count);

for (var i = 0; i < totalSections; i++)
for (var i = 0; i < totalSections - 1; i++)
{
Assert.Equal(max[i], 3);
Assert.Equal(1, max[i].Max);
}

Assert.Equal(2, max[3].Max);
Assert.Equal(1, max[3].StartBits);
Assert.Equal(1, max[3].EndBits);
}

using (var tx = Env.WriteTransaction())
{
var page = tx.LowLevelTransaction.AllocatePage(3);
Assert.Equal(lastPageInAllSections, page.PageNumber);
tx.Commit();
for (int i = 0; i < totalPages; i += 2048)
{
Env.FreeSpaceHandling.FreePage(tx.LowLevelTransaction, i + 2);
Env.FreeSpaceHandling.FreePage(tx.LowLevelTransaction, i + 4);
}

tx.LowLevelTransaction.AllocatePage(4);

var max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);

for (var i = 0; i < totalSections; i++)
for (var i = 0; i < totalSections - 1; i++)
{
Assert.Equal(max[i], 3);
Assert.Equal(3, max[i].Max);
Assert.Equal(0, max[i].EndBits);
}

Assert.Equal(4, max[3].Max);
Assert.Equal(1, max[3].StartBits);
Assert.Equal(1, max[3].EndBits);

Assert.Equal(2, max[4].Max);
Assert.Equal(2, max[4].StartBits);

Assert.Equal(0, max[5].StartBits);

var page = tx.LowLevelTransaction.AllocatePage(3);
Assert.Equal(lastPageInAllSections, page.PageNumber);
tx.Commit();

// shouldn't find anything and update the max consecutive
tx.LowLevelTransaction.AllocatePage(2);

max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);

for (var i = 0; i < totalSections; i++)
for (var i = 0; i < totalSections + 1; i++)
{
Assert.Equal(max[i], 2);
Assert.Equal(2, max[i].Max);
}
}

Expand All @@ -334,8 +372,9 @@ public void CanUpdateMaxConsecutiveRanges()

var max = Env.FreeSpaceHandling.GetMaxConsecutiveRangePerSection(tx.LowLevelTransaction);

Assert.Equal(max[1], 2);
Assert.Equal(max[3], 2);
Assert.Equal(2, max[1].Max);
Assert.Equal(2, max[3].Max);
Assert.Equal(2, max[4].Max);
}

using (var tx = Env.WriteTransaction())
Expand Down

0 comments on commit 11c6fcd

Please sign in to comment.