3737
3838#include "server.h"
3939
40+
41+ /* TODO: comment */
42+ #define EPPOOL_SIZE 20
43+
44+ /* Create an array of expiration pools, one for each database. */
45+ static struct robj * * * ExpirationPools ;
46+
47+ void expirationPoolAlloc (void ) {
48+ int dbnum = server .dbnum ;
49+
50+ ExpirationPools = zmalloc (sizeof (struct expirationPoolEntry * * ) * dbnum );
51+
52+ for (int i = 0 ; i < dbnum ; i ++ ) {
53+ ExpirationPools [i ] = zmalloc (sizeof (struct expirationPoolEntry * ) * EPPOOL_SIZE );
54+ for (int j = 0 ; j < EPPOOL_SIZE ; j ++ ) {
55+ ExpirationPools [i ][j ] = NULL ;
56+ }
57+ }
58+ }
59+
60+ void expirationPoolPopulate (serverDb * db , robj * val , long long expiretime ) {
61+ struct robj * * pool = ExpirationPools [db -> id ];
62+ int k ;
63+
64+ k = 0 ;
65+ while (k < EPPOOL_SIZE && pool [k ] && objectGetExpire (pool [k ]) < expiretime ) k ++ ;
66+
67+ /* If we can't insert (all slots filled with sooner-to-expire entries), return */
68+ if (k == 0 && pool [EPPOOL_SIZE - 1 ] != NULL ) {
69+ return ;
70+ } else if (k < EPPOOL_SIZE && pool [k ] == NULL ) {
71+ /* Inserting into empty position. No setup needed before insert. */
72+ } else {
73+ /* Inserting in the middle. Now k points to the first element
74+ * with expire time greater than the element to insert. */
75+ if (pool [EPPOOL_SIZE - 1 ] == NULL ) {
76+ /* Free space on the right? Insert at k shifting
77+ * all the elements from k to end to the right. */
78+ memmove (pool + k + 1 , pool + k , sizeof (pool [0 ]) * (EPPOOL_SIZE - k - 1 ));
79+ } else {
80+ /* No free space on right? Insert at k-1 */
81+ k -- ;
82+ /* Shift all elements on the left of k (included) to the
83+ * left, so we discard the element with smallest expire time. */
84+ if (pool [0 ]) decrRefCount (pool [0 ]);
85+ memmove (pool , pool + 1 , sizeof (pool [0 ]) * k );
86+ }
87+ }
88+
89+ /* Store the entry and increment its refcount */
90+ pool [k ] = val ;
91+ incrRefCount (val );
92+ }
93+
4094/*-----------------------------------------------------------------------------
4195 * Incremental collection of expired keys.
4296 *
@@ -135,7 +189,8 @@ typedef struct {
135189void expireScanCallback (void * privdata , void * entry ) {
136190 robj * val = entry ;
137191 expireScanData * data = privdata ;
138- long long ttl = objectGetExpire (val ) - data -> now ;
192+ long long expiretime = objectGetExpire (val );
193+ long long ttl = expiretime - data -> now ;
139194 if (activeExpireCycleTryExpire (data -> db , val , data -> now )) {
140195 data -> expired ++ ;
141196 /* Propagate the DEL command */
@@ -145,6 +200,9 @@ void expireScanCallback(void *privdata, void *entry) {
145200 /* We want the average TTL of keys yet not expired. */
146201 data -> ttl_sum += ttl ;
147202 data -> ttl_samples ++ ;
203+
204+ /* Try to add this entry to the expiration pool for future expiration */
205+ expirationPoolPopulate (data -> db , val , expiretime );
148206 }
149207 data -> sampled ++ ;
150208}
@@ -252,6 +310,38 @@ void activeExpireCycle(int type) {
252310
253311 if (kvstoreSize (db -> expires )) dbs_performed ++ ;
254312
313+ /* First, try to expire keys from the expire pool */
314+ struct robj * * pool = ExpirationPools [db -> id ];
315+ long long now = mstime ();
316+ int expired_from_pool = 0 ;
317+
318+ /* Traverse from back to front, stop when we find a non-expired key */
319+ for (int i = EPPOOL_SIZE - 1 ; i >= 0 ; i -- ) {
320+ if (pool [i ] == NULL ) continue ;
321+ if (objectGetExpire (pool [i ]) <= now ) {
322+ /* Key is expired, try to expire it */
323+ if (activeExpireCycleTryExpire (db , pool [i ], now )) {
324+ expired_from_pool ++ ;
325+ /* Propagate the DEL command */
326+ postExecutionUnitOperations ();
327+ }
328+
329+ /* Clean up the pool entry */
330+ decrRefCount (pool [i ]);
331+ pool [i ] = NULL ;
332+ } else {
333+ /* Since we're traversing from back to front and keys are sorted by expire time,
334+ * if we find a non-expired key, all keys before it are also not expired */
335+ break ;
336+ }
337+ }
338+
339+ /* If we found expired keys in the pool, update stats */
340+ if (expired_from_pool > 0 ) {
341+ total_expired += expired_from_pool ;
342+ total_sampled += expired_from_pool ;
343+ }
344+
255345 /* Continue to expire if at the end of the cycle there are still
256346 * a big percentage of keys to expire, compared to the number of keys
257347 * we scanned. The percentage, stored in config_cycle_acceptable_stale
0 commit comments