lua-resty-redis-cluster-fast

The use must have the root permission.

Version compatibility

This page preserves the installation and usage instructions for the legacy implementation and adds instructions for the new 1.0.2 implementation of lua-resty-redis-cluster-fast-1.31.8. “New implementation” refers to the standalone dynamic module. Legacy packages for other Nginx series use the legacy instructions even if their package version number is the same.

ImplementationSupported Nginx versionsUsage instructions
New 1.0.2 implementationNginx 1.31.8 only, bundled with OpenResty 1.31.8.xSee “New implementation usage” below.
Legacy implementationMatching legacy packages for OpenResty 1.21.4.x, 1.25.3.x, 1.27.1.x, or 1.31.8.xSee “Legacy implementation usage” below.

The new implementation does not support older Nginx versions. Continue using the matching legacy library with older OpenResty versions. Upgrading to the new implementation requires upgrading OpenResty and updating the Nginx configuration.

Performance relative to the open-source client

In a typical concurrent-read Redis Cluster application scenario, lua-resty-redis-cluster-fast achieved 7.14 times the throughput of open-source lua-resty-redis-cluster (latest on 2026-11-05), a 613.8% increase.

Metriclua-resty-redis-cluster-fastlua-resty-redis-cluster (latest on 2026-11-05)
Median GET throughput192,848 commands/s27,019 commands/s
Median GET p99 latency0.972 ms6.821 ms

The comparison uses shared mode at equal Lua concurrency with different physical connection counts. Actual gains depend on workload, connection settings, and deployment.

Prerequisites

  • The use must have the root permission.

  • The machine needs to be able to access the following domains to download packages and GPG keys:

    You can check the accessibility of the domains by running the following commands:

    curl https://openresty.org/package/pubkey.gpg
    curl https://pkg2.openresty.com/xray-priv-libs/
    

Configure the software repository

Replace CLIENT_TOKEN with a valid token from your subscription email. Download the repository configuration script, then follow the section for the implementation you use. These commands configure repositories; they do not install library packages.

curl -o get-xray-priv-lib-repo.sh https://pkg2.openresty.com/scripts/get-xray-priv-lib-repo.sh

Both implementations also require the OpenResty open-source repository to install OpenResty and its dependencies. If it is not configured, follow the Linux packages instructions.

New implementation repository for OpenResty 1.31.8.x

The new implementation continues to use the existing coro-hiredis-nginx-module repository, which distributes lua-resty-redis-cluster-fast-1.31.8. Configuring this repository does not install the coro-hiredis-nginx-module package.

sudo bash get-xray-priv-lib-repo.sh -l coro-hiredis-nginx-module -t CLIENT_TOKEN

The new implementation does not require installing the coro-nginx-module or coro-hiredis-nginx-module packages or loading their Nginx dynamic modules.

Legacy implementation repositories

For matching legacy packages, configure both private repositories:

sudo bash get-xray-priv-lib-repo.sh -l coro-nginx-module -t CLIENT_TOKEN

sudo bash get-xray-priv-lib-repo.sh -l coro-hiredis-nginx-module -t CLIENT_TOKEN

Installation

For OpenResty-1.21.4.x

For systems using yum, run the following command to install the private library:

sudo yum install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.21.4

For systems using dnf, run the following command to install the private library:

sudo dnf install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.21.4

For systems using apt, run the following command to install the private library:

sudo apt-get install -y lua-resty-redis-cluster-fast-1.21.4

For OpenResty 1.25.3.x

For systems using yum, run the following command to install the private library:

sudo yum install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.25.3

For systems using dnf, run the following command to install the private library:

sudo dnf install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.25.3

For systems using apt, run the following command to install the private library:

sudo apt-get install -y lua-resty-redis-cluster-fast-1.25.3

For OpenResty 1.27.1.x

For systems using the yum package manager, run the following command to install the private library:

sudo yum install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.27.1

For systems using the dnf package manager, run the following command to install the private library:

