skip to content

Your services run a dozen pieces of Lua logic on Redis via EVALSHA with a NOSCRIPT fallback, and the servers were just upgraded to Redis 7. How would you decide whether to migrate that logic to Redis Functions, and how would you carry out the migration?

level: principalimportance: nice to knowfreq 18%

answer

  1. Functions = deployed artifact; EVAL = ad-hoc query
  2. Kills the untested NOSCRIPT retry path
  3. no-writes unlocks FCALL_RO + killability
  4. Same blocking, same no rollback — nothing gets faster
  5. Side-by-side library names, flag the call sites, then delete

basics

~20 s

Migrate when the logic is long-lived, shared across services, and its NOSCRIPT/versioning handling costs you — Functions give named, deployed, replicated code. Keep EVAL for ad-hoc or client-generated scripts. Migrate side-by-side: load a new library, dual-run, cut traffic over, delete the old path.

solid answer

~50 s

**Decide by ownership and lifetime.** Functions win when the code is a stable, shared capability: it gets a name, a deployment step, survives restart and failover (no `NOSCRIPT` handling, no cold cache on a promoted replica), can be inventoried with `FUNCTION LIST WITHCODE`, and declares flags like `no-writes` that unlock `FCALL_RO` on replicas. EVAL still wins for one-off, dynamically generated, or client-private scripts where installing server-side code is overkill. **Costs to weigh honestly:** client and proxy support for `FCALL`; managed-service restrictions on `FUNCTION`; a new deployment surface that can drift from the repo; per-shard loading in Cluster; and the fact that nothing about correctness improves — the same blocking, atomicity, key-declaration and determinism rules apply. **Execute side-by-side:** port each script into a library with distinct function names, load with `REPLACE` on every primary, switch call sites behind a flag, compare results/latency, then remove the script path and its NOSCRIPT machinery. Never big-bang all twelve at once.

go deeper

for a junior

Recognize that Functions are the newer, named, persistent way to run server-side Lua and that EVAL still works; do not be expected to plan a fleet migration.

for a middle

Name the concrete wins — no NOSCRIPT handling, survives restart and failover, named and inspectable — and note that execution semantics are unchanged.

for a senior

Lay out a staged migration: inventory, library grouping, idempotent REPLACE deploys per primary, flagged cut-over per call site, then removal of the script path and its retry code.

for a principal

Argue the ownership question — which logic deserves to be a deployed artifact of the data tier — and be explicit about what migration does not fix and which ecosystem constraints could veto it.

