Tair (Redis OSS-compatible) インスタンスは Lua コマンドをサポートしています。Lua スクリプトは、CAS (Compare-And-Set) コマンドを効率的に処理し、インスタンスのパフォーマンスを向上させ、以前は実装が困難または非効率だった機能の実装を簡素化できます。このトピックでは、Lua スクリプトの構文と使用方法について説明します。
構文
パフォーマンス最適化の実践
メモリとネットワークのオーバーヘッドを削減する
重複した機能を持つ多くのスクリプトがインスタンスにキャッシュされると、大量のメモリを消費し、メモリ不足 (OOM) エラーを引き起こすことさえあります。以下は、不正な使用例です。
EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0ソリューション:
メモリ使用量を削減するために、パラメーターを定数として Lua スクリプトに渡さないでください。
# 次のコマンドは、前のサンプルコマンドと同じ目的を果たしますが、スクリプトを一度だけキャッシュします。 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])" # このコマンドを実行すると、Redis は "55b22c0d0cedf3866879ce7c854970626dcef0c3" を返します EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2
Lua スクリプトキャッシュをフラッシュする
Lua スクリプトキャッシュがインスタンスのメモリを占有するため、インスタンスの使用済みメモリが予想よりも高くなることがあります。インスタンスの使用済みメモリが上限に近づくか超えると、OOM エラーが返されます。エラー例:
-OOM command not allowed when used memory > 'maxmemory'.ソリューション:
クライアントで SCRIPT FLUSH コマンドを実行して、Lua スクリプトキャッシュをフラッシュします。FLUSHALL コマンドとは異なり、SCRIPT FLUSH コマンドは同期的です。インスタンスが多くの Lua スクリプトをキャッシュしている場合、SCRIPT FLUSH コマンドはインスタンスを長時間ブロックし、関連するインスタンスが利用できなくなる可能性があります。注意して進めてください。この操作はオフピーク時に実行することを推奨します。
コンソールで データをクリアする をクリックしても、データはクリアできますが、Lua スクリプトキャッシュはフラッシュできません。
過剰なメモリを占有する可能性のある Lua スクリプトを記述しないでください。また、大量のデータを含む Lua スクリプトも記述しないでください。そうしないと、メモリ使用量が大幅に増加し、OOM エラーが発生する可能性があります。メモリ使用量を削減するには、データのエビクション (インスタンスではデフォルトで有効、volatile-lru モード) を有効にすることを推奨します。ただし、データのエビクションが有効になっているかどうかに関係なく、インスタンスは Lua スクリプトキャッシュをエビクションしません。
エラー処理ガイド
NOSCRIPT エラー
EVALSHA コマンドを使用するときにスクリプトがインスタンスにキャッシュされていない場合、インスタンスは NOSCRIPT エラーを返します。エラー例:
(error) NOSCRIPT No matching script. Please use EVAL.ソリューション:
EVAL または SCRIPT LOAD コマンドを実行してスクリプトをインスタンスにキャッシュし、再試行してください。インスタンスの移行や構成の変更などの特定のシナリオでは、インスタンスは Lua スクリプトの永続性と複製可能性を保証できないため、Lua スクリプトキャッシュをフラッシュします。この場合、クライアントがエラーを処理できる必要があります。詳細については、「永続性とレプリケーションの問題」をご参照ください。
次のサンプル Python コードは、NOSCRIPT エラーを処理するメソッドを示しています。サンプルコードは、Lua スクリプトを使用して文字列を先頭に追加します。
redis-py を使用してこのエラーを処理することもできます。redis-py は、NOSCRIPT エラーの catch 文など、Redis の Lua スクリプトの判断ロジックをカプセル化する Script クラスを提供します。
import redis
import hashlib
# strin は Lua スクリプト内の文字列です。この関数は strin の sha1 値を文字列形式で返します。
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: # スクリプトが Redis にすでにキャッシュされているかどうかを確認します。
return self.evalsha(script_sha1, 1, key, value) # スクリプトがすでにキャッシュされている場合は、EVALSHA コマンドを使用してスクリプトを実行します。
else:
return self.eval(script_content, 1, key, value) # それ以外の場合は、EVAL コマンドを使用してスクリプトを実行します。EVAL コマンドは Redis にスクリプトをキャッシュできることに注意してください。SCRIPT LOAD および EVALSHA コマンドを使用することもできます。
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 スクリプトはインスタンス内で原子的に実行されるため、遅い Lua リクエストはインスタンスをブロックする可能性があります。1 つの Lua スクリプトは最大 5 秒間インスタンスをブロックできます。5 秒後、スクリプトの実行が完了するまで、インスタンスは他のコマンドに対して BUSY エラーを返します。
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.ソリューション:
SCRIPT KILL コマンドを実行して Lua スクリプトを終了させるか、Lua スクリプトの実行が完了するまで待ちます。
説明遅い Lua スクリプトが実行されている最初の 5 秒間は、インスタンスがブロックされているため、SCRIPT KILL コマンドは効果がありません。
インスタンスが長時間ブロックされるのを防ぐために、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 OSS-compatible) は、インスタンスが再起動されないか、インスタンスに対して SCRIPT FLUSH コマンドが実行されない限り、インスタンスで実行された Lua スクリプトをキャッシュし続けます。ただし、Tair (Redis OSS-compatible) は、インスタンスの移行、構成の変更、バージョンのスペックアップ、インスタンスのスイッチオーバーなどのシナリオにおいて、Lua スクリプトの永続性や、現在のノードから他のノードへの Lua スクリプトの同期を保証できません。
ソリューション:
すべての Lua スクリプトをオンプレミスデバイスに保存します。必要に応じて、EVAL または SCRIPT LOAD コマンドを使用して、Tair (Redis OSS-compatible) に Lua スクリプトを再キャッシュします。これにより、インスタンスの再起動または高可用性 (HA) スイッチオーバー中に Lua スクリプトがクリアされたときに NOSCRIPT エラーが発生するのを防ぎます。
クラスターインスタンスの特別な制限
クラスターアーキテクチャの制約
実行の原子性を確保するために、Lua スクリプトは分割できず、クラスターインスタンス内の 1 つのシャードでのみ実行できます。ほとんどの場合、キーを使用して Lua スクリプトがルーティングされるシャードを決定します。したがって、クラスターインスタンスで Lua スクリプトを実行するときは、少なくとも 1 つのキーを指定する必要があります。複数のキーを読み書きする場合、1 つの Lua スクリプト内のキーは同じスロットに属している必要があります。そうしないと、異常な実行結果が返されます。キーを持たない Lua スクリプト (KEYS、SCAN、FLUSHDB など) は正常に実行できます。ただし、単一のシャードのデータのみが返されます。この制限は、クラスターインスタンスのアーキテクチャに起因します。
1 つのノードで SCRIPT LOAD コマンドを実行しても、Lua スクリプトが他のノードに保存されない場合があります。
プロキシモードのエラーコード
プロキシは構文チェックを実行して、複数のスロットに属するキーを特定し、トラブルシューティングを支援するために事前に例外をスローします。プロキシは Lua 仮想マシンとは異なるチェックメソッドを使用します。これにより、プロキシモードでの Lua スクリプトの実行に追加の制限が課せられます。たとえば、UNPACK コマンドはサポートされておらず、EVAL、EVALSHA、および SCRIPT コマンドは MULTI および EXEC トランザクションではサポートされていません。
また、script_check_enable パラメーターを設定して、プロキシモードでの Lua 構文の追加チェックを無効にすることもできます。
読み書き分離インスタンスに対して readonly_lua_route_ronode_enable パラメーターが 1 に設定されている場合、プロキシノードは Lua スクリプトに読み取り専用コマンドのみが含まれているかどうかをチェックし、読み取り専用ノードに転送するかどうかを決定します。このチェックロジックは Lua 構文に制限を課します。
script_check_enable パラメーターを 0 に設定すると、インスタンスにどのような影響がありますか?
インスタンスが Redis 5.0 (マイナーバージョン 5.0.8 未満) および 4.0 以前と互換性がある場合、チェックを無効にしないことを推奨します。そうしないと、スクリプトが正常に実行されない場合に正常な結果が返される可能性があります。
他のバージョンでチェックを無効にすると、プロキシは Lua 構文をチェックしなくなりますが、データノードは引き続き Lua 構文をチェックします。
以下にエラーコードとその原因を説明します。
Redis Cluster アーキテクチャの制限
エラーコード:
-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\n説明: Lua スクリプトを実行するときは、キーを含める必要があります。プロキシノードはキーを使用して、Lua スクリプトが転送されるシャードを決定します。
# 有効な使用例 EVAL "return redis.call('get', KEYS[1])" 1 fooeval # 無効な使用例 EVAL "return redis.call('get', 'foo')" 0エラーコード:
-ERR 'xxx' command keys must in same slot説明: Lua スクリプト内の複数のキーは、同じスロットに属している必要があります。
# 有効な使用例: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar # 無効な使用例: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobar
プロキシモードでの Lua 構文チェックによる追加の制限
script_check_enable パラメーターを 無効 にして、プロキシが Lua 構文の追加チェックを実行しないようにすることができます。
エラーコード:
-ERR bad lua script for redis cluster, nested redis.call/redis.pcall説明: ネストされた呼び出しはサポートされていません。ローカル変数を使用して Lua スクリプトを呼び出すことができます。
# 有効な使用例 EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar # 無効な使用例 EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo barエラーコード:
-ERR bad lua script for redis cluster, first parameter of redis.call/pcall must be a single literal string説明: redis.call/pcall で呼び出されるコマンドは、文字列リテラルである必要があります。
# 有効な使用例 eval "redis.call('GET', KEYS[1])" 1 foo # 無効な使用例 eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo
読み取り/書き込み権限の問題
エラーコード:
-ERR Write commands are not allowed from read-only scripts説明: EVAL_RO コマンドを使用して送信された Lua スクリプトに書き込みコマンドを含めることはできません。
エラーコード:
-ERR bad write command in no write privilege説明: 読み取り専用アカウントによって送信された Lua スクリプトに書き込みコマンドを含めることはできません。
サポートされていないコマンド
エラーコード:
-ERR script debug not support説明: SCRIPT DEBUG コマンドはプロキシモードではサポートされていません。
エラーコード:
-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxx説明: Lua スクリプトには、プロキシモードでサポートされていないコマンドが含まれています。詳細については、「クラスターアーキテクチャおよび読み書き分離インスタンスのコマンドの制限」をご参照ください。
Lua 構文エラー
エラーコード:
-ERR bad lua script for redis cluster, redis.call/pcall expect '('または-ERR bad lua script for redis cluster, redis.call/redis.pcall definition is not complete, expect ')'説明: Lua 構文エラー。
redis.call関数には、完全な(と)のセットが続く必要があります。エラーコード:
-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE説明: ZUNIONSTORE および ZINTERSTORE コマンドの numkeys パラメーターは 0 より大きい必要があります。
エラーコード:
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeys説明: ZUNIONSTORE および ZINTERSTORE コマンドのキーの数は numkeys の値より小さいです。
エラーコード:
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error説明: XREAD または XREADGROUP コマンドの構文が正しくありません。パラメーターの数を確認してください。
エラーコード:
-ERR Redis クラスター用の不正な Lua スクリプト、xread/xreadgroup コマンドの構文エラー、ストリームが指定されていません説明: XREAD および XREADGROUP コマンドでは streams パラメーターが必要です。
エラーコード:
-ERR bad lua script for redis cluster, sort command syntax error説明: SORT コマンドの構文が正しくありません。
よくある質問
Q: DMS は Lua スクリプトの実行をサポートしていますか?
A: Lua スクリプトに関連するコマンドは、現在 DMS コンソールではサポートされていません。クライアントまたは redis-cli を使用してインスタンスに接続し、Lua スクリプトを使用できます。