sudo dnf install --disablerepo="*" --enablerepo="coro-nginx-module,coro-hiredis-nginx-module" -y lua-resty-redis-cluster-fast-1.27.1

For systems using the apt package manager, run the following command to install the private library:

sudo apt-get install -y lua-resty-redis-cluster-fast-1.27.1

For OpenResty 1.31.8.x

The new package is named lua-resty-redis-cluster-fast-1.31.8 and is provided in RPM and DEB formats with the same usage and configuration. It requires OpenResty 1.31.8.1 or a newer version in the same series. Configure the OpenResty open-source repository first so the package manager can resolve the required dependencies. The new implementation does not depend on the coro dynamic module packages used by the legacy implementation.

The following commands install the version available in the repository. After upgrading to the new 1.0.2 implementation, use “New implementation usage” below. Earlier packages using the legacy implementation still use “Legacy implementation usage”.

For systems using the yum package manager, run the following command to install the private library:

sudo yum install -y lua-resty-redis-cluster-fast-1.31.8

For systems using the dnf package manager, run the following command to install the private library:

sudo dnf install -y lua-resty-redis-cluster-fast-1.31.8

For systems using the apt package manager, run the following command to install the private library:

sudo apt-get install -y lua-resty-redis-cluster-fast-1.31.8

Installing OpenResty

For the new implementation, select an OpenResty 1.31.8.x package containing Nginx 1.31.8, with a minimum version of 1.31.8.1. For legacy libraries, select the matching OpenResty series. Before installing or upgrading, query the repository for the full version and install the matching package to avoid dynamic module incompatibility.

Use the command for your package manager to query available versions:

Package managerQuery commandInstall a specific version
yumyum list --showduplicates openrestysudo yum install openresty-VERSION
dnfdnf list --showduplicates openrestysudo dnf install openresty-VERSION
aptapt-cache madison openrestysudo apt-get install openresty=VERSION

Replace VERSION with the full version from the query results that matches the selected private library.

Usage

Select the configuration below for the implementation in your installed package.

New implementation usage

This section applies to the new 1.0.2 implementation and Nginx 1.31.8. Configuration and examples for the legacy implementation are retained in “Legacy implementation usage”.

New Nginx configuration

Load the dynamic module provided by the package in the main context of nginx.conf:

load_module /usr/local/openresty/nginx/modules/ngx_redis_cluster_module.so;

This module replaces the two coro modules required by the legacy implementation. This library no longer needs coro_preload or coro_stack_size. Retain configuration needed by other private libraries that still use those modules or directives.

The package installs Lua bytecode modules under /usr/local/openresty/site/lualib. Standard OpenResty packages load them through the default search path. A custom lua_package_path must retain the defaults (;;) or include /usr/local/openresty/site/lualib/?.ljbc. The Nginx worker account must be able to read these files and any required TLS certificate and private key files.

New Lua example

The only supported entry point is require "resty.redis.cluster.fast". When upgrading, replace any previous require "resty.redis.cluster" import with this entry point. The following is a complete nginx.conf example:

load_module /usr/local/openresty/nginx/modules/ngx_redis_cluster_module.so;

worker_processes auto;
error_log logs/error.log;
events {
    worker_connections 1024;
}

http {
    server {
        listen 8080;

        location /test {
            content_by_lua_block {
                local redis = require "resty.redis.cluster.fast"
                local red = redis:new()
                red:set_timeouts(1000, 1000, 1000)

                local ok, err = red:connect("127.0.0.1", 6379, {
                    cluster = "cluster",
                    other_nodes = {
                        { "127.0.0.1", 6380 },
                        { "127.0.0.1", 6381 },
                        { "127.0.0.1", 6382 },
                    },
                    pool_size = 8,
                    connection_count = 3,
                    no_slaves = true,
                })
                if not ok then
                    ngx.say("failed to connect: ", err)
                    return
                end

                ok, err = red:set("dog", "an animal")
                if err ~= nil then
                    red:close()
                    ngx.say("failed to set dog: ", err)
                    return
                end

                local res, err = red:get("dog")
                if err ~= nil then
                    red:close()
                    ngx.say("failed to get dog: ", err)
                    return
                end

                ngx.say("dog: ", res == ngx.null and "not found" or res)

                ok, err = red:set_keepalive()
                if not ok then
                    red:close()
                    ngx.say("failed to set keepalive: ", err)
                end
            }
        }
    }
}

