Active rehashing

This commit is contained in:
antirez 2010-04-15 18:07:57 +02:00
parent 5413c40da7
commit 8ca3e9d10b
4 changed files with 90 additions and 35 deletions

38
dict.c
View File

@ -41,6 +41,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include <sys/time.h>
#include "dict.h" #include "dict.h"
#include "zmalloc.h" #include "zmalloc.h"
@ -237,6 +238,25 @@ int dictRehash(dict *d, int n) {
return 1; return 1;
} }
long long timeInMilliseconds(void) {
struct timeval tv;
gettimeofday(&tv,NULL);
return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);
}
/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
int dictRehashMilliseconds(dict *d, int ms) {
long long start = timeInMilliseconds();
int rehashes = 0;
while(dictRehash(d,100)) {
rehashes += 100;
if (timeInMilliseconds()-start > ms) break;
}
return rehashes;
}
/* This function performs just a step of rehashing, and only if there are /* This function performs just a step of rehashing, and only if there are
* not iterators bound to our hash table. When we have iterators in the middle * not iterators bound to our hash table. When we have iterators in the middle
* of a rehashing we can't mess with the two hash tables otherwise some element * of a rehashing we can't mess with the two hash tables otherwise some element
@ -527,7 +547,7 @@ static unsigned long _dictNextPower(unsigned long size)
* index is always returned in the context of the second (new) hash table. */ * index is always returned in the context of the second (new) hash table. */
static int _dictKeyIndex(dict *d, const void *key) static int _dictKeyIndex(dict *d, const void *key)
{ {
unsigned int h, h1, h2; unsigned int h, idx, table;
dictEntry *he; dictEntry *he;
/* Expand the hashtable if needed */ /* Expand the hashtable if needed */
@ -535,24 +555,18 @@ static int _dictKeyIndex(dict *d, const void *key)
return -1; return -1;
/* Compute the key hash value */ /* Compute the key hash value */
h = dictHashKey(d, key); h = dictHashKey(d, key);
h1 = h & d->ht[0].sizemask; for (table = 0; table <= 1; table++) {
h2 = h & d->ht[1].sizemask; idx = h & d->ht[table].sizemask;
/* Search if this slot does not already contain the given key */ /* Search if this slot does not already contain the given key */
he = d->ht[0].table[h1]; he = d->ht[table].table[idx];
while(he) { while(he) {
if (dictCompareHashKeys(d, key, he->key)) if (dictCompareHashKeys(d, key, he->key))
return -1; return -1;
he = he->next; he = he->next;
} }
if (!dictIsRehashing(d)) return h1; if (!dictIsRehashing(d)) break;
/* Check the second hash table */
he = d->ht[1].table[h2];
while(he) {
if (dictCompareHashKeys(d, key, he->key))
return -1;
he = he->next;
} }
return h2; return idx;
} }
void dictEmpty(dict *d) { void dictEmpty(dict *d) {

25
dict.h
View File

@ -122,24 +122,25 @@ typedef struct dictIterator {
/* API */ /* API */
dict *dictCreate(dictType *type, void *privDataPtr); dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *ht, unsigned long size); int dictExpand(dict *d, unsigned long size);
int dictAdd(dict *ht, void *key, void *val); int dictAdd(dict *d, void *key, void *val);
int dictReplace(dict *ht, void *key, void *val); int dictReplace(dict *d, void *key, void *val);
int dictDelete(dict *ht, const void *key); int dictDelete(dict *d, const void *key);
int dictDeleteNoFree(dict *ht, const void *key); int dictDeleteNoFree(dict *d, const void *key);
void dictRelease(dict *ht); void dictRelease(dict *d);
dictEntry * dictFind(dict *ht, const void *key); dictEntry * dictFind(dict *d, const void *key);
int dictResize(dict *ht); int dictResize(dict *d);
dictIterator *dictGetIterator(dict *ht); dictIterator *dictGetIterator(dict *d);
dictEntry *dictNext(dictIterator *iter); dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter); void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *ht); dictEntry *dictGetRandomKey(dict *d);
void dictPrintStats(dict *ht); void dictPrintStats(dict *d);
unsigned int dictGenHashFunction(const unsigned char *buf, int len); unsigned int dictGenHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *ht); void dictEmpty(dict *d);
void dictEnableResize(void); void dictEnableResize(void);
void dictDisableResize(void); void dictDisableResize(void);
int dictRehash(dict *d, int n); int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
/* Hash table types */ /* Hash table types */
extern dictType dictTypeHeapStringCopyKey; extern dictType dictTypeHeapStringCopyKey;

