Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
47a4572
Initial commit
vidyasilai Nov 11, 2025
47dc604
Added some more comments
vidyasilai Nov 12, 2025
99d750f
Remove conan file changes
vidyasilai Nov 12, 2025
969c140
Some bug fixes; new test case
vidyasilai Nov 15, 2025
ca5b89b
More bug fixes
vidyasilai Nov 17, 2025
51a4e02
KVStoreScanner updates
vidyasilai Nov 18, 2025
b126c03
Subtree test config change
vidyasilai Nov 18, 2025
afa9940
First round of feedback
vidyasilai Nov 21, 2025
8dbab04
Improvements to Subtree test
vidyasilai Nov 24, 2025
969efc0
More feedback
vidyasilai Nov 28, 2025
84112d8
Small style updates
vidyasilai Dec 3, 2025
75e815a
More feedback, fix try_merge interface
vidyasilai Dec 9, 2025
9e36ed9
More Subtree fixes
vidyasilai Dec 16, 2025
318c1b6
Merge branch 'main' into remove_impl
vidyasilai Dec 16, 2025
3b0b16f
Fix decay to items bug
vidyasilai Dec 16, 2025
2382169
InMemoryLeaf updates
vidyasilai Jan 6, 2026
4b0f792
Fix node find key
vidyasilai Jan 7, 2026
6ff7e02
Merge in changes from main branch
vidyasilai Feb 3, 2026
ac7e078
Initial commit, tests passing
vidyasilai Feb 24, 2026
4c44767
Updated filter test
vidyasilai Feb 25, 2026
c398037
New batteries functions
vidyasilai Mar 2, 2026
22aa457
Merge in main changes
vidyasilai Mar 2, 2026
f19922b
Start refactor of merge functions
vidyasilai Mar 2, 2026
8fbfc05
More merge function refactoring
vidyasilai Mar 4, 2026
06ae727
Fix compiler errors
vidyasilai Mar 9, 2026
31b8118
Upgrade batteries and llfs
vidyasilai Mar 10, 2026
54e1a5c
Fix try_split code to use new batteries overloads
vidyasilai Mar 10, 2026
0a6d005
Fix test code to use new batteries overloads
vidyasilai Mar 10, 2026
67a5c46
Start adding ActivePivotSet concept.
tonyastolfi Mar 10, 2026
ee66720
Finish writing ActivePivotsSet concept
vidyasilai Mar 10, 2026
3872edf
Remove old code
vidyasilai Mar 11, 2026
ee0c29f
Merge branch 'active_pivots_changes' into remove_impl
vidyasilai Mar 11, 2026
ace4445
Fix build issues
vidyasilai Mar 11, 2026
424b4d7
Fix pop_front_pivots function
vidyasilai Mar 11, 2026
c8950fa
Modify the ActivePivotsSet concepts a bit.
tonyastolfi Mar 16, 2026
6a097d0
Fix split_pivot bug re: new piecewise filters.
tonyastolfi Mar 16, 2026
0167649
Make sure InMemoryNode::Segmment::active_pivots is initialized empty.
tonyastolfi Mar 16, 2026
fe715a8
Replace magic number `4` with kMinPivotCount constant.
tonyastolfi Mar 16, 2026
203c1e0
Move the minimum pivots constant next to the max one (config.hpp); re…
tonyastolfi Mar 16, 2026
b9a533f
Add some more defensive checks that we never exceed the maximum numbe…
tonyastolfi Mar 16, 2026
e9eadbd
Add comment about new kPivotCountMask.
tonyastolfi Mar 16, 2026
6e01a50
assert size of packed active pivots set.
tonyastolfi Mar 16, 2026
dde7018
Static asserts: ActivePivotsSet.
tonyastolfi Mar 16, 2026
1c7efe1
Revert change.
tonyastolfi Mar 16, 2026
d0f1745
Start adding HybridLevel
vidyasilai Mar 16, 2026
fa6a609
Test changes
vidyasilai Mar 16, 2026
b3d7ec2
Finish adding HybridLevel
vidyasilai Mar 20, 2026
c7714d9
Merge with main
vidyasilai Mar 20, 2026
eb53e2f
Bug fixes
vidyasilai Mar 23, 2026
43e38f2
Fix some comments
vidyasilai Mar 23, 2026
dd18c3d
Fix Subtree::flush_and_shrink loop logic
vidyasilai Mar 31, 2026
f92f249
Small style fix
vidyasilai Mar 31, 2026
d2621d7
Some feedback changes
vidyasilai Mar 31, 2026
0def570
Some more Subtree updates
vidyasilai Apr 1, 2026
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
2 changes: 1 addition & 1 deletion src/turtle_kv/core/merge_compactor.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,4 +666,4 @@ TEST_F(ResultSetConcatTest, ConcatDeath)
"the second ResultSet!");
}

} // namespace
} // namespace
3 changes: 3 additions & 0 deletions src/turtle_kv/kv_store_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ Status KVStoreScanner::set_next_item()