New connection options and behavior

connect(host, port, options_table) still requires a string cluster name. other_nodes remains an array of {host, port} seed entries. The new implementation supports TCP and TLS, but not Unix domain sockets. Configure an Nginx resolver capable of resolving any hostnames used by seeds or advertised by cluster nodes.

OptionNew behavior
pool_sizeMaximum retained idle connections per node and pool identity, default 8; does not limit concurrent Lua clients.
connection_countShared ordinary-command connections per worker, node, and pool identity; default 3. Integer from 1 through 2147483647; connections open on demand.
max_pendingMaximum outstanding operations per physical connection; default 0 (unlimited). Integer from 0 through 2147483647.
backlogAccepted for compatibility and ignored; no waiting queue is created.
max_idle_timeoutIdle expiry in milliseconds, default 300000; 0 disables expiry. Set this in connect().
no_slavesDefault false, allowing read-only commands to use replicas. Set to true for reads that must observe preceding writes.
passwordPassword for the default user, or Redis 6+ ACL authentication when combined with username.
usernameACL username; requires password. Credentials apply to seeds, discovered nodes, and subscription connections.
use_resp3Set to true for RESP3; defaults to RESP2.
sslSet to true to enable TLS. TLS-specific options also enable TLS; combining them with ssl = false is an error.
ssl_verifyVerify the server certificate chain; default true.
ssl_ca_certCA certificate file; otherwise use OpenSSL’s default trust store.
ssl_cert, ssl_keyClient certificate and private key files for mutual TLS.
ssl_server_nameTLS SNI name; aliases are server_name and ssl_sni.
ssl_verify_nameExpected certificate DNS name or IP; requires ssl_verify. Set separately from SNI.
pubsub_max_messagesMaximum queued replies for a subscription or MONITOR connection; positive integer, default 1024.
pubsub_max_bytesMaximum bytes for those queued replies; positive integer, default 8388608.

TLS and ACL example (replace paths, hostnames, and the password variable with your configuration):

local ok, err = red:connect("redis.example.com", 8000, {
    cluster = "orders-tls",
    username = "orders-app",
    password = password,
    ssl = true,
    ssl_ca_cert = "/etc/redis/ca.crt",
    ssl_server_name = "redis.example.com",
    ssl_verify_name = "redis.example.com",
    no_slaves = true,
})

For compatibility with earlier interfaces, the new implementation also accepts serv_list = {{ip = host, port = port}, ...} for additional seeds. When no_slaves is omitted, no_slave = true or no_slave = 1 can be used as an alias.

Authentication and return values

Authentication and TLS settings apply to seeds, discovered masters and replicas, redirect targets, and subscription connections. Allow PING and CLUSTER SLOTS for connection setup and topology discovery, HELLO for RESP3, and READONLY for replica reads. Incorrect credentials configured in connect() fail the connection.

Alternatively, connect without credentials and call red:auth(password) or red:auth(username, password). Success returns "OK", isolates the client’s session, and applies the credentials to future node connections. Redis authentication errors return nil, err and preserve previous credentials; connection or initialization failures also return nil, err. Protected commands return Redis’s NOAUTH errors until authentication succeeds. Passwords can be empty strings or contain binary data; users sharing a password still have separate connections and permissions.

Ordinary commands return status replies as strings, integers as numbers, and Redis nil as ngx.null. Redis errors and connection or transport failures all return nil, err. Redis errors inside arrays, pipelines, or EXEC replies remain {false, err} entries. A RESP3 boolean false without an error string is a valid result. When upgrading from an implementation returning false, err, replace error checks such as res == false with err ~= nil, and handle ngx.null separately.