30
redis.c
View File

@ -91,7 +91,7 @@
#define REDIS_CONFIGLINE_MAX 1024 #define REDIS_CONFIGLINE_MAX 1024
#define REDIS_OBJFREELIST_MAX 1000000 /* Max number of objects to cache */ #define REDIS_OBJFREELIST_MAX 1000000 /* Max number of objects to cache */
#define REDIS_MAX_SYNC_TIME 60 /* Slave can't take more to sync */ #define REDIS_MAX_SYNC_TIME 60 /* Slave can't take more to sync */
#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* try to expire 10 keys/loop */ #define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */
#define REDIS_MAX_WRITE_PER_EVENT (1024*64) #define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */ #define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
@ -379,6 +379,7 @@ struct redisServer {
char *requirepass; char *requirepass;
int shareobjects; int shareobjects;
int rdbcompression; int rdbcompression;
int activerehashing;
/* Replication related */ /* Replication related */
int isslave; int isslave;
char *masterauth; char *masterauth;
@ -1208,6 +1209,21 @@ static void tryResizeHashTables(void) {
} }
} }
/* Our hash table implementation performs rehashing incrementally while
* we write/read from the hash table. Still if the server is idle, the hash
* table will use two tables for a long time. So we try to use 1 millisecond
* of CPU time at every serverCron() loop in order to rehash some key. */
static void incrementallyRehash(void) {
int j;
for (j = 0; j < server.dbnum; j++) {
if (dictIsRehashing(server.db[j].dict)) {
dictRehashMilliseconds(server.db[j].dict,1);
break; /* already used our millisecond for this loop... */
}
}
}
/* A background saving child (BGSAVE) terminated its work. Handle this. */ /* A background saving child (BGSAVE) terminated its work. Handle this. */
void backgroundSaveDoneHandler(int statloc) { void backgroundSaveDoneHandler(int statloc) {
int exitcode = WEXITSTATUS(statloc); int exitcode = WEXITSTATUS(statloc);
@ -1337,10 +1353,9 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD
* if we resize the HT while there is the saving child at work actually * if we resize the HT while there is the saving child at work actually
* a lot of memory movements in the parent will cause a lot of pages * a lot of memory movements in the parent will cause a lot of pages
* copied. */ * copied. */
if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1 && if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) {
!(loops % 10)) if (!(loops % 10)) tryResizeHashTables();
{ if (server.activerehashing) incrementallyRehash();
tryResizeHashTables();
} }
/* Show information about connected clients */ /* Show information about connected clients */
@ -1568,6 +1583,7 @@ static void initServerConfig() {
server.requirepass = NULL; server.requirepass = NULL;
server.shareobjects = 0; server.shareobjects = 0;
server.rdbcompression = 1; server.rdbcompression = 1;
server.activerehashing = 1;
server.maxclients = 0; server.maxclients = 0;
server.blpop_blocked_clients = 0; server.blpop_blocked_clients = 0;
server.maxmemory = 0; server.maxmemory = 0;
@ -1801,6 +1817,10 @@ static void loadServerConfig(char *filename) {
if ((server.rdbcompression = yesnotoi(argv[1])) == -1) { if ((server.rdbcompression = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) { } else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
if ((server.daemonize = yesnotoi(argv[1])) == -1) { if ((server.daemonize = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;

View File

@ -262,6 +262,26 @@ glueoutputbuf yes
hash-max-zipmap-entries 64 hash-max-zipmap-entries 64
hash-max-zipmap-value 512 hash-max-zipmap-value 512
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation redis uses (see dict.c)
# performs a lazy rehashing: the more operation you run into an hash table
# that is rhashing, the more rehashing "steps" are performed, so if the
# server is idle the rehashing is never complete and some more memory is used
# by the hash table.
#
# The default is to use this millisecond 10 times every second in order to
# active rehashing the main dictionaries, freeing memory when possible.
#
# If unsure:
# use "activerehashing no" if you have hard latency requirements and it is
# not a good thing in your environment that Redis can reply form time to time
# to queries with 2 milliseconds delay.
#
# use "activerehashing yes" if you don't have such hard requirements but
# want to free memory asap when possible.
activerehashing yes
################################## INCLUDES ################################### ################################## INCLUDES ###################################
# Include one or more other config files here. This is useful if you # Include one or more other config files here. This is useful if you