すべてのプロダクト
Search
ドキュメントセンター

ApsaraDB for Redis:Luaスクリプトの使用仕様と一般的なエラーに対するソリューション

最終更新日:Sep 12, 2024

ApsaraDB for RedisインスタンスはLuaコマンドをサポートしています。 Luaスクリプトを使用して、比較設定 (CAS) コマンドを効率的に処理できます。 これにより、ApsaraDB for Redisのパフォーマンスが向上し、機能の実装が簡素化されます。 このトピックでは、ApsaraDB for RedisでのLuaスクリプトの構文と使用方法について説明します。

注意事項

Luaスクリプトに関連するコマンドは、データ管理 (DMS) コンソールでは使用できません。 DMSの詳細については、「概要」をご参照ください。 クライアントまたはredis-cliを使用して、ApsaraDB for Redisインスタンスに接続し、Luaスクリプトを使用できます。

構文

コマンド

構文

説明

EVAL

EVALスクリプトnumkeys [key [key ...]]] [arg [arg ...]]

パラメーターを受け取る指定されたスクリプトを実行し、出力を返します。

パラメーター:

  • script: Luaスクリプト。

  • numkeys: KEYS配列内の引数の数。 この数は、非負の整数である。

  • KEYS[]: 引数としてスクリプトに渡すRedisキー。

  • ARGV[]: スクリプトに渡す追加の引数。 KEYS[] およびARGV[] パラメータのインデックスは1から始まります。

説明
  • EVALコマンドは、script LOADコマンドと同様の方法で、スクリプトをApsaraDB for Redisのスクリプトキャッシュにロードします。

  • KEYS[] パラメーターとARGV[] パラメーターの混合使用または誤用により、ApsaraDB for Redisインスタンス (特にクラスターインスタンス) が異常に実行される場合があります。 詳細については、「クラスターインスタンスのLuaスクリプトの制限」をご参照ください。

  • パラメータをLuaスクリプトにエンコードするのではなく、KEYS[] およびARGV[] パラメータで値を渡してLuaスクリプトを呼び出すことを推奨します。 そうしないと、Lua仮想マシンのメモリ使用量が増加し、できるだけ早く減らすことができません。 最悪のシナリオでは、インスタンスでメモリ不足 (OOM) エラーが発生し、データが失われます。

EVALSHA

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

SHA1ダイジェストによってキャッシュされたスクリプトを評価し、スクリプトを実行します。

EVALSHAコマンドの使用時にスクリプトがApsaraDB for Redisにキャッシュされていない場合、ApsaraDB for RedisはNOSCRIPTエラーを返します。 EVALまたはscript LOADコマンドを使用して、スクリプトをApsaraDB for Redisにキャッシュし、再試行します。 詳細については、「NOSCRIPTエラーの処理」をご参照ください。

スクリプトロード

SCRIPTロードスクリプト

ApsaraDB for Redisで指定されたスクリプトをキャッシュし、スクリプトのSHA1ダイジェストを返します。

スクリプトが存在

SCRIPT EXISTSスクリプト [script ...]

対応するSHA1ダイジェストを使用して、スクリプトキャッシュ内の1つ以上のスクリプトの存在に関する情報を返します。 指定されたスクリプトが存在する場合、値1が返されます。 そうでなければ、0の値が返される。

SCRIPT KILL

SCRIPT KILL

実行中のLuaスクリプトを終了します。

SCRIPT FLUSH

SCRIPT FLUSH

RedisサーバーのスクリプトキャッシュからすべてのLuaスクリプトを削除します。

Redisコマンドの詳細については、 Redis公式ウェブサイト