Connection lifecycle and timeouts

Ordinary commands can share physical connections within one Nginx worker. connection_count defaults to 3; successive commands rotate through connections in round-robin order, including commands from the same Lua client. Each pipeline batch selects one connection per destination node and advances its rotation once. Connections open on demand, and failed or expired shared connections are replaced on their next turn. Set connection_count = 1 for single-connection multiplexing. A pool_size smaller than connection_count can discard idle connections before their next turn.

Pools distinguish nodes, cluster names, credentials, protocol, TLS settings, connection_count, max_pending, and subscription buffer limits. Timeouts, seeds, and routing policies do not create separate physical connection pools. Total connections also depend on worker count, destination nodes, and exclusive sessions. Do not share one Lua client object between concurrent operations.

When max_pending is reached, the call waits within its read timeout and returns nil, "pending queue full" on expiry. A zero read timeout permits an unbounded wait. This limit is separate from idle retention and concurrent Lua client counts.

set_keepalive() releases the client’s references to connections for all visited nodes, returns 1, and closes the Lua object. Its two compatibility arguments are ignored. close() also releases references and returns 1 on success, closing a physical connection only when no other client uses it. After release, connection operations, including get_reused_times(), return nil, "closed". A successful connect() reopens the object; a failed reconnect leaves it closed. A client in Pub/Sub, MONITOR, WATCH, or a transaction cannot be returned with set_keepalive().

set_timeouts(connect_ms, send_ms, read_ms) independently controls TCP/TLS connection setup, command sending, and reply waiting; all default to 60000 milliseconds. set_timeout(ms) sets all three thresholds, and 0 disables the corresponding timeout. These are independent inactivity timeouts: connecting or sending does not consume the next phase’s budget, and relevant I/O progress renews its deadline. On a shared connection, only progress through a request’s own outgoing bytes renews its send timeout; an earlier request’s progress does not extend its wait. Topology discovery also uses send/read timeouts within the overall seed connection budget.

Timeouts must be integer milliseconds from 0 through 2147483647. Invalid numeric values return nil, "invalid timeout", or identify the invalid phase for set_timeouts(), such as "invalid read_timeout", without changing existing settings. Successful timeout setters return no values; do not require a truthy return to detect success.

Blocking commands, transactions, WATCH, subscriptions, and connection-state commands use isolated connections. Settings such as CLIENT SETNAME and HELLO persist for subsequent commands and pipelines on the same node. Releasing that client discards the changed connection so later borrowers receive a clean session.

WAIT and WAITAOF use the preceding command’s physical connection because acknowledgements refer to writes sent on that connection. They do not reconnect or switch to an idle connection to obtain acknowledgements, and do not cover writes on other round-robin connections or across the cluster. On a shared connection, they can also wait for preceding writes from other clients; a pending WAIT can delay other commands sharing that connection.

Pipelines and transactions

The new implementation supports init_pipeline(), commit_pipeline(), and cancel_pipeline(), preserving reply order without guaranteeing execution order across nodes or atomicity of the entire pipeline. MGET, MSET, DEL, EXISTS, UNLINK, and TOUCH can split cross-slot requests, which can partly succeed. Same-slot requests remain single Redis commands, preserving their atomicity and full argument validation before writes. Pipelines containing writes use masters.

AUTH, HELLO (including HELLO without AUTH), RESET, WAIT, and WAITAOF form ordered pipeline boundaries: preceding requests finish before these commands execute, and following requests use the resulting session state. Successful HELLO protocol changes, including those executed by EXEC, also apply to future node connections. Failed HELLO commands, canceled pipelines, and aborted or discarded transactions preserve the previous protocol setting.

Transactions and WATCH use exclusive connections. Transaction keys must belong to one hash slot; use matching hash tags, such as order:{42}:state and order:{42}:version. Transactions cannot start inside ordinary pipelines, and subscription commands cannot be queued in them.

