Skip to content

Commit e55568e

Browse files
authored
Add RM_RdbLoad and RM_RdbSave module API functions (redis#11852)
Add `RM_RdbLoad()` and `RM_RdbSave()` to load/save RDB files from the module API. In our use case, we have our clustering implementation as a module. As part of this implementation, the module needs to trigger RDB save operation at specific points. Also, this module delivers RDB files to other nodes (not using Redis' replication). When a node receives an RDB file, it should be able to load the RDB. Currently, there is no module API to save/load RDB files. This PR adds four new APIs: ```c RedisModuleRdbStream *RM_RdbStreamCreateFromFile(const char *filename); void RM_RdbStreamFree(RedisModuleRdbStream *stream); int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags); int RM_RdbSave(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags); ``` The first step is to create a `RedisModuleRdbStream` object. This PR provides a function to create RedisModuleRdbStream from the filename. (You can load/save RDB with the filename). In the future, this API can be extended if needed: e.g., `RM_RdbStreamCreateFromFd()`, `RM_RdbStreamCreateFromSocket()` to save/load RDB from an `fd` or a `socket`. Usage: ```c /* Save RDB */ RedisModuleRdbStream *stream = RedisModule_RdbStreamCreateFromFile("example.rdb"); RedisModule_RdbSave(ctx, stream, 0); RedisModule_RdbStreamFree(stream); /* Load RDB */ RedisModuleRdbStream *stream = RedisModule_RdbStreamCreateFromFile("example.rdb"); RedisModule_RdbLoad(ctx, stream, 0); RedisModule_RdbStreamFree(stream); ```
1 parent f263b6d commit e55568e

File tree

8 files changed

+563
-19
lines changed

8 files changed

+563
-19
lines changed

runtest-moduleapi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ $TCLSH tests/test_helper.tcl \
5454
--single unit/moduleapi/postnotifications \
5555
--single unit/moduleapi/async_rm_call \
5656
--single unit/moduleapi/moduleauth \
57+
--single unit/moduleapi/rdbloadsave \
5758
"${@}"

src/module.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12751,6 +12751,137 @@ int RM_LoadConfigs(RedisModuleCtx *ctx) {
1275112751
return REDISMODULE_OK;
1275212752
}
1275312753

12754+
/* --------------------------------------------------------------------------
12755+
* ## RDB load/save API
12756+
* -------------------------------------------------------------------------- */
12757+
12758+
#define REDISMODULE_RDB_STREAM_FILE 1
12759+
12760+
typedef struct RedisModuleRdbStream {
12761+
int type;
12762+
12763+
union {
12764+
char *filename;
12765+
} data;
12766+
} RedisModuleRdbStream;
12767+
12768+
/* Create a stream object to save/load RDB to/from a file.
12769+
*
12770+
* This function returns a pointer to RedisModuleRdbStream which is owned
12771+
* by the caller. It requires a call to RM_RdbStreamFree() to free
12772+
* the object. */
12773+
RedisModuleRdbStream *RM_RdbStreamCreateFromFile(const char *filename) {
12774+
RedisModuleRdbStream *stream = zmalloc(sizeof(*stream));
12775+
stream->type = REDISMODULE_RDB_STREAM_FILE;
12776+
stream->data.filename = zstrdup(filename);
12777+
return stream;
12778+
}
12779+
12780+
/* Release an RDB stream object. */
12781+
void RM_RdbStreamFree(RedisModuleRdbStream *stream) {
12782+
switch (stream->type) {
12783+
case REDISMODULE_RDB_STREAM_FILE:
12784+
zfree(stream->data.filename);
12785+
break;
12786+
default:
12787+
serverAssert(0);
12788+
break;
12789+
}
12790+
zfree(stream);
12791+
}
12792+
12793+
/* Load RDB file from the `stream`. Dataset will be cleared first and then RDB
12794+
* file will be loaded.
12795+
*
12796+
* `flags` must be zero. This parameter is for future use.
12797+
*
12798+
* On success REDISMODULE_OK is returned, otherwise REDISMODULE_ERR is returned
12799+
* and errno is set accordingly.
12800+
*
12801+
* Example:
12802+
*
12803+
* RedisModuleRdbStream *s = RedisModule_RdbStreamCreateFromFile("exp.rdb");
12804+
* RedisModule_RdbLoad(ctx, s, 0);
12805+
* RedisModule_RdbStreamFree(s);
12806+
*/
12807+
int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) {
12808+
UNUSED(ctx);
12809+
12810+
if (!stream || flags != 0) {
12811+
errno = EINVAL;
12812+
return REDISMODULE_ERR;
12813+
}
12814+
12815+
/* Not allowed on replicas. */
12816+
if (server.masterhost != NULL) {
12817+
errno = ENOTSUP;
12818+
return REDISMODULE_ERR;
12819+
}
12820+
12821+
/* Drop replicas if exist. */
12822+
disconnectSlaves();
12823+
freeReplicationBacklog();
12824+
12825+
if (server.aof_state != AOF_OFF) stopAppendOnly();
12826+
12827+
/* Kill existing RDB fork as it is saving outdated data. Also killing it
12828+
* will prevent COW memory issue. */
12829+
if (server.child_type == CHILD_TYPE_RDB) killRDBChild();
12830+
12831+
emptyData(-1,EMPTYDB_NO_FLAGS,NULL);
12832+
12833+
/* rdbLoad() can go back to the networking and process network events. If
12834+
* RM_RdbLoad() is called inside a command callback, we don't want to
12835+
* process the current client. Otherwise, we may free the client or try to
12836+
* process next message while we are already in the command callback. */
12837+
if (server.current_client) protectClient(server.current_client);
12838+
12839+
serverAssert(stream->type == REDISMODULE_RDB_STREAM_FILE);
12840+
int ret = rdbLoad(stream->data.filename,NULL,RDBFLAGS_NONE);
12841+
12842+
if (server.current_client) unprotectClient(server.current_client);
12843+
if (server.aof_state != AOF_OFF) startAppendOnly();
12844+
12845+
if (ret != RDB_OK) {
12846+
errno = (ret == RDB_NOT_EXIST) ? ENOENT : EIO;
12847+
return REDISMODULE_ERR;
12848+
}
12849+
12850+
errno = 0;
12851+
return REDISMODULE_OK;
12852+
}
12853+
12854+
/* Save dataset to the RDB stream.
12855+
*
12856+
* `flags` must be zero. This parameter is for future use.
12857+
*
12858+
* On success REDISMODULE_OK is returned, otherwise REDISMODULE_ERR is returned
12859+
* and errno is set accordingly.
12860+
*
12861+
* Example:
12862+
*
12863+
* RedisModuleRdbStream *s = RedisModule_RdbStreamCreateFromFile("exp.rdb");
12864+
* RedisModule_RdbSave(ctx, s, 0);
12865+
* RedisModule_RdbStreamFree(s);
12866+
*/
12867+
int RM_RdbSave(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) {
12868+
UNUSED(ctx);
12869+
12870+
if (!stream || flags != 0) {
12871+
errno = EINVAL;
12872+
return REDISMODULE_ERR;
12873+
}
12874+
12875+
serverAssert(stream->type == REDISMODULE_RDB_STREAM_FILE);
12876+
12877+
if (rdbSaveToFile(stream->data.filename) != C_OK) {
12878+
return REDISMODULE_ERR;
12879+
}
12880+
12881+
errno = 0;
12882+
return REDISMODULE_OK;
12883+
}
12884+
1275412885
/* Redis MODULE command.
1275512886
*
1275612887
* MODULE LIST
@@ -13627,4 +13758,8 @@ void moduleRegisterCoreAPI(void) {
1362713758
REGISTER_API(RegisterEnumConfig);
1362813759
REGISTER_API(LoadConfigs);
1362913760
REGISTER_API(RegisterAuthCallback);
13761+
REGISTER_API(RdbStreamCreateFromFile);
13762+
REGISTER_API(RdbStreamFree);
13763+
REGISTER_API(RdbLoad);
13764+
REGISTER_API(RdbSave);
1363013765
}

src/rdb.c

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,31 +1437,29 @@ int rdbSaveRioWithEOFMark(int req, rio *rdb, int *error, rdbSaveInfo *rsi) {
14371437
return C_ERR;
14381438
}
14391439

1440-
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
1441-
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
1442-
char tmpfile[256];
1440+
static int rdbSaveInternal(int req, const char *filename, rdbSaveInfo *rsi, int rdbflags) {
14431441
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
1444-
FILE *fp = NULL;
14451442
rio rdb;
14461443
int error = 0;
1444+
int saved_errno;
14471445
char *err_op; /* For a detailed log */
14481446

1449-
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
1450-
fp = fopen(tmpfile,"w");
1447+
FILE *fp = fopen(filename,"w");
14511448
if (!fp) {
1449+
saved_errno = errno;
14521450
char *str_err = strerror(errno);
14531451
char *cwdp = getcwd(cwd,MAXPATHLEN);
14541452
serverLog(LL_WARNING,
14551453
"Failed opening the temp RDB file %s (in server root dir %s) "
14561454
"for saving: %s",
1457-
tmpfile,
1455+
filename,
14581456
cwdp ? cwdp : "unknown",
14591457
str_err);
1458+
errno = saved_errno;
14601459
return C_ERR;
14611460
}
14621461

14631462
rioInitWithFile(&rdb,fp);
1464-
startSaving(RDBFLAGS_NONE);
14651463

14661464
if (server.rdb_save_incremental_fsync) {
14671465
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
@@ -1481,7 +1479,46 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
14811479
serverLog(LL_NOTICE,"Unable to reclaim cache after saving RDB: %s", strerror(errno));
14821480
}
14831481
if (fclose(fp)) { fp = NULL; err_op = "fclose"; goto werr; }
1484-
fp = NULL;
1482+
1483+
return C_OK;
1484+
1485+
werr:
1486+
saved_errno = errno;
1487+
serverLog(LL_WARNING,"Write error while saving DB to the disk(%s): %s", err_op, strerror(errno));
1488+
if (fp) fclose(fp);
1489+
unlink(filename);
1490+
errno = saved_errno;
1491+
return C_ERR;
1492+
}
1493+
1494+
/* Save DB to the file. Similar to rdbSave() but this function won't use a
1495+
* temporary file and won't update the metrics. */
1496+
int rdbSaveToFile(const char *filename) {
1497+
startSaving(RDBFLAGS_NONE);
1498+
1499+
if (rdbSaveInternal(SLAVE_REQ_NONE,filename,NULL,RDBFLAGS_NONE) != C_OK) {
1500+
int saved_errno = errno;
1501+
stopSaving(0);
1502+
errno = saved_errno;
1503+
return C_ERR;
1504+
}
1505+
1506+
stopSaving(1);
1507+
return C_OK;
1508+
}
1509+
1510+
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
1511+
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
1512+
char tmpfile[256];
1513+
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
1514+
1515+
startSaving(RDBFLAGS_NONE);
1516+
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
1517+
1518+
if (rdbSaveInternal(req,tmpfile,rsi,rdbflags) != C_OK) {
1519+
stopSaving(0);
1520+
return C_ERR;
1521+
}
14851522

