Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

In-depth Analysis of LFU algorithm in Redis

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)06/01 Report--

Preface

In the French calculation of LRU in Redis, it is said that LRU has a defect in the following situations:

~ after a long period of time, please do not know what to do.

~ ~ Baking, Billing, Billing, Baking, Billing, Billing, Baking, Baking, Billing, Baking, Baking, Billing, Baking, Baking,

~ Cellular Centrum ~ |

~ D~D |

Data D will be mistaken for the data most likely to be accessed in the future.

The author of Redis once wanted to improve the LRU algorithm, but found that Redis's LRU algorithm is limited by the random sampling number maxmemory_samples, which is very close to the ideal LRU algorithm performance when the maxmemory_samples is equal to 10, that is to say, the LRU algorithm itself is very difficult to go further.

So, going back to the origin, the original intention of the elimination algorithm is to retain the data that is most likely to be accessed again in the future, while the LRU algorithm only predicts that the recently accessed data is most likely to be accessed in the future. We can change our thinking and adopt a LFU (Least Frequently Used) algorithm, that is, the most frequently accessed data is most likely to be accessed in the future. In the above case, based on the frequent access, the retention priority can be determined as follows: B > A > blocked.

LFU thought in Redis

In the LFU algorithm, you can maintain a counter for each key. The counter increments each time the key is accessed. The larger the counter, the more frequently it can be accessed.

There are two problems with the above simple algorithm:

In the LRU algorithm, we can maintain a two-way linked list, and then simply move the visited nodes to the beginning of the linked list, but it is not feasible in LFU. Nodes should be sorted strictly according to counters. When adding new nodes or updating node locations, the time complexity may reach O (N). It's just that the simple way to increase the counter is not perfect. Access patterns change frequently, and key that is accessed frequently over a period of time may be rarely accessed after a period of time, and only increasing counters does not reflect this trend.

The first problem is easy to solve. You can learn from the experience of LRU implementation and maintain a pool that needs to be phased out of key. The solution to the second problem is to record the last time key was accessed, and then lower the counter over time.

The Redis object is structured as follows:

Typedef struct redisObject {unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; / * LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). * / int refcount; void * ptr;} robj

In the LRU algorithm, 24 bits lru is used to record LRU time, and this field can also be used in LFU, but it is divided into 16 bits and 8 bits:

16 bits 8 bits +-+-+ Last decr time | LOG_C | +-+-+

The high 16 bits is used to record the time ldt of the last counter drop, in minutes, and the low 8 bits records the counter value counter.

LFU configuration

Redis4.0 then added two LFU modes to the maxmemory_policy phase-out strategy:

Volatile-lfu: use LFU elimination algorithm for key with expiration time allkeys-lfu: use LFU phase-out algorithm for all key

There are two other configurations that can adjust the LFU algorithm:

Lfu-log-factor 10lfu-decay-time 1

Lfu-log-factor can adjust the growth rate of the counter counter. The larger the lfu-log-factor, the slower the counter growth.

Lfu-decay-time is a value in minutes that adjusts the speed of counter reduction.

Source code implementation

In lookupKey:

Robj * lookupKey (redisDb * db, robj * key, int flags) {dictEntry * de = dictFind (db- > dict,key- > ptr); if (de) {robj * val = dictGetVal (de); / * Update the access time for the ageing algorithm. * Don't do it if we have a saving child, as this will trigger * a copy on write madness. * / if (server.rdb_child_pid =-1 & & server.aof_child_pid = =-1 & &! (flags & LOOKUP_NOTOUCH)) {if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {updateLFU (val);} else {val- > lru = LRU_CLOCK ();} return val;} else {return NULL;}

When the LFU policy is adopted, updateLFU updates the lru:

/ * Update LFU when an object is accessed. * Firstly, decrement the counter if the decrement time is reached. * Then logarithmically increment the counter, and update the access time. * / void updateLFU (robj * val) {unsigned long counter = LFUDecrAndReturn (val); counter = LFULogIncr (counter); val- > lru = (LFUGetTimeInMinutes () > 8; unsigned long counter = o-> lru & 255; unsigned long num_periods = server.lfu_decay_time? LFUTimeElapsed (ldt) / server.lfu_decay_time: 0; if (num_periods) counter = (num_periods > counter)? 0: counter-num_periods; return counter;}

The function first takes the most recent drop time ldt of 16 bits and the counter counter of 8 bits, and then calculates how much it should be reduced based on the configured lfu_decay_time.

LFUTimeElapsed is used to calculate the difference between the current time and ldt:

/ * Return the current time in minutes, just taking the least significant * 16 bits. The returned time is suitable to be stored as LDT (last decrement * time) for the LFU implementation. * / unsigned long LFUGetTimeInMinutes (void) {return (server.unixtime/60) & 65535;} / * Given an object last access time, compute the minimum number of minutes * that elapsed since the last access. Handle overflow (ldt greater than * the current 16 bits minutes time) considering the time as wrapping * exactly once. * / unsigned long LFUTimeElapsed (unsigned long ldt) {unsigned long now = LFUGetTimeInMinutes (); if (now > = ldt) return now-ldt; return 65535

Specifically, after the current time is converted into minutes, the value is reduced by 16 bits, and then the difference between now-ldt and ldt is calculated. When ldt > now, the default is that one cycle has elapsed (16 bits, maximum 65535), and the value is 65535-ldt+now.

Then divide the configuration lfu_decay_time by the difference, LFUTimeElapsed (ldt) / server.lfu_decay_time, n lfu_decay_time have passed, then the counter is reduced by n counter-num_periods.

Growth LFULogIncr

The growth function LFULogIncr is as follows:

/ * Logarithmically increment a counter. The greater is the current counter value * the less likely is that it gets really implemented. Saturate it at 255. * / uint8_t LFULogIncr (uint8_t counter) {if (counter = = 255) return 255; double r = (double) rand () / RAND_MAX; double baseval = counter-LFU_INIT_VAL; if (baseval)

< 0) baseval = 0; double p = 1.0/(baseval*server.lfu_log_factor+1); if (r < p) counter++; return counter;} counter并不是简单的访问一次就+1,而是采用了一个0-1之间的p因子控制增长。counter最大值为255。取一个0-1之间的随机数r与p比较,当rencoding = OBJ_ENCODING_RAW; o->

Ptr = ptr; o-> refcount = 1; / * Set the LRU to the current lruclock (minutes resolution), or * alternatively the LFU counter. * / if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {o-> lru = (LFUGetTimeInMinutes ()

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report