err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"diskstore-path") && argc == 2) {
- zfree(server.ds_path);
- server.ds_path = zstrdup(argv[1]);
+ sdsfree(server.ds_path);
+ server.ds_path = sdsnew(argv[1]);
} else if (!strcasecmp(argv[0],"cache-max-memory") && argc == 2) {
server.cache_max_memory = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"hash-max-zipmap-entries") && argc == 2) {
} else {
sds copy = sdsdup(key->ptr);
dictAdd(db->dict, copy, val);
+ if (server.ds_enabled) {
+ /* FIXME: remove entry from negative cache */
+ }
return REDIS_OK;
}
}
*/
#include "redis.h"
+#include "sha1.h"
#include <fcntl.h>
#include <sys/stat.h>
return REDIS_OK;
}
+/* Convert key into full path for this object. Dirty but hopefully
+ * is fast enough. */
+void dsKeyToPath(redisDb *db, unsigned char *buf, robj *key) {
+ SHA1_CTX ctx;
+ unsigned char hash[20];
+ char *hex, digits[] = "0123456789abcdef";
+ int j, l;
+
+ SHA1Init(&ctx);
+ SHA1Update(&ctx,key->ptr,sdslen(key->ptr));
+ SHA1Final(hash,&ctx);
+
+ /* Convert the hash into hex format */
+ for (j = 0; j < 20; j++) {
+ hex[j*2] = digits[(hash[j]&0xF0)>>4];
+ hex[(j*2)+1] = digits[hash[j]&0x0F];
+ }
+
+ /* Create the object path. Start with server.ds_path that's the root dir */
+ l = sdslen(server.ds_path);
+ memcpy(buf,server.ds_path,l);
+ buf += l;
+ *buf++ = '/';
+
+ /* Then add xx/yy/ that is the two level directories */
+ buf[0] = hex[0];
+ buf[1] = hex[1];
+ buf[2] = '/';
+ buf[3] = hex[2];
+ buf[4] = hex[3];
+ buf[5] = '/';
+ buf += 6;
+
+ /* Add the database number followed by _ and finall the SHA1 hex */
+ l = ll2string(buf,64,db->id);
+ buf += l;
+ buf[0] = '_';
+ memcpy(buf+1,hex,40);
+ buf[41] = '\0';
+}
+
int dsSet(redisDb *db, robj *key, robj *val) {
+ char buf[1024];
+ FILE *fp;
+ int retval;
+
+ dsKeyToPath(buf,key);
+ fp = fopen(buf,"w");
+ if ((retval = rdbSaveKeyValuePair(fp,db,key,val,time(NULL))) == -1)
+ return REDIS_ERR;
+ fclose(fp);
+ if (retval == 0) unlink(buf); /* Expired key */
+ return REDIS_OK;
}
-robj *dsGet(redisDb *db, robj *key) {
+robj *dsGet(redisDb *db, robj *key, time_t *expire) {
return createStringObject("foo",3);
}
* - What happens with MULTI/EXEC?
*
* Good question.
+ *
+ * - If dsSet() fails on the write thread log the error and reschedule the
+ * key for flush.
*/
/* Virtual Memory is composed mainly of two subsystems:
(unsigned char*)j->key->ptr);
if (j->type == REDIS_IOJOB_LOAD) {
/* Create the key-value pair in the in-memory database */
- dbAdd(j->db,j->key,j->val);
- incrRefCount(j->val);
+ if (j->val != NULL) {
+ dbAdd(j->db,j->key,j->val);
+ incrRefCount(j->val);
+ setExpire(j->db,j->key,j->expire);
+ } else {
+ /* The key does not exist. Create a negative cache entry
+ * for this key. */
+ /* FIXME: add this entry into the negative cache */
+ }
/* Handle clients waiting for this key to be loaded. */
handleClientsBlockedOnSwappedKey(j->db,j->key);
freeIOJob(j);
/* Process the Job */
if (j->type == REDIS_IOJOB_LOAD) {
- j->val = dsGet(j->db,j->key);
- redisAssert(j->val != NULL);
+ time_t expire;
+
+ j->val = dsGet(j->db,j->key,&expire);
+ if (j->val) j->expire = expire;
} else if (j->type == REDIS_IOJOB_SAVE) {
redisAssert(j->val->storage == REDIS_DS_SAVING);
if (j->val)
return len;
}
+/* Save a key-value pair, with expire time, type, key, value.
+ * On error -1 is returned.
+ * On success if the key was actaully saved 1 is returned, otherwise 0
+ * is returned (the key was already expired). */
+int rdbSaveKeyValuePair(FILE *fp, redisDb *db, robj *key, robj *val,
+ time_t now)
+{
+ time_t expiretime;
+
+ expiretime = getExpire(db,&key);
+
+ /* Save the expire time */
+ if (expiretime != -1) {
+ /* If this key is already expired skip it */
+ if (expiretime < now) return 0;
+ if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) return -1;
+ if (rdbSaveTime(fp,expiretime) == -1) return -1;
+ }
+ /* Save type, key, value */
+ if (rdbSaveType(fp,val->type) == -1) return -1;
+ if (rdbSaveStringObject(fp,&key) == -1) return -1;
+ if (rdbSaveObject(fp,val) == -1) return -1;
+ return 1;
+}
+
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
int rdbSave(char *filename) {
dictIterator *di = NULL;
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetEntryKey(de);
robj key, *o = dictGetEntryVal(de);
- time_t expiretime;
initStaticStringObject(key,keystr);
- expiretime = getExpire(db,&key);
-
- /* Save the expire time */
- if (expiretime != -1) {
- /* If this key is already expired skip it */
- if (expiretime < now) continue;
- if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr;
- if (rdbSaveTime(fp,expiretime) == -1) goto werr;
- }
- /* Save type, key, value */
- if (rdbSaveType(fp,o->type) == -1) goto werr;
- if (rdbSaveStringObject(fp,&key) == -1) goto werr;
- if (rdbSaveObject(fp,o) == -1) goto werr;
+ if (rdbSaveKeyValuePair(fp,db,key,o,now) == -1) goto werr;
}
dictReleaseIterator(di);
}
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
server.maxmemory_samples = 3;
server.ds_enabled = 0;
- server.ds_path = zstrdup("/tmp/redis.ds");
+ server.ds_path = sdsnew("/tmp/redis.ds");
server.cache_max_memory = 64LL*1024*1024; /* 64 MB of RAM */
server.cache_blocked_clients = 0;
server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES;
robj *key; /* This I/O request is about this key */
robj *val; /* the value to swap for REDIS_IOJOB_SAVE, otherwise this
* field is populated by the I/O thread for REDIS_IOJOB_LOAD. */
+ time_t expire; /* Expire time for this key on REDIS_IOJOB_LOAD */
} iojob;
/* When diskstore is enabled and a flush operation is requested we push
off_t rdbSavedObjectPages(robj *o);
robj *rdbLoadObject(int type, FILE *fp);
void backgroundSaveDoneHandler(int statloc);
+int rdbSaveKeyValuePair(FILE *fp, redisDb *db, robj *key, robj *val, time_t now);
/* AOF persistence */
void flushAppendOnlyFile(void);