1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
/*
* Key Metadata (keymeta)
*
* High-level idea
* ----------------
* keymeta is a framework for attaching & maintaining metadata to keys.
*
* - Up to 8 different metadata classes can be registered globally. First one
* is reserved for EXPIRE.
* - Each class has a unique ID and name, like modules datat-types names, yet
* it has its own namespace.
* - Each key metadata class provides a set of callbacks for key lifecycle operations,
* ensuring consistent handling across copy, rename, logical removal (unlink),
* actual deallocation (free), persistence (RDB/AOF), and defragmentation.
* - Each key can carry up to 8 independent metadata values. Each value is related
* to a specific metadata class.
* - The 8-byte slot can either hold inline data or a pointer/handle to a larger,
* externally managed structure.
*
* Relation to other components
* ----------------------------
* - kvobj: 8 bits metabits field in kvobj is used to indicate active metadata.
* bit number corresponds to class ID.
* - Expiration: class ID 0 is reserved for TTL/expire;
* - Registration: redisServer.keyMetaClass[] stores registered classes. Modules
* register via keyMetaClassCreate (see redismodule.h) and may provide callbacks
* for persistence, copy/rename behavior, and lifecycle hooks (unlink/free).
* - modules: modules can register metadata classes and provide callbacks.
*/
#ifndef __KEYMETA_H
#define __KEYMETA_H
#include <stdint.h>
#include <stddef.h>
#include "sds.h"
#include "object.h"
/* fwd decls */
struct redisDb;
struct redisObject;
struct RedisModuleIO;
struct RedisModuleKeyOptCtx;
struct RedisModuleDefragCtx;
struct RedisModule;
typedef int KeyMetaClassId; /* Index into redisServer.keyMetaClass[] */
/* kvmeta - Metadata to be attached to kvobj */
#define KEY_META_ID_EXPIRE 0 /* Must be first */
/* IDs 1..7 are available for modules */
#define KEY_META_ID_MODULE_FIRST 1
#define KEY_META_ID_MODULE_LAST 7
#define KEY_META_ID_MAX 8
#define KEY_META_MAX_NUM_MODULES (KEY_META_ID_MODULE_LAST - KEY_META_ID_MODULE_FIRST + 1)
#define KEY_META_MASK_NONE 0
#define KEY_META_MASK_MODULES (((1U << KEY_META_MAX_NUM_MODULES) - 1) << KEY_META_ID_MODULE_FIRST)
#define KEY_META_MASK_EXPIRE (1U << KEY_META_ID_EXPIRE)
/* RDB load callback: Return 1 to attach, 0 to skip, -1 on error */
typedef int (*KeyMetaLoadFunc)(RedisModuleIO *rdb, uint64_t *meta, int encver);
typedef void (*KeyMetaSaveFunc)(RedisModuleIO *rdb, void *value, uint64_t *meta);
typedef void (*KeyMetaAOFRewriteFunc)(RedisModuleIO *aof, void *value, uint64_t meta);
typedef void (*KeyMetaFreeFunc)(const char *keyname, uint64_t meta);
typedef int (*KeyMetaCopyFunc)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
typedef int (*KeyMetaRenameFunc)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
typedef int (*KeyMetaDefragFunc)(RedisModuleDefragCtx *ctx, RedisModuleString *keyname, uint64_t meta);
typedef size_t (*KeyMetaMemUsageFunc)(struct RedisModuleKeyOptCtx *ctx, size_t sample_size, uint64_t meta);
typedef size_t (*KeyMetaFreeEffortFunc)(struct RedisModuleKeyOptCtx *ctx, uint64_t meta);
typedef void (*KeyMetaUnlinkFunc)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
typedef int (*KeyMetaMoveFunc)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
/* For explanation, see struct RedisModuleKeyMetaClassConfig */
typedef struct KeyMetaClassConf {
#define KEY_META_FLAGS_RDB_MASK 0x7 /* First 3 flags are serialized into RDB with key */
#define KEY_META_FLAG_ALLOW_IGNORE 0 /* Aligned with: REDISMODULE_META_ALLOW_IGNORE */
#define KEY_META_FLAG_RBB_RESERVED_1 1 /* Reserved for future use */
#define KEY_META_FLAG_RBB_RESERVED_2 2 /* Reserved for future use */
uint64_t flags;
/* Sentinel value meaning "no resource attached". It guarantees callbacks are
* ONLY invoked when meta != reset_value. This prevents double-free, avoids
* persisting sentinels to RDB/AOF, and simplifies module logic. */
uint64_t reset_value;
int (*copy)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
int (*rename)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
int (*move)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
void (*unlink)(struct RedisModuleKeyOptCtx *ctx, uint64_t *meta);
void (*free)(const char *keyname, uint64_t meta);
int (*rdb_load)(struct RedisModuleIO *rdb, uint64_t *meta, int metaver);
void (*rdb_save)(struct RedisModuleIO *rdb, void *value, uint64_t *meta);
void (*aof_rewrite)(struct RedisModuleIO *aof, void *value, uint64_t meta);
/****************************** TBD: ******************************/
int (*defrag) (struct RedisModuleDefragCtx *ctx, struct redisObject *key, uint64_t meta);
size_t (*mem_usage)(struct RedisModuleKeyOptCtx *ctx, size_t sample_size, uint64_t meta);
size_t (*free_effort)(struct RedisModuleKeyOptCtx *ctx, uint64_t meta);
} KeyMetaClassConf;
/* KeyMetaSpec - Used by dbAddInternal() to describe metadata of a new key */
typedef struct KeyMetaSpec {
uint16_t numMeta; /* Num active metadata entries. Aligned with metabits */
uint16_t metabits;
/* Array of metadata values. Entries are populated in reverse order
* (from the end of the array backward) to make bulk copying with
* memcpy more efficient. During insertion, the next slot is:
* meta[KEY_META_ID_MAX - (++numMeta)]
*
* For example if numMeta=2, and metabits=0b101, then the last entry holds
* value for class 0, and the previous entry holds value for class 2.
*/
uint64_t meta[KEY_META_ID_MAX];
} KeyMetaSpec;
/* init Keys metadata on server startup */
void keyMetaInit(void);
/* Key metadata event callbacks */
void keyMetaOnUnlink(struct redisDb *db, robj *key,kvobj *kv);
void keyMetaOnFree(kvobj *kv);
void keyMetaOnRename(struct redisDb *db, kvobj *kv, robj *oldKey, robj *newKey, KeyMetaSpec *kms);
void keyMetaOnMove(kvobj *kv, robj *key, int srcDbId, int dstDbId, KeyMetaSpec *kms);
void keyMetaOnCopy(kvobj *kv, robj *srcKey, robj *dstKey, int srcDbId, int dstDbId, KeyMetaSpec *kms);
int keyMetaOnAof(rio *r, robj *key, kvobj *kv, int dbid);
/* RDB serialization */
int rdbSaveKeyMetadata(rio *rdb, robj *key, kvobj *kv, int dbid);
int rdbLoadKeyMetadata(rio *rdb, int dbid, int numClasses, KeyMetaSpec *kms);
void keyMetaResetModuleValues(kvobj *kv);
void keyMetaTransition(kvobj *kvOld, kvobj *kvNew);
/* return 0 if failed to create. Otherwise return handle (between 1 and 7) */
KeyMetaClassId keyMetaClassCreate(struct RedisModule *ctx, const char *metaname, int metaver, KeyMetaClassConf *conf);
/* Destroy (release) a previously created class. Return 1 on success, 0 on failure. */
int keyMetaClassRelease(KeyMetaClassId class_id);
kvobj *keyMetaSetMetadata(struct redisDb *db, kvobj *kv, KeyMetaClassId kmcId, uint64_t metadata);
int keyMetaGetMetadata(KeyMetaClassId kmcId, kvobj *kv, uint64_t *metadata);
int keyMetaRemoveMetadata(KeyMetaClassId kmcId, RedisModuleKey *key);
/* bit operations on metabits */
static inline uint32_t getNumMeta(uint16_t metabits);
static inline uint32_t getModuleMetaBits(uint16_t metabits);
/********** Inline functions **********/
static inline void keyMetaResetValues(kvobj *kv) {
if (unlikely(kv->metabits & KEY_META_MASK_MODULES))
keyMetaResetModuleValues(kv);
/* Must be first meta (optimized) */
if (kv->metabits & KEY_META_MASK_EXPIRE)
((uint64_t *)kv)[-1] = -1;
}
static inline void keyMetaSpecInit(KeyMetaSpec *keymeta) {
/* Enough to init metabits and numMeta. meta[] is not used. */
keymeta->metabits = 0;
keymeta->numMeta = 0;
}
/* Add metadata to keymeta spec. metaid must be in range 0..7 and added in order! */
void keyMetaSpecAdd(KeyMetaSpec *keymeta, int metaid, uint64_t metaval);
/* Free any metadata stored in a KeyMetaSpec. This is called when RDB load fails after
* some metadata has been loaded. It invokes the free cb for each metadata class that
* was already loaded, preventing memory leaks from partially-loaded metadata. */
void keyMetaSpecCleanup(KeyMetaSpec *kms);
static inline uint32_t getNumMeta(uint16_t metabits) {
/* Assumed expire is always first meta */
return __builtin_popcount(metabits);
}
static inline uint32_t getModuleMetaBits(uint16_t metabits) {
return metabits & KEY_META_MASK_MODULES;
}
#endif // __KEYMETA_H
|