Tair (Redis OSS-compatible) インスタンスはLuaコマンドをサポートしています。 Luaスクリプトを使用して、比較設定 (CAS) コマンドを効率的に処理できます。 これにより、Redisのパフォーマンスが向上し、機能の実装が簡素化されます。 このトピックでは、Luaスクリプトの構文と使用方法について説明します。
注意事項
Luaスクリプトに関連するコマンドは、データ管理 (DMS) コンソールでは使用できません。 DMSの詳細については、「概要」をご参照ください。 クライアントまたはredis-cliを使用してRedisインスタンスに接続し、Luaスクリプトを使用できます。
構文
コマンド | 構文 | 説明 |
EVAL |
| パラメーターを含む特定のスクリプトを実行し、出力を返します。 パラメーター:
説明
|
EVALSHA |
| SHA1ダイジェストによってキャッシュされたスクリプトを評価し、スクリプトを実行します。 EVALSHAコマンドを使用するときにスクリプトがRedisにキャッシュされていない場合、RedisはNOSCRIPTエラーを返します。 EVALまたはscript LOADコマンドを使用してRedisにスクリプトをキャッシュし、もう一度お試しください。 詳細については、「NOSCRIPTエラーの処理」をご参照ください。 |
スクリプトロード |
| Redisで指定されたスクリプトをキャッシュし、スクリプトのSHA1ダイジェストを返します。 |
スクリプトが存在 |
| 対応するSHA1ダイジェストを使用して、スクリプトキャッシュ内の1つ以上のスクリプトの存在に関する情報を返します。 特定のスクリプトが存在する場合、値1が返されます。 そうでなければ、0の値が返される。 |
SCRIPT KILL |
| 実行中のLuaスクリプトを終了します。 |
SCRIPT FLUSH |
| RedisサーバーのスクリプトキャッシュからすべてのLuaスクリプトを削除します。 |
Redisコマンドの詳細については、
Redis公式ウェブサイト。次のサンプルコードは、特定のRedisコマンドの例を示します。 次のコマンドを実行する前に、SET foo values_test
コマンドを実行します。
サンプルEVALコマンド:
EVAL "return redis.call('GET', KEYS[1])" 1 foo
サンプル出力:
"value_test"
サンプルSCRIPT LOADコマンド:
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
サンプル出力:
"620cd258c2c9c88c9d10db67812ccf663d96bdc6"
サンプルEVALSHAコマンド:
EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 foo
サンプル出力:
"value_test"
サンプルSCRIPT EXISTSコマンド:
SCRIPT EXISTS 620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffff
サンプル出力:
1) (integer) 1 2) (integer) 0
サンプルSCRIPT FLUSHコマンド:
警告このコマンドは、キャッシュされたすべてのLuaスクリプトをインスタンスから削除します。 このコマンドを実行する前に、必ずLuaスクリプトをバックアップしてください。
SCRIPT FLUSH
サンプル出力:
OK
メモリとネットワークのオーバーヘッドを削減
問題:
同じ目的を果たす多数のスクリプトがRedisにキャッシュされます。 これらのスクリプトは大量のメモリを占有し、メモリ不足 (OOM) エラーを引き起こす可能性があります。 無効な使用の例:
EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0
解決策:
メモリ使用量を減らすために、パラメータを定数としてLuaスクリプトに渡さないでください。
# The following commands serve the same purposes as the preceding sample commands but cache scripts only once. EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k1 v1 EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k2 v2
次のコマンド構文を使用して、メモリとネットワークのオーバーヘッドを減らします。
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # After this command is run, the following output is returned: "55b22c0d0cedf3866879ce7c854970626dcef0c3". EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2
Luaスクリプトキャッシュをフラッシュする
問題:
Luaスクリプトキャッシュがインスタンスのメモリを占有するため、インスタンスの使用済みメモリが予想よりも高くなる可能性があります。 インスタンスの使用済みメモリがmaxmemoryで指定された上限に近づくか超えると、OOMエラーが発生します。 エラー例:
-OOM command not allowed when used memory > 'maxmemory'.
解決策:
クライアントでscript Flushコマンドを実行して、Luaスクリプトキャッシュをフラッシュします。 FLUSHALLコマンドとは異なり、SCRIPT FLUSHコマンドは同期しています。 Redisが多数のLuaスクリプトをキャッシュしている場合、SCRIPT FLUSHコマンドはRedisを長期間ブロックし、関連するインスタンスが使用できなくなることがあります。 作業は慎重に行ってください。 そのため、ピーク時間外に操作を実行することを推奨します。
コンソールで データの消去 をクリックすると、データはクリアできますが、Luaスクリプトキャッシュはフラッシュできません。
大量のメモリリソースを消費する可能性のあるLuaスクリプトや、大量のデータを含むLuaスクリプトを記述しないでください。 そうしないと、メモリ使用量が大幅に増加し、OOMエラーが発生する可能性があります。 メモリ使用量を減らすために、volatile-lruポリシーを使用してdata evictionを有効にすることを推奨します。 デフォルトでは、データ削除はTairで有効になっています。 ただし、データの削除が有効になっているかどうかに関係なく、TairはLuaスクリプトキャッシュを削除しません。
NOSCRIPTエラーの処理
問題:
EVALSHAコマンドを使用するときにスクリプトがRedisにキャッシュされていない場合、RedisはNOSCRIPTエラーを返します。 エラー例:
(error) NOSCRIPT No matching script. Please use EVAL.
解決策:
EVALまたはSCRIPT LOADコマンドを実行して、Redisでスクリプトをキャッシュし、再試行します。 インスタンスの移行や構成の変更などの特定のシナリオでは、RedisはLuaスクリプトの永続性と複製可能性を保証できないため、RedisはLuaスクリプトキャッシュをフラッシュします。 この場合、クライアントはエラーを処理できる必要があります。 詳細については、「スクリプトのキャッシュ、永続化、レプリケーション」をご参照ください。
次のサンプルPythonコードは、NOSCRIPTエラーを処理するメソッドを示しています。 サンプルコードは、Luaスクリプトを使用して文字列の先頭に追加します。
redis-pyを使用してエラーを処理することもできます。 redis-pyは、NOSCRIPTエラーのcatchステートメントなど、RedisのLuaスクリプトの判断ロジックをカプセル化するScriptクラスを提供します。
import redis
import hashlib
# strin specifies a string in Lua scripts. The following function returns the SHA1 value of strin in the string format.
def calcSha1(strin):
sha1_obj = hashlib.sha1()
sha1_obj.update(strin.encode('utf-8'))
sha1_val = sha1_obj.hexdigest()
return sha1_val
class MyRedis(redis.Redis):
def __init__(self, host="localhost", port=6379, password=None, decode_responses=False):
redis.Redis.__init__(self, host=host, port=port, password=password, decode_responses=decode_responses)
def prepend_inLua(self, key, value):
script_content = """\
local suffix = redis.call("get", KEYS[1])
local prefix = ARGV[1]
local new_value = prefix..suffix
return redis.call("set", KEYS[1], new_value)
"""
script_sha1 = calcSha1(script_content)
if self.script_exists(script_sha1)[0] == True: # Check whether the script is already cached in Redis.
return self.evalsha(script_sha1, 1, key, value) # If the script is already cached, the EVALSHA command is used to run the script.
else:
return self.eval(script_content, 1, key, value) # Otherwise, use the EVAL command to run the script. Note that the EVAL command can cache scripts in Redis. You can also use the SCRIPT LOAD and EVALSHA commands.
r = MyRedis(host="r-******.redis.rds.aliyuncs.com", password="***:***", port=6379, decode_responses=True)
print(r.prepend_inLua("k", "v"))
print(r.get("k"))
Luaスクリプトのタイムアウトの処理
問題:
LuaスクリプトはTairでアトミックに実行されるため、遅いLuaリクエストはTairをブロックする可能性があります。 1つのLuaスクリプトでTairを最大5秒間ブロックできます。 5秒後、スクリプトの実行が完了するまで、Tairは他のコマンドのBUSYエラーを返します。
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
解決策:
SCRIPT KILLコマンドを実行してLuaスクリプトを終了するか、Luaスクリプトの実行が完了するまで待ちます。
説明低速Luaスクリプトが実行されている最初の5秒間は、Tairがブロックされているため、script KILLコマンドは有効になりません。
Tairが長期間ブロックされないようにするには、Luaスクリプトを作成するときにLuaスクリプトの実行に必要な時間を見積もり、無限ループを確認し、必要に応じてLuaスクリプトを分割することをお勧めします。
問題:
Luaスクリプトがデータセットに対して書き込みコマンドを実行している場合、script KILLコマンドは有効になりません。 エラー例:
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
解決策:
Tairコンソールの インスタンス一覧 ページで、管理するインスタンスを見つけ、[操作] 列の [再起動] をクリックします。
スクリプトのキャッシュ、永続化、およびレプリケーション
問題:
Redisは、インスタンスが再起動されていない場合、またはインスタンスに対してSCRIPT FLUSHコマンドが実行されていない場合、インスタンスで実行されたLuaスクリプトをキャッシュし続けます。 ただし、Redisは、インスタンスの移行、構成の変更、バージョンのアップグレード、インスタンスの切り替えなどのシナリオでは、Luaスクリプトの永続性または現在のノードから他のノードへのLuaスクリプトの同期を保証できません。
解決策:
すべてのLuaスクリプトをオンプレミスデバイスに保存します。 必要に応じてEVALまたはSCRIPT LOADコマンドを使用して、RedisでLuaスクリプトを再キャッシュします。 これにより、インスタンスの再起動または高可用性 (HA) の切り替え中にLuaスクリプトがクリアされたときにNOSCRIPTエラーが発生するのを防ぎます。
クラスターインスタンスのLuaスクリプトの制限
オープンソースのRedisクラスターでは、Luaスクリプトの使用に制限があります。 Tairクラスターインスタンスには、Luaスクリプトの使用に関する次の追加制限があります。
特定のマイナーバージョンを使用するインスタンスは、EVAL関連のコマンドをサポートしません。
ERRコマンドeval not support for normal user
エラーメッセージが返された場合は、インスタンスのマイナーバージョンを更新して、もう一度お試しください。 詳細については、「」「インスタンスのマイナーバージョンの更新」をご参照ください。すべてのキーは同じスロットになければなりません。 それ以外の場合、システムは
-ERR eval/evalshaコマンドキーが同じスロットにある必要があります \r\n
エラーを返します。CLUSTER KEYSLOTコマンドを実行して、キーのハッシュスロットを取得できます。
1つのノードでSCRIPT LOADコマンドを実行すると、Luaスクリプトが他のノードに格納されない場合があります。
PSUBSCRIBE、PUBSUB、PUBLISH、PUUNSUBSCRIBE、SUBSCRIBE、UNSUBSCRIBEのPub/Subコマンドはサポートされていません。
UNPACK機能はサポートされていません。
すべての操作を同じハッシュスロットで実行でき、TairクラスターアーキテクチャによってLuaスクリプトに課せられた制限を突破する場合は、コンソールでscript_check_enableパラメーターを0に設定できます。 このように、システムはバックエンドでLuaスクリプトをチェックしません。 この場合、プロキシノードがLuaスクリプトに含まれる要求をルーティングできるように、KEYS配列に少なくとも1つのキーを指定する必要があります。 すべての操作が同じハッシュスロットで実行されることを確認できない場合は、エラーが返されます。 詳細については、「」「インスタンスパラメーターの設定」をご参照ください。
プロキシモードでのLuaスクリプトの追加チェック
script_check_enableパラメーターを指定して、Luaスクリプトで次のチェックを無効にすることができます。 チェックを無効にしないことを推奨します。
Luaスクリプトは、redis.ca llまたはredis.pcall関数を使用してTairコマンドを実行します。 すべてのキーはkeys配列を使用して指定する必要があり、Lua変数で置き換えることはできません。 KEYS配列を使用してキーを指定しない場合、次のエラーメッセージが返されます。
-ERR bad lua script for redis cluster, スクリプトが使用するすべてのキーはkeys配列 \r\nを使用して渡される必要があります
。説明この制限は、Redis 5.0を実行し、マイナーバージョンが5.0.8より前のインスタンスと、Redis 4.0以前を実行するインスタンスにのみ適用されます。
有効および無効なコマンドの使用例:
# To prepare for this example, run the following commands: SET foo foo_value SET {foo}bar bar_value # Example of valid command usage: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar # Example of invalid command usage: EVAL "return redis.call('mget', KEYS[1], '{foo}bar')" 1 foo # This command is invalid because the '{foo}bar' key must be specified by using the KEYS array. EVAL "local i = 2 return redis.call('mget', KEYS[1], KEYS[i])" 2 foo {foo}bar # This command is invalid because the index of keys consists of variables, which is not allowed for an instance in proxy mode. Instances in direct connection mode are not subject to this limit. EVAL "return redis.call('mget', KEYS[1], ARGV[1])" 1 foo {foo}bar # This command is invalid because ARGV[1] cannot be specified as a key.
redis.ca llまたはredis.pcall関数でLuaスクリプトを使用する場合、最初に指定するパラメーターは、実行するTairコマンドを表す文字列リテラルである必要があります。 最初のパラメーターとして変数または非リテラル文字列を使用しようとすると、次のエラーメッセージが返されます。
-ERR bad lua script for redis cluster, first parameter o f redis.ca ll/redis.pcallは単一のリテラル文字列でなければなりません
。有効および無効なコマンドの使用例:
# Example of valid command usage eval "redis.call('GET', KEYS[1])" 1 foo # Example of invalid command usage eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo
実行するすべてのコマンドにキーを含める必要があります。 それ以外の場合、次のエラーメッセージが返されます。
-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\n
。説明この制限は、Redis 5.0を実行し、マイナーバージョンが5.0.8より前のインスタンスと、Redis 4.0以前を実行するインスタンスにのみ適用されます。
有効および無効なコマンドの使用例:
# Example of valid command usage EVAL "return redis.call('get', KEYS[1])" 1 fooeval # Example of invalid command usage EVAL "return redis.call('get', 'foo')" 0
ネストされた呼び出しは、RedisクラスターのLuaスクリプトではサポートされません。 ネストされた呼び出しを含むスクリプトを実行しようとすると、次のエラーメッセージが返されます。
-ERR bad lua script for redis cluster, neste d redis.ca ll/redis.pcall
ローカル変数を使用して、この制限を回避できます。 有効および無効なコマンドの使用例:
# Example of valid command usage EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar # Example of invalid command usage EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo bar
MULTIまたはEXECトランザクションでEVAL、EVALSHA、またはSCRIPTコマンドを実行することはできません。
KEYSやSCANなど、複数のTairノードを含むコマンドは、Luaスクリプトではサポートされていません。
Luaスクリプトがアトミックに実行されるようにするために、プロキシノードはKEYSパラメーターを使用してLuaスクリプトをTairノードにルーティングして実行し、結果を取得します。 これにより、Tairノードと他のノードのコマンド出力が異なります。
プロキシモードで使用できない機能を使用する場合は、Tairクラスターインスタンスの直接接続モードを有効にできます。 ただし、プロキシモードの要件を満たさないLuaスクリプトが直接接続モードで実行されている場合、Tairクラスターインスタンスの移行または構成変更は実行できません。 これは、クラスターインスタンスが移行および構成変更中にプロキシノードに依存するためです。
この問題を解決するには、直接接続モードでスクリプトを実行する前に、Luaスクリプトがプロキシモードの要件を満たしていることを確認してください。