multi() sends MULTI immediately; subsequent commands return Redis queue acknowledgements or immediate command errors. The first keyed command selects the execution node. If necessary, the preceding unexecuted keyless queue moves to that node, preserving queue-time errors. A WATCH connection stays on its selected node; UNWATCH, EXEC, and DISCARD allow connection reuse after completion. AUTH inside a transaction updates credentials only when EXEC executes it successfully; DISCARD or an aborted transaction preserves previous credentials.

The initial WATCH follows MOVED and ASK on an exclusive master connection; later WATCH calls retain that connection after success. Canceling an unconfirmed WATCH prevents that connection from being reused. Uppercase or mixed-case methods, add_commands(), and send_req() follow the same transaction and subscription restrictions.

Subscriptions, MONITOR, and RESET

Subscriptions use a dedicated connection, and read timeouts preserve subscriptions. Redis confirms every unsubscribe argument, including unknown and repeated names. Confirmations retain their received order with messages and remain readable through read_reply() after the last known subscription disappears. Read timeouts preserve confirmations still in flight.

Ordinary commands and ordinary command pipelines issued while subscribed retain the subscription connection and Redis’s protocol-specific command restrictions. Commands receive their own replies; subscription messages remain available through read_reply(). Canceling or timing out an ordinary command discards its late reply without ending the subscription or letting that reply satisfy a later command. A MONITOR stream rejects ordinary pipelines while retaining its connection.

Exceeding a queued-reply limit closes the connection and clears the queue; read_reply() keeps returning nil, "pub/sub buffer limit exceeded" until reconnect. Applications must reconnect and subscribe again. The byte count includes reply containers, strings, and queue entries, excluding allocator overhead and the reply currently being parsed. Consuming replies releases their queue budget.

The new implementation supports Redis 7 sharded Pub/Sub through ssubscribe, sunsubscribe, and spublish; ssubscribe and spublish route by the channel’s hash slot. Shard channels are independent of global channels or patterns with the same name. An active subscriber stays on its current server; use separate Lua clients for subscriptions on different nodes. Redis subscription errors such as MOVED and CROSSSLOT preserve the existing stream; the initial subscription can follow redirects.

monitor() starts a command stream on an isolated connection; read_reply() returns observed commands as strings, and read timeouts preserve the stream. Use reset() or close() to stop monitoring. A monitoring connection cannot execute other commands or return to the idle pool, and MONITOR cannot be queued in pipelines or transactions.

Successful RESET ends subscriptions, MONITOR, and WATCH, clears stored credentials, and restores RESP2. Authenticate again with auth() on protected servers; use hello(3, "AUTH", username, password) to authenticate and select RESP3 together. Failed RESET or HELLO preserves the existing stream. Redis’s RESP2 subscription restrictions still apply; RESP3 permits AUTH while subscribed. Authentication and protocol controls receive their own replies, while pending subscription messages remain available through read_reply().

Failure recovery and retries

After connection failures, ordinary operations attempt to refresh routes through surviving seeds or known peers. Refreshes are shared within a worker, try at most three peers, and rotate candidates between attempts. The refresh budget derives from the configured connect timeout, capped at 3000 milliseconds; a connect timeout of 0 still uses 3000 milliseconds. Candidate connect/send/read timeouts are allocated from the remaining budget, and continuing I/O can extend elapsed time. Attempts are separated by at least 100 milliseconds after completion.

A read or command known not to have been sent can retry once if discovery selects another node. Writes that may have been sent and partially executed write pipelines are not replayed automatically; refreshed routes apply to later operations. Handle the returned error and avoid unconditional retries that could duplicate writes. Transactions, subscriptions, and other connection-state operations are not migrated this way.

See the API manual for details.

Legacy implementation usage

The following configuration and API descriptions are retained for matching legacy packages. For the new 1.0.2 implementation, use “New implementation usage” above.

Nginx configuration

