Valkey Scripting Reference
This page contains code references for working with custom Lua scripts in Valkey GLIDE.
Migration from Direct EVAL
Section titled “Migration from Direct EVAL”GLIDE’s Script interface offers several advantages over using direct EVAL commands via custom_command.
- Automatic Caching: Scripts are cached automatically
- Better Error Handling: More specific error types
- Cluster Support: Automatic routing in cluster mode
- Type Safety: Better integration with GLIDE’s type system
- Performance: Optimized execution path
The following shows how to covert your EVAL command to GLIDE’s Script interface.
Direct EVAL (Before)
Section titled “Direct EVAL (Before)”# Old approach with custom commands (not recommended)result = await client.custom_command([ "EVAL", "return redis.call('SET', KEYS[1], ARGV[1])", "1", "mykey", "myvalue"])// Old approach with custom commands (not recommended)Object result = client.customCommand(new String[]{ "EVAL", "return redis.call('SET', KEYS[1], ARGV[1])", "1", "mykey", "myvalue"}).get();// Old approach with custom commands (not recommended)const result = await client.customCommand([ "EVAL", "return redis.call('SET', KEYS[1], ARGV[1])", "1", "mykey", "myvalue"]);// Old approach with custom commands (not recommended)result, err := client.CustomCommand(context.Background(), []string{ "EVAL", "return redis.call('SET', KEYS[1], ARGV[1])", "1", "mykey", "myvalue",})// Old approach with custom commands (not recommended)var result = await client.CustomCommandAsync(new GlideString[] { "EVAL", "return redis.call('SET', KEYS[1], ARGV[1])", "1", "mykey", "myvalue"});// PHP uses eval() method directly$result = $client->eval( "return redis.call('SET', KEYS[1], ARGV[1])", ['mykey', 'myvalue'], 1 // num_keys);Script Class (After)
Section titled “Script Class (After)”# New approach with Script class (recommended)from glide import Script
script = Script("return redis.call('SET', KEYS[1], ARGV[1])")result = await client.invoke_script( script, keys=["mykey"], args=["myvalue"])// New approach with Script class (recommended)import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;
try (Script script = new Script("return redis.call('SET', KEYS[1], ARGV[1])", false)) { Object result = client.invokeScript( script, ScriptOptions.builder() .key("mykey") .arg("myvalue") .build() ).get();}// New approach with Script class (recommended)import {Script} from "@valkey/valkey-glide";
const script = new Script("return redis.call('SET', KEYS[1], ARGV[1])");const result = await client.invokeScript(script, { keys: ["mykey"], args: ["myvalue"]});script.release();// New approach with Script class (recommended)import "github.com/valkey-io/valkey-glide/go/v2/options"
script := options.NewScript("return redis.call('SET', KEYS[1], ARGV[1])")result, err := client.InvokeScriptWithOptions( context.Background(), *script, *options.NewScriptOptionsBuilder(). Keys([]string{"mykey"}). Args([]string{"myvalue"}). Build(),)// New approach with Script class (recommended)using Valkey.Glide;
using var script = new Script("return redis.call('SET', KEYS[1], ARGV[1])");var result = await client.ScriptInvokeAsync( script, new ScriptOptions() .WithKeys("mykey") .WithArgs("myvalue"));Common Script Patterns
Section titled “Common Script Patterns”The following are common Lua script patterns used by clients.
Rate Limiting
Section titled “Rate Limiting”local key = KEYS[1]local limit = tonumber(ARGV[1])local window = tonumber(ARGV[2])
local current = redis.call('GET', key)if current == false then redis.call('SET', key, 1) redis.call('EXPIRE', key, window) return {1, limit}end
current = tonumber(current)if current < limit then local new_val = redis.call('INCR', key) local ttl = redis.call('TTL', key) return {new_val, limit}else local ttl = redis.call('TTL', key) return {current, limit, ttl}endfrom glide import Script
# Read script from filewith open("rate_limit.lua", "r") as f: rate_limit = f.read()
rate_limit_script = Script(rate_limit)
# Usageresult = await client.invoke_script( rate_limit_script, keys=["rate_limit:user:123"], args=["10", "60"] # 10 requests per 60 seconds)import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;import java.nio.file.Files;import java.nio.file.Paths;
// Read script from fileString rateLimit = Files.readString(Paths.get("rate_limit.lua"));
try (Script rateLimitScript = new Script(rateLimit, false)) { // Usage Object result = client.invokeScript( rateLimitScript, ScriptOptions.builder() .key("rate_limit:user:123") .arg("10") .arg("60") // 10 requests per 60 seconds .build() ).get();}import {Script} from "@valkey/valkey-glide";import {readFileSync} from "fs";
// Read script from fileconst rateLimit = readFileSync("rate_limit.lua", "utf-8");const rateLimitScript = new Script(rateLimit);
// Usageconst result = await client.invokeScript(rateLimitScript, { keys: ["rate_limit:user:123"], args: ["10", "60"] // 10 requests per 60 seconds});import ( "github.com/valkey-io/valkey-glide/go/v2/options" "os")
// Read script from filerateLimit, _ := os.ReadFile("rate_limit.lua")rateLimitScript := options.NewScript(string(rateLimit))
// Usageresult, err := client.InvokeScriptWithOptions( context.Background(), *rateLimitScript, *options.NewScriptOptionsBuilder(). Keys([]string{"rate_limit:user:123"}). Args([]string{"10", "60"}). // 10 requests per 60 seconds Build(),)using Valkey.Glide;
// Read script from filevar rateLimit = File.ReadAllText("rate_limit.lua");using var rateLimitScript = new Script(rateLimit);
// Usagevar result = await client.ScriptInvokeAsync( rateLimitScript, new ScriptOptions() .WithKeys("rate_limit:user:123") .WithArgs("10", "60")); // 10 requests per 60 seconds// Read script from file$rateLimit = file_get_contents('rate_limit.lua');
// Usage$result = $client->eval( $rateLimit, ['rate_limit:user:123', '10', '60'], // 10 requests per 60 seconds 1 // num_keys);Distributed Lock
Section titled “Distributed Lock”if redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1else return 0endif redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1])else return 0endfrom glide import Script
# Read scripts from fileswith open("acquire_lock.lua", "r") as f: acquire_lock = f.read()with open("release_lock.lua", "r") as f: release_lock = f.read()
acquire_lock_script = Script(acquire_lock)release_lock_script = Script(release_lock)
# Acquire locklock_acquired = await client.invoke_script( acquire_lock_script, keys=["lock:resource:123"], args=["unique_token", "30"] # 30 second expiration)
if lock_acquired: try: # Do work while holding lock pass finally: # Release lock await client.invoke_script( release_lock_script, keys=["lock:resource:123"], args=["unique_token"] )import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;import java.nio.file.Files;import java.nio.file.Paths;
// Read scripts from filesString acquireLock = Files.readString(Paths.get("acquire_lock.lua"));String releaseLock = Files.readString(Paths.get("release_lock.lua"));
try (Script acquireLockScript = new Script(acquireLock, false); Script releaseLockScript = new Script(releaseLock, false)) {
// Acquire lock Object lockAcquired = client.invokeScript( acquireLockScript, ScriptOptions.builder() .key("lock:resource:123") .arg("unique_token") .arg("30") // 30 second expiration .build() ).get();
if ((Long) lockAcquired == 1) { try { // Do work while holding lock } finally { // Release lock client.invokeScript( releaseLockScript, ScriptOptions.builder() .key("lock:resource:123") .arg("unique_token") .build() ).get(); } }}import {Script} from "@valkey/valkey-glide";import {readFileSync} from "fs";
// Read scripts from filesconst acquireLock = readFileSync("acquire_lock.lua", "utf-8");const releaseLock = readFileSync("release_lock.lua", "utf-8");
const acquireLockScript = new Script(acquireLock);const releaseLockScript = new Script(releaseLock);
// Acquire lockconst lockAcquired = await client.invokeScript(acquireLockScript, { keys: ["lock:resource:123"], args: ["unique_token", "30"] // 30 second expiration});
if (lockAcquired) { try { // Do work while holding lock } finally { // Release lock await client.invokeScript(releaseLockScript, { keys: ["lock:resource:123"], args: ["unique_token"] }); }}import ( "github.com/valkey-io/valkey-glide/go/v2/options" "os")
// Read scripts from filesacquireLockBytes, _ := os.ReadFile("acquire_lock.lua")releaseLockBytes, _ := os.ReadFile("release_lock.lua")
acquireLockScript := options.NewScript(string(acquireLockBytes))releaseLockScript := options.NewScript(string(releaseLockBytes))
// Acquire locklockAcquired, err := client.InvokeScriptWithOptions( context.Background(), *acquireLockScript, *options.NewScriptOptionsBuilder(). Keys([]string{"lock:resource:123"}). Args([]string{"unique_token", "30"}). // 30 second expiration Build(),)
if err == nil && lockAcquired.(int64) == 1 { defer func() { // Release lock client.InvokeScriptWithOptions( context.Background(), *releaseLockScript, *options.NewScriptOptionsBuilder(). Keys([]string{"lock:resource:123"}). Args([]string{"unique_token"}). Build(), ) }() // Do work while holding lock}using Valkey.Glide;
// Read scripts from filesvar acquireLock = File.ReadAllText("acquire_lock.lua");var releaseLock = File.ReadAllText("release_lock.lua");
using var acquireLockScript = new Script(acquireLock);using var releaseLockScript = new Script(releaseLock);
// Acquire lockvar lockAcquired = await client.ScriptInvokeAsync( acquireLockScript, new ScriptOptions() .WithKeys("lock:resource:123") .WithArgs("unique_token", "30")); // 30 second expiration
if ((bool)lockAcquired){ try { // Do work while holding lock } finally { // Release lock await client.ScriptInvokeAsync( releaseLockScript, new ScriptOptions() .WithKeys("lock:resource:123") .WithArgs("unique_token")); }}// Read scripts from files$acquireLock = file_get_contents('acquire_lock.lua');$releaseLock = file_get_contents('release_lock.lua');
// Acquire lock$lockAcquired = $client->eval( $acquireLock, ['lock:resource:123', 'unique_token', '30'], // 30 second expiration 1 // num_keys);
if ($lockAcquired) { try { // Do work while holding lock } finally { // Release lock $client->eval( $releaseLock, ['lock:resource:123', 'unique_token'], 1 // num_keys ); }}Conditional Update
Section titled “Conditional Update” local current = redis.call('GET', KEYS[1]) if current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]) return 1 else return 0 endfrom glide import Script
# Read script from filewith open("conditional_update.lua", "r") as f: conditional_update = f.read()
conditional_update_script = Script(conditional_update)
# Update only if current value matches expectedupdated = await client.invoke_script( conditional_update_script, keys=["user:123:status"], args=["pending", "active"] # Change from "pending" to "active")import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;import java.nio.file.Files;import java.nio.file.Paths;
// Read script from fileString conditionalUpdate = Files.readString(Paths.get("conditional_update.lua"));
try (Script conditionalUpdateScript = new Script(conditionalUpdate, false)) { // Update only if current value matches expected Object updated = client.invokeScript( conditionalUpdateScript, ScriptOptions.builder() .key("user:123:status") .arg("pending") .arg("active") // Change from "pending" to "active" .build() ).get();}import {Script} from "@valkey/valkey-glide";import {readFileSync} from "fs";
// Read script from fileconst conditionalUpdate = readFileSync("conditional_update.lua", "utf-8");const conditionalUpdateScript = new Script(conditionalUpdate);
// Update only if current value matches expectedconst updated = await client.invokeScript(conditionalUpdateScript, { keys: ["user:123:status"], args: ["pending", "active"] // Change from "pending" to "active"});import ( "github.com/valkey-io/valkey-glide/go/v2/options" "os")
// Read script from fileconditionalUpdateBytes, _ := os.ReadFile("conditional_update.lua")conditionalUpdateScript := options.NewScript(string(conditionalUpdateBytes))
// Update only if current value matches expectedupdated, err := client.InvokeScriptWithOptions( context.Background(), *conditionalUpdateScript, *options.NewScriptOptionsBuilder(). Keys([]string{"user:123:status"}). Args([]string{"pending", "active"}). // Change from "pending" to "active" Build(),)using Valkey.Glide;
// Read script from filevar conditionalUpdate = File.ReadAllText("conditional_update.lua");using var conditionalUpdateScript = new Script(conditionalUpdate);
// Update only if current value matches expectedvar updated = await client.ScriptInvokeAsync( conditionalUpdateScript, new ScriptOptions() .WithKeys("user:123:status") .WithArgs("pending", "active")); // Change from "pending" to "active"// Read script from file$conditionalUpdate = file_get_contents('conditional_update.lua');
// Update only if current value matches expected$updated = $client->eval( $conditionalUpdate, ['user:123:status', 'pending', 'active'], // Change from "pending" to "active" 1 // num_keys);Error Handling Patterns
Section titled “Error Handling Patterns”Common Script Errors
Section titled “Common Script Errors”from glide import Script, RequestError
# Handle script execution errorsscript = Script("return redis.call('INCR', 'not_a_number')")
try: result = await client.invoke_script(script)except RequestError as e: if "WRONGTYPE" in str(e) or "not an integer" in str(e): print("Type error in script") elif "syntax error" in str(e).lower(): print("Lua syntax error in script") elif "unknown command" in str(e).lower(): print("Invalid Redis command in script") else: print(f"Script error: {e}")import glide.api.models.Script;import glide.api.models.exceptions.RequestException;
// Handle script execution errorstry (Script script = new Script("return redis.call('INCR', 'not_a_number')", false)) { try { Object result = client.invokeScript(script).get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RequestException) { String message = cause.getMessage(); if (message.contains("WRONGTYPE") || message.contains("not an integer")) { System.out.println("Type error in script"); } else if (message.toLowerCase().contains("syntax error")) { System.out.println("Lua syntax error in script"); } else if (message.toLowerCase().contains("unknown command")) { System.out.println("Invalid Redis command in script"); } else { System.out.println("Script error: " + message); } } }}import {Script, RequestError} from "@valkey/valkey-glide";
// Handle script execution errorsconst script = new Script("return redis.call('INCR', 'not_a_number')");
try { const result = await client.invokeScript(script);} catch (e) { if (e instanceof RequestError) { const message = e.message; if (message.includes("WRONGTYPE") || message.includes("not an integer")) { console.log("Type error in script"); } else if (message.toLowerCase().includes("syntax error")) { console.log("Lua syntax error in script"); } else if (message.toLowerCase().includes("unknown command")) { console.log("Invalid Redis command in script"); } else { console.log(`Script error: ${message}`); } }}import ( "github.com/valkey-io/valkey-glide/go/v2/options" "strings")
// Handle script execution errorsscript := options.NewScript("return redis.call('INCR', 'not_a_number')")
result, err := client.InvokeScript(context.Background(), *script)if err != nil { errMsg := err.Error() if strings.Contains(errMsg, "WRONGTYPE") || strings.Contains(errMsg, "not an integer") { fmt.Println("Type error in script") } else if strings.Contains(strings.ToLower(errMsg), "syntax error") { fmt.Println("Lua syntax error in script") } else if strings.Contains(strings.ToLower(errMsg), "unknown command") { fmt.Println("Invalid Redis command in script") } else { fmt.Printf("Script error: %v\n", err) }}using Valkey.Glide;using Valkey.Glide.Errors;
// Handle script execution errorsusing var script = new Script("return redis.call('INCR', 'not_a_number')");
try{ var result = await client.ScriptInvokeAsync(script);}catch (RequestException e){ var message = e.Message; if (message.Contains("WRONGTYPE") || message.Contains("not an integer")) Console.WriteLine("Type error in script"); else if (message.Contains("syntax error", StringComparison.OrdinalIgnoreCase)) Console.WriteLine("Lua syntax error in script"); else if (message.Contains("unknown command", StringComparison.OrdinalIgnoreCase)) Console.WriteLine("Invalid Redis command in script"); else Console.WriteLine($"Script error: {message}");}// Handle script execution errors$script = "return redis.call('INCR', 'not_a_number')";
try { $result = $client->eval($script, ['not_a_number'], 0);} catch (ValkeyGlideException $e) { $message = $e->getMessage(); if (str_contains($message, 'WRONGTYPE') || str_contains($message, 'not an integer')) { echo "Type error in script\n"; } elseif (str_contains(strtolower($message), 'syntax error')) { echo "Lua syntax error in script\n"; } elseif (str_contains(strtolower($message), 'unknown command')) { echo "Invalid Redis command in script\n"; } else { echo "Script error: $message\n"; }}Script Timeout Handling
Section titled “Script Timeout Handling”from glide import GlideClient, GlideClientConfiguration, NodeAddress, Script, RequestError
# Configure client timeout for long-running scriptsconfig = GlideClientConfiguration( addresses=[NodeAddress("localhost", 6379)], request_timeout=30000 # 30 seconds for long scripts (default is usually 5000ms))client = await GlideClient.create(config)
# Handle long-running scriptslong_script = Script(""" local start = redis.call('TIME')[1] while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operation end return 'Done'""")
try: result = await client.invoke_script(long_script) print(f"Script completed: {result.decode('utf-8')}")except RequestError as e: if "timeout" in str(e).lower(): print("Client timeout - script may still be running on server!") print("Consider increasing request_timeout in client configuration") elif "Script killed" in str(e): print("Script was killed by server (only possible for read-only scripts)") else: print(f"Script error: {e}")import glide.api.GlideClient;import glide.api.models.configuration.GlideClientConfiguration;import glide.api.models.configuration.NodeAddress;import glide.api.models.Script;import glide.api.models.exceptions.RequestException;
// Configure client timeout for long-running scriptsGlideClientConfiguration config = GlideClientConfiguration.builder() .address(NodeAddress.builder().host("localhost").port(6379).build()) .requestTimeout(30000) // 30 seconds for long scripts (default is usually 5000ms) .build();
GlideClient client = GlideClient.createClient(config).get();
// Handle long-running scriptstry (Script longScript = new Script(""" local start = redis.call('TIME')[1] while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operation end return 'Done' """, false)) {
try { Object result = client.invokeScript(longScript).get(); System.out.println("Script completed: " + result); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RequestException) { String message = cause.getMessage(); if (message.toLowerCase().contains("timeout")) { System.out.println("Client timeout - script may still be running on server!"); System.out.println("Consider increasing request_timeout in client configuration"); } else if (message.contains("Script killed")) { System.out.println("Script was killed by server (only possible for read-only scripts)"); } else { System.out.println("Script error: " + message); } } }}import {GlideClient, GlideClientConfiguration, Script, RequestError} from "@valkey/valkey-glide";
// Configure client timeout for long-running scriptsconst config: GlideClientConfiguration = { addresses: [{host: "localhost", port: 6379}], requestTimeout: 30000 // 30 seconds for long scripts (default is usually 5000ms)};
const client = await GlideClient.createClient(config);
// Handle long-running scriptsconst longScript = new Script(` local start = redis.call('TIME')[1] while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operation end return 'Done'`);
try { const result = await client.invokeScript(longScript); console.log(`Script completed: ${result}`);} catch (e) { if (e instanceof RequestError) { const message = e.message; if (message.toLowerCase().includes("timeout")) { console.log("Client timeout - script may still be running on server!"); console.log("Consider increasing request_timeout in client configuration"); } else if (message.includes("Script killed")) { console.log("Script was killed by server (only possible for read-only scripts)"); } else { console.log(`Script error: ${message}`); } }}import ( glide "github.com/valkey-io/valkey-glide/go/v2" "github.com/valkey-io/valkey-glide/go/v2/config" "github.com/valkey-io/valkey-glide/go/v2/options")
// Configure client timeout for long-running scriptsmyConfig := config.NewClientConfiguration(). WithAddress(&config.NodeAddress{Host: "localhost", Port: 6379}). WithRequestTimeout(30000) // 30 seconds for long scripts (default is usually 5000ms)
client, err := glide.NewClient(myConfig)
// Handle long-running scriptslongScript := options.NewScript(` local start = redis.call('TIME')[1] while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operation end return 'Done'`)
result, err := client.InvokeScript(context.Background(), *longScript)if err != nil { errMsg := err.Error() if strings.Contains(strings.ToLower(errMsg), "timeout") { fmt.Println("Client timeout - script may still be running on server!") fmt.Println("Consider increasing request_timeout in client configuration") } else if strings.Contains(errMsg, "Script killed") { fmt.Println("Script was killed by server (only possible for read-only scripts)") } else { fmt.Printf("Script error: %v\n", err) }} else { fmt.Printf("Script completed: %v\n", result)}using Valkey.Glide;using Valkey.Glide.Errors;using static Valkey.Glide.ConnectionConfiguration;
// Configure client timeout for long-running scriptsvar config = new StandaloneClientConfigurationBuilder() .WithAddress("localhost", 6379) .WithRequestTimeout(TimeSpan.FromSeconds(30)) // 30 seconds for long scripts .Build();await using var client = await GlideClient.CreateClient(config);
// Handle long-running scriptsusing var longScript = new Script(@" local start = redis.call('TIME')[1] while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operation end return 'Done'");
try{ var result = await client.ScriptInvokeAsync(longScript); Console.WriteLine($"Script completed: {result}");}catch (RequestException e){ var message = e.Message; if (message.Contains("timeout", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("Client timeout - script may still be running on server!"); Console.WriteLine("Consider increasing request timeout in client configuration"); } else if (message.Contains("Script killed")) Console.WriteLine("Script was killed by server (only possible for read-only scripts)"); else Console.WriteLine($"Script error: {message}");}Requirements: PHP 8.1+
// Configure client timeout for long-running scripts$client = new ValkeyGlide();$client->connect( addresses: [['host' => 'localhost', 'port' => 6379]], request_timeout: 30000 // 30 seconds for long scripts (default is 250ms));
// Handle long-running scripts$longScript = <<<'LUA'local start = redis.call('TIME')[1]while redis.call('TIME')[1] - start < 25 do redis.call('GET', 'dummy_key') -- Read-only operationendreturn 'Done'LUA;
try { $result = $client->eval($longScript, [], 0); echo "Script completed: $result\n";} catch (ValkeyGlideException $e) { $message = $e->getMessage(); if (str_contains(strtolower($message), 'timeout')) { echo "Client timeout - script may still be running on server!\n"; echo "Consider increasing request_timeout in client configuration\n"; } elseif (str_contains($message, 'Script killed')) { echo "Script was killed by server (only possible for read-only scripts)\n"; } else { echo "Script error: $message\n"; }}Cluster-Specific Errors
Section titled “Cluster-Specific Errors”from glide import RequestError
# Handle cluster routing errorstry: result = await cluster_client.invoke_script( script, keys=["key1", "key2"] # Might be in different slots )except RequestError as e: if "CROSSSLOT" in str(e): print("Keys are in different slots") # Use hash tags or route explicitlyimport glide.api.models.exceptions.RequestException;
// Handle cluster routing errorstry { Object result = clusterClient.invokeScript( script, ScriptOptions.builder() .key("key1") .key("key2") // Might be in different slots .build() ).get();} catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof RequestException && cause.getMessage().contains("CROSSSLOT")) { System.out.println("Keys are in different slots"); // Use hash tags or route explicitly }}import {RequestError} from "@valkey/valkey-glide";
// Handle cluster routing errorstry { const result = await clusterClient.invokeScript(script, { keys: ["key1", "key2"] // Might be in different slots });} catch (e) { if (e instanceof RequestError && e.message.includes("CROSSSLOT")) { console.log("Keys are in different slots"); // Use hash tags or route explicitly }}// Handle cluster routing errorsresult, err := clusterClient.InvokeScriptWithOptions( context.Background(), *script, *options.NewScriptOptionsBuilder(). Keys([]string{"key1", "key2"}). // Might be in different slots Build(),)if err != nil && strings.Contains(err.Error(), "CROSSSLOT") { fmt.Println("Keys are in different slots") // Use hash tags or route explicitly}using Valkey.Glide;using Valkey.Glide.Errors;
// Handle cluster routing errorstry{ var result = await clusterClient.ScriptInvokeAsync(script, new ScriptOptions() .WithKeys("key1", "key2")); // Might be in different slots}catch (RequestException e) when (e.Message.Contains("CROSSSLOT")){ Console.WriteLine("Keys are in different slots"); // Use hash tags or route explicitly}// Handle cluster routing errorstry { $result = $clusterClient->eval( $script, ['key1', 'key2'], // Might be in different slots 2 // num_keys );} catch (ValkeyGlideException $e) { if (str_contains($e->getMessage(), 'CROSSSLOT')) { echo "Keys are in different slots\n"; // Use hash tags or route explicitly }}Best Practices
Section titled “Best Practices”1. Use Scripts for Atomic Non-primitive Operations
Section titled “1. Use Scripts for Atomic Non-primitive Operations”from glide import Script
# Good: Conditional update with multiple data structuresconditional_update = Script(""" local current = redis.call('GET', KEYS[1]) local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1 else return 0 end""")
result = await client.invoke_script( conditional_update, keys=["user:score", "user:history"], args=["100", "50", "86400"] # new score, threshold, expire in 1 day)import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;
// Good: Conditional update with multiple data structurestry (Script conditionalUpdate = new Script(""" local current = redis.call('GET', KEYS[1]) local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1 else return 0 end """, false)) {
Object result = client.invokeScript( conditionalUpdate, ScriptOptions.builder() .key("user:score") .key("user:history") .arg("100") .arg("50") .arg("86400") // new score, threshold, expire in 1 day .build() ).get();}import {Script} from "@valkey/valkey-glide";
// Good: Conditional update with multiple data structuresconst conditionalUpdate = new Script(` local current = redis.call('GET', KEYS[1]) local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1 else return 0 end`);
const result = await client.invokeScript(conditionalUpdate, { keys: ["user:score", "user:history"], args: ["100", "50", "86400"] // new score, threshold, expire in 1 day});import "github.com/valkey-io/valkey-glide/go/v2/options"
// Good: Conditional update with multiple data structuresconditionalUpdate := options.NewScript(` local current = redis.call('GET', KEYS[1]) local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1 else return 0 end`)
result, err := client.InvokeScriptWithOptions( context.Background(), *conditionalUpdate, *options.NewScriptOptionsBuilder(). Keys([]string{"user:score", "user:history"}). Args([]string{"100", "50", "86400"}). // new score, threshold, expire in 1 day Build(),)using Valkey.Glide;
// Good: Conditional update with multiple data structuresusing var conditionalUpdate = new Script(@" local current = redis.call('GET', KEYS[1]) local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1 else return 0 end");
var result = await client.ScriptInvokeAsync( conditionalUpdate, new ScriptOptions() .WithKeys("user:score", "user:history") .WithArgs("100", "50", "86400")); // new score, threshold, expire in 1 day// Good: Conditional update with multiple data structures$conditionalUpdate = <<<'LUA'local current = redis.call('GET', KEYS[1])local threshold = tonumber(ARGV[2])
if current and tonumber(current) >= threshold then redis.call('SET', KEYS[1], ARGV[1]) redis.call('LPUSH', KEYS[2], ARGV[1]) redis.call('EXPIRE', KEYS[2], ARGV[3]) return 1else return 0endLUA;
$result = $client->eval( $conditionalUpdate, ['user:score', 'user:history', '100', '50', '86400'], // new score, threshold, expire in 1 day 2 // num_keys);2. Handle Nil Values Properly
Section titled “2. Handle Nil Values Properly”from glide import Script
# Good: Proper nil handlingsafe_script = Script(""" local val = redis.call('GET', KEYS[1]) if val then return val else return 'default_value' end""")import glide.api.models.Script;
// Good: Proper nil handlingtry (Script safeScript = new Script(""" local val = redis.call('GET', KEYS[1]) if val then return val else return 'default_value' end """, false)) { // Use script}import {Script} from "@valkey/valkey-glide";
// Good: Proper nil handlingconst safeScript = new Script(` local val = redis.call('GET', KEYS[1]) if val then return val else return 'default_value' end`);import "github.com/valkey-io/valkey-glide/go/v2/options"
// Good: Proper nil handlingsafeScript := options.NewScript(` local val = redis.call('GET', KEYS[1]) if val then return val else return 'default_value' end`)using Valkey.Glide;
// Good: Proper nil handlingusing var safeScript = new Script(@" local val = redis.call('GET', KEYS[1]) if val then return val else return 'default_value' end");// Good: Proper nil handling$safeScript = <<<'LUA'local val = redis.call('GET', KEYS[1])if val then return valelse return 'default_value'endLUA;3. Use Appropriate Data Types
Section titled “3. Use Appropriate Data Types”from glide import Script
# Good: Return appropriate typestyped_script = Script(""" local value = redis.call('GET', KEYS[1]) return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nil""")import glide.api.models.Script;
// Good: Return appropriate typestry (Script typedScript = new Script(""" local value = redis.call('GET', KEYS[1]) return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nil """, false)) { // Use script}import {Script} from "@valkey/valkey-glide";
// Good: Return appropriate typesconst typedScript = new Script(` local value = redis.call('GET', KEYS[1]) return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nil`);import "github.com/valkey-io/valkey-glide/go/v2/options"
// Good: Return appropriate typestypedScript := options.NewScript(` local value = redis.call('GET', KEYS[1]) return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nil`)using Valkey.Glide;
// Good: Return appropriate typesusing var typedScript = new Script(@" local value = redis.call('GET', KEYS[1]) return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nil");// Good: Return appropriate types$typedScript = <<<'LUA'local value = redis.call('GET', KEYS[1])return tonumber(value) or 0 -- Ensure numeric return, default to 0 if nilLUA;4. Consider Cluster Constraints
Section titled “4. Consider Cluster Constraints”from glide import Script
# Good: Use hash tags for related keyscluster_script = Script(""" redis.call('SET', KEYS[1], ARGV[1]) redis.call('SET', KEYS[2], ARGV[2]) return 'OK'""")
# Execute with hash tagsawait cluster_client.invoke_script( cluster_script, keys=["user:{123}:name", "user:{123}:email"], args=["John", "john@example.com"])import glide.api.models.Script;import glide.api.models.commands.ScriptOptions;
// Good: Use hash tags for related keystry (Script clusterScript = new Script(""" redis.call('SET', KEYS[1], ARGV[1]) redis.call('SET', KEYS[2], ARGV[2]) return 'OK' """, false)) {
// Execute with hash tags clusterClient.invokeScript( clusterScript, ScriptOptions.builder() .key("user:{123}:name") .key("user:{123}:email") .arg("John") .arg("john@example.com") .build() ).get();}import {Script} from "@valkey/valkey-glide";
// Good: Use hash tags for related keysconst clusterScript = new Script(` redis.call('SET', KEYS[1], ARGV[1]) redis.call('SET', KEYS[2], ARGV[2]) return 'OK'`);
// Execute with hash tagsawait clusterClient.invokeScript(clusterScript, { keys: ["user:{123}:name", "user:{123}:email"], args: ["John", "john@example.com"]});import "github.com/valkey-io/valkey-glide/go/v2/options"
// Good: Use hash tags for related keysclusterScript := options.NewScript(` redis.call('SET', KEYS[1], ARGV[1]) redis.call('SET', KEYS[2], ARGV[2]) return 'OK'`)
// Execute with hash tagsclusterClient.InvokeScriptWithOptions( context.Background(), *clusterScript, *options.NewScriptOptionsBuilder(). Keys([]string{"user:{123}:name", "user:{123}:email"}). Args([]string{"John", "john@example.com"}). Build(),)using Valkey.Glide;
// Good: Use hash tags for related keysusing var clusterScript = new Script(@" redis.call('SET', KEYS[1], ARGV[1]) redis.call('SET', KEYS[2], ARGV[2]) return 'OK'");
// Execute with hash tagsawait clusterClient.ScriptInvokeAsync( clusterScript, new ScriptOptions() .WithKeys("user:{123}:name", "user:{123}:email") .WithArgs("John", "john@example.com"));// Good: Use hash tags for related keys$clusterScript = <<<'LUA'redis.call('SET', KEYS[1], ARGV[1])redis.call('SET', KEYS[2], ARGV[2])return 'OK'LUA;
// Execute with hash tags$clusterClient->eval( $clusterScript, ['user:{123}:name', 'user:{123}:email', 'John', 'john@example.com'], 2 // num_keys);