次のサンプルコードは、特定のRedisコマンドの例を示します。 次のコマンドを実行する前に、SET foo values_testコマンドを実行します。

  • サンプルEVALコマンド:

    EVAL "retur n redis.ca ll('GET' 、KEYS[1])" 1 foo

    サンプル出力:

    "value_test"
  • サンプルSCRIPT LOADコマンド:

    スクリプトロード "retur n redis.ca ll('GET' 、KEYS[1])"

    サンプル出力:

    「620cd258c2c9c88c9d10db67812ccf663d96bdc6」
  • サンプルEVALSHAコマンド:

    EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 foo

    サンプル出力:

    "value_test"
  • サンプルSCRIPT EXISTSコマンド:

    スクリプトが存在する620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffff

    サンプル出力:

    1) (整数) 1
    2) (整数) 0 
  • サンプルSCRIPT FLUSHコマンド:

    警告

    このコマンドは、キャッシュされたすべてのLuaスクリプトをインスタンスから削除します。 このコマンドを実行する前に、必ずLuaスクリプトをバックアップしてください。

    SCRIPT FLUSH

    サンプル出力:

    OK

メモリとネットワークのオーバーヘッドを最適化する

問題:

同じ目的を果たす多数のスクリプトがApsaraDB for Redisにキャッシュされています。 これらのスクリプトは大量のメモリを占有し、メモリ不足 (OOM) エラーを引き起こす可能性があります。 無効な使用の例:

EVAL "retur n redis.ca ll('set' 、'k1' 、'v1')" 0
EVAL "retur n redis.ca ll('set' 、'k2' 、'v2')" 0 

解決策:

  • メモリ使用量を減らすために、パラメータを定数としてLuaスクリプトに渡さないでください。

    # 次のコマンドは、上記のサンプルコマンドと同じ目的を果たしますが、スクリプトを1回だけキャッシュします。 
    EVAL "retur n redis.ca ll('set' 、KEYS[1] 、ARGV[1])" 1 k1 v1
    EVAL "retur n redis.ca ll('set' 、KEYS[1] 、ARGV[1])" 1 k2 v2 
  • 次のコマンド構文を使用して、メモリとネットワークのオーバーヘッドを減らします。

    SCRIPT LOAD "retur n redis.ca ll('set', KEYS[1], ARGV[1])"# このコマンドが実行されると、次の出力が返されます。
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2 

Luaスクリプトキャッシュをフラッシュする

問題:

Luaスクリプトキャッシュがインスタンスのメモリを占有するため、インスタンスの使用済みメモリが予想よりも高くなる可能性があります。 インスタンスの使用メモリが上限に近づくか、上限を超えると、OOMエラーが返されます。 エラー例:

-使用済みメモリ> 'maxmemy' の場合、OOMコマンドは許可されません。

解決策:

クライアントでscript Flushコマンドを実行して、Luaスクリプトキャッシュをフラッシュします。 FLUSHALLコマンドとは異なり、SCRIPT FLUSHコマンドは同期しています。 ApsaraDB for Redisが多数のLuaスクリプトをキャッシュしている場合、SCRIPT FLUSHコマンドはApsaraDB for Redisを長期間ブロックし、関連するインスタンスが使用できなくなる可能性があります。 作業は慎重に行ってください。 そのため、ピーク時間外に操作を実行することを推奨します。

説明

ApsaraDB for Redisコンソールで データの消去 をクリックすると、データはクリアできますが、Luaスクリプトキャッシュはフラッシュできません。

過剰なメモリを占有する可能性があるLuaスクリプトを記述しないでください。 さらに、大量のデータを含むLuaスクリプトを記述しないでください。 そうしないと、メモリ使用量が大幅に増加し、OOMエラーが発生する可能性があります。 メモリ使用量を減らすために、volatile-lruポリシーを使用してデータ削除を有効にすることを推奨します。 ApsaraDB for Redisでは、デフォルトでデータ削除が有効になっています。 データ削除の詳細については、ApsaraDB for Redisはデフォルトでデータをどのように消去しますか? ただし、ApsaraDB for Redisは、データの削除が有効になっているかどうかにかかわらず、Luaスクリプトキャッシュを削除しません。

NOSCRIPTエラーの処理

問題:

EVALSHAコマンドの使用時にスクリプトがApsaraDB for Redisにキャッシュされていない場合、ApsaraDB for RedisはNOSCRIPTエラーを返します。 エラー例:

(エラー) NOSCRIPT一致するスクリプトがありません。 EVALをご利用ください。

解決策:

EVALまたはSCRIPT LOADコマンドを実行して、スクリプトをApsaraDB for Redisにキャッシュし、再試行します。 インスタンスの移行や構成の変更などの特定のシナリオでは、ApsaraDB for RedisはLuaスクリプトの永続性と複製可能性を保証できないため、ApsaraDB for Redisは引き続きLuaスクリプトキャッシュをフラッシュします。 このため、クライアントはこのエラーを処理できる必要があります。 詳細については、「スクリプトのキャッシュ、永続化、レプリケーション」をご参照ください。

次のサンプルPythonコードは、NOSCRIPTエラーを処理するメソッドを示しています。 サンプルコードは、Luaスクリプトを使用して文字列の先頭に追加します。

説明

redis-pyを使用してこのエラーを処理することもできます。 redis-pyは、NOSCRIPTエラーのcatchステートメントなど、ApsaraDB for RedisのLuaスクリプトの判断ロジックをカプセル化するScriptクラスを提供します。

import redis
hashlibのインポート

# strinはLuaスクリプトの文字列を示します。 この関数は、strinのsha1値を文字列形式で返します。 
def calcSha1(strin):
    sha1_obj = hashlib.sha1()
    sha1_obj.update(strin.encode('utf-8 '))
    sha1_val = sha1_obj.hexdigest()
    sha1_valを返します

