summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2013-03-11 11:10:33 +0100
committerantirez <antirez@gmail.com>2013-03-11 11:30:13 +0100
commit8e7d962291b2aa9131229afb0fc5b8e2b9efaad9 (patch)
tree873539f53f525c39009b0d84758bd74cdc6f897d
parentc5afbb6e2a2079685cd9ebf6b9069dea2fe1e471 (diff)
downloadredis-8e7d962291b2aa9131229afb0fc5b8e2b9efaad9.tar.gz
activeExpireCycle() smarter with many DBs and under expire pressure.
activeExpireCycle() tries to test just a few DBs per iteration so that it scales if there are many configured DBs in the Redis instance. However this commit makes it a bit smarter when one a few of those DBs are under expiration pressure and there are many many keys to expire. What we do is to remember if in the last iteration had to return because we ran out of time. In that case the next iteration we'll test all the configured DBs so that we are sure we'll test again the DB under pressure. Before of this commit after some mass-expire in a given DB the function tested just a few of the next DBs, possibly empty, a few per iteration, so it took a long time for the function to reach again the DB under pressure. This resulted in a lot of memory being used by already expired keys and never accessed by clients.
-rw-r--r--src/redis.c24
1 files changed, 20 insertions, 4 deletions
diff --git a/src/redis.c b/src/redis.c
index 884b3f16b..f7118f6ab 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -622,19 +622,31 @@ void updateDictResizePolicy(void) {
* No more than REDIS_DBCRON_DBS_PER_CALL databases are tested at every
* iteration. */
void activeExpireCycle(void) {
- static unsigned int current_db = 0;
+ /* This function has some global state in order to continue the work
+ * incrementally across calls. */
+ static unsigned int current_db = 0; /* Last DB tested. */
+ static int timelimit_exit = 0; /* Time limit hit in previous call? */
+
unsigned int j, iteration = 0;
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
long long start = ustime(), timelimit;
- /* Don't test more DBs than we have. */
- if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;
+ /* We usually should test REDIS_DBCRON_DBS_PER_CALL per iteration, with
+ * two exceptions:
+ *
+ * 1) Don't test more DBs than we have.
+ * 2) If last time we hit the time limit, we want to scan all DBs
+ * in this iteration, as there is work to do in some DB and we don't want
+ * expired keys to use memory for too much time. */
+ if (dbs_per_call > server.dbnum || timelimit_exit)
+ dbs_per_call = server.dbnum;
/* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time
* per iteration. Since this function gets called with a frequency of
* server.hz times per second, the following is the max amount of
* microseconds we can spend in this function. */
timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/server.hz/100;
+ timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1;
for (j = 0; j < dbs_per_call; j++) {
@@ -692,7 +704,11 @@ void activeExpireCycle(void) {
* caller waiting for the other active expire cycle. */
iteration++;
if ((iteration & 0xf) == 0 && /* check once every 16 iterations. */
- (ustime()-start) > timelimit) return;
+ (ustime()-start) > timelimit)
+ {
+ timelimit_exit = 1;
+ return;
+ }
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
}