Redis Interview Questions

Redis Functions and Programmability

questions
Scroll to track progress

You're using Lua scripts (EVAL) for complex operations. As your application grows, you have 200+ Lua scripts across multiple services, each with different versions. Script management is chaotic: duplicates, versioning issues, no central registry. Redis 7.0+ introduced Functions (FUNCTION LOAD, FCALL). Should you migrate to Functions and why?

Functions vs Lua scripts: both execute code atomically in Redis, but Functions provide better management. Advantages of Functions: (1) persistent storage: functions are stored in Redis and persisted to RDB/AOF. Scripts are ephemeral (lost on restart unless re-sent). (2) named functions: functions have explicit names and versions. FCALL my_func arg1 arg2. Scripts use SHA hash. (3) library loading: functions can use dependency libraries (RedisJSON, etc.). Scripts have limited library support. (4) atomic replacement: FUNCTION REPLACE or FUNCTION LOAD ensure no incomplete updates. (5) easier versioning: FUNCTION LOAD v2 replaces v1 atomically. Scripts require manual versioning. Migration benefits for your case: (1) reduce duplication: instead of 200 script copies, maintain 200 function definitions in Redis. (2) centralized versioning: store function versions in git, deploy with versioning metadata. (3) simplify deployment: FUNCTION LOAD from compiled library file (one command). Migration strategy: (1) inventory existing scripts: collect all 200 Lua scripts. Identify duplicates. (2) deduplicate: combine similar scripts into parameterized functions. (3) create library: organize functions into modules: accounting_functions.lua (for transfers), user_functions.lua (for user ops). (4) deploy: redis-cli FUNCTION LOAD < library.lua. (5) update clients: change from redis.eval(script_text, ...) to redis.fcall(function_name, ...). (6) verify: compare old EVAL results with new FCALL results. Trade-off: (1) Functions require Redis 7.0+. If you're on 6.x, upgrade first. (2) Functions have slightly higher overhead per call (but negligible). (3) learning curve for team (new API). For immediate: (1) don't force migration. Coexist: keep scripts for backward-compat, use Functions for new code. (2) migrate incrementally: hot functions first (high-volume), less-critical later. (3) test: benchmark EVAL vs FCALL to ensure no latency increase.

Follow-up: If you have scripts that are version-specific (some use Lua 5.1, some use Lua 5.3), how would you manage this in Functions?

You deploy a Redis Function that increments a counter within a transaction. The function runs FCALL on 10 replicas concurrently (for performance testing). However, the counter ends up with value 7 instead of expected 10 (should have incremented 10 times). Why did the function execute fewer times than called?

Potential causes: (1) running on replica: if you called FCALL on replicas (which are read-only), writes would fail. Functions on replicas reject write commands. (2) connection failures: if some FCALL calls failed (network error), they didn't execute. Check for exceptions in client code. (3) transaction conflict: if function uses WATCH/MULTI, some transactions might abort. (4) function error: if function errors on some calls, those calls are skipped. Check FUNCTION LIST for errors. (5) concurrent execution: if 10 calls happen near-simultaneously, there might be race conditions if function isn't idempotent. Test with sequential execution first. Debug: (1) verify function executed: FCALL should return result. Check return value. If error, function crashed. (2) log inside function: add REDIS_LOG statement in function to debug. (3) check replica mode: INFO replication should show role:slave on replicas. FCALL would fail with READONLY error. (4) test with single call: FCALL once and verify counter = 1. Then FCALL twice and verify counter = 2. Sequential execution should be deterministic. (5) use FUNCTION LIST to see function definition and verify it's correct. Prevention: (1) don't call FCALL on replicas for writes. Use primary only. (2) implement idempotent functions: if FCALL is called multiple times with same input, result should be same. Use SET with NX flag: FCALL myfunc key value should do SETNX (set only if not exists), not SET (which overwrites). (3) error handling: wrap function in try-catch. Return error status on failure. Client can retry. (4) test concurrency: use redis-benchmark --eval with -c 100 to simulate concurrent calls. Verify result is consistent. Implementation: (1) change function to idempotent: REDIS_REPLICATE_COMMANDS redis.call('SET', KEYS[1], ARGV[1], 'NX'). This ensures idempotency. (2) test: call function 10 times sequentially, verify counter = 10. (3) add logging: REDIS_LOG(redis.LOG_NOTICE, "Function called with arg: " .. ARGV[1]). Check logs to verify all calls executed.

Follow-up: If a Function performs a long-running operation (5 seconds), can you interrupt it without waiting, and what happens to partial execution?

