Skip to content

Commit

Permalink
issue-1226: Use LRU Cache for MixedBlocks (#2627)
Browse files Browse the repository at this point in the history
  • Loading branch information
debnatkh authored Jan 6, 2025
1 parent 5ead9ee commit c397ea2
Show file tree
Hide file tree
Showing 15 changed files with 451 additions and 17 deletions.
7 changes: 7 additions & 0 deletions cloud/filestore/config/storage.proto
Original file line number Diff line number Diff line change
Expand Up @@ -505,4 +505,11 @@ message TStorageConfig
// Enables directory creation in shards (by default directories are created
// only in the main tablet).
optional bool DirectoryCreationInShardsEnabled = 414;

// Mixed blocks map are stored in memory only for actively used ranges.
// Additionally MixedBlocksOffloadedRangesCapacity ranges that are not
// actively used are also stored in memory. Their memory footprint is
// proportional to the aforementioned value multiplied my the size of
// TRange
optional uint64 MixedBlocksOffloadedRangesCapacity = 415;
}
2 changes: 2 additions & 0 deletions cloud/filestore/libs/storage/core/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ using TAliases = NProto::TStorageConfig::TFilestoreAliases;
xxx(DestroyFilestoreDenyList, TVector<TString>, {} )\
\
xxx(SSProxyFallbackMode, bool, false )\
\
xxx(MixedBlocksOffloadedRangesCapacity, ui64, 0 )\
// FILESTORE_STORAGE_CONFIG

#define FILESTORE_STORAGE_CONFIG_REF(xxx) \
Expand Down
1 change: 1 addition & 0 deletions cloud/filestore/libs/storage/core/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ class TStorageConfig
bool GetDirectoryCreationInShardsEnabled() const;

bool GetGuestWritebackCacheEnabled() const;
ui64 GetMixedBlocksOffloadedRangesCapacity() const;
};

} // namespace NCloud::NFileStore::NStorage
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,9 @@ TVector<TDeletionMarker> TDeletionMarkers::Extract()
return Impl->Extract();
}

void TDeletionMarkers::Swap(TDeletionMarkers& other)
{
Impl.swap(other.Impl);
}

} // namespace NCloud::NFileStore::NStorage
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class TDeletionMarkers
ui32 Apply(TBlock& block) const;
ui32 Apply(TArrayRef<TBlock> blocks) const;
TVector<TDeletionMarker> Extract();
void Swap(TDeletionMarkers& other);
};

} // namespace NCloud::NFileStore::NStorage
91 changes: 74 additions & 17 deletions cloud/filestore/libs/storage/tablet/model/mixed_blocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "alloc.h"
#include "deletion_markers.h"

#include <cloud/storage/core/libs/common/lru_cache.h>

#include <util/generic/hash_set.h>
#include <util/generic/intrlist.h>

Expand Down Expand Up @@ -97,7 +99,15 @@ struct TRange

ui64 RefCount = 1;

TRange(IAllocator* alloc)
void Swap(TRange& other)
{
Blobs.swap(other.Blobs);
DeletionMarkers.Swap(other.DeletionMarkers);
Levels.swap(other.Levels);
std::swap(RefCount, other.RefCount);
}

explicit TRange(IAllocator* alloc)
: Blobs{alloc}
, DeletionMarkers(alloc)
{}
Expand All @@ -109,32 +119,70 @@ struct TRange

struct TMixedBlocks::TImpl
{
// Holds all ranges that are actively used, i.e. have ref count > 0
using TRangeMap = THashMap<ui32, TRange>;
// Is used to store ranges that are not actively used, i.e. have
// ref count == 0. May be useful for caching
using TOffloadedRangeMap = NCloud::TLRUCache<ui32, TRange>;

void SetOffloadedRangesCapacity(ui64 offloadedRangesCapacity)
{
OffloadedRanges.SetCapacity(offloadedRangesCapacity);
}

explicit TImpl(IAllocator* alloc)
: Alloc(alloc)
, OffloadedRanges(alloc)
{}

TRange* FindRange(ui32 rangeId)
{
auto* it = Ranges.FindPtr(rangeId);
if (it) {
return it;
}
return OffloadedRanges.FindPtr(rangeId);
}

IAllocator* Alloc;
TRangeMap Ranges;
TOffloadedRangeMap OffloadedRanges;
};

////////////////////////////////////////////////////////////////////////////////

TMixedBlocks::TMixedBlocks(IAllocator* alloc)
: Impl(new TImpl{alloc, {}})
: Impl(new TImpl(alloc))
{}

TMixedBlocks::~TMixedBlocks()
{}

void TMixedBlocks::Reset(ui64 offloadedRangesCapacity)
{
Impl->SetOffloadedRangesCapacity(offloadedRangesCapacity);
}

