diff --git a/src/db.cpp b/src/db.cpp index 45bc16f02..dd75c28e9 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -1122,11 +1122,13 @@ int dbSwapDatabases(int id1, int id2) { db1->setexpire = db2->setexpire; db1->expireitr = db2->expireitr; db1->avg_ttl = db2->avg_ttl; + db1->last_expire_set = db2->last_expire_set; db2->pdict = aux.pdict; db2->setexpire = aux.setexpire; db2->expireitr = aux.expireitr; db2->avg_ttl = aux.avg_ttl; + db2->last_expire_set = aux.last_expire_set; /* Now we need to handle clients blocked on lists: as an effect * of swapping the two DBs, a client that was waiting for list @@ -1220,6 +1222,19 @@ void setExpire(client *c, redisDb *db, robj *key, long long when) { expireEntry e((sds)dictGetKey(kde), when); ((robj*)dictGetVal(kde))->SetFExpires(true); + /* Update TTL stats (exponential moving average) */ + /* Note: We never have to update this on expiry since we reduce it by the current elapsed time here */ + long long now = g_pserver->mstime; + db->avg_ttl -= (now - db->last_expire_set); // reduce the TTL by the time that has elapsed + if (db->setexpire->empty()) + db->avg_ttl = 0; + else + db->avg_ttl -= db->avg_ttl / db->setexpire->size(); // slide one entry out the window + if (db->avg_ttl < 0) + db->avg_ttl = 0; // TTLs are never negative + db->avg_ttl += (double)(when-now) / (db->setexpire->size()+1); // add the new entry + db->last_expire_set = now; + db->setexpire->insert(e); int writable_slave = listLength(g_pserver->masters) && g_pserver->repl_slave_ro == 0; diff --git a/src/expire.cpp b/src/expire.cpp index 55ea83411..38a65cf44 100644 --- a/src/expire.cpp +++ b/src/expire.cpp @@ -155,8 +155,8 @@ void activeExpireCycle(int type) { /* If there is nothing to expire try next DB ASAP. */ if (db->setexpire->empty()) { - // TODO: Compute db->avg_ttl somewhere... but probably not here db->avg_ttl = 0; + db->last_expire_set = now; continue; } diff --git a/src/server.cpp b/src/server.cpp index 008459034..e6e86f6ea 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2926,6 +2926,7 @@ void initServer(void) { g_pserver->db[j].watched_keys = dictCreate(&keylistDictType,NULL); g_pserver->db[j].id = j; g_pserver->db[j].avg_ttl = 0; + g_pserver->db[j].last_expire_set = 0; g_pserver->db[j].defrag_later = listCreate(); } @@ -4566,10 +4567,17 @@ sds genRedisInfoString(const char *section) { keys = dictSize(g_pserver->db[j].pdict); vkeys = g_pserver->db[j].setexpire->size(); + + // Adjust TTL by the current time + g_pserver->db[j].avg_ttl -= (g_pserver->mstime - g_pserver->db[j].last_expire_set); + if (g_pserver->db[j].avg_ttl < 0) + g_pserver->db[j].avg_ttl = 0; + g_pserver->db[j].last_expire_set = g_pserver->mstime; + if (keys || vkeys) { info = sdscatprintf(info, "db%d:keys=%lld,expires=%lld,avg_ttl=%lld\r\n", - j, keys, vkeys, g_pserver->db[j].avg_ttl); + j, keys, vkeys, static_cast(g_pserver->db[j].avg_ttl)); } } } diff --git a/src/server.h b/src/server.h index 74960bbab..8bc30a0f5 100644 --- a/src/server.h +++ b/src/server.h @@ -844,7 +844,8 @@ typedef struct redisDb { dict *ready_keys; /* Blocked keys that received a PUSH */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ int id; /* Database ID */ - long long avg_ttl; /* Average TTL, just for stats */ + long long last_expire_set; /* when the last expire was set */ + double avg_ttl; /* Average TTL, just for stats */ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;