14861523
/* Use RENAME to make sure the DB file is changed atomically only
14871524
* if the generate DB file is ok. */
@@ -1499,21 +1536,19 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
14991536
stopSaving(0);
15001537
return C_ERR;
15011538
}
1502-
if (fsyncFileDir(filename) == -1) { err_op = "fsyncFileDir"; goto werr; }
1539+
if (fsyncFileDir(filename) != 0) {
1540+
serverLog(LL_WARNING,
1541+
"Failed to fsync directory while saving DB: %s", strerror(errno));
1542+
stopSaving(0);
1543+
return C_ERR;
1544+
}
15031545

15041546
serverLog(LL_NOTICE,"DB saved on disk");
15051547
server.dirty = 0;
15061548
server.lastsave = time(NULL);
15071549
server.lastbgsave_status = C_OK;
15081550
stopSaving(1);
15091551
return C_OK;
1510-
1511-
werr:
1512-
serverLog(LL_WARNING,"Write error saving DB on disk(%s): %s", err_op, strerror(errno));
1513-
if (fp) fclose(fp);
1514-
unlink(tmpfile);
1515-
stopSaving(0);
1516-
return C_ERR;
15171552
}
15181553

15191554
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
@@ -3361,7 +3396,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
33613396
/* Reclaim the cache backed by rdb */
33623397
if (retval == C_OK && !(rdbflags & RDBFLAGS_KEEP_CACHE)) {
33633398
/* TODO: maybe we could combine the fopen and open into one in the future */
3364-
rdb_fd = open(server.rdb_filename, O_RDONLY);
3399+
rdb_fd = open(filename, O_RDONLY);
33653400
if (rdb_fd > 0) bioCreateCloseJob(rdb_fd, 0, 1);
33663401
}
33673402
return (retval==C_OK) ? RDB_OK : RDB_FAILED;

