Redis 6.2&7 Rehash 関連の最適化 | 高度な運用と保守

1. Redis の再ハッシュ問題のレビュー

簡単に確認した後、キーのしきい値を超えた後は、追加のハッシュ テーブル メモリ サイズが必要になります。

キー値の数 追加のハッシュ テーブル メモリ サイズが必要
134,217,728 2GB
67,108,864 1GB
33,554,432 512.0MB
16,777,216 256.0MB
8,388,608 128.0MB
4,194,304 64.0MB
2,097,152 32.0MB
1,048,576 16.0MB
524,288 8.0MB

キーの数が増えると、再ハッシュに必要な追加メモリが増加し、その結果、可用性リスク (大量エビクション、Redis 同期) とデータ損失 (エビクション) リスクが高まります。

2. Redis 6.2&7+ Rehash 関連の最適化

Rehash は Redis 6.2 以降に最適化されました。

Limit the main db dictionaries expansion to prevent key eviction (#7954)
  In the past big dictionary rehashing could result in massive data eviction.Now this rehashing is delayed (up to a limit), which can result in performance loss due to hash collisions.

簡単な翻訳:

在大的hash表(键值数多)场景下,rehash会延迟执行防止大量数据丢失和可用性问题。

3. 実験

1. バージョンの選択

  • Redis 6.0.15

  • Redis 7.0.11

2. 実験条件

  • 最大メモリ = 5.6GB

  • 最初の注入: 67,100,000 個のキー、メモリを観察する

  • 2 回目のプッシュ: 2,000,000 キー、可用性とエビクションを監視

3. 実験を開始する

(1) Redis 6.0.15

最初の注入: 67,100,000 キー、メモリを観察します。

maxmemory:         5.6GB
used_memory_human: 5.5GB
dbsize:            67,100,000  

2 回目のプッシュ: 2,000,000 キー、可用性とエビクションを監視

  • クライアントの実行がタイムアウトしました。タイムアウトはほぼ 30 秒以上でした。redis-cli --latency-history

    min: 0, max: 1, avg: 0.06 (1485 samples) -- 15.01 seconds range
    min: 0, max: 36511, avg: 45.63 (801 samples) -- 44.61 seconds range
    
  • 大量のキーを削除します: 500 万以上

evicted_keys:5,909,376
  • ピークメモリ: 再ハッシュ用にさらに 1 GB

used_memory_peak_human:6.50G

(2) Redis 7.0.11

最初の注入: 67,100,000 キー、メモリを観察します。

maxmemory:         5.6GB
used_memory_human: 5.5GB
dbsize:            67,100,000  

2 回目のプッシュ: 2,000,000 キー、可用性とエビクションを監視

  • タイムアウトなしのクライアント実行、redis-cli --latency-history

    min: 0, max: 2, avg: 0.05 (1484 samples) -- 15.01 seconds range
    min: 0, max: 3, avg: 0.24 (1454 samples) -- 15.00 seconds range
    
  • 通常のエビクション (0.1GB 残り)

evicted_keys:152485
evicted_keys:443253
evicted_keys:751165
evicted_keys:1058191
evicted_keys:1367445
evicted_keys:1662485
evicted_keys:1662485
  • ピークメモリ: 再ハッシュなし

used_memory_peak_human:5.50G

4. 実験比較

バージョン リハッシュが発生するかどうか クリティカルタイムアウト
6.0.15 持っている はい、30 秒以上利用できません
7.0.11 なし なし、通常のエビクション

4. コード分析

1.辞書:

新しく追加されたexpandAllowedは、dictが現在再ハッシュされているかどうかを決定します

typedef struct dictType {
   ....
    int (*expandAllowed)(size_t moreMem, double usedRatio);
  ....
} dictType;

dictが展開されると、新しい判定が追加されます dictTypeExpandAllowed

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
   ......
    if (!dictTypeExpandAllowed(d))
        return DICT_OK;
    ......
}

dictのexpandAllowedが空の場合は再ハッシュが許可され、それ以外の場合はexpandAllowedが実行されます。

static int dictTypeExpandAllowed(dict *d) {
    if (d->type->expandAllowed == NULL) return 1;
    return d->type->expandAllowed(
                    DICTHT_SIZE(_dictNextExp(d->ht_used[0] + 1)) * sizeof(dictEntry*),
                    (double)d->ht_used[0] / DICTHT_SIZE(d->ht_size_exp[0]));
}

2.redis辞書

Redis の dbDictType および dbExpiresDictType

/* Db->dict, keys are sds strings, vals are Redis objects. */
dictType dbDictType = {
    dictSdsHash,                /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCompare,          /* key compare */
    dictSdsDestructor,          /* key destructor */
    dictObjectDestructor,       /* val destructor */
    dictExpandAllowed,          /* allow to expand */
    dictEntryMetadataSize       /* size of entry metadata in bytes */
};

/* Db->expires */
dictType dbExpiresDictType = {
    dictSdsHash,                /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCompare,          /* key compare */
    NULL,                       /* key destructor */
    NULL,                       /* val destructor */
    dictExpandAllowed           /* allow to expand */
};

見られます:

  • dict の使用率がハッシュ負荷率より大きい: 再ハッシュが可能

  • dict の使用量がハッシュ負荷係数よりも小さい場合:

    • maxmemory=0 の場合、再ハッシュを行うことができます

    • 現在使用されているメモリ + 再ハッシュに必要なメモリが maxmemory 未満の場合は、再ハッシュを実行できます。

    • 現在のオブジェクト メモリ (使用済みメモリ - 追加メモリ (さまざまなバッファ)) + 再ハッシュに必要なメモリが maxmemory より少ない場合、再ハッシュを実行できます。

int dictExpandAllowed(size_t moreMem, double usedRatio) {
    if (usedRatio <= HASHTABLE_MAX_LOAD_FACTOR) {
        return !overMaxmemoryAfterAlloc(moreMem);
    } else {
        return 1;
    }
}

/* Return 1 if used memory is more than maxmemory after allocating more memory,
 * return 0 if not. Redis may reject user's requests or evict some keys if used
 * memory exceeds maxmemory, especially, when we allocate huge memory at once. */
int overMaxmemoryAfterAlloc(size_t moremem) {
    if (!server.maxmemory) return  0; /* No limit. */

    /* Check quickly. */
    size_t mem_used = zmalloc_used_memory();
    if (mem_used + moremem <= server.maxmemory) return 0;

    size_t overhead = freeMemoryGetNotCountedMemory();
    mem_used = (mem_used > overhead) ? mem_used - overhead : 0;
    return mem_used + moremem > server.maxmemory;
}

V. 結論

  • Redis6.2+ バージョンは、大規模な dict での再ハッシュによって引き起こされるユーザビリティとエビクションの問題を効果的に解決します。

  • Redis7 は現在比較的安定しており (2022 年 4 月リリース)、7.0.11 がオンラインで広く使用されています。

  • 新しいバージョンでは、リハッシュの遅延処理のみを実行し、リハッシュの実行を停止するわけではありません。また、負荷率によって発生する可能性のあるパフォーマンスの問題には適切に注意する必要があります。

  • Redis6.2 以降では、エビクションに関するいくつかの最適化も行われています。これについては、後で紹介します。 増分エビクション処理

     

おすすめ

転載: blog.csdn.net/LinkSLA/article/details/131723713