for (;;) {
if (this->heap_.empty()) {
if (this->next_item_ && this->next_item_->value.is_delete()) {
this->next_item_ = None;
}
return OkStatus();
}

Expand Down
36 changes: 34 additions & 2 deletions src/turtle_kv/tree/active_pivots_set.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ class ActivePivotsSetBase
this->bit_set_ = insert_bit(this->bit_set_, i, v);
}

BATT_ALWAYS_INLINE void remove(i32 i)
{
this->bit_set_ = remove_bit(this->bit_set_, i);
}

BATT_ALWAYS_INLINE bool is_empty() const
{
return this->bit_set_ == Bitset{};
Expand Down Expand Up @@ -153,11 +158,11 @@ class ActivePivotsSet128 : public ActivePivotsSetBase<std::array<u64, 2>>
BATT_ALWAYS_INLINE auto printable() const
{
return [this](std::ostream& out) {
out << std::bitset<64>{this->bit_set_[1]} << "," << std::bitset<64>{this->bit_set_[1]};
out << std::bitset<64>{this->bit_set_[1]} << "," << std::bitset<64>{this->bit_set_[0]};
};
}

/** \brief Removes the specified number (`count`) pivots from the bit set.
/** \brief Removes the specified number (`count`) pivots from the beginning of the bit set.
*/
BATT_ALWAYS_INLINE void pop_front_pivots(i32 count)
{
Expand All @@ -173,6 +178,33 @@ class ActivePivotsSet128 : public ActivePivotsSetBase<std::array<u64, 2>>
this->bit_set_[1] = 0;
}
}

/** \brief Removes the specified number (`count`) pivots from the end of the bit set.
*/
BATT_ALWAYS_INLINE void pop_back_pivots(i32 count)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe push_front_pivots?

{
if (count < 1) {
return;
}

if (count < 64) {
this->bit_set_[1] = (this->bit_set_[1] << count) | (this->bit_set_[0] >> (64 - count));
this->bit_set_[0] <<= count;
} else {
this->bit_set_[1] = this->bit_set_[0] << (count - 64);
this->bit_set_[0] = 0;
}
}

/** \brief Performs a bit wise OR operation with another bit set.
*/
BATT_ALWAYS_INLINE Self& operator|=(const Self& other)
{
this->bit_set_[0] |= other.bit_set_[0];
this->bit_set_[1] |= other.bit_set_[1];

return *this;
}
};