src/rdb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
157157
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
158158
int rdbSaveToSlavesSockets(int req, rdbSaveInfo *rsi);
159159
void rdbRemoveTempFile(pid_t childpid, int from_signal);
160+
int rdbSaveToFile(const char *filename);
160161
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags);
161162
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid);
162163
size_t rdbSavedObjectLen(robj *o, robj *key, int dbid);

src/redismodule.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
881881
typedef struct RedisModuleScanCursor RedisModuleScanCursor;
882882
typedef struct RedisModuleUser RedisModuleUser;
883883
typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx;
884+
typedef struct RedisModuleRdbStream RedisModuleRdbStream;
884885

885886
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
886887
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
@@ -1303,6 +1304,10 @@ REDISMODULE_API int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, co
13031304
REDISMODULE_API int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
13041305
REDISMODULE_API int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
13051306
REDISMODULE_API int (*RedisModule_LoadConfigs)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
1307+
REDISMODULE_API RedisModuleRdbStream *(*RedisModule_RdbStreamCreateFromFile)(const char *filename) REDISMODULE_ATTR;
1308+
REDISMODULE_API void (*RedisModule_RdbStreamFree)(RedisModuleRdbStream *stream) REDISMODULE_ATTR;
1309+
REDISMODULE_API int (*RedisModule_RdbLoad)(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) REDISMODULE_ATTR;
1310+
REDISMODULE_API int (*RedisModule_RdbSave)(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) REDISMODULE_ATTR;
13061311

13071312
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
13081313

@@ -1658,6 +1663,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
16581663
REDISMODULE_GET_API(RegisterStringConfig);
16591664
REDISMODULE_GET_API(RegisterEnumConfig);
16601665
REDISMODULE_GET_API(LoadConfigs);
1666+
REDISMODULE_GET_API(RdbStreamCreateFromFile);
1667+
REDISMODULE_GET_API(RdbStreamFree);
1668+
REDISMODULE_GET_API(RdbLoad);
1669+
REDISMODULE_GET_API(RdbSave);
16611670

16621671
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
16631672
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);

tests/modules/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ TEST_MODULES = \
6161
publish.so \
6262
usercall.so \
6363
postnotifications.so \
64-
moduleauthtwo.so
64+
moduleauthtwo.so \
65+
rdbloadsave.so
6566

6667
.PHONY: all
6768

0 commit comments

Comments
 (0)