## Frame the decision, not the syntax The rewrite itself is mechanical: `KEYS`/`ARGV` globals become the `keys`/`args` parameters of a registered callback, the file gains a `#!lua name=…` shebang, and `EVALSHA <sha> n …` becomes `FCALL <fname> n …`. The judgment is about whether server-side code should become a **deployed artifact** in your architecture. ## Where Functions genuinely pay **The NOSCRIPT tax disappears.** With EVALSHA every client must carry the source and implement retry-on-NOSCRIPT. That path is rarely exercised, therefore rarely tested, and it fires exactly during incidents — after a restart, after `SCRIPT FLUSH`, on a freshly promoted replica whose cache is cold. Libraries are part of the dataset, so they are there after restart and failover. **Shared logic gets an owner and a name.** If three services all EVALSHA the same rate-limiter, each embeds a copy of the source; drift between copies is invisible. One library with a named function makes the contract explicit and lets `FUNCTION LIST WITHCODE` show what is actually installed. **Flags become part of the contract.** Registering with `no-writes` makes a function callable via `FCALL_RO` on replicas and makes it killable if it runs away — a real availability property, not a nicety. **Auditability.** Security review of "what code executes inside our datastore" is answerable by querying the server rather than grepping every client repo. ## Where EVAL should stay - **Ad-hoc and generated scripts** — an admin tool, a migration job, a script whose body depends on runtime input. Installing and later deleting a library for a one-off is worse than sending it. - **Client-private glue** owned entirely by one service with a short lifetime. - **Environments you do not control**: managed Redis offerings and proxies sometimes restrict `FUNCTION` subcommands or predate 7.0; some client libraries and connection pools still lack first-class `FCALL` support or its cluster key routing. Verify before committing the fleet. - **Anything you would otherwise have to rewrite under time pressure.** Migration is optional; EVAL is not deprecated in 7.x. ## What migration does not fix Be explicit about this in an interview, because it is where candidates oversell. Functions run in the same Lua sandbox on the same single command-processing thread. A function that iterates a huge collection blocks the whole server exactly as the script did. Atomicity, no rollback on error, the requirement to declare every key, single-slot restriction in Cluster, and effects-based replication are all unchanged. If your problem is a slow script or an unkillable one, the fix is bounding the work and flagging `no-writes`, not changing the invocation command. ## A migration plan that survives contact with production 1. **Inventory.** Enumerate every script: its source, its call sites, its key pattern, its p99 latency, whether it writes. Some will turn out to be dead, and some will turn out to be better expressed as plain commands or a pipeline. 2. **Group into libraries by ownership.** One library per bounded capability (`ratelimit`, `inventory`), not one library per script. Function names are unique server-wide, so agree a naming convention early. 3. **Port and unit-test.** Callbacks taking `(keys, args)` are testable outside Redis; add tests for the branches the NOSCRIPT-era code never had. 4. **Deploy the library first, call it later.** Load with `FUNCTION LOAD REPLACE` on every primary as an idempotent pipeline step. Loading unused code is harmless. 5. **Cut over per call site behind a flag.** Keep the EVALSHA path alive. For high-risk paths, dual-run in shadow (call the read-only variant and compare) before switching writes. 6. **Watch the right signals.** `FUNCTION STATS` for running functions, `SLOWLOG` and latency percentiles for regressions, error rates for flag mistakes (a `no-writes` function that occasionally writes will error only in the rare branch). 7. **Delete the old path.** Remove the script constants, the SHA caching and the NOSCRIPT retry from the clients. If you leave the fallback in, you have paid the cost and kept the risk. 8. **Version deliberately.** Either in-place `REPLACE` when the contract is unchanged, or side-by-side `orders_v2` with new function names and a traffic flip when it is not — the latter is the only cheap rollback. ## The decision in one sentence Migrate the logic that behaves like a shipped capability of the data tier — long-lived, shared, operationally load-bearing — and leave the scripts that behave like queries.

  • What would make you decide against migrating at all, even on Redis 7?
    Client or proxy support gaps for FCALL, a managed service restricting FUNCTION subcommands, or a fleet where per-shard loading is not yet in the deployment pipeline. Also scripts that are inherently ad-hoc or generated — installing and deleting libraries for one-off work adds operational surface without removing risk. If the NOSCRIPT path has never actually hurt you, the migration is optional cleanup, not a fix.
  • How do you keep deployed libraries from drifting away from the code in your repository?
    Make loading an idempotent pipeline step that always uses FUNCTION LOAD REPLACE, so every deploy converges the fleet. Then verify: FUNCTION LIST LIBRARYNAME x WITHCODE on each primary, hashed and compared against the repository source, run as a periodic check or a post-deploy assertion. Alert on mismatch, and treat a drifted node the way you would treat a node running an old binary.
  • During the cutover you find one function is much slower than the script it replaced. Where do you look?
    First confirm it is the code and not the call pattern: FUNCTION STATS and SLOWLOG will show the invocation and its duration. Common causes are a changed key declaration causing extra round trips outside the function, an accidentally unbounded range command, or having merged several small scripts into one larger function that now does more work per call. The fix is bounding the work per invocation, not tuning Redis.

saying these in an interview costs you the question

  • Claiming Functions are faster or non-blocking compared to EVAL
  • Presenting migration as mandatory because EVAL is 'deprecated' — it is not in 7.x
  • Big-bang migrating all scripts and deleting the EVALSHA fallback in the same release
  • Ignoring client/proxy/managed-service support for FCALL
  • Assuming one FUNCTION LOAD covers a whole cluster

context