lua-resty-redis-cluster-fast
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.
| Implementation | Supported Nginx versions | Usage instructions |
|---|---|---|
New 1.0.2 implementation | Nginx 1.31.8 only, bundled with OpenResty 1.31.8.x | See “New implementation usage” below. |
| Legacy implementation | Matching legacy packages for OpenResty 1.21.4.x, 1.25.3.x, 1.27.1.x, or 1.31.8.x | See “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.
| Metric | lua-resty-redis-cluster-fast | lua-resty-redis-cluster (latest on 2026-11-05) |
|---|---|---|
| Median GET throughput | 192,848 commands/s | 27,019 commands/s |
| Median GET p99 latency | 0.972 ms | 6.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 manager | Query command | Install a specific version |
|---|---|---|
yum | yum list --showduplicates openresty | sudo yum install openresty-VERSION |
dnf | dnf list --showduplicates openresty | sudo dnf install openresty-VERSION |
apt | apt-cache madison openresty | sudo 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.
| Option | New behavior |
|---|---|
pool_size | Maximum retained idle connections per node and pool identity, default 8; does not limit concurrent Lua clients. |
connection_count | Shared ordinary-command connections per worker, node, and pool identity; default 3. Integer from 1 through 2147483647; connections open on demand. |
max_pending | Maximum outstanding operations per physical connection; default 0 (unlimited). Integer from 0 through 2147483647. |
backlog | Accepted for compatibility and ignored; no waiting queue is created. |
max_idle_timeout | Idle expiry in milliseconds, default 300000; 0 disables expiry. Set this in connect(). |
no_slaves | Default false, allowing read-only commands to use replicas. Set to true for reads that must observe preceding writes. |
password | Password for the default user, or Redis 6+ ACL authentication when combined with username. |
username | ACL username; requires password. Credentials apply to seeds, discovered nodes, and subscription connections. |
use_resp3 | Set to true for RESP3; defaults to RESP2. |
ssl | Set to true to enable TLS. TLS-specific options also enable TLS; combining them with ssl = false is an error. |
ssl_verify | Verify the server certificate chain; default true. |
ssl_ca_cert | CA certificate file; otherwise use OpenSSL’s default trust store. |
ssl_cert, ssl_key | Client certificate and private key files for mutual TLS. |
ssl_server_name | TLS SNI name; aliases are server_name and ssl_sni. |
ssl_verify_name | Expected certificate DNS name or IP; requires ssl_verify. Set separately from SNI. |
pubsub_max_messages | Maximum queued replies for a subscription or MONITOR connection; positive integer, default 1024. |
pubsub_max_bytes | Maximum 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:
clusterThis is a mandatory field that specifies a custom name for the Redis cluster connection pool being used.
pool_sizeSpecifies the size of the connections pool. The default value is 128.
other_nodesSpecifies 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_nodesis 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_slavesWhether 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-sourcelua-resty-redislibrary, 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.