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

Compaction lossy hash #14283

Merged
merged 9 commits into from
Oct 25, 2023
Merged

Conversation

dotnwat
Copy link
Member

@dotnwat dotnwat commented Oct 19, 2023

Introduces a new offset key map implementation that maps compaction key space into sha256(key) space.

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v23.2.x
  • v23.1.x
  • v22.3.x

Release Notes

  • none

@dotnwat dotnwat force-pushed the compaction-lossy-hash branch 2 times, most recently from 95e0561 to e40cf8b Compare October 20, 2023 22:42
@dotnwat dotnwat marked this pull request as ready for review October 20, 2023 22:43
@dotnwat dotnwat requested a review from andrwng October 20, 2023 22:43
@dotnwat dotnwat force-pushed the compaction-lossy-hash branch 2 times, most recently from c03dece to 45a02cc Compare October 21, 2023 00:42
This is useful for the open-source build when vtools is not available.

Signed-off-by: Noah Watkins <[email protected]>
@dotnwat dotnwat force-pushed the compaction-lossy-hash branch 2 times, most recently from 3a971d2 to 0ce2a75 Compare October 23, 2023 21:47
@vbotbuildovich
Copy link
Collaborator

@vbotbuildovich
Copy link
Collaborator

@emaxerrno
Copy link
Contributor

i was skeptical at first w/ the regular byte comparison hash. this makes sense.

Copy link
Contributor

@VladLazar VladLazar left a comment

Choose a reason for hiding this comment

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

Very nice. A few questions/suggestions

src/v/utils/fragmented_vector.h Outdated Show resolved Hide resolved
src/v/utils/fragmented_vector.h Outdated Show resolved Hide resolved
Comment on lines +141 to +172
/**
* Uses successive chunks of sizeof(index_type) bytes taken from hash(key)
* as probes into the hash table. When `next()` returns null then the caller
* should switch to linear probing.
*/
struct probe {
using index_type = uint32_t;
static_assert(sizeof(index_type) <= hash_type::digest_size);

explicit probe(const hash_type::digest_type&);

std::optional<index_type> next();

hash_type::digest_type::const_pointer iter;
hash_type::digest_type::const_pointer end;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

So the idea here is to probe at random places in the backing vector. This is neat. Does this smartness have a name?

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked extensively to see if this had a name, but I don't think it does.

IMO this is probably just a different flavor/generalization of double hashing where if the first hash position is a collision a different hash function is used as the next probe. Here the hash output is large enough that we wouldn't use all those bits anyway for the first position.

src/v/storage/key_offset_map.cc Show resolved Hide resolved
src/v/storage/key_offset_map.cc Outdated Show resolved Hide resolved
src/v/storage/key_offset_map.cc Outdated Show resolved Hide resolved
Copy link
Contributor

@andrwng andrwng left a comment

Choose a reason for hiding this comment

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

LGTM! Just some nits

src/v/storage/tests/key_offset_map_test.cc Show resolved Hide resolved
tools/format-cc Show resolved Hide resolved
Comment on lines 235 to 237
* The expected use case for this is to allocate a large vector in a fiber
* using a series of smaller resize() invocations allowing for cooperative
* yield calls to be inserted to avoid reactor stalls. The optimal strategy
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I wonder if there's a magic number over which it's not safe to call this?

Alternatively, I wonder if there are async helper methods worth adding that encapsulates this expected use case?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think there are any magic numbers here. Since fragmented vector isn't futurized, it be the same concern as resizing a std::vector or avoiding calling fragmented_vector::copy on a large fragmented vector.

src/v/storage/key_offset_map.cc Show resolved Hide resolved
@vbotbuildovich
Copy link
Collaborator

@dotnwat
Copy link
Member Author

dotnwat commented Oct 24, 2023

force-push

  • added cap on load factor at 95%
  • enforced capacity on put()
  • removed fragmented_vector::resize its a bit awkward
  • added helper fragmented_vector_clear_async
  • added helper fragmented_vector_fill_async
  • added hash_key_offset_map::initialize for resetting hash table context that avoids reallocation with resize()
  • switched to large fragmented vector variant

@vbotbuildovich
Copy link
Collaborator

@dotnwat
Copy link
Member Author

dotnwat commented Oct 25, 2023

Failure is #14218

andrwng
andrwng previously approved these changes Oct 25, 2023
};

// handle a non-normalized probe position
// returns true if key is inserted
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: comment needs an update

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

probe_count_ = 0;
}

seastar::future<> hash_key_offset_map::initialize() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe consider naming this reset() or something? As is it seems like something we should always call before using the map, but I don't think that's the case (presumably we just need to use reset(size_bytes)?

Copy link
Member Author

@dotnwat dotnwat Oct 25, 2023

Choose a reason for hiding this comment

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

reset(size) implies initialize(). but in practice you really only want to call reset(size) once at boot-up, and then call initialize() each time you want to use the table in a new context because it avoids freeing and reallocating all the memory.

Signed-off-by: Noah Watkins <[email protected]>
@dotnwat dotnwat merged commit 1be948f into redpanda-data:dev Oct 25, 2023
10 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants