Initial implementation of append-only mode. Loading still not implemented.

This commit is contained in:
antirez 2009-10-30 01:04:17 +01:00
parent 43e5ccdf57
commit 44b38ef432
2 changed files with 89 additions and 0 deletions

72
redis.c
View File

@ -261,6 +261,9 @@ struct redisServer {
int maxidletime;
int dbnum;
int daemonize;
int appendonly;
int appendfd;
int appendseldb;
char *pidfile;
int bgsaveinprogress;
pid_t bgsavechildpid;
@ -269,6 +272,7 @@ struct redisServer {
char *logfile;
char *bindaddr;
char *dbfilename;
char *appendfilename;
char *requirepass;
int shareobjects;
/* Replication related */
@ -364,6 +368,7 @@ static void incrRefCount(robj *o);
static int rdbSaveBackground(char *filename);
static robj *createStringObject(char *ptr, size_t len);
static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc);
static int syncWithMaster(void);
static robj *tryObjectSharing(robj *o);
static int tryObjectEncoding(robj *o);
@ -1023,8 +1028,12 @@ static void initServerConfig() {
server.bindaddr = NULL;
server.glueoutputbuf = 1;
server.daemonize = 0;
server.appendonly = 0;
server.appendfd = -1;
server.appendseldb = -1; /* Make sure the first time will not match */
server.pidfile = "/var/run/redis.pid";
server.dbfilename = "dump.rdb";
server.appendfilename = "appendonly.log";
server.requirepass = NULL;
server.shareobjects = 0;
server.sharingpoolsize = 1024;
@ -1084,6 +1093,15 @@ static void initServer() {
server.stat_numconnections = 0;
server.stat_starttime = time(NULL);
aeCreateTimeEvent(server.el, 1000, serverCron, NULL, NULL);
if (server.appendonly) {
server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT);
if (server.appendfd == -1) {
redisLog(REDIS_WARNING, "Can't open the append-only file: %s",
strerror(errno));
exit(1);
}
}
}
/* Empty the whole database */
@ -1223,6 +1241,10 @@ static void loadServerConfig(char *filename) {
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
if ((server.appendonly = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
server.requirepass = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
@ -1538,6 +1560,8 @@ static int processCommand(redisClient *c) {
/* Exec the command */
dirty = server.dirty;
cmd->proc(c);
if (server.appendonly != 0)
feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc);
if (server.dirty-dirty != 0 && listLength(server.slaves))
replicationFeedSlaves(server.slaves,cmd,c->db->id,c->argv,c->argc);
if (listLength(server.monitors))
@ -1622,6 +1646,54 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di
if (outv != static_outv) zfree(outv);
}
/* TODO: translate EXPIREs into EXPIRETOs */
static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
int j;
ssize_t nwritten;
/* The DB this command was targetting is not the same as the last command
* we appendend. To issue a SELECT command is needed. */
if (dictid != server.appendseldb) {
char seldb[64];
snprintf(seldb,sizeof(seldb),"%d",dictid);
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
strlen(seldb),seldb);
}
/* Append the actual command */
buf = sdscatprintf(buf,"*%d\r\n",argc);
for (j = 0; j < argc; j++) {
robj *o = argv[j];
if (o->encoding != REDIS_ENCODING_RAW)
o = getDecodedObject(o);
buf = sdscatprintf(buf,"$%d\r\n",sdslen(o->ptr));
buf = sdscatlen(buf,o->ptr,sdslen(o->ptr));
buf = sdscatlen(buf,"\r\n",2);
if (o != argv[j])
decrRefCount(o);
}
/* We want to perform a single write. This should be guaranteed atomic
* at least if the filesystem we are writing is a real physical one.
* While this will save us against the server being killed I don't think
* there is much to do about the whole server stopping for power problems
* or alike */
nwritten = write(server.appendfd,buf,sdslen(buf));
if (nwritten != (unsigned)sdslen(buf)) {
/* Ooops, we are in troubles. The best thing to do for now is
* to simply exit instead to give the illusion that everything is
* working as expected. */
if (nwritten == -1) {
redisLog(REDIS_WARNING,"Aborting on error writing to the append-only file: %s",strerror(errno));
} else {
redisLog(REDIS_WARNING,"Aborting on short write while writing to the append-only file: %s",strerror(errno));
}
abort();
}
fsync(server.appendfd); /* Let's try to get this data on the disk */
}
static void processInputBuffer(redisClient *c) {
again:
if (c->bulklen == -1) {

View File

@ -107,6 +107,23 @@ databases 16
# maxmemory <bytes>
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. If you can live
# with the idea that the latest records will be lost if something like a crash
# happens this is the preferred way to run Redis. If instead you care a lot
# about your data and don't want to that a single record can get lost you should
# enable the append only mode: when this mode is enabled Redis will append
# every write operation received in the file appendonly.log. This file will
# be read on startup in order to rebuild the full dataset in memory.
#
# Note that you can have both the async dumps and the append only file if you
# like (you have to comment the "save" statements above to disable the dumps).
# Still if append only mode is enabled Redis will load the data from the
# log file at startup ignoring the dump.rdb file.
# appendonly yes
############################### ADVANCED CONFIG ###############################
# Glue small output buffers together in order to send small replies in a