Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The
ReplicationClient
sends read-only commands to replicas by default and all other commands to the primary/master. You can override the decision by callingon_primary
oron_replica
. This is useful for when this shard doesn't know about the command you're using. For example, it's not yet implemented (issues/PRs welcome!) or it's from a custom Redis module.If you want some commands to automatically be routed to replicas, you can add them to the set of
Redis::READ_ONLY_COMMANDS
. Add this to your app's Redis configuration file:Then any time you use those commands (even passing arbitrary commands to
ReplicationClient#run
), they'll automatically be routed to replicas. This constant was extracted fromCluster
so both classes could benefit from it and expanded to contain all of the read-only commands thatredis-stack-server
knows about (which should remove the need for lines likerequire "redis/cluster/json"
in your app)How is
ReplicationClient
different fromCluster
?Redis replication is a distinct concept from Redis's "cluster mode" so
Redis::ReplicationClient
has to be a separate concept fromRedis::Cluster
.Cluster mode shards your data across multiple primaries and has replicas follow those primaries. There are restrictions on which primaries can operate on which keys since you can only operate on a key that exists on the node you sent the command to. That also means that atomic multi-key operations (like
LPOPRPUSH
orBLPOP
against multiple lists) requires that all specified keys reside on the same shard.Replication, conversely, involves no sharding. It just replicates all of your data in the primary to the replicas. This means there are no limitations imposed by Redis on what operations you can perform on which keys on the primary.
The way cluster mode and replication are exposed to clients is also very different —
INFO CLUSTER
vsINFO REPLICATION
. We could probably paper over some of that, butRedis::Cluster
defines some methods specifically because the keys aren't all colocated on the same node. For example,Cluster#pipeline
requires akey : String
argument thatConnection
does not because it has to know which node you're going to run the pipeline on. This method doesn't make sense for plain-old replication. If you want to run a pipeline withReplicationClient
, you specifyredis.on_primary &.pipeline { |pipe| ... }
.Why
ReplicationClient
?Yeah, I don't love the name. I really like how succinct the name
Cluster
is, butReplica
doesn't make sense here because it also talks to the primary.Depending on how we move forward with this, though,
Cluster
could end up usingReplicationClient
under the hood to handle the split between primaries and replicas. Currently it implements its own based on the data returned fromINFO CLUSTER
.Chained replication is not (yet?) supported
If
C
is aREPLICAOF
B
which is aREPLICAOF A
,ReplicationClient
will send write commands toA
and read-only commands toB
, but will ignoreC
completely.It might be a good idea not to support chained replication, depending on why it's setup that way. For example, if
C
is used for long-running commands (think analytical vs transactional queries), you may not want your app sending queries to it expecting them to be fast.Replication topology discovery
You can point this class at any Redis node in your setup (I keep having to stop myself from saying "cluster") and it will discover which is the primary and which are the replicas.
Additionally, changes in the replication topology (replicas added, removed, or primary failover) will automatically be updated in
ReplicationClient
. Currently, this is implemented by throwing away the previous connection pools, but this could be improved to make smaller changes.Unfortunately, in the current implementation, taking a replica offline won't be handled gracefully. You'll still get errors in trying to talk to replicas, but those should disappear during the next periodic topology scan (defaults to 10-second intervals).
No replication required
You can even use this if you aren't doing replication at all — it will just send all commands to the one node. If you add replicas later, it will automatically pick them up during the next topology scan and begin routing read-only commands to the replicas.
This is important if you replicate only to a single node, which you take offline to upgrade before upgrading the primary.
Real-world testing
I've been testing this on a 3-node Dragonfly DB in one of my Kubernetes clusters (they offer a Kubernetes operator that's easy to work with and the Redis operator is enterprise-only) and it's working really, really well. As noted above, I get some errors when removing replicas (chaos engineering ftw), but they go away after a few seconds.
Closes #8