クラス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.ca ll("get", KEYS[1])
        local prefix = ARGV[1]
        local new_value = prefix .. suffix
        retur n redis.ca ll("set" 、KEYS[1] 、new_value)
        """
        script_sha1 = calcSha1(script_content)
        if self.script_exists(script_sha1)[0] == True: # ApsaraDB for Redisが既にスクリプトをキャッシュしているかどうかを確認します。 
            return self.evalsha(script_sha1, 1, key, value) # スクリプトが既にキャッシュされている場合、EVALSHAコマンドを使用してスクリプトを実行します。
        else:
            return self.eval(script_content, 1, key, value) # それ以外の場合は、EVALコマンドを使用してスクリプトを実行します。 EVALコマンドは、ApsaraDB for Redisのスクリプトをキャッシュできます。 SCRIPT LOADおよびEVALSHAコマンドを使用することもできます。 

r = MyRedis(host="r-****** .redis.rds.aliyuncs.com" 、password="***:***" 、port=6379、decode_responses=True)

プリント (r.prepend_inLua("k" 、"v"))
プリント (r.get("k"))
            

Luaスクリプトのタイムアウトの処理

  • 問題:

    遅いLua要求はブロックするかもしれないRedisLuaスクリプトはアトミックに実行されるためRedis. 1つのLuaスクリプトで Redisを最大5秒間ブロックできます。 5秒後、 Redisは、スクリプトの実行が完了するまで、他のコマンドのBUSYエラーを返します。

    BUSY Redisはスクリプトの実行に忙しいです。 SCRIPT KILLまたはSHUTDOWN NOSAVEのみを呼び出すことができます。

    解決策:

    SCRIPT KILLコマンドを実行してLuaスクリプトを終了するか、Luaスクリプトの実行が完了するまで待ちます。

    説明
    • 低速Luaスクリプトが実行されている最初の5秒間は、Tairがブロックされているため、script KILLコマンドは有効になりません。

    • Redisが長期間ブロックされないようにするには、Luaスクリプトの作成時にLuaスクリプトの実行に必要な時間を見積もり、無限ループを確認し、必要に応じてLuaスクリプトを分割することをお勧めします。

  • 問題:

    Luaスクリプトがデータセットに対して書き込みコマンドを実行している場合、script KILLコマンドは有効になりません。 エラー例:

    (エラー) UNKILLABLEスクリプトが既にデータセットに対して書き込みコマンドを実行しています。 SHUTDOWN NOSAVEコマンドを使用して、スクリプトの終了を待つか、サーバーを強制的に終了させることができます。

    解決策:

    Tairコンソールの インスタンス一覧 ページで、管理するインスタンスを見つけ、[操作] 列の 再起動 をクリックします。

スクリプトのキャッシュ、永続化、およびレプリケーション

問題:

ApsaraDB for Redisは、インスタンスが再起動されていない場合、またはインスタンスに対してSCRIPT FLUSHコマンドが実行されていない場合、インスタンスで実行されたLuaスクリプトを引き続きキャッシュします。 ただし、ApsaraDB for Redisは、インスタンスの移行、構成の変更、バージョンのアップグレード、インスタンスの切り替えなどのシナリオでは、Luaスクリプトの永続性または現在のノードから他のノードへのLuaスクリプトの同期を保証できません。

解決策:

すべてのLuaスクリプトをオンプレミスデバイスに保存します。 必要に応じてEVALまたはSCRIPT LOADコマンドを使用して、ApsaraDB for RedisのLuaスクリプトをリキャッシュします。 これにより、インスタンスの再起動または高可用性 (HA) の切り替え中にLuaスクリプトがクリアされたときにNOSCRIPTエラーが発生するのを防ぎます。

クラスターインスタンスのLuaスクリプトの制限

オープンソースのRedisクラスターでは、Luaスクリプトの使用に制限があります。 Redisクラスターインスタンスには、Luaスクリプトの使用に次の追加制限があります。

  • 特定のマイナーバージョンを使用するインスタンスは、EVAL関連のコマンドをサポートしません。 ERRコマンドeval not support for normal userエラーメッセージが返された場合は、インスタンスのマイナーバージョンを更新して、もう一度お試しください。 詳細については、「」「インスタンスのマイナーバージョンの更新」をご参照ください。

  • すべてのキーは同じスロットになければなりません。 それ以外の場合、システムは -ERR eval/evalshaコマンドキーが同じスロットにある必要があります \r\nエラーを返します。

    CLUSTER KEYSLOTコマンドを実行して、キーのハッシュスロットを取得できます。

  • 1つのノードでSCRIPT LOADコマンドを実行すると、Luaスクリプトが他のノードに格納されない場合があります。

  • PSUBSCRIBEPUBSUBPUBLISHPUUNSUBSCRIBESUBSCRIBEUNSUBSCRIBEのPub/Subコマンドはサポートされていません。

  • UNPACK機能はサポートされていません。

説明

すべての操作を同じハッシュスロットで実行でき、 RedisクラスターアーキテクチャによってLuaスクリプトに課せられた制限を突破したい場合は、コンソールでscript_check_enableパラメーターを0に設定できます。 このように、システムはバックエンドでLuaスクリプトをチェックしません。 この場合、プロキシノードがLuaスクリプトに含まれる要求をルーティングできるように、KEYS配列に少なくとも1つのキーを指定する必要があります。 すべての操作が同じハッシュスロットで実行されることを確認できない場合は、エラーが返されます。 詳細については、「」「インスタンスパラメーターの設定」をご参照ください。

プロキシモードでのLuaスクリプトの追加チェック

script_check_enableパラメーターを指定して、Luaスクリプトで次のチェックを無効にすることができます。 チェックを無効にしないことを推奨します。

  • Luaスクリプトは、redis.ca llまたはredis.pcall関数を使用して Redisコマンドを実行します。 すべてのキーはkeys配列を使用して指定する必要があり、Lua変数で置き換えることはできません。 KEYS配列を使用してキーを指定しない場合、次のエラーメッセージが返されます。-ERR bad lua script for redis cluster, スクリプトが使用するすべてのキーはkeys配列 \r\nを使用して渡される必要があります

    説明

    この制限は、Redis 5.0を実行し、マイナーバージョンが5.0.8より前のインスタンスと、Redis 4.0以前を実行するインスタンスにのみ適用されます。

    有効および無効なコマンドの使用例:

    # この例を準備するには、次のコマンドを実行します。SET foo foo_value
    SET {foo}bar bar_value
    
    # 有効なコマンドの使用例:
    EVAL "retur n redis.ca ll('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar
    
    # 無効なコマンドの使用例:
    EVAL "retur n redis.ca ll('mget', KEYS[1], '{foo}bar')" 1 foo# このコマンドは、KEYS配列を使用して'{foo}bar' キーを指定する必要があるため、無効です。 
    EVAL "local i = 2 retur n redis.ca ll('mget', KEYS[1], KEYS[i])" 2 foo {foo}bar# このコマンドは無効です。これは、キーのインデックスが変数で構成されているためです。プロキシモードのインスタンスでは許可されていません。 直接接続モードのインスタンスは、この制限の対象にはなりません。 
    EVAL "retur n redis.ca ll('mget', KEYS[1], ARGV[1])" 1 foo {foo}bar# このコマンドは、ARGV[1] をキーとして指定できないため無効である。 
  • redis.ca llまたはredis.pcall関数でLuaスクリプトを使用する場合、最初に指定するパラメーターは、実行する Redisコマンドを表す文字列リテラルである必要があります。 最初のパラメーターとして変数または非リテラル文字列を使用しようとすると、次のエラーメッセージが返されます。-ERR bad lua script for redis cluster, first parameter o f redis.ca ll/redis.pcallは単一のリテラル文字列でなければなりません

    有効および無効なコマンドの使用例:

    # 有効なコマンドの使用例
    eval "redis.ca ll('GET' 、KEYS[1])" 1 foo
    
    # 不正なコマンドの使用例
    eval "local cmd = 'GET'; redis.ca ll(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以前を実行するインスタンスにのみ適用されます。

    有効および無効なコマンドの使用例:

    # 有効なコマンドの使用例
    EVAL "retur n redis.ca ll('get' 、KEYS[1])" 1 fooeval
    
    # 不正なコマンドの使用例
    EVAL "retur n redis.ca ll('get' 、'foo')" 0 
  • ネストされた呼び出しは、RedisクラスターのLuaスクリプトではサポートされません。 ネストされた呼び出しを含むスクリプトを実行しようとすると、次のエラーメッセージが返されます。-ERR bad lua script for redis cluster, neste d redis.ca ll/redis.pcall

    ローカル変数を使用して、この制限を回避できます。 有効および無効なコマンドの使用例:

    # 有効なコマンドの使用例
    EVAL "local value = redis.ca ll('GET', KEYS[1]); redis.ca ll('SET', KEYS[2], value)" 2 foo bar
    
    # 不正なコマンドの使用例
    EVAL "redis.ca ll('SET' 、KEYS[1] 、redis.ca ll('GET' 、KEYS[2]))" 2 fooバー 
  • MULTIまたはEXECトランザクションでEVAL、EVALSHA、またはSCRIPTコマンドを実行することはできません。

  • KEYSやSCANなどの複数のRedisノードを含むコマンドは、Luaスクリプトではサポートされていません。

    Luaスクリプトがアトミックに実行されるようにするために、プロキシノードはKEYSパラメーターを使用してLuaスクリプトをRedisノードにルーティングして実行し、結果を取得します。 これにより、Redisノードと他のノードのコマンド出力が異なります。

説明

プロキシモードで使用できない機能を使用する場合は、ApsaraDB for Redisクラスターインスタンスの直接接続モードを有効にできます。 ただし、プロキシモードの要件を満たさないLuaスクリプトが直接接続モードで実行されている場合、ApsaraDB for Redisクラスターインスタンスの移行または構成変更は実行できません。 これは、クラスターインスタンスが移行および構成変更中にプロキシノードに依存するためです。

この問題を解決するには、直接接続モードでスクリプトを実行する前に、Luaスクリプトがプロキシモードの要件を満たしていることを確認してください。