Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating defragmentation to defrag both bloomfiltertype and bloomfiler structs #24

Merged
merged 6 commits into from
Dec 10, 2024

Conversation

zackcam
Copy link
Contributor

@zackcam zackcam commented Nov 22, 2024

Overview

Updated the defragmentation callback. Now it defrags the inner BloomFilter by looping over the filters and calling defrag method on each filter. Afterwards we call the defrag on the BloomFilterType and update that value as well.

In order to allow defragmentation on the inner BloomFilter from the BloomFilterType we needed to update the vec in type to hold a box of a BloomFilter this way we could get ownership of the BloomFilter object and update it as necessary. Sometimes due to the nature of rust the just unallocated addresses could be used by a following defrag call and reallocation

Example output of showing the addresses change when defragged

Command Ran: bf.insert scale CAPACITY 10 ITEMS 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17

DefragResult Address: 0x7fab07c56780
new_filter Address: 0x7fab07c56780

Before Address: 0x7fab07c511c0
DefragResult Address: 0x7fab07c510e0
new_filter Address: 0x7fab07c510e0

Before Address of Top level. 0x7fab07c1a618
After Address of Top level. 0x7fab07c1afa0 


Before Address: 0x7fab07c56780
DefragResult Address: 0x7fab07c511c0
new_filter Address: 0x7fab07c511c0

Before Address: 0x7fab07c510e0
DefragResult Address: 0x7fab07c56780
new_filter Address: 0x7fab07c56780

Before Address of Top level. 0x7fab07c1afa0
After Address of Top level. 0x7fab07c1a618 


Before Address: 0x7fab07c511c0
DefragResult Address: 0x7fab07c510e0
new_filter Address: 0x7fab07c510e0

Before Address: 0x7fab07c56780
DefragResult Address: 0x7fab07c511c0
new_filter Address: 0x7fab07c511c0

Before Address of Top level. 0x7fab07c1a618
After Address of Top level. 0x7fab07c1afa0 

@zackcam zackcam force-pushed the defrag_branch branch 2 times, most recently from 2bf0ce8 to d490857 Compare November 26, 2024 01:51
@zackcam
Copy link
Contributor Author

zackcam commented Nov 26, 2024

Added Defragging for Bloom as well. This involved also setting it in BloomFilter to be inside a box in order to gain ownership

Example output of showing the addresses of bloom change when defragged

Before bloom filter 1 Address: 0x7f8393249540
After bloom filter 1 Address: 0x7f83932493c0

Before bloom filter 2 Address: 0x7f8393249300
After bloom filter 2 Address: 0x7f8393249540

Before bloom filter 1 Address: 0x7f83932493c0
After bloom filter 1 Address: 0x7f8393249300

Before bloom filter 2 Address: 0x7f8393249540
After bloom filter 2 Address: 0x7f83932493c0