bool TMixedBlocks::IsLoaded(ui32 rangeId) const
{
auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
return range;
}

void TMixedBlocks::RefRange(ui32 rangeId)
{
TImpl::TRangeMap::insert_ctx ctx;
TImpl::TRangeMap::insert_ctx ctx = nullptr;
if (auto it = Impl->Ranges.find(rangeId, ctx); it == Impl->Ranges.end()) {
Impl->Ranges.emplace_direct(ctx, rangeId, Impl->Alloc);
// If the range is offloaded, move it to active ranges. Otherwise,
// create a new range
if (auto offloadedIt = Impl->OffloadedRanges.find(rangeId)) {
Impl->Ranges.emplace_direct(ctx, rangeId, Impl->Alloc);
Impl->Ranges.at(rangeId).Swap(offloadedIt->second);
Impl->Ranges.at(rangeId).RefCount = 1;
Impl->OffloadedRanges.erase(offloadedIt);
} else {
Impl->Ranges.emplace_direct(ctx, rangeId, Impl->Alloc);
}
} else {
it->second.RefCount++;
}
Expand All @@ -147,7 +195,16 @@ void TMixedBlocks::UnRefRange(ui32 rangeId)
Y_ABORT_UNLESS(it->second.RefCount, "invalid ref count for range: %u", rangeId);

it->second.RefCount--;
if (!it->second.RefCount) {

// If ref count drops to 0, move the range to offloaded ranges. No need to
// offload the range if the capacity is 0
if (it->second.RefCount == 0) {
if (Impl->OffloadedRanges.capacity() != 0) {
auto [_, inserted] =
Impl->OffloadedRanges.emplace(rangeId, Impl->Alloc);
Y_DEBUG_ABORT_UNLESS(inserted);
Impl->OffloadedRanges.at(rangeId).Swap(it->second);
}
Impl->Ranges.erase(it);
}
}
Expand All @@ -158,7 +215,7 @@ bool TMixedBlocks::AddBlocks(
TBlockList blockList,
const TMixedBlobStats& stats)
{
auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

// TODO: pick level
Expand Down Expand Up @@ -186,7 +243,7 @@ bool TMixedBlocks::RemoveBlocks(
const TPartialBlobId& blobId,
TMixedBlobStats* stats)
{
auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

auto it = range->Blobs.find(blobId);
Expand Down Expand Up @@ -216,7 +273,7 @@ void TMixedBlocks::FindBlocks(
ui32 blockIndex,
ui32 blocksCount) const
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

// TODO: limit range scan
Expand Down Expand Up @@ -246,15 +303,15 @@ void TMixedBlocks::AddDeletionMarker(
ui32 rangeId,
TDeletionMarker deletionMarker)
{
auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

range->DeletionMarkers.Add(deletionMarker);
}

TVector<TDeletionMarker> TMixedBlocks::ExtractDeletionMarkers(ui32 rangeId)
{
auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

return range->DeletionMarkers.Extract();
Expand All @@ -266,15 +323,15 @@ void TMixedBlocks::ApplyDeletionMarkers(
{
const auto rangeId = GetMixedRangeIndex(hasher, blocks);

const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

range->DeletionMarkers.Apply(MakeArrayRef(blocks));
}

TVector<TMixedBlobMeta> TMixedBlocks::ApplyDeletionMarkers(ui32 rangeId) const
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

TVector<TMixedBlobMeta> result;
Expand All @@ -293,7 +350,7 @@ TVector<TMixedBlobMeta> TMixedBlocks::ApplyDeletionMarkers(ui32 rangeId) const
auto TMixedBlocks::ApplyDeletionMarkersAndGetMetas(ui32 rangeId) const
-> TVector<TDeletionMarkerApplicationResult>
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

TVector<TDeletionMarkerApplicationResult> result;
Expand All @@ -313,7 +370,7 @@ auto TMixedBlocks::ApplyDeletionMarkersAndGetMetas(ui32 rangeId) const

TVector<TMixedBlobMeta> TMixedBlocks::GetBlobsForCompaction(ui32 rangeId) const
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

TVector<TMixedBlobMeta> result;
Expand All @@ -331,7 +388,7 @@ TVector<TMixedBlobMeta> TMixedBlocks::GetBlobsForCompaction(ui32 rangeId) const

TMixedBlobMeta TMixedBlocks::FindBlob(ui32 rangeId, TPartialBlobId blobId) const
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_ABORT_UNLESS(range);

TVector<TMixedBlobMeta> result;
Expand All @@ -347,7 +404,7 @@ TMixedBlobMeta TMixedBlocks::FindBlob(ui32 rangeId, TPartialBlobId blobId) const

ui32 TMixedBlocks::CalculateGarbageBlockCount(ui32 rangeId) const
{
const auto* range = Impl->Ranges.FindPtr(rangeId);
auto* range = Impl->FindRange(rangeId);
Y_DEBUG_ABORT_UNLESS(range);
if (!range) {
return 0;
Expand Down
2 changes: 2 additions & 0 deletions cloud/filestore/libs/storage/tablet/model/mixed_blocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class TMixedBlocks
TMixedBlocks(IAllocator* allocator);
~TMixedBlocks();

void Reset(ui64 offloadedRangesCapacity);

bool IsLoaded(ui32 rangeId) const;

void RefRange(ui32 rangeId);
Expand Down
87 changes: 87 additions & 0 deletions cloud/filestore/libs/storage/tablet/model/mixed_blocks_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,93 @@ Y_UNIT_TEST_SUITE(TMixedBlocksTest)
mixedBlocks.UnRefRange(rangeId);
UNIT_ASSERT(!mixedBlocks.IsLoaded(rangeId));
}

Y_UNIT_TEST(ShouldEvictLeastRecentlyUsedRanges)
{
constexpr ui32 rangeId1 = 0;
constexpr ui32 rangeId2 = 1;
constexpr ui32 rangeId3 = 2;

constexpr ui64 nodeId = 1;
constexpr ui64 minCommitId = MakeCommitId(12, 345);
constexpr ui64 maxCommitId = InvalidCommitId;

constexpr ui32 blockIndex = 123456;
constexpr size_t blocksCount = 100;

TBlock block(nodeId, blockIndex, minCommitId, maxCommitId);

auto list = TBlockList::EncodeBlocks(
block,
blocksCount,
TDefaultAllocator::Instance());

TMixedBlocks mixedBlocks(TDefaultAllocator::Instance());
mixedBlocks.Reset(1);
mixedBlocks.RefRange(rangeId1);
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId1));

mixedBlocks.RefRange(rangeId2);
mixedBlocks.AddBlocks(rangeId2, TPartialBlobId(), list);
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId1));
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId2));

mixedBlocks.AddBlocks(rangeId1, TPartialBlobId(), list);
mixedBlocks.UnRefRange(rangeId2);
// So now the least recently used range is rangeId2. It should be added
// to the offloaded list

// The rangeId2 is not evicted, because it fits into the capacity of
// offloaded ranges
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId2));
mixedBlocks.RefRange(rangeId3);
mixedBlocks.UnRefRange(rangeId3);

