{
  "id": "lua-debugging",
  "title": "Debugging Lua scripts in Redis",
  "url": "https://un5pn9hmggug.julianrbryant.com/docs/latest/develop/programmability/lua-debugging/",
  "summary": "How to use the built-in Lua debugger",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-04-01T08:10:08-05:00",
  "page_type": "content",
  "content_hash": "8c924c136e26652cccda4550c88c3e074a087f4e10bfd15fbbbfed823b1d70ff",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Starting with version 3.2 Redis includes a complete Lua debugger, that can be\nused in order to make the task of writing complex Redis scripts much simpler.\n\nThe Redis Lua debugger, codenamed LDB, has the following important features:\n\n* It uses a server-client model, so it's a remote debugger.\nThe Redis server acts as the debugging server, while the default client is `redis-cli`. \nHowever other clients can be developed by following the simple protocol implemented by the server.\n* By default every new debugging session is a forked session.\nIt means that while the Redis Lua script is being debugged, the server does not block and is usable for development or in order to execute multiple debugging sessions in parallel.\nThis also means that changes are **rolled back** after the script debugging session finished, so that's possible to restart a new debugging session again, using exactly the same Redis data set as the previous debugging session.\n* An alternative synchronous (non forked) debugging model is available on demand, so that changes to the dataset can be retained.\nIn this mode the server blocks for the time the debugging session is active.\n* Support for step by step execution.\n* Support for static and dynamic breakpoints.\n* Support from logging the debugged script into the debugger console.\n* Inspection of Lua variables.\n* Tracing of Redis commands executed by the script.\n* Pretty printing of Redis and Lua values.\n* Infinite loops and long execution detection, which simulates a breakpoint."
    },
    {
      "id": "quick-start",
      "title": "Quick start",
      "role": "content",
      "text": "A simple way to get started with the Lua debugger is to watch this video\nintroduction:\n\n<iframe width=\"560\" height=\"315\" src=\"https://un5gmtkzgkvecnwrqr1g.julianrbryant.com/embed/IMvRfStaoyM\" frameborder=\"0\" allowfullscreen></iframe>\n\n> Important Note:  please make sure to avoid debugging Lua scripts using your Redis production server.\nUse a development server instead.\nAlso note that using the synchronous debugging mode (which is NOT the default) results in the Redis server blocking for all the time the debugging session lasts.\n\nTo start a new debugging session using `redis-cli` do the following:\n\n1. Create your script in some file with your preferred editor. Let's assume you are editing your Redis Lua script located at `/tmp/script.lua`.\n2. Start a debugging session with:\n\n    ./redis-cli --ldb --eval /tmp/script.lua\n\nNote that with the `--eval` option of `redis-cli` you can pass key names and arguments to the script, separated by a comma, like in the following example:\n\n[code example]\n\nYou'll enter a special mode where `redis-cli` no longer accepts its normal\ncommands, but instead prints a help screen and passes the unmodified debugging\ncommands directly to Redis.\n\nThe only commands which are not passed to the Redis debugger are:\n\n* `quit` -- this will terminate the debugging session.\nIt's like removing all the breakpoints and using the `continue` debugging command.\nMoreover the command will exit from `redis-cli`.\n* `restart` -- the debugging session will restart from scratch, **reloading the new version of the script from the file**.\nSo a normal debugging cycle involves modifying the script after some debugging, and calling `restart` in order to start debugging again with the new script changes.\n* `help` -- this command is passed to the Redis Lua debugger, that will print a list of commands like the following:\n\n[code example]\n\nNote that when you start the debugger it will start in **stepping mode**.\nIt will stop at the first line of the script that actually does something before executing it.\n\nFrom this point you usually call `step` in order to execute the line and go to the next line.\nWhile you step Redis will show all the commands executed by the server like in the following example:\n\n[code example]\n\nThe `<redis>` and `<reply>` lines show the command executed by the line just\nexecuted, and the reply from the server. Note that this happens only in stepping mode.\nIf you use `continue` in order to execute the script till the next breakpoint, commands will not be dumped on the screen to prevent too much output."
    },
    {
      "id": "termination-of-the-debugging-session",
      "title": "Termination of the debugging session",
      "role": "content",
      "text": "When the scripts terminates naturally, the debugging session ends and\n`redis-cli` returns in its normal non-debugging mode. You can restart the\nsession using the `restart` command as usual.\n\nAnother way to stop a debugging session is just interrupting `redis-cli`\nmanually by pressing `Ctrl+C`. Note that also any event breaking the\nconnection between `redis-cli` and the `redis-server` will interrupt the\ndebugging session.\n\nAll the forked debugging sessions are terminated when the server is shut\ndown."
    },
    {
      "id": "abbreviating-debugging-commands",
      "title": "Abbreviating debugging commands",
      "role": "content",
      "text": "Debugging can be a very repetitive task. For this reason every Redis\ndebugger command starts with a different character, and you can use the single\ninitial character in order to refer to the command.\n\nSo for example instead of typing `step` you can just type `s`."
    },
    {
      "id": "breakpoints",
      "title": "Breakpoints",
      "role": "content",
      "text": "Adding and removing breakpoints is trivial as described in the online help.\nJust use `b 1 2 3 4` to add a breakpoint in line 1, 2, 3, 4.\nThe command `b 0` removes all the breakpoints. Selected breakpoints can be\nremoved using as argument the line where the breakpoint we want to remove is, but prefixed by a minus sign. \nSo for example `b -3` removes the breakpoint from line 3.\n\nNote that adding breakpoints to lines that Lua never executes, like declaration of local variables or comments, will not work.\nThe breakpoint will be added but since this part of the script will never be executed, the program will never stop."
    },
    {
      "id": "dynamic-breakpoints",
      "title": "Dynamic breakpoints",
      "role": "content",
      "text": "Using the `breakpoint` command it is possible to add breakpoints into specific\nlines. However sometimes we want to stop the execution of the program only\nwhen something special happens. In order to do so, you can use the\n`redis.breakpoint()` function inside your Lua script. When called it simulates\na breakpoint in the next line that will be executed.\n\n[code example]\nThis feature is extremely useful when debugging, so that we can avoid\ncontinuing the script execution manually multiple times until a given condition\nis encountered."
    },
    {
      "id": "synchronous-mode",
      "title": "Synchronous mode",
      "role": "content",
      "text": "As explained previously, but default LDB uses forked sessions with rollback\nof all the data changes operated by the script while it has being debugged.\nDeterminism is usually a good thing to have during debugging, so that successive\ndebugging sessions can be started without having to reset the database content\nto its original state.\n\nHowever for tracking certain bugs, you may want to retain the changes performed\nto the key space by each debugging session. When this is a good idea you\nshould start the debugger using a special option, `ldb-sync-mode`, in `redis-cli`.\n\n[code example]\n\n> Note: Redis server will be unreachable during the debugging session in this mode, so use with care.\n\nIn this special mode, the `abort` command can stop the script half-way taking the changes operated to the dataset.\nNote that this is different compared to ending the debugging session normally. \nIf you just interrupt `redis-cli` the script will be fully executed and then the session terminated.\nInstead with `abort` you can interrupt the script execution in the middle and start a new debugging session if needed."
    },
    {
      "id": "logging-from-scripts",
      "title": "Logging from scripts",
      "role": "content",
      "text": "The `redis.debug()` command is a powerful debugging facility that can be\ncalled inside the Redis Lua script in order to log things into the debug\nconsole:\n\n[code example]\n\nIf the script is executed outside of a debugging session, `redis.debug()` has no effects at all.\nNote that the function accepts multiple arguments, that are separated by a comma and a space in the output.\n\nTables and nested tables are displayed correctly in order to make values simple to observe for the programmer debugging the script."
    },
    {
      "id": "inspecting-the-program-state-with-print-and-eval",
      "title": "Inspecting the program state with `print` and `eval`",
      "role": "content",
      "text": "While the `redis.debug()` function can be used in order to print values\ndirectly from within the Lua script, often it is useful to observe the local\nvariables of a program while stepping or when stopped into a breakpoint.\n\nThe `print` command does just that, and performs lookup in the call frames\nstarting from the current one back to the previous ones, up to top-level.\nThis means that even if we are into a nested function inside a Lua script,\nwe can still use `print foo` to look at the value of `foo` in the context\nof the calling function. When called without a variable name, `print` will\nprint all variables and their respective values.\n\nThe `eval` command executes small pieces of Lua scripts **outside the context of the current call frame** (evaluating inside the context of the current call frame is not possible with the current Lua internals).\nHowever you can use this command in order to test Lua functions.\n\n[code example]"
    },
    {
      "id": "debugging-clients",
      "title": "Debugging clients",
      "role": "content",
      "text": "LDB uses the client-server model where the Redis server acts as a debugging server that communicates using [RESP](). While `redis-cli` is the default debug client, any client can be used for debugging as long as it meets one of the following conditions:\n\n1. The client provides a native interface for setting the debug mode and controlling the debug session.\n2. The client provides an interface for sending arbitrary commands over RESP.\n3. The client allows sending raw messages to the Redis server.\n\nFor example, the [Redis plugin](https://un5pn9hm2w.julianrbryant.com/blog/zerobrane-studio-plugin-for-redis-lua-scripts) for [ZeroBrane Studio](https://umn0c0dzxk5pckxrq1y55d8.julianrbryant.com/) integrates with LDB using [redis-lua](https://github.com/nrk/redis-lua). The following Lua code is a simplified example of how the plugin achieves that:\n\n[code example]"
    }
  ],
  "examples": [
    {
      "id": "quick-start-ex0",
      "language": "plaintext",
      "code": "./redis-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2",
      "section_id": "quick-start"
    },
    {
      "id": "quick-start-ex1",
      "language": "plaintext",
      "code": "lua debugger> help\nRedis Lua debugger help:\n[h]elp               Show this help.\n[s]tep               Run current line and stop again.\n[n]ext               Alias for step.\n[c]ontinue           Run till next breakpoint.\n[l]ist               List source code around current line.\n[l]ist [line]        List source code around [line].\n                     line = 0 means: current position.\n[l]ist [line] [ctx]  In this form [ctx] specifies how many lines\n                     to show before/after [line].\n[w]hole              List all source code. Alias for 'list 1 1000000'.\n[p]rint              Show all the local variables.\n[p]rint <var>        Show the value of the specified variable.\n                     Can also show global vars KEYS and ARGV.\n[b]reak              Show all breakpoints.\n[b]reak <line>       Add a breakpoint to the specified line.\n[b]reak -<line>      Remove breakpoint from the specified line.\n[b]reak 0            Remove all breakpoints.\n[t]race              Show a backtrace.\n[e]val <code>        Execute some Lua code (in a different callframe).\n[r]edis <cmd>        Execute a Redis command.\n[m]axlen [len]       Trim logged Redis replies and Lua var dumps to len.\n                     Specifying zero as <len> means unlimited.\n[a]bort              Stop the execution of the script. In sync\n                     mode dataset changes will be retained.\n\nDebugger functions you can call from Lua scripts:\nredis.debug()        Produce logs in the debugger console.\nredis.breakpoint()   Stop execution as if there was a breakpoint in the\n                     next line of code.",
      "section_id": "quick-start"
    },
    {
      "id": "quick-start-ex2",
      "language": "plaintext",
      "code": "* Stopped at 1, stop reason = step over\n-> 1   redis.call('ping')\nlua debugger> step\n<redis> ping\n<reply> \"+PONG\"\n* Stopped at 2, stop reason = step over",
      "section_id": "quick-start"
    },
    {
      "id": "dynamic-breakpoints-ex0",
      "language": "plaintext",
      "code": "if counter > 10 then redis.breakpoint() end",
      "section_id": "dynamic-breakpoints"
    },
    {
      "id": "synchronous-mode-ex0",
      "language": "plaintext",
      "code": "./redis-cli --ldb-sync-mode --eval /tmp/script.lua",
      "section_id": "synchronous-mode"
    },
    {
      "id": "logging-from-scripts-ex0",
      "language": "plaintext",
      "code": "lua debugger> list\n-> 1   local a = {1,2,3}\n   2   local b = false\n   3   redis.debug(a,b)\nlua debugger> continue\n<debug> line 3: {1; 2; 3}, false",
      "section_id": "logging-from-scripts"
    },
    {
      "id": "inspecting-the-program-state-with-print-and-eval-ex0",
      "language": "plaintext",
      "code": "lua debugger> e redis.sha1hex('foo')\n<retval> \"0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33\"",
      "section_id": "inspecting-the-program-state-with-print-and-eval"
    },
    {
      "id": "debugging-clients-ex0",
      "language": "Lua",
      "code": "local redis = require 'redis'\n\n-- add LDB's Continue command\nredis.commands['ldbcontinue'] = redis.command('C')\n\n-- script to be debugged\nlocal script = [[\n  local x, y = tonumber(ARGV[1]), tonumber(ARGV[2])\n  local result = x * y\n  return result\n]]\n\nlocal client = redis.connect('127.0.0.1', 6379)\nclient:script(\"DEBUG\", \"YES\")\nprint(unpack(client:eval(script, 0, 6, 9)))\nclient:ldbcontinue()",
      "section_id": "debugging-clients"
    }
  ]
}
