Skip to content

Valkey Scripting

Valkey GLIDE provide an interface for Valkey scripting allowing you to manage and execute custom logic directly on the server. This page will explain how GLIDE handles Valkey scripting and its features.

For more detail on just Valkey scripting, visit the Valkey documentation.

GLIDE script provide the following advantages:

  • Automatic Caching: Scripts are cached server-side using SHA1 hashes, improving performance and reducing overhead
  • Cluster Support: Scripts work seamlessly in both standalone and cluster modes
  • Management: Built-in support for script routing and lifecycle management

Valkey GLIDE provides a Script class that wraps Valkey’s Lua scripts and handles their execution efficiently.

from glide import Script, GlideClient, GlideClientConfiguration, NodeAddress
# Create a client
config = GlideClientConfiguration(addresses=[NodeAddress("localhost", 6379)])
client = await GlideClient.create(config)
# Create a simple script
script = Script("return 'Hello, Valkey!'")
# Execute the script
result = await client.invoke_script(script)
print(result) # b'Hello, Valkey!'

Valkey provides KEYS and ARGV to handle input parameters:

  • KEYS: Used to pass key names in Valkey (e.g., product:123)
  • ARGV: Used for general input parameters
product_key = "product:shoe:stock"
buy_quantity = "3"
result = await client.invoke_script(
purchase_script,
keys=[product_key], # Maps to KEYS[1] in Lua
args=[buy_quantity] # Maps to ARGV[1] in Lua
)

Scripts are executed physically on a node and thus it is important for the node to contain the keys being accessed.

GLIDE handle this by send the script to the correct node based on the KEYS provided. If no KEYS are provided, GLIDE will route the script to a random node for execution. Thus, hardcoding the keys will most likely lead to errors.

For proper execution, make sure to:

  • Pass all accessed keys via the KEYS argument
  • Only interact with keys listed in KEYS
  • KEYS must belong to the same slot (use hashtags like user:{100}:name if needed)

Another benefit is that using ARGV and KEYS allows for efficient caching of scripts. Since Valkey caches scripts based on their SHA1 hash of the code, hardcoding changing values directly into the script would create unique scripts each time, preventing effective caching.

# Bad: Creates a new hash for each quantity
bad_script = Script(f"return server.call('DECRBY', {product_key}, {buy_quantity})")
# Good: Reuses the same cached script
good_script = Script("return server.call('DECRBY', KEYS[1], ARGV[1])")
await client.invoke_script(good_script, keys=[product_key], args=[buy_quantity])

When you create a Script object in GLIDE, it is hashed using SHA1. GLIDE then automatically caches scripts with each invoke_script() call.

script = Script("return 'Hello'")
hash_value = script.get_hash()
print(f"Script hash: {hash_value}") # SHA1 hash of the script

Scripts with identical code produce identical hashes, enabling efficient caching and reuse.

script1 = Script("return 'Hello'")
script2 = Script("return 'Hello'")
assert script1.get_hash() == script2.get_hash() # Same hash

Scripts remain cached on the Valkey server until:

  • The server restarts
  • Memory pressure triggers eviction
  • You explicitly flush the cache with script_flush() All of which are handled automatically by GLIDE.

To learn more about script lifecycle, see Valkey’s documentation.

A script can be safely killed if it has only performed read-only operations. However, once it executes any write operation, it becomes uninterruptible and must either run to completion or reach a timeout. This prevents data corruption as once a script modifies data it must complete to ensure consistency.

read-only.lua
-- Read-only script
local start = server.call('TIME')[1]
while server.call('TIME')[1] - start < 10 do
server.call('GET', 'some_key') -- Read-only
end
return 'Done'
with-write.lua
-- This script writes data - cannot be killed after the SET
server.call('SET', 'temp', 'value') -- Write operation
local start = server.call('TIME')[1]
while server.call('TIME')[1] - start < 10 do
-- Long operation after write
end
return 'Done'

Valkey executes scripts atomically in a single-threaded manner. While a script runs:

  • No other commands can execute
  • The script has exclusive access to the data
  • All operations within the script are guaranteed to complete without interruption

In cluster mode, Valkey distributes data across multiple nodes using hash slots. Each key is mapped to one of 16,384 slots based on its hash:

slot = CRC16(key) mod 16384

GLIDE automatically route scripts to the appropriate nodes. When executing a script in cluster mode, all KEYS must belong to the same shard.

# This works the same in cluster mode
script = Script("return redis.call('SET', KEYS[1], ARGV[1])")
result = await cluster_client.invoke_script(
script,
keys=["user:1000"], # Routed based on key hash
args=["John"]
)

GLIDE provide options for explicit control over script routing in cluster mode.

from glide import SlotKeyRoute, SlotType, AllPrimaries
# Route to the node holding a specific key's slot
route = SlotKeyRoute(SlotType.PRIMARY, "user:1000")
# Route to all primary nodes
route = AllPrimaries()
await cluster_client.invoke_script_route(script, route=route)

In cluster mode, scripts that access multiple keys must ensure all keys belong to the same slot. Adding a hash tag {} in the key allow you to specify the specific slot.

# Both keys hash to the same slot because of {1000}
keys = ["user:{1000}:name", "user:{1000}:email"]

To lean more about hash tags, see the documentation.

Currently, invoke_script is not supported in batch operations (pipelines/transactions). To use Lua scripts within an atomic batch (MULTI/EXEC transaction), you must use the EVAL command with custom_command.

# Script with keys and arguments
batch = Batch(is_atomic=True)
batch.custom_command([
"EVAL",
"return redis.call('SET', KEYS[1], ARGV[1])",
"1", # Number of keys
"script-key", # Key
"script-value" # Argument
])
batch.get("script-key")
results = await client.exec(batch, raise_on_error=False)
print(f"EVAL result: {results[0]}") # b'OK'
print(f"GET result: {results[1]}") # b'script-value'

To learn how execute custom scripts in practice, check out the guide on Valkey scripting.

See our reference page for more examples of custom scripts in GLIDE.

You can learn more about Valkey scripting in the Valkey documentation.