分散ロック、レートリミッターの実装、条件付き更新など、単一のデータベース操作内で複数のコマンドをアトミックに実行する必要がある場合、複数のネットワークラウンドトリップはレイテンシーを増加させ、競合状態(race condition)のリスクを引き起こします。Orca は Redis 互換プロトコルであり、複数の Redis 互換コマンドを 1 つのスクリプトにカプセル化できる Lua スクリプティング機能を提供します。データベースはこのスクリプトをアトミックに実行するため、上記の課題を効果的に解決し、複雑な操作における実行効率およびデータ整合性を向上させます。
特長
Redis 互換プロトコルである Orca の Lua スクリプティング機能では、Redis と同様に Lua スクリプトを実行できます。主な利点は原子性と高性能です。
原子性:スクリプト内のすべてのコマンドは分割不可能な単位として実行されます。他のコマンドやスクリプトが実行中のスクリプトを中断することはできません。これにより、アトミックな操作が保証され、部分実行によって生じる中間状態が防止されます。
高性能:
ネットワークオーバーヘッドの削減:複数のコマンドを 1 つのスクリプトにパッケージ化し、一度にデータベースへ送信できます。これにより、クライアントとサーバー間のネットワークラウンドトリップが大幅に削減されます。
スクリプトキャッシュ:事前に
SCRIPT LOADコマンドを使用してスクリプトをクラスタのメモリにプリロードし、SHA1 チェックサムを取得できます。その後、EVALSHAコマンドでこの短い SHA1 値のみを送信することでスクリプトを呼び出せます。これにより、ネットワーク上で転送されるデータ量がさらに削減されます。
適用範囲
クラスタバージョン:クラスタバージョンは MySQL 8.0.2 以上である必要があります。また、マイナーエンジンバージョンは 8.0.2.2.33 以降である必要があります。
エンドポイント:Lua スクリプトコマンドを実行するには、Orca アドレス を使用し、読み書きモード を 読み書き (自動読み書き分離) に設定する必要があります。
サポートされていないコマンド:
redis.call()またはredis.pcall()を使用した Lua スクリプト内では、以下のコマンドを実行できません。これらのコマンドを使用すると、エラーが返されます。トランザクション制御コマンド:
WATCH、UNWATCH、MULTI、EXEC、またはDISCARDブロッキングコマンド:
BLPOPまたはBRPOPLua スクリプティングコマンド:
EVAL、EVAL_RO、EVALSHA、EVALSHA_RO、またはSCRIPTPub/Sub コマンド:
SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、またはPUNSUBSCRIBE接続管理コマンド:
AUTH、HELLO、またはCLIENT
基本構文
以下は、Lua スクリプトを操作するための主要コマンドです。詳細については、公式 Redis ウェブサイトの「Lua スクリプトコマンド」および「Lua を用いたスクリプティング」をご参照ください。
コマンド | 構文 | 説明 |
|
| 指定された Lua スクリプトを直接実行します。これは最も基本的な実行方法です。パラメーターの説明は以下のとおりです。
|
|
| 読み取り専用モードで Lua スクリプトを実行します。これは読み書き分離のシナリオに適用され、スクリプトが書き込み操作を行わないことを保証します。スクリプトに書き込みコマンドが含まれている場合、エラーが返されます。パラメーターの説明は |
|
| SHA1 チェックサムを使用してキャッシュ済みのスクリプトを実行します。スクリプトがキャッシュされていない場合、 |
|
| SHA1 チェックサムを使用して、読み取り専用モードでキャッシュ済みのスクリプトを実行します。これは読み書き分離のシナリオに適用され、スクリプトが書き込み操作を行わないことを保証します。スクリプトに書き込みコマンドが含まれている場合、エラーが返されます。パラメーターの説明は |
|
| スクリプトをキャッシュに読み込み、後続の |
|
| 1 つ以上の SHA1 チェックサムに対応するスクリプトがキャッシュ内に存在するかどうかを確認します。存在する場合は 1、存在しない場合は 0 を返します。 |
|
| 現在実行中の Lua スクリプトを終了します。Orca では、任意のスクリプト(書き込みスクリプトを含む)を終了でき、そのスクリプトによって行われたデータ変更を自動的にロールバックして、データ整合性を確保します。 |
|
| 現在のクラスタ内のすべての Lua スクリプトキャッシュをクリアします。
|
操作例
テストデータを準備します:
SET polardb orcaテストコマンド:
EVAL
EVAL "return redis.call('GET', KEYS[1])" 1 polardb返り値の例:
"orca"EVAL_RO
EVAL_RO "return redis.call('GET', KEYS[1])" 1 polardb返り値の例:
"orca"SCRIPT LOAD
SCRIPT LOAD "return redis.call('GET', KEYS[1])"返り値の例:
"d3c21d0c2b9ca22f82737626a27bcaf5d288f99f"EVALSHA
EVALSHA d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返り値の例:
"orca"EVALSHA_R
EVALSHA_RO d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返り値の例:
"orca"SCRIPT EXISTS
SCRIPT EXISTS d3c21d0c2b9ca22f82737626a27bcaf5d288f99f ffffffffffffffffffffffffffffffffffffffff返り値の例:
1) (integer) 1 2) (integer) 0SCRIPT FLUSH
警告このコマンドはクラスタ内のすべての Lua スクリプトキャッシュをクリアします。
SCRIPT LOADまたはEVALによって生成されたキャッシュも削除されます。キャッシュをクリアした後、
EVALSHAまたはEVALSHA_ROに依存するすべての呼び出しでNOSCRIPTエラーが発生する可能性があります。スクリプトを再読み込みして操作を再試行する必要があります。多数のスクリプトがキャッシュされている場合、
SCRIPT FLUSHコマンドがクラスタを長時間ブロックする可能性があります。ピーク時を避けて実行することを推奨します。スクリプトのソースコードはクライアントまたはアプリケーション側に保持し、
NOSCRIPTエラーに対する自動回復機構を実装することを推奨します。たとえば、SCRIPT LOADを使用してスクリプトを再読み込みしたり、EVALを使用して再登録したりできます。
SCRIPT FLUSH返り値の例:
OK
よくあるユースケース
アトミックカウンターの設定と実行
同時実行の INCR 操作によって生じる競合状態を回避できます。これにより、カウンターが厳密に増分され、条件付きでリセット可能になります。
処理の概要
Lua スクリプトを作成します。
クラスタのキャッシュに読み込みます。
EVALSHAを使用してスクリプトを再利用・実行します。
SCRIPT LOAD + EVALSHA の組み合わせを使用すると、ネットワークトラフィックが削減され、パフォーマンスおよびべき等性が向上します。また、スクリプトの内容はリクエストボディに公開されません。
操作手順
スクリプトを作成し、ローカルに保存します:
-- counter.lua: キーが存在しない場合は 0 に設定し、その後 1 増分する。新しい値を返す。 if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])スクリプトを読み込んで SHA1 チェックサムを取得します:
SCRIPT LOAD "if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])" -- 返り値 "b547eabbcde73b25330442e4f4e4dc1783b91241"(推奨)スクリプトを再利用して実行します:
EVALSHA b547eabbcde73b25330442e4f4e4dc1783b91241 1 my_counter -- 返り値 (integer) 1 -- 再度実行した場合の返り値 (integer) 2
読み取り専用スクリプトの実行
スクリプトに書き込み操作が含まれていないことを確認します:スクリプトが
SET、DEL、HSET、またはLPUSHなどの書き込みコマンドを呼び出していないかを確認します。書き込みコマンドが含まれている場合は、EVAL_ROを使用しないでください。説明スクリプトに書き込みコマンドが含まれている場合、エラー
(error) ERR Write commands are not allowed from read-only scripts.が返されます。EVAL_ROを使用してスクリプトを実行します:EVAL_RO "return {redis.call('SET', KEYS[1]), redis.call('TTL', KEYS[1])}" 1 polardb -- 返り値 1) "orca" 2) (integer) -1
異常稼働中の Lua スクリプトを停止する
長時間実行されるスクリプトがクラスタをブロックするのを防ぐことができます。このため、SCRIPT KILL コマンドを使用して実行を中断します。システムはデータ整合性を維持するために、そのスクリプトによって行われたすべての変更を自動的にロールバックします。
現在実行中のスクリプトのみを終了できます。SHA1 値を指定して特定のスクリプトを終了することはできません。
SCRIPT KILL
-- 返り値
OKパフォーマンス最適化のベストプラクティス
Lua スクリプトによるクラスタへのブロッキング影響を軽減し、機能的に重複した多数のスクリプトのキャッシュによる過剰なメモリ使用量を防ぐため、以下のベストプラクティスに従うことを推奨します:
タイムアウト制限:単一の Lua スクリプトのデフォルト実行タイムアウトは 300 秒です。
過剰なメモリ消費を防ぐため、過大な Lua スクリプトの作成は避けてください。
Lua スクリプト内で長時間実行される処理や大量データの書き込みは避けてください。
ブロッキングリスク: Lua スクリプトはクラスタ内でアトミックに実行されます。実行中は他のコマンドおよびスクリプトをブロックします。そのため、書き込みバッチサイズおよび実行時間を制御し、長時間のループや広範囲の走査を回避する必要があります。必要に応じて、複雑なロジックを複数の独立したスクリプトに分割して実行できます。
永続化されないスクリプトキャッシュ:Lua スクリプトキャッシュはクラスタの実行中にクリアされません。ただし、クラスタの再起動、高可用性(HA)スイッチオーバー、または
SCRIPT FLUSHコマンドの実行後にキャッシュはクリアされます。これらの場合は、スクリプトを再登録する必要があります。スクリプト作成ガイドライン:
KEYS[]およびARGV[]を使用してパラメーターを渡します。スクリプト内にパラメーターをハードコーディングしないでください。ネットワークトラフィックの削減:
SCRIPT LOAD + EVALSHAの組み合わせを使用して、最適なパフォーマンスとネットワークトラフィックの削減を実現します。
操作例
スクリプトの読み込み:アプリケーションの初期化時または初回使用時に、
SCRIPT LOADを使用してスクリプトをクラスタに読み込み、SHA1 値を取得します。アプリケーションはこの SHA1 値をローカルに保存する必要があります。# キーの値を設定するスクリプトを読み込む SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # 返り値の例 "55b22c0d0cedf3866879ce7c854970626dcef0c3"スクリプトの実行:その後、前ステップで取得した SHA1 値を
EVALSHAコマンドで使用してスクリプトを実行します。# キャッシュ済みのスクリプトを使用して k1 = v1 を設定 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 # 同じキャッシュ済みのスクリプトを使用して k2 = v2 を設定 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2NOSCRIPTエラーの処理:EVALSHAがNOSCRIPTエラーを返した場合、アプリケーションはこのエラーを捕捉する必要があります。その後、アプリケーションは自動的にEVALまたはSCRIPT LOADコマンドを使用してスクリプトを 1 回実行し、クラスタにキャッシュできます。EVAL:実行中にスクリプトを自動的に再キャッシュします。SCRIPT LOAD:スクリプトをクラスタに読み込み、SHA1 値を取得します。
Lua スクリプトのメモリ使用量のクリア:今後 Lua スクリプトを実行する予定がない場合は、
SCRIPT FLUSHコマンドを実行して Lua スクリプトキャッシュをクリアできます。クラスタに多数の Lua スクリプトがキャッシュされている場合、このコマンドがクラスタを長時間ブロックする可能性があります。ピーク時を避けて実行することを推奨します。
よくある質問
NOSCRIPT No matching script. Please use EVAL.エラーをどう処理すればよいですか?原因:このエラーは、
EVALSHAまたはEVALSHA_ROコマンドを使用して、クラスタのキャッシュに存在しないスクリプトを実行しようとした場合に発生します。このエラーは、通常、クラスタの再起動、高可用性(HA)スイッチオーバー、またはSCRIPT FLUSHコマンドの実行後に発生します。
解決策:クライアントコードにエラー処理ロジックを実装する必要があります。NOSCRIPTエラーを捕捉した場合、EVALまたはSCRIPT LOADコマンドを使用してスクリプトを再実行し、クラスタにキャッシュできます。その後、EVALSHAの呼び出しは正常に動作します。Lua スクリプトの実行がタイムアウトした場合はどうすればよいですか?
デフォルトのスクリプトタイムアウトは 300 秒です。タイムアウトエラーは、通常、スクリプトのロジックが複雑すぎるか、処理するデータ量が多すぎるために発生します。
解決策:スクリプトのロジックを最適化します。スクリプト内で非効率なループや大規模なデータ走査を避けてください。
スクリプトの実行時間が長すぎる場合は、
SCRIPT KILLコマンドを使用して終了できます。PolarDB は、そのスクリプトによって行われたすべての変更をロールバックします。複雑なビジネスロジックを、複数の小さな高速なスクリプトに分割し、段階的に実行します。
EVAL_ROを使用してスクリプトを実行した際に、エラーERR Write commands are not allowed from read-only scripts.が返されるのはなぜですか?EVAL_ROおよびEVALSHA_ROは読み取り専用モードであり、SET、HSET、またはDELなどの書き込み操作を厳密に禁止します。このエラーは、スクリプトに書き込みコマンドが含まれていることを示しています。
解決策:スクリプトを確認し、すべての書き込みコマンドを削除します。
書き込み操作を実行するには、
EVALまたはEVALSHAコマンドを使用します。