-
Couldn't load subscription status.
- Fork 24.3k
RED-167994: shared-strings API #14425
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -3127,6 +3127,7 @@ standardConfig static_configs[] = { | |
| createBoolConfig("hide-user-data-from-log", NULL, MODIFIABLE_CONFIG, server.hide_user_data_from_log, 0, NULL, NULL), | ||
| createBoolConfig("lazyexpire-nested-arbitrary-keys", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.lazyexpire_nested_arbitrary_keys, 1, NULL, NULL), | ||
| createBoolConfig("cluster-slot-stats-enabled", NULL, MODIFIABLE_CONFIG, server.cluster_slot_stats_enabled, 0, NULL, NULL), | ||
| createBoolConfig("shared-strings-enabled", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, server.shared_strings_enabled, 1, NULL, NULL), | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think we can find a better name for this config first we need to decide if we can see this feautre used in redis core or only for modules in addition, since we are already adding this config, let's make use it - make sure it's a only-startup config and edit sobj.c and possibly module.c accordingly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. basically it's a hint for data-type codepaths, to know if they should use shared-strings in ther implementations if the core decides this opt will never be relevant, than the "right" thing to do it to rem it here and add it only on our fork instead of creating some stub usage for that | ||
| | ||
| /* String Configs */ | ||
| createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL), | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -139,6 +139,20 @@ typedef struct { | |
| unsigned long cursor; | ||
| } defragModuleCtx; | ||
| | ||
| /* Defrag helper for bare-bone allocation without freeing old pointer, and ignoring defrag hint. */ | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oranagra @moticless how are we feeling about the changes in defrag.c? note that the purpose of these changes is to allow if we think that anyway we would need these sorts of "defrag-no-free" changes in the future then all is good, but generally speaking, since the purpose is to duplicate an object without using tcache, maybe a better approach is to create "dup-no-tcache" functions in object.c) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question is if we think that allocating an object without using tcache is more of a defrag.c code or object.c There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think the reason to use NoTcache is defrag related, so i'm ok adding such capabilities to defrag.c There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main issue was with the possibility that robj will be defraged and sds will not. this could lead to robjA freeing an sds that's still in use by robjB. solution was to tie them together (if one's deffragged, so must the other..). on another note - after rediscussing with @inbaryuval and @guybe7 we decided to not add the feature to OSS yet, because of other features that redis supports (like async-flush) or will support (like ASM) that'll break the current implementation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so are you going to do that (if one is duplicated, duplicate both, but if neither required being moved, don't move either)? | ||
| void* activeDefragAllocWithoutHint(void *ptr) { | ||
| size_t size; | ||
| void *newptr; | ||
| /* move this allocation to a new allocation. | ||
| * make sure not to use the thread cache. so that we don't get back the same | ||
| * pointers we try to free */ | ||
| size = zmalloc_usable_size(ptr); | ||
| newptr = zmalloc_no_tcache(size); | ||
| memcpy(newptr, ptr, size); | ||
| server.stat_active_defrag_hits++; | ||
| return newptr; | ||
| } | ||
| | ||
| /* this method was added to jemalloc in order to help us understand which | ||
| * pointers are worthwhile moving and which aren't */ | ||
| int je_get_defrag_hint(void* ptr); | ||
| | @@ -149,20 +163,11 @@ int je_get_defrag_hint(void* ptr); | |
| * Note: The caller is responsible for freeing the old pointer if this function | ||
| * returns a non-NULL value. */ | ||
| void* activeDefragAllocWithoutFree(void *ptr) { | ||
| size_t size; | ||
| void *newptr; | ||
| if(!je_get_defrag_hint(ptr)) { | ||
| server.stat_active_defrag_misses++; | ||
| return NULL; | ||
| } | ||
| /* move this allocation to a new allocation. | ||
| * make sure not to use the thread cache. so that we don't get back the same | ||
| * pointers we try to free */ | ||
| size = zmalloc_usable_size(ptr); | ||
| newptr = zmalloc_no_tcache(size); | ||
| memcpy(newptr, ptr, size); | ||
| server.stat_active_defrag_hits++; | ||
| return newptr; | ||
| return activeDefragAllocWithoutHint(ptr); | ||
| } | ||
| | ||
| void activeDefragFree(void *ptr) { | ||
| | @@ -227,10 +232,10 @@ void activeDefragFreeRaw(void *ptr) { | |
| * | ||
| * returns NULL in case the allocation wasn't moved. | ||
| * when it returns a non-null value, the old pointer was already released | ||
| * and should NOT be accessed. */ | ||
| sds activeDefragSds(sds sdsptr) { | ||
| * and should NOT be accessed (unless duplicate was specified). */ | ||
| sds activeDefragSdsLogic(sds sdsptr, int duplicate) { | ||
| void* ptr = sdsAllocPtr(sdsptr); | ||
| void* newptr = activeDefragAlloc(ptr); | ||
| void* newptr = duplicate ? activeDefragAllocWithoutHint(ptr) : activeDefragAlloc(ptr); | ||
| if (newptr) { | ||
| size_t offset = sdsptr - (char*)ptr; | ||
| sdsptr = (char*)newptr + offset; | ||
| | @@ -239,6 +244,10 @@ sds activeDefragSds(sds sdsptr) { | |
| return NULL; | ||
| } | ||
| | ||
| sds activeDefragSds(sds sdsptr) { | ||
| return activeDefragSdsLogic(sdsptr, 0); | ||
| } | ||
| | ||
| /* Defrag helper for hfield strings | ||
| * | ||
| * returns NULL in case the allocation wasn't moved. | ||
| | @@ -284,30 +293,31 @@ void *activeDefragHfieldAndUpdateRef(void *ptr, void *privdata) { | |
| * reference count is not 1, in these cases, the caller must explicitly pass | ||
| * in the reference count, otherwise defragmentation will not be performed. | ||
| * Note that the caller is responsible for updating any other references to the robj. */ | ||
| robj *activeDefragStringObEx(robj* ob, int expected_refcount) { | ||
| robj *activeDefragStringObEx(robj* ob, int expected_refcount, int duplicate) { | ||
| void* (*defragAllocator)(void*) = duplicate ? activeDefragAllocWithoutHint : activeDefragAlloc; | ||
| robj *ret = NULL; | ||
| if (ob->refcount!=expected_refcount) | ||
| return NULL; | ||
| | ||
| /* try to defrag robj (only if not an EMBSTR type (handled below). */ | ||
| if (ob->type!=OBJ_STRING || ob->encoding!=OBJ_ENCODING_EMBSTR) { | ||
| if ((ret = activeDefragAlloc(ob))) { | ||
| if ((ret = defragAllocator(ob))) { | ||
| ob = ret; | ||
| } | ||
| } | ||
| | ||
| /* try to defrag string object */ | ||
| if (ob->type == OBJ_STRING) { | ||
| if(ob->encoding==OBJ_ENCODING_RAW) { | ||
| sds newsds = activeDefragSds((sds)ob->ptr); | ||
| sds newsds = activeDefragSdsLogic((sds)ob->ptr, duplicate); | ||
| if (newsds) { | ||
| ob->ptr = newsds; | ||
| } | ||
| } else if (ob->encoding==OBJ_ENCODING_EMBSTR) { | ||
| /* The sds is embedded in the object allocation, calculate the | ||
| * offset and update the pointer in the new allocation. */ | ||
| long ofs = (intptr_t)ob->ptr - (intptr_t)ob; | ||
| if ((ret = activeDefragAlloc(ob))) { | ||
| if ((ret = defragAllocator(ob))) { | ||
| ret->ptr = (void*)((intptr_t)ret + ofs); | ||
| } | ||
| } else if (ob->encoding!=OBJ_ENCODING_INT) { | ||
| | @@ -323,7 +333,7 @@ robj *activeDefragStringObEx(robj* ob, int expected_refcount) { | |
| * when it returns a non-null value, the old pointer was already released | ||
| * and should NOT be accessed. */ | ||
| robj *activeDefragStringOb(robj* ob) { | ||
| return activeDefragStringObEx(ob, 1); | ||
| return activeDefragStringObEx(ob, 1, 0); | ||
| } | ||
| | ||
| /* Defrag helper for lua scripts | ||
| | @@ -1095,7 +1105,7 @@ void defragPubsubScanCallback(void *privdata, const dictEntry *de, dictEntryLink | |
| | ||
| /* Try to defrag the channel name. */ | ||
| serverAssert(channel->refcount == (int)dictSize(clients) + 1); | ||
| newchannel = activeDefragStringObEx(channel, dictSize(clients) + 1); | ||
| newchannel = activeDefragStringObEx(channel, dictSize(clients) + 1, 0); | ||
| if (newchannel) { | ||
| kvstoreDictSetKey(pubsub_channels, ctx->kvstate.slot, (dictEntry*)de, newchannel); | ||
| | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -45,6 +45,7 @@ | |
| #include "call_reply.h" | ||
| #include "hdr_histogram.h" | ||
| #include "crc16_slottable.h" | ||
| #include "sobj.h" | ||
| #include <dlfcn.h> | ||
| #include <sys/stat.h> | ||
| #include <sys/wait.h> | ||
| | @@ -380,6 +381,10 @@ typedef struct RedisModuleConfigIterator { | |
| int is_glob; /* Is the pattern a glob-pattern or a fixed string? */ | ||
| } RedisModuleConfigIterator; | ||
| | ||
| typedef struct RedisModulePool { | ||
| dict *pool; /* Intern pool of same-group objects. Implemented over a lookup-set (dict w/ `.no_value = 1`) */ | ||
| } RedisModulePool; | ||
| | ||
| /* Flags for moduleCreateArgvFromUserFormat(). */ | ||
| #define REDISMODULE_ARGV_REPLICATE (1<<0) | ||
| #define REDISMODULE_ARGV_NO_AOF (1<<1) | ||
| | @@ -2650,6 +2655,31 @@ RedisModuleString *RM_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t | |
| return o; | ||
| } | ||
| | ||
| RedisModulePool *RM_CreateSharePool(void) { | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really need several of these pools? maybe there should be only one pool, where all modules (and possibly redis core) save their shared strings to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. even if we do decide to have several pools (let's say one for each module and one for redis core) i think that the pool (dict) should be created on module initialization and kept within the so the API should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we should even be more explicit and define a new type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there are pros and cons for mono/multi-pool approaches. for e.g. single pool will have larger refcounts, which means longer time to defrag (in which 2 copies are sustained...). you may chose your approach please note the repeating argument is that you could functionally perform any of your suggestions with the existing API, whilst also being able to do other things we don't think on currently | ||
| RedisModulePool *pool = zmalloc(sizeof(*pool)); | ||
| pool->pool = sobj_init(); | ||
| | ||
| return pool; | ||
| } | ||
| | ||
| void RM_ReleaseSharePool(RedisModulePool *pool) { | ||
| sobj_release(pool->pool); | ||
| zfree(pool); | ||
| } | ||
| | ||
| RedisModuleString *RM_CreateSharedString(const char *ptr, size_t len, RedisModulePool *pool) { | ||
| return (RedisModuleString*)sobj_new(ptr, len, pool->pool); | ||
| } | ||
| | ||
| void RM_FreeSharedString(RedisModuleString *str, RedisModulePool *pool) { | ||
| sobj_free((sobj*)str, pool->pool); | ||
| } | ||
| | ||
| RedisModuleString *RM_DefragRedisModuleSharedString(RedisModuleDefragCtx *ctx, RedisModuleString *str, RedisModulePool *pool) { | ||
| UNUSED(ctx); | ||
| return (RedisModuleString*)sobj_defrag((sobj*)str, pool->pool); | ||
| } | ||
| | ||
| /* Create a new module string object from a printf format and arguments. | ||
| * The returned string must be freed with RedisModule_FreeString(), unless | ||
| * automatic memory is enabled. | ||
| | @@ -14640,6 +14670,10 @@ void moduleRegisterCoreAPI(void) { | |
| REGISTER_API(CreateStringFromStreamID); | ||
| REGISTER_API(CreateStringPrintf); | ||
| REGISTER_API(FreeString); | ||
| REGISTER_API(CreateSharePool); | ||
| REGISTER_API(ReleaseSharePool); | ||
| REGISTER_API(CreateSharedString); | ||
| REGISTER_API(FreeSharedString); | ||
| REGISTER_API(StringPtrLen); | ||
| REGISTER_API(AutoMemory); | ||
| REGISTER_API(Replicate); | ||
| | @@ -14897,6 +14931,7 @@ void moduleRegisterCoreAPI(void) { | |
| REGISTER_API(DefragAllocRaw); | ||
| REGISTER_API(DefragFreeRaw); | ||
| REGISTER_API(DefragRedisModuleString); | ||
| REGISTER_API(DefragRedisModuleSharedString); | ||
| REGISTER_API(DefragRedisModuleDict); | ||
| REGISTER_API(DefragShouldStop); | ||
| REGISTER_API(DefragCursorSet); | ||
| | ||
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.
unrelated: add test module