Before you can use lua-resty-redis-cluster-fast, you need to add the following configuration items to the configuration file nginx.conf to load the related dynamic modules.

    ...

    load_module "/usr/local/openresty-coro-nginx-module/lib/ngx_http_coro_module.so";
    load_module "/usr/local/openresty-coro-nginx-module/lib/ngx_http_coro_hiredis_module.so";

    http {
        coro_preload /usr/local/openresty-plus/hiredis/lib/libhiredis.so;

        coro_stack_size  32768;
        ...
    }

Lua interface

Interfaces connect and set_keepalive are changed. Other interfaces of this library are consistent with those of the open-source lua-resty-redis. Let’s see the following example:

    load_module "/usr/local/openresty-coro-nginx-module/lib/ngx_http_coro_module.so";
    load_module "/usr/local/openresty-coro-nginx-module/lib/ngx_http_coro_hiredis_module.so";

    http {
        coro_preload /usr/local/openresty-plus/hiredis/lib/libhiredis.so;
        coro_stack_size  32768;

        server {
            location /test {
                # need to specify the resolver to resolve the hostname
                resolver 8.8.8.8;

                content_by_lua_block {
                    local redis = require "resty.redis.cluster.fast"
                    local red = redis:new()

                    red:set_timeouts(1000, 1000, 1000) -- 1 sec

                    -- connect via ip address directly
                    local ok, err = red:connect("127.0.0.1", 6379, {
                        cluster = "cluster",
                        other_nodes = {
                            { "127.0.0.1", 6380 },
                            { "127.0.0.1", 6381 },
                            { "127.0.0.1", 6382 },
                        },
                        no_slaves = true
                    })

                    if not ok then
                        ngx.say("failed to connect: ", err)
                        return
                    end

                    ok, err = red:set("dog", "an animal")
                    if not ok then
                        ngx.say("failed to set dog: ", err)
                        return
                    end

                    ngx.say("set result: ", ok)

                    local res, err = red:get("dog")
                    if not res then
                        ngx.say("failed to get dog: ", err)
                        return
                    end

                    if res == ngx.null then
                        ngx.say("dog not found.")
                        return
                    end

                    ngx.say("dog: ", res)

                    local ok, err = red:set_keepalive()
                    if not ok then
                        ngx.say("failed to set keepalive: ", err)
                        return
                    end

                    -- or just close the connection right away:
                    -- local ok, err = red:close()
                    -- if not ok then
                    --     ngx.say("failed to close: ", err)
                    --     return
                    -- end
                }
            }
        }
    }

Interfaces connect and set_keepalive are described as follows:

connect

TODO: unix domain socket and SSL are currently not supported.

syntax: ok, err = red:connect(host, port, options_table)

Connects to the host and port that the ingress node of Redis is listening on. This method will always look for a matching free connection in the connections pool created by a previous call of this method before actually connecting to the remote backend.

The parameter options_table is a Lua table that includes the following parameters:

  • cluster

    This is a mandatory field that specifies a custom name for the Redis cluster connection pool being used.

  • pool_size

    Specifies the size of the connections pool. The default value is 128.

  • other_nodes

    Specifies other Redis nodes that can be connected to. When the connection to the ingress node fails, it will try to connect to other nodes.

    The parameter other_nodes is a Lua table that stores configuration information of multiple nodes, e.g:

    {
        { "127.0.0.1", 6379 },
        { "127.0.0.1", 6380 },
        { "127.0.0.1", 6381 },
    }
    
  • no_slaves

    Whether to disable reading data from Redis slave nodes. The default value is false.

set_keepalive

syntax: ok, err = red:set_keepalive()

To be compatible with the set_keepalive(max_idle_timeout, pool_size) method of the open-source lua-resty-redis library, we still allow passing two arguments, but we will ignore the incoming arguments.

Add the current Redis connection into the cosocket connections pool.

Call this method only where you want to call the close method. Calling this method will immediately turn the current Redis object into a closed state. Any subsequent operation on the current object other than connect() will return a closed error.