2793


OSX上でRedisの動作を確認しているのですが、以下のエラーが出る原因を詳しく知りたいです。

`OOM command not allowed when used memory`

環境は、

  * Redis 2.8.19
  * maxmemory-policy allkeys-lru

設定ファイルは、`maxmemory-policy allkeys-lru`としているのに、上記のエラーが出るのは何故か。と調べたところ、

英語版のstackoverflowにありました。

  * [Redis out of memory, even with allkeys-lru policy](https://stackoverflow.com/questions/24990555/redis-out-of-memory-even-with-allkeys-lru-policy)

この回答によると`used_memory_lua`がメモリを多く使用しているからという感じでしたが、  
used_memory_luaが多くなる原因が知りたいです。

ローカル環境で、

  * maxmemory 1080KB
  * maxmemory-policy allkeys-lru

として、メモリ使用量が超えた時の動作を確認していたのです。  
（一度エラーが出た後に、Redisを再起動するとエラーがなかなか出ません。）

コマンド`info`で確認すると、

[code]

    \# Memory
    used_memory:1090400
    used_memory_human:1.04M
    used_memory_rss:1916928
    used_memory_peak:1105312
    used_memory_peak_human:1.05M
    used_memory_lua:35840
    mem_fragmentation_ratio:1.76
    mem_allocator:libc
    
[/code]

という状態なのですが、used_memory_luaが上記より増加していくのか、  
増加するとOOMエラーが出るのか。その上限値は？など。

目的は

  * redisサーバはAWS Elasticache 256MB（cache.t1.micro）を使っている
  * redisのデータはTTL（期限？）が設定してあるものとないものが混在
  * キャッシュと一時的なデータだけなので永続化は不要
  * メモリ容量が超えた場合の削除順もランダムでいい

なのですが、例えば以下の環境だと、エラーを出さずにデータをランダムに削除して使うには、  
どのような設定になるのでしょう？

  * サーバのメモリが512MB
  * Redisのmaxmemoryが256MB
  * used_memory_luaはどのくらいの容量になる？
  * 目的を果たすにはどのような設定が必要なのか？

==== 追記（エラー時のメモリinfo） ====

エラー時の値です。エラーが出る基準がわかりません。

[code]

    \# Memory
    used_memory:1038432
    used_memory_human:1014.09K
    used_memory_rss:2031616
    used_memory_peak:1105312
    used_memory_peak_human:1.05M
    used_memory_lua:35840
    mem_fragmentation_ratio:1.96
    mem_allocator:libc
    
[/code]


CC BY-SA 3.0
2014-12-29T07:02:16.520
2775
2014-12-29T12:56:28.937
2017-05-23T12:38:56.083
-1
512
question
aws redis
RedisのOOM command not allowed when used memoryエラーについて


redis のソースコードを調べてみると、以下の部分が問題の場所になります。

[code]

    scripting.c:luaRedisGenericCommand() function
    /* If we reached the memory limit configured via maxmemory, commands that
     * could enlarge the memory usage are not allowed, but only if this is the
     * first write in the context of this script, otherwise we can't stop
     * in the middle. */
    if (server.maxmemory && server.lua_write_dirty == 0 &&
        (cmd->flags & REDIS_CMD_DENYOOM))
    {
        if (freeMemoryIfNeeded() == REDIS_ERR) {
            luaPushError(lua, shared.oomerr->ptr);
            goto cleanup;
        }
    }
    
    if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
    if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
    
[/code]

shared.oom->ptr が例のエラーメッセージ(OOM command 〜)文字列へのポインタになります。  
これを見ますと、以下の条件が成立した場合に当該のエラーが発生することになります。

  * maxmemory が設定されている
  * lua_write_dirty flag が off
  * 実行中の redis のサブコマンド？に REDIS_CMD_DENYOOM が設定されているもの(set や push などには設定されていて、get や pop には設定されていないので、write 系のコマンドと思われる)
  * サーバ内で使用中のメモリ領域から一定量のメモリを開放できない

`lua_write_dirty` というのがよく判らないのですが、コメントでは以下の様に説明されています。

[code]

    True if a write command was called during the execution of the current script.
    
[/code]

Lua script の実行を開始して、スクリプト内で最初の write
系サブコマンドが実行された時点でセットされます。ただ、`REDIS_CMD_WRITE` と `REDIS_CMD_DENYOOM`
はほとんどのサブコマンドで一緒に設定されていますので、実際には一番最初に実行される write
系サブコマンドでのみ上記のチェックが実行されることになります(コメントの内容の通り)。

次に `freeMemoryIfNeeded()` ですが、コメント欄に、

[code]

    /* This function gets called when 'maxmemory' is set on the config file to limit
     * the max memory used by the server, before processing a command.
     *
     * The goal of the function is to free enough memory to keep Redis under the
     * configured memory limit.
    
[/code]

と記載されています。そして、

[code]

    mem_used = zmalloc_used_memory();
                 :
    /* Check if we are over the memory limit. */
    if (mem_used <= server.maxmemory) return REDIS_OK;
                 :
    /* Compute how much memory we need to free. */
    mem_tofree = mem_used - server.maxmemory;
    
[/code]

となっています。この後の処理を見ますと、free されるのは key object だけの様です(enters a loop selecting the
best keys to evict accordingly to the configured policy)。Lua script
が利用するバッファ領域などはそのままですので(当たり前ですが)、その領域が肥大化すれば Redis
サーバ内で使用しているメモリをより多く開放せざるを得なくなりますね。

※ なんだか奇妙な構造だとは思いますが…

以上の状況から、maxmemory を十分に増やすか、Lua script
のメモリ使用量を抑える様に書き換える(できるのでしょうか？)対応が考えられます。maxmemory
を設定しないという事も考えられますが、余計に酷い状況になりそうなので、やめておいた方が無難でしょう。

追記:

Lua の garbage collection の頻度がコマンド実行50回に1回と設定されています。

[code]

    scripting.c:evalGenericCommand() function
    /* Call the Lua garbage collector from time to time to avoid a
     * full cycle performed by Lua, which adds too latency.
     *
     * The call is performed every LUA_GC_CYCLE_PERIOD executed commands
     * (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
     * for every command uses too much CPU. */
    #define LUA_GC_CYCLE_PERIOD 50
    {
        static long gc_count = 0;
    
        gc_count++;
        if (gc_count == LUA_GC_CYCLE_PERIOD) {
            lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
            gc_count = 0;
        }
    }
    
[/code]

この GC の頻度を上げれば、あるいは Lua のメモリ使用量の増大を緩和できるかもしれません。ただ、Redis
のリビルドが必要となりますし、コメントにある通り CPU 負荷が上がって Lua script の実行性能が低下することになります。


CC BY-SA 3.0
2014-12-29T11:45:06.063
2793
2014-12-29T12:56:28.937
2014-12-29T12:56:28.937
2775
answer