smithery/pluginagentmarketplace

redis-transactions

Master Redis transactions - MULTI/EXEC, WATCH for optimistic locking, Lua scripting, and atomic operation patterns

Installation

$ npx skills add smithery/pluginagentmarketplace --skill redis-transactions

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/pluginagentmarketplace · top by installs.

npx skills add smithery/pluginagentmarketplace

Browse all from smithery/pluginagentmarketplace

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version2.1.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 9,840 B
  • docs SUMMARY.md 140 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Redis Transactions Skill

Overview

Production-grade transaction handling for Redis. Master MULTI/EXEC for atomic command batches, WATCH for optimistic locking, and Lua scripting for complex atomic operations.

Transaction Types Comparison

Feature MULTI/EXEC WATCH+MULTI Lua Script
Atomicity
Isolation Partial Partial ✅ Full
Conditional logic
Read-modify-write
Cluster support ⚠️ Same slot ⚠️ Same slot ⚠️ Same slot
Debugging Easy Medium Harder

MULTI/EXEC Transactions

Basic Transaction

MULTI                                # Start transaction
SET user:123:balance 100
INCR user:123:login_count
LPUSH user:123:actions "login"
EXEC                                 # Execute atomically
# Returns: [OK, 1, 1]

Discard Transaction

MULTI
SET key1 "value1"
SET key2 "value2"
DISCARD                              # Abort, nothing executed

Transaction Guarantees

  • Atomic execution: All commands execute without interruption
  • All or nothing: If EXEC fails, nothing is applied
  • No rollback: Individual command failures don't rollback others
  • No read-modify-write: Can't use previous values in transaction

WATCH (Optimistic Locking)

Pattern: Check-and-Set

WATCH user:123:balance               # Start watching
balance = GET user:123:balance       # Read current value

# Client-side logic
if balance >= 50:
    MULTI
    DECRBY user:123:balance 50
    INCRBY merchant:456:balance 50
    EXEC                             # nil if balance changed
else:
    UNWATCH                          # Release watch

Multiple Keys

WATCH key1 key2 key3                 # Watch multiple keys
# ... read values ...
MULTI
# ... modify values ...
EXEC                                 # nil if ANY watched key changed
UNWATCH                              # Always call after failed EXEC

Watch Retry Pattern (Python)

MAX_RETRIES = 5
for attempt in range(MAX_RETRIES):
    try:
        pipe = r.pipeline(True)      # True = use MULTI
        pipe.watch('user:123:balance')

        balance = int(pipe.get('user:123:balance') or 0)
        if balance < 50:
            pipe.unwatch()
            raise InsufficientFunds()

        pipe.multi()
        pipe.decrby('user:123:balance', 50)
        pipe.incrby('merchant:456:balance', 50)
        pipe.execute()
        break  # Success

    except redis.WatchError:
        continue  # Retry
    finally:
        pipe.reset()

Lua Scripting

Basic Script

-- Atomic increment with cap
local current = tonumber(redis.call('GET', KEYS[1]) or 0)
local max = tonumber(ARGV[1])

if current < max then
    return redis.call('INCR', KEYS[1])
else
    return current
end

Execute Script

EVAL "return redis.call('GET', KEYS[1])" 1 mykey

# Load and execute by hash (faster for repeated calls)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# Returns: "a42059b356c875f0717db19a51f6aaa9161e77a2"

EVALSHA a42059b356c875f0717db19a51f6aaa9161e77a2 1 mykey

Script Management

SCRIPT EXISTS <sha1> [sha1 ...]      # Check if loaded
SCRIPT FLUSH [ASYNC|SYNC]            # Clear cache
SCRIPT KILL                          # Kill running (if no writes)
SCRIPT DEBUG YES|SYNC|NO             # Enable debugging

Production Patterns

Pattern 1: Fund Transfer

-- transfer.lua
local from = KEYS[1]
local to = KEYS[2]
local amount = tonumber(ARGV[1])

local from_balance = tonumber(redis.call('GET', from) or 0)

if from_balance >= amount then
    redis.call('DECRBY', from, amount)
    redis.call('INCRBY', to, amount)
    return 1  -- Success
else
    return 0  -- Insufficient funds
end

Pattern 2: Distributed Lock

-- acquire_lock.lua
local lock_key = KEYS[1]
local holder = ARGV[1]
local ttl = tonumber(ARGV[2])

if redis.call('SET', lock_key, holder, 'NX', 'EX', ttl) then
    return 1
else
    return 0
end

-- release_lock.lua
local lock_key = KEYS[1]
local holder = ARGV[1]

