This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

The problem

Redis large key is a headache. If a large key appears on the Redis line, del cannot be executed immediately, because deleting the large key will cause blocking. During the blocking period, all requests may cause timeout. When the timeout is increasing, new requests will continue to come in, which will cause the Redis connection pool to be exhausted, causing various online services that depend on Redis to be abnormal.

Here’s a simple test

The script first writes a large amount of data to Redis:

127.0.0.1:6379> hlen hset_test
(integer) 3784945
Copy the code

Here we see more than 3 million data, let’s perform a del to see:

127.0.0.1:6379> del hset_test
(integer) 1
(3.90s)
Copy the code

You can see that it takes about 4s. We know that the Redis core is single-threaded, so redis is unable to process other requests during this block.

Low peak deletion

The simplest way is to delete the data during the low business peak. For example, most scenarios are deleted at around 4 a.m. Of course, this approach cannot avoid requests during blocking, and is generally applicable to businesses with very small QPS during execution.

Scan for partial

Since the big key cannot be deleted all at once, we will delete it in batches.

hset

For hSET, we hSAN delete in batches.

HSCAN key 0 COUNT 100 HDEL key fieldsCopy the code

Take 100 at a time and delete them

set

For a set, we can take a random batch of data at a time and delete it

SRANDMEMBER key 10 SREM key fieldsCopy the code

zset

With zSet, you can simply delete a batch of data at a time

ZREMRANGEBYRANK key 0 10Copy the code

list

For list, just pop

I :=0 for {lpop key I ++ if I %100 == 0 {sleep(1ms)}}Copy the code

Asynchronous delete

Expired key deletion policy

Some people say that since deleting a large key online will cause blocking, then set a TTL for this key and give redis to delete it by itself. Let me first look at redis’ expired key removal policy:

Regular deletion:

We know redis has expired keys and permanent keys. Redis keeps a separate list of keys in the dictionary. Redis knows that keys in the dictionary can expire at any time, so I will come to deal with them regularly. By default, the command is executed every 100ms. Each time the serveCron is executed, it will randomly select some keys from the TTL keys and check them. If the keys are expired, it will perform synchronous deletion. Reasons for random check:

  1. Impossible to check all, block thread
  2. Random words are fair to some extent

Lazy to delete

By periodically deleting keys, we can delete a batch of expired keys each time. However, if a key has expired and is not cleared after periodic deletion, the user who reads the key cannot return the key directly. In this case, the user will also check whether the key has expired.

Elimination strategy

  1. Noeviction: Will return an error when memory usage exceeds the configuration and will not expel any keys
  2. Allkeys-lru: Expels the longest unused keys via the LRU algorithm
  3. Volatile -lru: Expunts the longest unused key from the set of keys with an expiration time set by the LRU algorithm
  4. Allkeys-random: deletes allkeys randomly
  5. Volatile-random: Banishes randomly from a set of expired keys
  6. Volatile-ttl: Expels keys that are about to expire from keys with expiration time
  7. Volatile -lfu: Expels the least frequently used key out of all keys with expiration time
  8. Allkeys-lfu: expel the least frequently used key from allkeys

The obsolescence policy is a flexible option. Generally, the appropriate obsolescence policy is selected according to the business. When is the customized obsolescence policy triggered? When we add a key or update a bigger key. So its deletion is also synchronous, and if it happens to eliminate a large key, unfortunately it will also currently block.

Bottom line: No matter which of the above three triggers a delete, it is synchronized. So even if you add TTL, redis will be deleted synchronously, and the big key will still block.

Asynchronous delete

In redis4.0, the author also considers the blocking problem caused by large key deletion, so there is asynchronous deletion, which can be divided into user active and program passive.

Take the initiative to delete

unlink

For active deletion, Redis provides unlink as an alternative to del. When we are in unlink, Redis will first check the number of elements to be deleted (such as collection). If the number of elements in the collection is less than or equal to 64, it will directly perform synchronous deletion, because this is not a big key and will not waste a lot of overhead. However, when the number of keys exceeds 64, Redis will consider that there is a high probability that it is a large key, and redis will delete the key from the dictionary first, and the actual value will be handed over to the asynchronous thread, which will not affect the main thread at all.

Flushall, flushdb

Flushall [ASYNC] : flushdb; flushall [ASYNC] : flushdb; flushall [ASYNC] : flushdb; flushall [ASYNC] : flushDB; flushall [ASYNC] : flushDB; The old dictionary is left to asynchronous threads to slowly delete.

Passive delete

Redis Configures policies

  • Lazyfree-lazy-eviction: Asynchronous deletion will be initiated when Redis has a memory deletion policy set to maxMemory. The disadvantage of asynchronous deletion in this scenario is that the memory will not be released if the memory is not deleted in time.
  • Lazyfree-lazy-expire: Specifies an asynchronous thread to delete a key with a TTL when redis clears it.
  • Up – lazy – flush: When the slave node joins the replication pool, it flushes its data. If it takes a long time to flush, more data will accumulate in the replication buffer. It takes a long time for the slave to synchronize data. Slave flush can be handled by asynchronous off-the-shelf to improve synchronization speed.
  • lazyfree-lazy-server-del: This option is used for directives such as rename a fieldRENAME key newkeyIf newKey exists, rename will delete the value of newKey. If newKey is a large key, this will block. When this option is enabled, it will also be handed over to an asynchronous thread, which will not block the main thread.

As an aside: Rename

Let’s start with a test:

127.0.0.1:6379> set A 1 OK 127.0.0.1:6379> eval "for I =1, 10000000000,1 do redis.call('hset','B', I,1) end" 0 (15.89s)Copy the code
  1. Set A to 1
  2. Add 1000W of data to B

If you want to rename A to B, run the rename A B command

127.0.0.1:6379> rename A B
OK
(11.07s)
Copy the code

It is found that the block is caused by redis deleting B. If there is a scene with rename, it must pay attention to whether newKey already exists and whether newkey is a big key.