let bloom_filter_box = bloom_filter_type.filters.remove(0);
let bloom_filter = Box::into_raw(bloom_filter_box);
let defrag_result = unsafe {
raw::RedisModule_DefragAlloc.unwrap()(
Copy link
Member

@KarthikSubbarao KarthikSubbarao Nov 26, 2024

Choose a reason for hiding this comment

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

For all the places we use this API, we should handle the case where it returns null re-using the same allocation / structure

You can use is_null() on a pointer that you have

https://doc.rust-lang.org/std/ptr/fn.null.html


let inner_bloom = mem::replace(
&mut defragged_filter.bloom,
Box::new(bloomfilter::Bloom::new(1, 1)),
Copy link
Member

@KarthikSubbarao KarthikSubbarao Nov 26, 2024

Choose a reason for hiding this comment

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

We want to avoid additional allocations and frees from the defrag code path.

Is there an alternative approach to get ownership of the inner bloom? any other std::mem function that can be used?

Would an Option on the inner bloom work instead of a Box? This allows us to use take and obtain ownership safely

Copy link
Member

@KarthikSubbarao KarthikSubbarao Nov 27, 2024

Choose a reason for hiding this comment

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

Other option is using a LazyStatic that contains a mutex of an Option of a Box of a Bloom :D

lazy_static! {
    static ref DEFRAG_BLOOM_FILTER: Mutex<Option<Box<Bloom<[u8]>>>> = Mutex::new(Some(Box::new(Bloom::<[u8]>::new(1, 1))));
}

With this, we can re-use the same allocation for every bloom operation - this is not allocated / freed and instead we swap it back and forth to help obtain ownership.

src/wrapper/bloom_callback.rs Outdated Show resolved Hide resolved
tests/test_bloom_metrics.py Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
@@ -112,7 +112,7 @@ impl ValkeyDataType for BloomFilterType {
num_items as u32,
capacity as u32,
);
filters.push(filter);
filters.push(Box::new(filter));
Copy link
Member

Choose a reason for hiding this comment

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

Is this to avoid re-assigning the ownership of filter variable? I don't understand the reason for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we put the filter in the box this allows us to take it out later and have ownership which means we can update the pointers address to actually change it. Without putting it into the box we couldn't actually change the addresses associated with it. Documentation for box

@@ -14,6 +14,7 @@ pub const BLOOM_FP_RATE_DEFAULT: f64 = 0.001;
pub const BLOOM_FP_RATE_MIN: f64 = 0.0;
pub const BLOOM_FP_RATE_MAX: f64 = 1.0;

pub const BLOOM_DEFRAG_DEAFULT: bool = true;
Copy link
Member

Choose a reason for hiding this comment

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

How will a user/operator change this value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The user can add this to the start up arguments:
--bf.bloom-defrag-enabled no
This will skip all the code related to bloom defragmenting and instantly just return 0

@@ -128,22 +139,113 @@ pub unsafe extern "C" fn bloom_free_effort(
curr_item.free_effort()
}

lazy_static! {
static ref DEFRAG_BLOOM_FILTER: Mutex<Option<Box<Bloom<[u8]>>>> =
Copy link
Member

Choose a reason for hiding this comment

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

Can you document the intention here? I am not able to reverse engineer it from code.

Copy link
Member

Choose a reason for hiding this comment

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

Why ref?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used as a safe way to take out the Bloom object without dropping it. This is a temporary bloom object which we put in while we attempt to defrag the original Bloom object. The ref is used to show it is a reference to the temp bloom object instead of the data of the temp object

@zackcam zackcam force-pushed the defrag_branch branch 4 times, most recently from d917921 to 17b961d Compare December 4, 2024 01:14

use valkey_module::raw;

pub struct Defrag {
Copy link
Member

Choose a reason for hiding this comment

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

Not a blocker. When you have time, let's contribute this to valkeymodule-rs. Once it is released, we can use it from there

///
/// Returns an `i32` where:
/// * 0 indicates successful complete defragmentation.
/// * 1 indicates incomplete defragmentation (not all filters were defragged).
pub unsafe extern "C" fn bloom_defrag(
Copy link
Member

Choose a reason for hiding this comment

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

This function is looking good functionally (and from testing). Thank you!

Next, we can think about monitoring:

For monitoring, we can consider adding hit and miss metrics for the different layers of allocations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add the metrics for hits and misses in a seperate pr

@zackcam zackcam force-pushed the defrag_branch branch 5 times, most recently from fc59870 to d8b41c2 Compare December 4, 2024 20:26
src/bloom/utils.rs Outdated Show resolved Hide resolved
src/bloom/utils.rs Outdated Show resolved Hide resolved
src/bloom/utils.rs Outdated Show resolved Hide resolved
src/bloom/utils.rs Outdated Show resolved Hide resolved
…mexists and add_items_till_capacity. Refactored metric tracking as well to reduce code repetition

Signed-off-by: zackcam <[email protected]>
@zackcam zackcam force-pushed the defrag_branch branch 3 times, most recently from e3573fa to bd24972 Compare December 8, 2024 01:51
@@ -79,6 +80,9 @@ impl ValkeyDataType for BloomFilterType {
return None;
};
let is_seed_random = is_seed_random_u64 == 1;

let mut filters = Vec::with_capacity(1);
Copy link
Member

@KarthikSubbarao KarthikSubbarao Dec 10, 2024

Choose a reason for hiding this comment

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

Can we add a short documentation explaining why we use this - Vec::with_capacity(1) here? I think I had made this comment earlier

mem::size_of::<BloomFilterType>(),
std::sync::atomic::Ordering::Relaxed,
);

BLOOM_NUM_OBJECTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Copy link
Member

@KarthikSubbarao KarthikSubbarao Dec 10, 2024

Choose a reason for hiding this comment

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

Can we move this line into the function bloom_filter_type_incr_metrics_on_new_create ? It is used in every caller site of the bloom_filter_type_incr_metrics_on_new_create and can help condense metrics code

BLOOM_NUM_OBJECTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);

You can update the following caller sites:

  • new_reserved
  • load_from_rdb
  • create_copy_from
  • decode_bloom_filter

/// # Safety
///
/// This function is temporary and will be removed once implemented in valkeymodule-rs .
pub unsafe fn cursorset(&self, cursor: u64) -> i32 {
Copy link
Member

@KarthikSubbarao KarthikSubbarao Dec 10, 2024

Choose a reason for hiding this comment

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

  1. Naming: set_cursor might make this more readable / Rust like
  2. Question: What is the return code here? What should the caller site do with this i32? if this is meant to be a void, can we remove the return code?

Copy link
Member

Choose a reason for hiding this comment

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

The return code is useful. But we can update it from i32 to use https://docs.rs/valkey-module/latest/valkey_module/raw/enum.Status.html#variant.Ok to follow the convention of this SDK

/// # Safety
///
/// This function is temporary and will be removed once implemented in valkeymodule-rs .
pub unsafe fn cursorget(&self, cursor: *mut u64) -> i32 {
Copy link
Member

@KarthikSubbarao KarthikSubbarao Dec 10, 2024

Choose a reason for hiding this comment

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

  1. Naming: get_cursor might make this more readable / Rust like
  2. Can this simply return an u64 instead of accepting a mutable pointer? It will a simpler interface to use

Copy link
Member

Choose a reason for hiding this comment

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

Maybe have this return Result<u64, ()> or Option<u64>

Comment on lines 196 to 197
print(total_memory_bites)
print(expected_memory)
Copy link
Member

Choose a reason for hiding this comment

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

We can remove this

assert total_memory_bites == expected_memory
assert num_objects == expected_num_objects
assert num_filters == expected_num_filters
assert num_items == expected_num_items
assert sum_capacity == expected_sum_capacity

def parse_valkey_info(self, section):
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a comment for documentation here

@KarthikSubbarao KarthikSubbarao merged commit d8a0508 into valkey-io:unstable Dec 10, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants