Redis刪除大Key

dubby發表於2018-10-18

原文連結:https://www.dubby.cn/detail.html?id=9112

>這裡說的大 key 是指包含很多元素的 set,sorted set,list 和 hash。

刪除操作,我們一般想到有 2 種,delexpire

DEL

>Time complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).

如果要刪除的 key 是一個集合,包含了很多元素,那麼DEL時的耗時和元素個數成正比,所以如果直接DEL,會很慢。

EXPIRE

>Note that calling EXPIRE/PEXPIRE with a non-positive timeout or EXPIREAT/PEXPIREAT with a time in the past will result in the key being deleted rather than expired (accordingly, the emitted key event will be del, not expired).

想著 expire 會不會可以不是直接刪除,可惜官網的描述讓我心灰意冷,如果 expire 後指定的 timeout 不是正數,也就是<=0,那其實就是DEL

一點一點刪

我們知道 Redis 的工作執行緒是單執行緒的,如果一個 command 堵塞了,那所有請求都會超時,這時候,一些騷操作也許可以幫助你。

其實如果想刪除 key,可以分解成 2 個目的,1:不想讓其他人訪問到這個 key,2:釋放空間。

那其實我們可以分解成兩步,先用RENAME把原先的 key rename 成另一個 key,比如:

RENAME userInfo:123 &quot;deleteKey:userInfo:123&quot;

然後可以慢慢去刪"deleteKey:userInfo:123",如果是 set,那麼可以用SREM慢慢刪,最後再用DEL徹底刪掉。

>這裡可以搞個 task 去SCAN deleteKey:*,然後慢慢刪除。

Redis 4.0.0 提供了一個更加方便的命令

>Available since 4.0.0.

>Time complexity: O(1) for each key removed regardless of its size. Then the command does O(N) work in a different thread in order to reclaim memory, where N is the number of allocations the deleted objects where composed of.

UNLINK其實是直接返回,然後在後臺執行緒慢慢刪除。

如果你的 Redis 版本>=4.0.0,那麼強烈建議使用UNLINK來刪除。

刪除耗時測試結果

>單位:微秒

Set 個數 DEL EXPIRE UNLINK
1 90 97 75
10 79 67 100
100 51 49 47
1000 303 296 49
10000 2773 2592 52
100000 31210 33157 51
1000000 549388 501536 62
package main

import (
    &quot;github.com/go-redis/redis&quot;
    &quot;fmt&quot;
    &quot;time&quot;
)

func main() {
    client := redis.NewClient(&amp;redis.Options{
        Addr:         &quot;localhost:6379&quot;,
        Password:     &quot;&quot;,
        DB:           0,
        ReadTimeout:  1000 * 1000 * 1000 * 60 * 60 * 24,
        WriteTimeout: 1000 * 1000 * 1000 * 60 * 60 * 24,
    })

    maxLength := int64(10000 * 100)

    for n := int64(1); n &lt;= maxLength; n *= 10 {
        fmt.Println(&quot;Set個數&quot;, n)
        TestDelBigSet(client, n)
        TestExpireBigSet(client, n)
        TestUnlinkBigSet(client, n)
        fmt.Println()
    }
}

func TestDelBigSet(client *redis.Client, count int64) {
    redisKey := fmt.Sprintf(&quot;%s%d&quot;, &quot;del:&quot;, time.Now().Nanosecond())

    for n := int64(0); n &lt; count; n++ {
        err := client.SAdd(redisKey, fmt.Sprintf(&quot;%d&quot;, n)).Err()
        if err != nil {
            panic(err)
        }
    }

    startTime := CurrentTimestampInMicroSecond()
    client.Del(redisKey)
    endTime := CurrentTimestampInMicroSecond()

    fmt.Println(&quot;Del&quot;, endTime-startTime)
}

func TestUnlinkBigSet(client *redis.Client, count int64) {
    redisKey := fmt.Sprintf(&quot;%s%d&quot;, &quot;unlink:&quot;, time.Now().Nanosecond())

    for n := int64(0); n &lt; count; n++ {
        err := client.SAdd(redisKey, fmt.Sprintf(&quot;%d&quot;, n)).Err()
        if err != nil {
            panic(err)
        }
    }
    startTime := CurrentTimestampInMicroSecond()
    client.Unlink(redisKey)
    endTime := CurrentTimestampInMicroSecond()

    fmt.Println(&quot;Unlink&quot;, endTime-startTime)
}

func TestExpireBigSet(client *redis.Client, count int64) {
    redisKey := fmt.Sprintf(&quot;%s%d&quot;, &quot;expire:&quot;, time.Now().Nanosecond())

    for n := int64(0); n &lt; count; n++ {
        err := client.SAdd(redisKey, fmt.Sprintf(&quot;%d&quot;, n)).Err()
        if err != nil {
            panic(err)
        }
    }
    startTime := CurrentTimestampInMicroSecond()
    client.Expire(redisKey, 0)
    endTime := CurrentTimestampInMicroSecond()

    fmt.Println(&quot;Expire&quot;, endTime-startTime)
}

func CurrentTimestampInMicroSecond() int64 {
    return time.Now().UnixNano() / 1000
}

更多原創文章乾貨分享,請關注公眾號
  • Redis刪除大Key
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章