diff --git a/redis.conf b/redis.conf index d37760176becfb1b97699bb3cf2a432331200c18..870849a798b953cceac32018947f9b302aee8e92 100644 --- a/redis.conf +++ b/redis.conf @@ -1194,6 +1194,22 @@ lua-time-limit 5000 # # cluster-replica-no-failover no +# This option, when set to yes, allows nodes to serve read traffic while the +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + # In order to setup your cluster make sure to read the documentation # available at http://redis.io web site. diff --git a/src/cluster.c b/src/cluster.c index 9e6ddb2c462c5a21634fe904cd1a3925dda533bb..4024035e9789796ffb47a50f336b4481783f74c1 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -5595,8 +5595,12 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in * without redirections or errors in all the cases. */ if (n == NULL) return myself; - /* Cluster is globally down but we got keys? We can't serve the request. */ - if (server.cluster->state != CLUSTER_OK) { + /* Cluster is globally down but we got keys? We only serve the request + * if it is a read command and when allow_reads_when_down is enabled. */ + if ((server.cluster->state != CLUSTER_OK) && + !(server.cluster_allow_reads_when_down && ((cmd->flags & CMD_READONLY) + || (cmd->proc == evalCommand) || (cmd->proc == evalShaCommand)))) + { if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE; return NULL; } @@ -5701,7 +5705,10 @@ int clusterRedirectBlockedClientIfNeeded(client *c) { dictEntry *de; dictIterator *di; - /* If the cluster is down, unblock the client with the right error. */ + /* If the cluster is down, unblock the client with the right error. + * If the cluster is configured to allow reads on cluster down, we + * still want to emit this error since a write will be required + * to unblock them which may never come. */ if (server.cluster->state == CLUSTER_FAIL) { clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE); return 1; diff --git a/src/config.c b/src/config.c index 5bfb0de5c659ae0f071db589e750645469bd2031..5ff333fec5ce1147cf1eaca6b163201b1502fa06 100644 --- a/src/config.c +++ b/src/config.c @@ -2166,6 +2166,8 @@ standardConfig configs[] = { createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL), createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL), createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly), + createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL}, + /* String Configs */ createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL), diff --git a/src/server.h b/src/server.h index f2c93241c1e58aeeb77b03a969c77562eb5fc1be..b5e51002b22c761ae878965fc40ddd133067e5a8 100644 --- a/src/server.h +++ b/src/server.h @@ -1334,6 +1334,8 @@ struct redisServer { to set in order to suppress certain native Redis Cluster features. Check the REDISMODULE_CLUSTER_FLAG_*. */ + int cluster_allow_reads_when_down; /* Are reads allowed when the cluster + is down? */ /* Scripting */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */ client *lua_client; /* The "fake client" to query Redis from Lua */