-
Notifications
You must be signed in to change notification settings - Fork 943
Adding support for sharing memory between the module and the engine #2472
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
base: unstable
Are you sure you want to change the base?
Conversation
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
ranshid
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yairgott placing some high level comments first:
- Not sure I like the ViewValue naming. I think the intention is to keep a "string" pointer and a length right? in that case maybe we should simply do that and call it a
StringValue? - I did not understand why we have to change the
entryGetValueAPI? I would prefer to keep a separate API to get the "string" value likeentryGetStringValue(or in your caseentryGetViewValue)
Signed-off-by: yairgott <[email protected]>
Renamed it to bufferView.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## unstable #2472 +/- ##
============================================
+ Coverage 72.03% 72.18% +0.15%
============================================
Files 126 127 +1
Lines 70490 70914 +424
============================================
+ Hits 50774 51187 +413
- Misses 19716 19727 +11
🚀 New features to boost your workflow:
|
|
I am still providing high level comments (although I do have detailed comments) since IMO we should solve the highlevel first. I think we should have a clear definition of the entry API. Currently the entry is defined as a storage for sds type field and sds type value. In your suggestion this is changed and an entry can be provided with a value which is either an sds or a pointer to a native string and will INTERNALLY encode it the way it would like to. Lets list the reason for this change IIUC:
Your suggestion is to change the entry API so:
I think that following this suggestion I would handle the following cases:
I also think the top comment might be missing some more alternatives that we consider(?):
|
Fixed
I'm not 100% clear but I'll note that outside of entry.c, one should still be using the public interface
entryCreate - creating an entry with a value view is not supported. An entry may switch to store a value view by calling t_hash.c, hashTypeSetValueView. For safety, I've incorporated a debug mode verification that the provided view buffer matches the entry SDS.
Naming is hard :) The name
|
entryGetValueRef, is meant for internal use and will not work in all cases, so it is not fit for a public API. if, for example I am a user of entry and I provided an entry with sds and I need to continue using an sds from that entry, I have no way of doing so aside for creating a new sds.
From reading the code I see that:
I think this needs to be clear. If the entry is allowing to change an entry which is created with an sds value to an entry which has a "view", why we cannot do the opposite? and why do we expose a public API that is simply asserting instead of preventing this in some way? I think that as the entry is a stand-alone module, it should be generic and flexible, or the API should be restrictive and not allow what is simply not supported.
Well we are not doing c++ unfortunately, and pointers here are treated as references IMO. So I think it is a better name, but will not make this the main point of the review. I just think that the way to distinguish a "native c style string" to what you have which requires to ALWAYS keep the provided reference is to use a name like stringref. To conclude. I would like to have a complete API which is both generic and standalone together with supportive to the existing usecases we have. As I mentioned before, we could allow entry to accept both sds value and "native c style strings" and encode it internally which is subject to the internal implementation which should prefer memory efficiency and performance (avoid large copies and better cache line locality). For first thing we should solve the part of the API which accepts values if we plan to NOT allow accepting . How do we handle the |
Right, I'll fix this. The idea is to handle adding expiry to a view value. Otherwise, the existing code, with slight changes, will handle updating a view value.
I'm not religious about the name, if you feel strongly about the
I think that making the API complete is not really an objective but rather supporting the use-cases. Take for example
Any update to the entry is handled by a call to |
I think that the entry should keep a clear and concrete API. this API is not going to be used ONLY by the search module, but potentially by other future parts of the application as well as future modules, and it would be great if we could make the API complete. But let me try and track it better in the detailed review.
So that would require to handle entryUpdate correctly. I see that you suggest you will fix that, so I will followup on that. |
|
@yairgott also please make a run through the current documentation and structure ascii and change them accordingly. |
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
entryUpdate already works correctly. Let me know if you find any issues. I've also added unittests.
Sure. Also, noting that I've done the renaming to stringRef. |
Signed-off-by: yairgott <[email protected]>
Signed-off-by: yairgott <[email protected]>
ranshid
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
went though some more stuff. will continue tomorrow
src/entry.c
Outdated
| return entryWrite(buf, buf_size, field, value, expiry, embed_value, embedded_field_sds_type, embedded_field_sds_size, embedded_value_sds_size, expiry_size); | ||
| } | ||
|
|
||
| entry *entrySetStringRef(entry *entry, const char *buf, size_t len, long long expiry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me it sounds like we are duplicating some stuff form entryUpdate. The way I imagined it is that the entry can be encoded either like:
1. field | embedded value
2. value ptr | field
3. ttl | field | embedded value
4. ttl | value ptr | field
5. vstring | vlen | field
6. ttl | vstring | vlen | field
So entryUpdate should be able to navigate between ALL these cases and as such can be used inside entrySetStringRef.
I also still not a big fan of the fact that there is no real way to create an entry with a stringRef. Maybe it is not needed right now, but the API seems strange that way IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decoupling with a dedicated function, rather than extending entryUpdate to support string ref, is much more straight forward both to implement and to maintain. Extending entryUpdate would involve:
- Supporting both types of input values, sds and [char * buf, size_t len].
- Extending the logic to properly handle all the possible combinations.
In general, we should strive for code which is easy to maintain and resilient. IMO entryUpdate is already too complicated and it already handles too many different cases which adds complexity to follow and to reason about.
In term of code reuse, I will try to explore how to improve the code section in lines 324-342.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edited!
I would like to suggest the following changes to improve the code quality:
- Starting with
entryUpdate:
entry *entryUpdate(entry *e, sds value, long long expiry) {
if (entryChangeLayout(e, value, expiry)) {
entry *new_entry = entryCreate((sds)e, value, expiry);
entryFree(e);
return new_entry;
}
// Layout was not changed, just apply the value and the expiry
entryReplaceValue(e, value);
if (expiry != EXPIRY_NONE) entryReplaceExpiry(e, expiry);
return e;
}
- Now that
entryReqSizeis called just byentryCreateand therefor its logic can be embedded insideentryCreateand refactored to greatly simplified.
If agreed with the above, I can drive this changes but I prefer to drive this after this PR is landed. WDYT?
|
We discussed this in the core team meeting today. If we've closed on the design and it's been reviewed by next Monday, we can merge it to 9.0 RC 2, but otherwise we can postpone it to 9.1. |
It's next monday. I guess we'll move this to 9.1. |
Are there any reservations about the design? please correct me if I misunderstood but based on the comments, my understanding is that there are a couple of implementation details which are needed to be sort out but there are no push backs on the design itself. |
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Co-authored-by: Ran Shidlansik <[email protected]> Signed-off-by: Yair Gottdenker <[email protected]>
Co-authored-by: Ran Shidlansik <[email protected]> Signed-off-by: Yair Gottdenker <[email protected]>
Co-authored-by: Ran Shidlansik <[email protected]> Signed-off-by: Yair Gottdenker <[email protected]>
Co-authored-by: Ran Shidlansik <[email protected]> Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
Signed-off-by: Yair Gottdenker <[email protected]>
|
Overall the code changes LGTM. NOTE: this might have future conflicts with #2618 @valkey-io/core-team how can we progress the major-decision-pending checkout? does any other maintainer wish to go over and check this? |
|
@ranshid Hey, what specifically do you want the core team to address? |
|
We reviewed the APIs, they seem minimal but there are no concerns with it. @JimB123 since Ran asked for another pair of eyes, PTAL. Also Ran, please approve if you are happy with the PR. |
Ack. Reviewing. |
| /* Sets a reference to the value. | ||
| * The function takes the hash key, hash field, and a buffer along with its length. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is insufficient documentation to understand the purpose and use of these APIs. We need to explain when/why this function should be used and what are the implications of using it.
I think it's assumed that the value should remain fundamentally the same in content, right? This isn't documented or validated.
How is this undone? What happens at deletion time? or when the value is modified by an HSET?
| @@ -0,0 +1,77 @@ | |||
| #include "valkeymodule.h" | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no documentation regarding the purpose of this test/example.
| VALKEYMODULE_NOT_USED(argc); | ||
| if (ValkeyModule_Init(ctx, "hash.stringref", 1, VALKEYMODULE_APIVER_1) == | ||
| VALKEYMODULE_OK && | ||
| ValkeyModule_CreateCommand(ctx, "hash.set_stringref", hashSetStringRef, "write", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no documentation regarding the format/parameters of this command. A reader has no way to know what the numbered parameters represent.
| VALKEYMODULE_OK && | ||
| ValkeyModule_CreateCommand(ctx, "hash.set_stringref", hashSetStringRef, "write", | ||
| 1, 1, 1) == VALKEYMODULE_OK && | ||
| ValkeyModule_CreateCommand(ctx, "hash.has_stringref", hashHasStringRef, "write", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no documentation regarding the format/parameters of this command. A reader has no way to know what the numbered parameters represent.
Also, is it correct that a command called "has_stringref" should be marked as a "write" command?
| /* Returns the location of a pointer to a separately allocated value. Only for | ||
| * an entry without an embedded value. */ | ||
| static sds *entryGetValueRef(const entry *entry) { | ||
| static inline void **entryGetValueRef(const entry *entry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are times when void is necessary. Allocation routines, for example, have no idea of the type that's being allocated. But that's not really the case here.
In this case, we have 2 distinct possibilities. In most(*) of the cases where this function is being used, the caller must check which case is being used and then cast the void into the proper type.
(*) Note, there is one place in updateValue that simply nulls out the reference.
I'll suggest that you should use 2 separate routines, each properly typed, and avoid void. If you want, this also provides a place to assert if the referencing/casting is being done incorrectly.
static sds * entryGetSdsValueRef(const entry *entry);
static stringRef * entryGetStringRefRef(const entry *entry);Doing this will eliminate a lot of the casting, and provide additional safety with enhanced type checking. This change would invert some of the logic.
Current logic is something like:
void **value_ref = entryGetValueRef(entry);
if (entryHasStringRef(entry)) {
stringRef *string_ref = (stringRef *)*value_ref;
...
} else {
sds sds = (sds)*value_ref;
...
}This can easily be converted to something like:
if (entryHasStringRef(entry)) {
stringRef *string_ref = *entryGetStringRefRef(entry);
...
} else {
sds sds = *entryGetSdsRef(entry);
...
}This eliminates the casting and the use of void. This also helps clarify (and typecheck) that sds IS a pointer, while stringRef IS a struct.
| return sdsfromlonglong(vll); | ||
| } | ||
| if (hi->encoding == OBJ_ENCODING_HASHTABLE) { | ||
| size_t vlen = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid unnecessary initializations, it can fake out static analysis.
| size_t len; | ||
| sds field = entryGetField(hi.next); | ||
| char *value_str = entryGetValue(hi.next, &len); | ||
| long long expiry = entryGetExpiry(hi.next); | ||
| /* Add a field-value pair to a new hash object. */ | ||
| entry *entry = entryCreate(field, sdsdup(value), expiry); | ||
| sds value = sdsnewlen(value_str, len); | ||
| entry *entry = entryCreate(field, value, expiry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't duplicating an entry be the responsibility of entry.c? Is there a reason that this is done here in hash.c?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that the entry lacks some explicit API for duplicate does not mean it needs to be implemented only for a single case. The code here is not doing anything intrusive to the entry, just using the offered API.
We can introduce a duplicate API but IMO it is negligible
| } else if (hi->encoding == OBJ_ENCODING_HASHTABLE) { | ||
| sds value = hashTypeCurrentFromHashTable(hi, what); | ||
| addWritePreparedReplyBulkCBuffer(wpc, value, sdslen(value)); | ||
| size_t len = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid meaningless initializations, it can fake out static analysis.
| free_callback = sdsfree; | ||
| } | ||
| vectorInit(&result, SCAN_VECTOR_INITIAL_ALLOC, sizeof(sds)); | ||
| vectorInit(&result, SCAN_VECTOR_INITIAL_ALLOC, sizeof(stringRef)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This represents an overloading of the stringRef type for a different purpose. Should it have a different type?
In the original, it represents a reference to a non-owned string used in the t_hash entry. There are ownership considerations to maintain. Elsewhere in this review I suggested the possibility of adding a callback to that struct to avoid coupling with server event capabilities.
In this case, you just need to keep track of a pointer/length pair. I'd recommend a new type, local to db.c, rather than coupling to the type created for t_hash.
EDIT: after looking at the other code, it seems that the stringRef type is only used internally in entry.c. This should definitely be a separate type, and stringRef should be removed from server.h.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stringRef is a simple struct which holds a pointer to a string and a length. It's currently used by the hash entry and here but may be used in any other place instead of the pair <const char * str, size_t len>. In a way, stringRef is similar to std::string_view, which is widely used in c++.
here are ownership considerations to maintain.
Can you clarify this statement? The existing code and so the PR changes, do not take ownership of the hash field entries.
In this case, you just need to keep track of a pointer/length pair. I'd recommend a new type, local to db.c, rather than coupling to the type created for t_hash.
- To clarify,
stringRefis not specific to t_hash and can be used in any scenario where the pair <const char * str, size_t len> is used. - The alternative is to create here a local data-type which is similar to
stringRef.
| /* Structure representing a non-owning view of a buffer. | ||
| * A stringRef struct does not manage the underlying memory, so its destruction | ||
| * will not free the buffer. */ | ||
| typedef struct stringRef { | ||
| const char *buf; /* Pointer to the externalized buffer */ | ||
| size_t len; /* Length of the buffer */ | ||
| } stringRef; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this type into entry.c. The only other usage is completely unrelated in db.c and suggested to be an independent type. Note this this does not need to be exposed in entry.h, it can be completely internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we kept it here for other occasions we might want to use it (eg scan).
I think I am fine with repeat internal re-implemet this, but why is it so bad to have this type?
ranshid
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me try and sum-up my thoughts about the status:
Documentation improvements
https://github.com/valkey-io/valkey/pull/2472/files#r2473588727
https://github.com/valkey-io/valkey/pull/2472/files#r2471218383
https://github.com/valkey-io/valkey/pull/2472/files#r2473736680
https://github.com/valkey-io/valkey/pull/2472/files#r2470424309
https://github.com/valkey-io/valkey/pull/2472/files#r2470568137
https://github.com/valkey-io/valkey/pull/2472/files#r2470572782
https://github.com/valkey-io/valkey/pull/2472/files#r2470572981
code refactor
create a dedicated scan type: https://github.com/valkey-io/valkey/pull/2472/files#r2479301622
add a dedicated stringRef getter: https://github.com/valkey-io/valkey/pull/2472/files#r2471108477
refactor entryGetValue: https://github.com/valkey-io/valkey/pull/2472/files#r2471145583
entryUpdate refactor: https://github.com/valkey-io/valkey/pull/2472/files#r2474627473 + https://github.com/valkey-io/valkey/pull/2472/files#r2478426332
https://github.com/valkey-io/valkey/pull/2472/files#r2474655831
entryUpdateAsStringRef: https://github.com/valkey-io/valkey/pull/2472/files#r2474037834 + https://github.com/valkey-io/valkey/pull/2472/files#r2478563045 + https://github.com/valkey-io/valkey/pull/2472/files#r2473574601 + https://github.com/valkey-io/valkey/pull/2472/files#r2479164080+ https://github.com/valkey-io/valkey/pull/2472/files#r2479155661
refactor entryConstruct: https://github.com/valkey-io/valkey/pull/2472/files#r2475553245
trivial code refactor
https://github.com/valkey-io/valkey/pull/2472/files#r2471200320
https://github.com/valkey-io/valkey/pull/2472/files#r2471192263
https://github.com/valkey-io/valkey/pull/2472/files#r2479118890
https://github.com/valkey-io/valkey/pull/2472/files#r2479166951
https://github.com/valkey-io/valkey/pull/2472/files#r2479190818
https://github.com/valkey-io/valkey/pull/2472/files#r2479214355
correctness
https://github.com/valkey-io/valkey/pull/2472/files#r2471130746
https://github.com/valkey-io/valkey/pull/2472/files#r2478894678
https://github.com/valkey-io/valkey/pull/2472/files#r2478794808
https://github.com/valkey-io/valkey/pull/2472/files#r2478831245
https://github.com/valkey-io/valkey/pull/2472/files#r2478899287
open issues
https://github.com/valkey-io/valkey/pull/2472/files#r2473620702
https://github.com/valkey-io/valkey/pull/2472/files#r2475548331 - should be handled when we rebase unstable to solve conflict
https://github.com/valkey-io/valkey/pull/2472/files#r2479323821
IMO the open issues + correctness + trivial code refactor can be completed in order to merge it.
We can consider followup in a different PR about the documentation and major code refactor
@JimB123 @yairgott WDYT?
|
|
||
| /* Returns the address of the entry allocation. */ | ||
| void *entryGetAllocPtr(const entry *entry) { | ||
| static void *entryGetAllocPtr(const entry *entry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JimB123 I am not sure exactly how much it would simplify existing cases. I might agree that having another function which is fetching the expiration ref would probably improve maintainability and clarity.
| debugServerAssert((((uintptr_t)buf & 0x7) == 0)); /* Test that the allocation is indeed 8 bytes aligned | ||
| * This is needed since we access the expiry as with pointer casting | ||
| * which require the access to be 8 bytes aligned. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JimB123 IIRC this followed your review comment when we introduced HFE pr. But leave aside the past. I agree this can be dropped.
| size_t field_size = sdsReqSize(sdslen(field), SDS_TYPE_8); | ||
| size_t alloc_size = field_size + sizeof(void *); | ||
| alloc_size += (expiry == EXPIRY_NONE) ? 0 : sizeof(expiry); | ||
|
|
||
| size_t expiry_size = 0; | ||
| if (expiry != EXPIRY_NONE) expiry_size = sizeof(expiry); | ||
| sds new_entry = entryConstruct(alloc_size, field, value, expiry, false, SDS_TYPE_8, expiry_size, sizeof(value), field_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this already changed with #2794 so we might just fix this over the rebase.
| if (entryHasExpiry(entry)) mem += sizeof(long long); | ||
| } | ||
| mem += sdsAllocSize(entryGetValue(entry)); | ||
| mem += sdsAllocSize((sds)entryGetValue(entry, NULL)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. we should probably separate into 2 cases:
when we have stringRef (ie take the len) and when we have sds value ref, we should use the sdsAllocSize
| /* Structure representing a non-owning view of a buffer. | ||
| * A stringRef struct does not manage the underlying memory, so its destruction | ||
| * will not free the buffer. */ | ||
| typedef struct stringRef { | ||
| const char *buf; /* Pointer to the externalized buffer */ | ||
| size_t len; /* Length of the buffer */ | ||
| } stringRef; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we kept it here for other occasions we might want to use it (eg scan).
I think I am fine with repeat internal re-implemet this, but why is it so bad to have this type?
| entry *entry = *entry_ref; | ||
| long long expiry = entryGetExpiry(entry); | ||
| void *new_entry = entryUpdateAsStringRef(entry, buf, len, expiry); | ||
| serverAssert(hashtableReplaceReallocatedEntry(ht, entry, new_entry)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be true, but we already fail apply your suggestion through many places in the code.
However since this is a small refactor, lets do it.
| size_t len; | ||
| sds field = entryGetField(hi.next); | ||
| char *value_str = entryGetValue(hi.next, &len); | ||
| long long expiry = entryGetExpiry(hi.next); | ||
| /* Add a field-value pair to a new hash object. */ | ||
| entry *entry = entryCreate(field, sdsdup(value), expiry); | ||
| sds value = sdsnewlen(value_str, len); | ||
| entry *entry = entryCreate(field, value, expiry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that the entry lacks some explicit API for duplicate does not mean it needs to be implemented only for a single case. The code here is not doing anything intrusive to the entry, just using the offered API.
We can introduce a duplicate API but IMO it is negligible
Overview
Sharing memory between the module and engine reduces memory overhead by eliminating redundant copies of stored records in the module. This is particularly beneficial for search workloads that require indexing large volumes of documents.
Vectors
Vector similarity search requires storing large volumes of high-cardinality vectors. For example, a single vector with 512 dimensions consumes 2048 bytes, and typical workloads often involve millions of vectors. Due to the lack of a memory-sharing mechanism between the module and the engine, valkey-search currently doubles memory consumption when indexing vectors, significantly increasing operational costs. This limitation introduces adoption friction and reduces valkey-search's competitiveness.
Memory Allocation Strategy
At a fundamental level, there are two primary allocation strategies:
For valkey-search, it is crucial that vectors reside in cache-aligned memory to maximize SIMD optimizations. Allowing the module to allocate memory provides greater flexibility for different use cases, though it introduces slightly higher implementation complexity.
Old Implementation
The old implementation was based on ref-counting and introduced a new SDS type. After further discussion, we agreed to simplify the design by removing ref-counting and avoiding the introduction of a new SDS type.
New Implementation - Key Points
VM_HashSetViewValue, which set value as a view of a buffer which is owned by the module. The function accepts the hash key, hash field, and a buffer along with its length.ViewValueis a new data type that captures the externalized buffer and its length.valkey-search Usage
Insertion
VM_HashSetViewValueto avoid keeping two copies of the vector.Deletion
When receiving a key space notification for a deleted hash key or hash field that was indexed as a vector, valkey-search deletes the corresponding entry from the index.
Update
Handled similarly to insertion.