if redis.call('GET', lock_key) == holder then
    return redis.call('DEL', lock_key)
else
    return 0
end

Pattern 3: Rate Limiter

-- rate_limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = tonumber(redis.call('GET', key) or 0)

if current < limit then
    redis.call('INCR', key)
    if current == 0 then
        redis.call('EXPIRE', key, window)
    end
    return 1  -- Allowed
else
    return 0  -- Denied
end

Lua Best Practices

-- ✅ GOOD: Always use KEYS and ARGV
local value = redis.call('GET', KEYS[1])
redis.call('SET', KEYS[2], ARGV[1])

-- ❌ BAD: Dynamic key names break cluster
local key = 'user:' .. ARGV[1]  -- Don't!
redis.call('GET', key)

-- ✅ GOOD: Early return for efficiency
if not redis.call('EXISTS', KEYS[1]) then
    return nil
end

-- ✅ GOOD: Use pcall for error handling
local ok, result = pcall(redis.call, 'GET', KEYS[1])
if not ok then
    return redis.error_reply("Operation failed")
end

Assets

  • distributed-lock.lua - Production-ready lock implementation
  • config.yaml - Transaction configuration

Scripts

  • lua-loader.sh - Load and manage Lua scripts
  • helper.py - Python transaction utilities

References

  • TRANSACTION_PATTERNS.md - Best practices guide
  • GUIDE.md - Complete reference

Troubleshooting Guide

Common Issues & Solutions

1. EXECABORT

EXECABORT Transaction discarded because of previous errors

Cause: Syntax error in queued command

Diagnosis:

MULTI
SET key                              # Missing value!
EXEC
# (error) EXECABORT...

Fix: Validate commands before queueing

2. Watch Keeps Failing

Cause: High contention on watched key

Solutions:

  • Reduce transaction time
  • Use Lua script instead
  • Implement exponential backoff
  • Consider different data model

3. BUSY Script Error

BUSY Redis is busy running a script

Cause: Lua script running too long (>lua-time-limit)

Recovery:

# Kill script (only if no writes performed)
SCRIPT KILL

# If script performed writes
# Must restart Redis or wait

Prevention:

# redis.conf
lua-time-limit 5000  # 5 seconds

4. NOSCRIPT Error

NOSCRIPT No matching script

Cause: Script not loaded (cache cleared or different node)

Fix:

# Always handle NOSCRIPT
try:
    result = r.evalsha(sha, 1, key)
except redis.NoScriptError:
    result = r.eval(script, 1, key)

Debug Checklist

□ All keys in same hash slot (cluster)?
□ WATCH before read operations?
□ UNWATCH after failed EXEC?
□ Lua script uses KEYS/ARGV properly?
□ Script execution time reasonable?
□ Handling NOSCRIPT error?
□ Retry logic for WatchError?

Transaction Performance

Operation Time Notes
MULTI O(1) Start transaction
Queue command O(1) Add to queue
EXEC O(N) Execute N commands
WATCH O(1) Per key
EVAL O(N) Script complexity
EVALSHA O(N) Same as EVAL (no load overhead)

Error Codes Reference

Code Name Description Recovery
T001 EXECABORT Syntax error in queue Fix command syntax
T002 WATCH_FAILED Key modified Retry transaction
T003 BUSY Script timeout SCRIPT KILL or wait
T004 NOSCRIPT Script not loaded Re-EVAL or LOAD
T005 CROSSSLOT Keys in different slots Use hash tags

Test Template

# test_redis_transactions.py
import redis
import pytest

@pytest.fixture
def r():
    return redis.Redis(decode_responses=True)

def test_multi_exec(r):
    pipe = r.pipeline()
    pipe.set("tx:key1", "value1")
    pipe.set("tx:key2", "value2")
    results = pipe.execute()
    assert results == [True, True]
    r.delete("tx:key1", "tx:key2")

def test_watch_success(r):
    r.set("tx:balance", "100")
    pipe = r.pipeline(True)
    pipe.watch("tx:balance")
    balance = int(pipe.get("tx:balance"))
    pipe.multi()
    pipe.set("tx:balance", balance - 50)
    results = pipe.execute()
    assert results == [True]
    assert r.get("tx:balance") == "50"
    r.delete("tx:balance")

def test_lua_script(r):
    script = "return redis.call('GET', KEYS[1])"
    r.set("tx:lua", "test_value")
    result = r.eval(script, 1, "tx:lua")
    assert result == "test_value"
    r.delete("tx:lua")