
# Refactor client structure to use modular data components ## Current State The client structure allocates memory for replication / pubsub / multi-keys / module / blocked data for every client, despite these features being used by only a small subset of clients. In addition the current field layout in the client struct is suboptimal, with poor alignment and unnecessary padding between fields, leading to a larger than necessary memory footprint of 896 bytes per client. Furthermore, fields that are frequently accessed together during operations are scattered throughout the struct, resulting in poor cache locality. ## This PR's Change 1. Lazy Initialization - **Components are only allocated when first used:** - PubSubData: Created on first SUBSCRIBE/PUBLISH operation - ReplicationData: Initialized only for replica connections - ModuleData: Allocated when module interaction begins - BlockingState: Created when first blocking command is issued - MultiState: Initialized on MULTI command 2. Memory Layout Optimization: - Grouped related fields for better locality - Moved rarely accessed fields (e.g., client->name) to struct end - Optimized field alignment to eliminate padding 3. Additional changes: - Moved watched_keys to be static allocated in the `mstate` struct - Relocated replication init logic to replication.c ### Key Benefits - **Efficient Memory Usage:** - 45% smaller base client structure - Basic clients now use 528 bytes (down from 896). - Better memory locality for related operations - Performance improvement in high throughput scenarios. No performance regressions in other cases. ### Performance Impact Tested with 650 clients and 512 bytes values. #### Single Thread Performance | Operation | Dataset | New (ops/sec) | Old (ops/sec) | Change % | |------------|---------|---------------|---------------|-----------| | SET | 1 key | 261,799 | 258,261 | +1.37% | | SET | 3M keys | 209,134 | ~209,000 | ~0% | | GET | 1 key | 281,564 | 277,965 | +1.29% | | GET | 3M keys | 231,158 | 228,410 | +1.20% | #### 8 IO Threads Performance | Operation | Dataset | New (ops/sec) | Old (ops/sec) | Change % | |------------|---------|---------------|---------------|-----------| | SET | 1 key | 1,331,578 | 1,331,626 | -0.00% | | SET | 3M keys | 1,254,441 | 1,152,645 | +8.83% | | GET | 1 key | 1,293,149 | 1,289,503 | +0.28% | | GET | 3M keys | 1,152,898 | 1,101,791 | +4.64% | #### Pipeline Performance (3M keys) | Operation | Pipeline Size | New (ops/sec) | Old (ops/sec) | Change % | |-----------|--------------|---------------|---------------|-----------| | SET | 10 | 548,964 | 538,498 | +1.94% | | SET | 20 | 606,148 | 594,872 | +1.89% | | SET | 30 | 631,122 | 616,606 | +2.35% | | GET | 10 | 628,482 | 624,166 | +0.69% | | GET | 20 | 687,371 | 681,659 | +0.84% | | GET | 30 | 725,855 | 721,102 | +0.66% | ### Observations: 1. Single-threaded operations show consistent improvements (1-1.4%) 2. Multi-threaded performance shows significant gains for large datasets: - SET with 3M keys: +8.83% improvement - GET with 3M keys: +4.64% improvement 3. Pipeline operations show consistent improvements: - SET operations: +1.89% to +2.35% - GET operations: +0.66% to +0.84% 4. No performance regressions observed in any test scenario Related issue:https://github.com/valkey-io/valkey/issues/761 --------- Signed-off-by: Uri Yagelnik <uriy@amazon.com> Signed-off-by: uriyage <78144248+uriyage@users.noreply.github.com> Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
234 lines
12 KiB
C
234 lines
12 KiB
C
#ifndef _MODULE_H_
|
|
#define _MODULE_H_
|
|
|
|
/* This header file exposes a set of functions defined in module.c that are
|
|
* not part of the module API, but are used by the core to interact with modules
|
|
*/
|
|
|
|
/* Extract encver / signature from a module type ID. */
|
|
#define VALKEYMODULE_TYPE_ENCVER_BITS 10
|
|
#define VALKEYMODULE_TYPE_ENCVER_MASK ((1 << VALKEYMODULE_TYPE_ENCVER_BITS) - 1)
|
|
#define VALKEYMODULE_TYPE_ENCVER(id) ((id) & VALKEYMODULE_TYPE_ENCVER_MASK)
|
|
#define VALKEYMODULE_TYPE_SIGN(id) \
|
|
(((id) & ~((uint64_t)VALKEYMODULE_TYPE_ENCVER_MASK)) >> VALKEYMODULE_TYPE_ENCVER_BITS)
|
|
|
|
/* Bit flags for moduleTypeAuxSaveFunc */
|
|
#define VALKEYMODULE_AUX_BEFORE_RDB (1 << 0)
|
|
#define VALKEYMODULE_AUX_AFTER_RDB (1 << 1)
|
|
|
|
struct ValkeyModule;
|
|
struct ValkeyModuleIO;
|
|
struct ValkeyModuleDigest;
|
|
struct ValkeyModuleCtx;
|
|
struct moduleLoadQueueEntry;
|
|
struct ValkeyModuleKeyOptCtx;
|
|
struct ValkeyModuleCommand;
|
|
struct clusterState;
|
|
|
|
/* Each module type implementation should export a set of methods in order
|
|
* to serialize and deserialize the value in the RDB file, rewrite the AOF
|
|
* log, create the digest for "DEBUG DIGEST", and free the value when a key
|
|
* is deleted. */
|
|
typedef void *(*moduleTypeLoadFunc)(struct ValkeyModuleIO *io, int encver);
|
|
typedef void (*moduleTypeSaveFunc)(struct ValkeyModuleIO *io, void *value);
|
|
typedef int (*moduleTypeAuxLoadFunc)(struct ValkeyModuleIO *rdb, int encver, int when);
|
|
typedef void (*moduleTypeAuxSaveFunc)(struct ValkeyModuleIO *rdb, int when);
|
|
typedef void (*moduleTypeRewriteFunc)(struct ValkeyModuleIO *io, struct serverObject *key, void *value);
|
|
typedef void (*moduleTypeDigestFunc)(struct ValkeyModuleDigest *digest, void *value);
|
|
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
|
typedef void (*moduleTypeFreeFunc)(void *value);
|
|
typedef size_t (*moduleTypeFreeEffortFunc)(struct serverObject *key, const void *value);
|
|
typedef void (*moduleTypeUnlinkFunc)(struct serverObject *key, void *value);
|
|
typedef void *(*moduleTypeCopyFunc)(struct serverObject *fromkey, struct serverObject *tokey, const void *value);
|
|
typedef int (*moduleTypeDefragFunc)(struct ValkeyModuleDefragCtx *ctx, struct serverObject *key, void **value);
|
|
typedef size_t (*moduleTypeMemUsageFunc2)(struct ValkeyModuleKeyOptCtx *ctx, const void *value, size_t sample_size);
|
|
typedef void (*moduleTypeFreeFunc2)(struct ValkeyModuleKeyOptCtx *ctx, void *value);
|
|
typedef size_t (*moduleTypeFreeEffortFunc2)(struct ValkeyModuleKeyOptCtx *ctx, const void *value);
|
|
typedef void (*moduleTypeUnlinkFunc2)(struct ValkeyModuleKeyOptCtx *ctx, void *value);
|
|
typedef void *(*moduleTypeCopyFunc2)(struct ValkeyModuleKeyOptCtx *ctx, const void *value);
|
|
typedef int (*moduleTypeAuthCallback)(struct ValkeyModuleCtx *ctx, void *username, void *password, const char **err);
|
|
|
|
|
|
/* The module type, which is referenced in each value of a given type, defines
|
|
* the methods and links to the module exporting the type. */
|
|
typedef struct ValkeyModuleType {
|
|
uint64_t id; /* Higher 54 bits of type ID + 10 lower bits of encoding ver. */
|
|
struct ValkeyModule *module;
|
|
moduleTypeLoadFunc rdb_load;
|
|
moduleTypeSaveFunc rdb_save;
|
|
moduleTypeRewriteFunc aof_rewrite;
|
|
moduleTypeMemUsageFunc mem_usage;
|
|
moduleTypeDigestFunc digest;
|
|
moduleTypeFreeFunc free;
|
|
moduleTypeFreeEffortFunc free_effort;
|
|
moduleTypeUnlinkFunc unlink;
|
|
moduleTypeCopyFunc copy;
|
|
moduleTypeDefragFunc defrag;
|
|
moduleTypeAuxLoadFunc aux_load;
|
|
moduleTypeAuxSaveFunc aux_save;
|
|
moduleTypeMemUsageFunc2 mem_usage2;
|
|
moduleTypeFreeEffortFunc2 free_effort2;
|
|
moduleTypeUnlinkFunc2 unlink2;
|
|
moduleTypeCopyFunc2 copy2;
|
|
moduleTypeAuxSaveFunc aux_save2;
|
|
int aux_save_triggers;
|
|
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
|
} moduleType;
|
|
|
|
/* In Object 'robj' structures of type OBJ_MODULE, the value pointer
|
|
* is set to the following structure, referencing the moduleType structure
|
|
* in order to work with the value, and at the same time providing a raw
|
|
* pointer to the value, as created by the module commands operating with
|
|
* the module type.
|
|
*
|
|
* So for example in order to free such a value, it is possible to use
|
|
* the following code:
|
|
*
|
|
* if (robj->type == OBJ_MODULE) {
|
|
* moduleValue *mt = robj->ptr;
|
|
* mt->type->free(mt->value);
|
|
* zfree(mt); // We need to release this in-the-middle struct as well.
|
|
* }
|
|
*/
|
|
typedef struct moduleValue {
|
|
moduleType *type;
|
|
void *value;
|
|
} moduleValue;
|
|
|
|
/* This structure represents a module inside the system. */
|
|
typedef struct ValkeyModule {
|
|
void *handle; /* Module dlopen() handle. */
|
|
char *name; /* Module name. */
|
|
int ver; /* Module version. We use just progressive integers. */
|
|
int apiver; /* Module API version as requested during initialization.*/
|
|
list *types; /* Module data types. */
|
|
list *usedby; /* List of modules using APIs from this one. */
|
|
list *using; /* List of modules we use some APIs of. */
|
|
list *filters; /* List of filters the module has registered. */
|
|
list *module_configs; /* List of configurations the module has registered */
|
|
int configs_initialized; /* Have the module configurations been initialized? */
|
|
int in_call; /* RM_Call() nesting level */
|
|
int in_hook; /* Hooks callback nesting level for this module (0 or 1). */
|
|
int options; /* Module options and capabilities. */
|
|
int blocked_clients; /* Count of ValkeyModuleBlockedClient in this module. */
|
|
ValkeyModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
|
|
ValkeyModuleDefragFunc defrag_cb; /* Callback for global data defrag. */
|
|
struct moduleLoadQueueEntry *loadmod; /* Module load arguments for config rewrite. */
|
|
int num_commands_with_acl_categories; /* Number of commands in this module included in acl categories */
|
|
int onload; /* Flag to identify if the call is being made from Onload (0 or 1) */
|
|
size_t num_acl_categories_added; /* Number of acl categories added by this module. */
|
|
} ValkeyModule;
|
|
|
|
/* This is a wrapper for the 'rio' streams used inside rdb.c in the server, so that
|
|
* the user does not have to take the total count of the written bytes nor
|
|
* to care about error conditions. */
|
|
typedef struct ValkeyModuleIO {
|
|
size_t bytes; /* Bytes read / written so far. */
|
|
rio *rio; /* Rio stream. */
|
|
moduleType *type; /* Module type doing the operation. */
|
|
int error; /* True if error condition happened. */
|
|
ValkeyModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
|
robj *key; /* Optional name of key processed */
|
|
int dbid; /* The dbid of the key being processed, -1 when unknown. */
|
|
sds pre_flush_buffer; /* A buffer that should be flushed before next write operation
|
|
* See rdbSaveSingleModuleAux for more details */
|
|
} ValkeyModuleIO;
|
|
|
|
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
|
* inside rdb.c according to the version of the value to load. */
|
|
static inline void moduleInitIOContext(ValkeyModuleIO *iovar,
|
|
moduleType *mtype,
|
|
rio *rioptr,
|
|
robj *keyptr,
|
|
int db) {
|
|
iovar->rio = rioptr;
|
|
iovar->type = mtype;
|
|
iovar->bytes = 0;
|
|
iovar->error = 0;
|
|
iovar->key = keyptr;
|
|
iovar->dbid = db;
|
|
iovar->ctx = NULL;
|
|
iovar->pre_flush_buffer = NULL;
|
|
}
|
|
|
|
/* This is a structure used to export DEBUG DIGEST capabilities to
|
|
* modules. We want to capture both the ordered and unordered elements of
|
|
* a data structure, so that a digest can be created in a way that correctly
|
|
* reflects the values. See the DEBUG DIGEST command implementation for more
|
|
* background. */
|
|
typedef struct ValkeyModuleDigest {
|
|
unsigned char o[20]; /* Ordered elements. */
|
|
unsigned char x[20]; /* Xored elements. */
|
|
robj *key; /* Optional name of key processed */
|
|
int dbid; /* The dbid of the key being processed */
|
|
} ValkeyModuleDigest;
|
|
|
|
/* Just start with a digest composed of all zero bytes. */
|
|
static inline void moduleInitDigestContext(ValkeyModuleDigest *mdvar) {
|
|
memset(mdvar->o, 0, sizeof(mdvar->o));
|
|
memset(mdvar->x, 0, sizeof(mdvar->x));
|
|
}
|
|
|
|
void moduleEnqueueLoadModule(sds path, sds *argv, int argc);
|
|
sds moduleLoadQueueEntryToLoadmoduleOptionStr(ValkeyModule *module,
|
|
const char *config_option_str);
|
|
ValkeyModuleCtx *moduleAllocateContext(void);
|
|
void moduleScriptingEngineInitContext(ValkeyModuleCtx *out_ctx,
|
|
ValkeyModule *module,
|
|
client *client);
|
|
void moduleFreeContext(ValkeyModuleCtx *ctx);
|
|
void moduleInitModulesSystem(void);
|
|
void moduleInitModulesSystemLast(void);
|
|
void modulesCron(void);
|
|
int moduleLoad(const char *path, void **argv, int argc, int is_loadex);
|
|
int moduleUnload(sds name, const char **errmsg);
|
|
void moduleLoadFromQueue(void);
|
|
int moduleGetCommandKeysViaAPI(struct serverCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
|
int moduleGetCommandChannelsViaAPI(struct serverCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
|
moduleType *moduleTypeLookupModuleByID(uint64_t id);
|
|
moduleType *moduleTypeLookupModuleByName(const char *name);
|
|
moduleType *moduleTypeLookupModuleByNameIgnoreCase(const char *name);
|
|
void moduleTypeNameByID(char *name, uint64_t moduleid);
|
|
const char *moduleTypeModuleName(moduleType *mt);
|
|
const char *moduleNameFromCommand(struct serverCommand *cmd);
|
|
void moduleFreeContext(ValkeyModuleCtx *ctx);
|
|
void moduleCallCommandUnblockedHandler(client *c);
|
|
int isModuleClientUnblocked(client *c);
|
|
void unblockClientFromModule(client *c);
|
|
void moduleHandleBlockedClients(void);
|
|
void moduleBlockedClientTimedOut(client *c, int from_module);
|
|
void modulePipeReadable(aeEventLoop *el, int fd, void *privdata, int mask);
|
|
size_t moduleCount(void);
|
|
void moduleAcquireGIL(void);
|
|
int moduleTryAcquireGIL(void);
|
|
void moduleReleaseGIL(void);
|
|
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
|
void firePostExecutionUnitJobs(void);
|
|
void moduleCallCommandFilters(client *c);
|
|
void modulePostExecutionUnitOperations(void);
|
|
void ModuleForkDoneHandler(int exitcode, int bysignal);
|
|
int TerminateModuleForkChild(int child_pid, int wait);
|
|
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
|
int moduleAllDatatypesHandleErrors(void);
|
|
int moduleAllModulesHandleReplAsyncLoad(void);
|
|
sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int sections);
|
|
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
|
void processModuleLoadingProgressEvent(int is_aof);
|
|
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
|
void moduleUnblockClient(client *c);
|
|
int moduleBlockedClientMayTimeout(client *c);
|
|
int moduleClientIsBlockedOnKeys(client *c);
|
|
void moduleNotifyUserChanged(client *c);
|
|
void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags);
|
|
size_t moduleGetFreeEffort(robj *key, robj *val, int dbid);
|
|
size_t moduleGetMemUsage(robj *key, robj *val, size_t sample_size, int dbid);
|
|
robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj *value);
|
|
int moduleDefragValue(robj *key, robj *obj, int dbid);
|
|
int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, monotime endtime, int dbid);
|
|
void moduleDefragGlobals(void);
|
|
void *moduleGetHandleByName(char *modulename);
|
|
int moduleIsModuleCommand(void *module_handle, struct serverCommand *cmd);
|
|
void freeClientModuleData(client *c);
|
|
|
|
#endif /* _MODULE_H_ */
|