Your team deploys a Redis Function that has a bug: it's in an infinite loop (while true: redis.call(...)). The function blocks Redis. All clients stall. FCALL doesn't timeout. You need to kill the function. But unlike Lua scripts where SCRIPT KILL works, Functions don't have built-in kill mechanism. How do you recover?

Functions block Redis like Lua scripts. SCRIPT KILL doesn't work for Functions. Recovery options: (1) kill Redis process: redis-cli SHUTDOWN NOSAVE (kills Redis). Functions are lost. Restart Redis, re-load functions. This is nuclear but fast. (2) use CONFIG SET lua-time-limit: Functions respect this timeout (usually 5 seconds). After 5 seconds, function is forcibly killed. So fix is: CONFIG SET lua-time-limit 2000 (2 seconds). Function exceeds limit, Redis kills it. But this must be done BEFORE function is called. If already blocked, this doesn't help. (3) set timeout at client-side: in client code, wrap FCALL with timeout: fcall_with_timeout(function_name, 5seconds). If timeout, disconnect and reconnect. Redis will abort the function on client disconnect. (4) use FUNCTION KILL (if available in Redis 7.1+): some newer Redis versions support FUNCTION KILL to abort in-progress functions. Check your Redis version. (5) avoid infinite loops: review function code before deployment. Use CI/CD tests to detect infinite loops (timeout test). Prevention: (1) review functions for infinite loops: SCAN function code for while(true), for loops without exit condition. (2) set lua-time-limit: CONFIG SET lua-time-limit 5000 (5 seconds) in redis.conf. This acts as safety net. (3) test function timeout: run function for exactly 5 seconds, verify it's killed by Redis (not graceful exit). (4) implement graceful shutdown: in function, check for cancellation signal: if redis.call("GET", "cancel:function-name") then return error("Cancelled") end. This allows external cancellation. (5) deploy canary: before deploying to production, test function on staging with simulated load. Implementation: (1) fix infinite loop: change while true to: for i=1,1000 do ... end (bounded loop). (2) set CONFIG: CONFIG SET lua-time-limit 5000. (3) restart Redis: redis-cli SHUTDOWN NOSAVE if already blocked, then redis-server to restart. (4) re-load function: FUNCTION LOAD < fixed-function.lua.

Follow-up: If you need to update a Function but clients are actively using the old version, can you do a zero-downtime replacement?

You deploy a set of Redis Functions organized in a library (FUNCTION LOAD mylib.lua with 50 functions). One function has a bug. Instead of reloading the entire library (which would interrupt all 49 other functions if any are running), you want to replace just the buggy function. Is this possible, and what's the best practice?

FUNCTION LOAD replaces entire library. If library has 50 functions and you FUNCTION LOAD with fixed code, all 50 functions are reloaded. If a function was running during reload, it might be interrupted. Better approach: (1) separate libraries per function: instead of 1 library with 50 functions, split into 50 libraries (mylib:func1, mylib:func2, ...). Then FUNCTION LOAD only replaces func1 without affecting others. (2) function aliasing: create a wrapper function (dispatcher) that calls actual functions. Update dispatcher to point to new function version. Old version continues running. (3) use function versioning: name functions with versions (transfer:v1, transfer:v2). Deploy new version, then gradually shift traffic. Once all clients use v2, delete v1. (4) atomic replacement at Redis 7.0+: FUNCTION REPLACE allows replacing without waiting for running functions to complete. But it still replaces entire library. To minimize disruption, keep functions in separate libraries. Implementation: (1) refactor library structure: instead of 1 mylib.lua, split into: func1.lua (contains function1), func2.lua (contains function2), etc. (2) FUNCTION LOAD each separately: FUNCTION LOAD < func1.lua, FUNCTION LOAD < func2.lua, etc. (3) fix bug in func1.lua: update function code, test locally. (4) redeploy: FUNCTION LOAD < func1.lua. This only affects func1. Other 49 functions are unaffected. (5) test: call other functions to verify they still work. Verify func1 bug is fixed. For production: (1) organize functions into separate libraries by team/feature. (2) deploy independently. (3) use FUNCTION FLUSH only in emergencies (nukes all functions). For normal updates: use FUNCTION DELETE (if available, v7.1+) or FUNCTION LOAD to replace. (4) test library structure: simulate bug fix scenario in staging. Verify other functions aren't affected.

Follow-up: If multiple teams contribute functions to the same Redis instance, how would you manage versioning and deployment conflicts?

Want to go deeper?