// Now the least recently used range is rangeId2, and it is evicted from
// the offloaded ranges. It is replaced by rangeId3
UNIT_ASSERT(!mixedBlocks.IsLoaded(rangeId2));
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId3));

mixedBlocks.UnRefRange(rangeId1);
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId1));

mixedBlocks.RefRange(rangeId1);
// The range is moved from offloaded ranges to active ranges and its
// data should be preserved
UNIT_ASSERT(mixedBlocks.IsLoaded(rangeId1));
UNIT_ASSERT_VALUES_EQUAL(
blocksCount,
mixedBlocks.FindBlob(rangeId1, TPartialBlobId()).Blocks.size());
{
TMixedBlockVisitor visitor;
mixedBlocks.FindBlocks(
visitor,
rangeId1,
nodeId,
minCommitId + 1,
blockIndex,
blocksCount);

auto blocks = visitor.Finish();
UNIT_ASSERT_VALUES_EQUAL(blocksCount, blocks.size());
}

// And this can not be said about rangeId2
mixedBlocks.RefRange(rangeId2);
{
TMixedBlockVisitor visitor;
mixedBlocks.FindBlocks(
visitor,
rangeId2,
nodeId,
minCommitId + 1,
blockIndex,
blocksCount);

auto blocks = visitor.Finish();
UNIT_ASSERT_VALUES_EQUAL(0, blocks.size());
}
}
}

} // namespace NCloud::NFileStore::NStorage
1 change: 1 addition & 0 deletions cloud/filestore/libs/storage/tablet/tablet_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ void TIndexTabletState::LoadState(
config.GetInMemoryIndexCacheNodeRefsCapacity(),
GetNodesCount(),
config.GetInMemoryIndexCacheNodesToNodeRefsCapacityRatio()));
Impl->MixedBlocks.Reset(config.GetMixedBlocksOffloadedRangesCapacity());

for (const auto& deletionMarker: largeDeletionMarkers) {
Impl->LargeBlocks.AddDeletionMarker(deletionMarker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ InMemoryIndexCacheNodesCapacity: 10
InMemoryIndexCacheNodeRefsCapacity: 10
InMemoryIndexCacheNodeAttrsCapacity: 10
UseMixedBlocksInsteadOfAliveBlocksInCompaction: true
MixedBlocksOffloadedRangesCapacity: 1024
1 change: 1 addition & 0 deletions cloud/storage/core/libs/common/lru_cache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "lru_cache.h"
Loading

0 comments on commit c397ea2

Please sign in to comment.