3737
3838#include "server.h"
3939
40+
41+ #define EVPOOL_SIZE 20
42+ #define EVPOOL_CACHED_SDS_SIZE 255
43+ struct expirePoolEntry {
44+ long long expiretime ; /* Absolute expiration time */
45+ robj * entry ; /* The entry with expire time */
46+ };
47+
48+ static struct expirePoolEntry * * ExpirePools ;
49+
50+ /* Create an array of expire pools, one for each database.
51+ * This allows us to separately track expiration candidates for each DB. */
52+ static struct expirePoolEntry * * ExpirePools ;
53+
54+ void expirePoolAlloc (void ) {
55+ int dbnum = server .dbnum ;
56+
57+ ExpirePools = zmalloc (sizeof (struct expirePoolEntry * ) * dbnum );
58+
59+ for (int i = 0 ; i < dbnum ; i ++ ) {
60+ ExpirePools [i ] = zmalloc (sizeof (struct expirePoolEntry ) * EVPOOL_SIZE );
61+ for (int j = 0 ; j < EVPOOL_SIZE ; j ++ ) {
62+ ExpirePools [i ][j ].expiretime = 0 ;
63+ ExpirePools [i ][j ].entry = NULL ;
64+ }
65+ }
66+ }
67+
68+ void expirePoolPopulate (serverDb * db , robj * val , long long expiretime ) {
69+ struct expirePoolEntry * pool = ExpirePools [db -> id ];
70+ int k ;
71+
72+ k = 0 ;
73+ while (k < EVPOOL_SIZE && pool [k ].entry && pool [k ].expiretime < expiretime ) k ++ ;
74+
75+ /* If we can't insert (all slots filled with sooner-to-expire entries), return */
76+ if (k == 0 && pool [EVPOOL_SIZE - 1 ].entry != NULL ) {
77+ return ;
78+ } else if (k < EVPOOL_SIZE && pool [k ].entry == NULL ) {
79+ /* Inserting into empty position. No setup needed before insert. */
80+ } else {
81+ /* Inserting in the middle. Now k points to the first element
82+ * with expire time greater than the element to insert. */
83+ if (pool [EVPOOL_SIZE - 1 ].entry == NULL ) {
84+ /* Free space on the right? Insert at k shifting
85+ * all the elements from k to end to the right. */
86+ memmove (pool + k + 1 , pool + k , sizeof (pool [0 ]) * (EVPOOL_SIZE - k - 1 ));
87+ } else {
88+ /* No free space on right? Insert at k-1 */
89+ k -- ;
90+ /* Shift all elements on the left of k (included) to the
91+ * left, so we discard the element with smallest expire time. */
92+ if (pool [0 ].entry ) decrRefCount (pool [0 ].entry );
93+ memmove (pool , pool + 1 , sizeof (pool [0 ]) * k );
94+ }
95+ }
96+
97+ /* Store the entry and increment its refcount */
98+ pool [k ].entry = val ;
99+ incrRefCount (val );
100+ pool [k ].expiretime = expiretime ;
101+ }
102+
40103/*-----------------------------------------------------------------------------
41104 * Incremental collection of expired keys.
42105 *
@@ -135,7 +198,8 @@ typedef struct {
135198void expireScanCallback (void * privdata , void * entry ) {
136199 robj * val = entry ;
137200 expireScanData * data = privdata ;
138- long long ttl = objectGetExpire (val ) - data -> now ;
201+ long long expiretime = objectGetExpire (val );
202+ long long ttl = expiretime - data -> now ;
139203 if (activeExpireCycleTryExpire (data -> db , val , data -> now )) {
140204 data -> expired ++ ;
141205 /* Propagate the DEL command */
@@ -145,6 +209,9 @@ void expireScanCallback(void *privdata, void *entry) {
145209 /* We want the average TTL of keys yet not expired. */
146210 data -> ttl_sum += ttl ;
147211 data -> ttl_samples ++ ;
212+
213+ /* Try to add this entry to the expiration pool for future expiration */
214+ expirePoolPopulate (data -> db , val , expiretime );
148215 }
149216 data -> sampled ++ ;
150217}
@@ -252,6 +319,39 @@ void activeExpireCycle(int type) {
252319
253320 if (kvstoreSize (db -> expires )) dbs_performed ++ ;
254321
322+ /* First, try to expire keys from the expire pool */
323+ struct expirePoolEntry * pool = ExpirePools [db -> id ];
324+ long long now = mstime ();
325+ int expired_from_pool = 0 ;
326+
327+ /* Traverse from back to front, stop when we find a non-expired key */
328+ for (int i = EVPOOL_SIZE - 1 ; i >= 0 ; i -- ) {
329+ if (pool [i ].entry == NULL ) continue ;
330+ if (pool [i ].expiretime <= now ) {
331+ /* Key is expired, try to expire it */
332+ if (activeExpireCycleTryExpire (db , pool [i ].entry , now )) {
333+ expired_from_pool ++ ;
334+ /* Propagate the DEL command */
335+ postExecutionUnitOperations ();
336+ }
337+
338+ /* Clean up the pool entry */
339+ decrRefCount (pool [i ].entry );
340+ pool [i ].entry = NULL ;
341+ pool [i ].expiretime = 0 ;
342+ } else {
343+ /* Since we're traversing from back to front and keys are sorted by expire time,
344+ * if we find a non-expired key, all keys before it are also not expired */
345+ break ;
346+ }
347+ }
348+
349+ /* If we found expired keys in the pool, update stats */
350+ if (expired_from_pool > 0 ) {
351+ total_expired += expired_from_pool ;
352+ total_sampled += expired_from_pool ;
353+ }
354+
255355 /* Continue to expire if at the end of the cycle there are still
256356 * a big percentage of keys to expire, compared to the number of keys
257357 * we scanned. The percentage, stored in config_cycle_acceptable_stale
0 commit comments