static_assert(ActivePivotsSet<ActivePivotsSet128>);
Expand Down
30 changes: 24 additions & 6 deletions src/turtle_kv/tree/algo/segmented_levels.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,21 @@ struct SegmentedLevelAlgorithms {

const PackedLeafPage& leaf = PackedLeafPage::view_of(pinned_page.get_page_buffer());

auto flushed_last = leaf.lower_bound(max_key);
if (flushed_last != leaf.items_end() && get_key(*flushed_last) == max_key) {
++flushed_last;
}

CInterval<KeyView> flush_key_crange{pivot_lower_bound_key, max_key};
segment.drop_key_range(flush_key_crange, leaf.items_slice());

if (flushed_last == leaf.items_end() || get_key(*flushed_last) >= pivot_upper_bound_key) {
auto pivot_first = leaf.lower_bound(pivot_lower_bound_key);
usize pivot_first_i = std::distance(leaf.items_begin(), pivot_first);

auto pivot_last = leaf.lower_bound(pivot_upper_bound_key);
usize pivot_last_i = std::distance(leaf.items_begin(), pivot_last);

Interval<u32> pivot_live_range =
segment.get_live_item_range(this->level_,
Interval<u32>{BATT_CHECKED_CAST(u32, pivot_first_i),
BATT_CHECKED_CAST(u32, pivot_last_i)});

if (pivot_live_range.empty()) {
segment.set_pivot_active(pivot_i, false);
}

Expand Down Expand Up @@ -283,6 +289,18 @@ struct SegmentedLevelAlgorithms {
return OkStatus();
}

/** \brief Merges the two given pivots, effectively erasing `right_pivot`.
*/
void merge_pivots(i32 left_pivot, i32 right_pivot)
{
const usize segment_count = this->level_.segment_count();

for (usize segment_i = 0; segment_i < segment_count; ++segment_i) {
SegmentT& segment = this->level_.get_segment(segment_i);
in_segment(segment).merge_pivots(left_pivot, right_pivot, this->level_);
}
}

/** \brief Invokes `fn` for each SegmentT& selected by `pivot_selector`.
*
* `pivot_selector` can be:
Expand Down
12 changes: 12 additions & 0 deletions src/turtle_kv/tree/algo/segments.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ struct SegmentAlgorithms {
return true;
}

/** \brief Merges the two given pivots, effectively erasing `right_pivot`.
*/
template <typename LevelT>
[[nodiscard]] void merge_pivots(i32 left_pivot, i32 right_pivot, const LevelT& level)
{
bool left_is_active = this->segment_.is_pivot_active(left_pivot);
bool right_is_active = this->segment_.is_pivot_active(right_pivot);
this->segment_.set_pivot_active(left_pivot, left_is_active | right_is_active);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| -> ||


this->segment_.remove_pivot(right_pivot);
}

/** \brief Invokes the speficied `fn` for each active pivot in the specified range, passing a
* reference to the segment and the pivot index (i32).
*/
Expand Down
79 changes: 79 additions & 0 deletions src/turtle_kv/tree/in_memory_leaf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,53 @@
#include <turtle_kv/tree/packed_leaf_page.hpp>
#include <turtle_kv/tree/the_key.hpp>

#include <batteries/algo/parallel_transform.hpp>

namespace turtle_kv {

//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
//
/*static*/ std::unique_ptr<InMemoryLeaf> InMemoryLeaf::unpack(
llfs::PinnedPage&& pinned_leaf_page,
const TreeOptions& tree_options,
const PackedLeafPage& packed_leaf,
batt::WorkerPool& worker_pool) noexcept
{
std::unique_ptr<InMemoryLeaf> new_leaf =
std::make_unique<InMemoryLeaf>(batt::make_copy(pinned_leaf_page), tree_options);

Slice<const PackedKeyValue> packed_items = packed_leaf.items_slice();
std::vector<EditView> buffer;
buffer.reserve(packed_items.size());

{
batt::ScopedWorkContext context{worker_pool};

const ParallelAlgoDefaults& algo_defaults = parallel_algo_defaults();
const batt::TaskCount max_tasks{worker_pool.size() + 1};

batt::parallel_transform(
context,
packed_items.begin(),
packed_items.end(),
buffer.data(),
[](const PackedKeyValue& pkv) -> EditView {
return to_edit_view(pkv);
},
/*min_task_size = */ algo_defaults.copy_edits.min_task_size,
/*max_tasks = */ max_tasks);
}

MergeCompactor::ResultSet</*decay_to_items=*/true> result_set;
const ItemView* first_edit = (const ItemView*)buffer.data();
result_set.append(std::move(buffer), as_slice(first_edit, packed_items.size()));
new_leaf->result_set = std::move(result_set);

new_leaf->set_edit_size_totals(compute_running_total(worker_pool, *(new_leaf->result_set)));

return {std::move(new_leaf)};
}

//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
//
SubtreeViability InMemoryLeaf::get_viability()
Expand Down Expand Up @@ -150,6 +195,40 @@ auto InMemoryLeaf::make_split_plan() const -> StatusOr<SplitPlan>
return plan;
}

//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
//
Status InMemoryLeaf::try_merge(BatchUpdateContext& context,
std::unique_ptr<InMemoryLeaf> sibling) noexcept
{
BATT_CHECK(this->result_set);
BATT_CHECK(sibling->result_set);

if (sibling->result_set->empty()) {
BATT_CHECK(batt::is_case<Viable>(this->get_viability()))
<< "Sibling leaf is not viable, so this leaf must be viable!";
return OkStatus();
}

if (this->result_set->empty()) {
BATT_CHECK(batt::is_case<Viable>(sibling->get_viability()))
<< "This leaf is not viable, so sibling leaf must be viable!";
this->pinned_leaf_page_ = std::move(sibling->pinned_leaf_page_);
this->result_set = std::move(sibling->result_set);
this->shared_edit_size_totals_ = sibling->shared_edit_size_totals_;
this->edit_size_totals = std::move(sibling->edit_size_totals);
return OkStatus();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably retain a pin on the sibling's leaf page in this; that means we may need to change InMemoryLeaf::pinned_leaf_page_ into pinned_leaf_pages_.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In regard to turning pinned_leaf_page_ into a vector of pinned pages, what are your thoughts on keeping pinned_leaf_page_ as is and adding another member variable to InMemoryLeaf that is of type std::vector<PinnedPage> that holds pins on sibling pages as a result of merges? My reason for suggesting this is due to the logic in InMemoryLeaf::apply_batch_update, where we check the state of pinned_leaf_page_ to determine how to extract result_set.

BATT_CHECK_LT(this->get_max_key(), sibling->get_min_key());

this->result_set = MergeCompactor::ResultSet<true>::concat(std::move(*this->result_set),
std::move(*(sibling->result_set)));

this->set_edit_size_totals(context.compute_running_total(*this->result_set));

return OkStatus();
}

//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - -
//
Status InMemoryLeaf::apply_batch_update(BatchUpdate& update) noexcept
Expand Down
9 changes: 9 additions & 0 deletions src/turtle_kv/tree/in_memory_leaf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ struct InMemoryLeaf {

//+++++++++++-+-+--+----- --- -- - - - -

static std::unique_ptr<InMemoryLeaf> unpack(llfs::PinnedPage&& pinned_leaf_page,
const TreeOptions& tree_options,
const PackedLeafPage& packed_leaf,
batt::WorkerPool& worker_pool) noexcept;

//+++++++++++-+-+--+----- --- -- - - - -

explicit InMemoryLeaf(llfs::PinnedPage&& pinned_leaf_page,
const TreeOptions& tree_options_arg) noexcept
: pinned_leaf_page_{std::move(pinned_leaf_page)}
Expand Down Expand Up @@ -98,6 +105,8 @@ struct InMemoryLeaf {

StatusOr<SplitPlan> make_split_plan() const;

Status try_merge(BatchUpdateContext& context, std::unique_ptr<InMemoryLeaf> sibling) noexcept;

Status apply_batch_update(BatchUpdate& update) noexcept;

Status start_serialize(TreeSerializeContext& context);
Expand Down
Loading