Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and enhance Redis::Distributed #817

Closed
wants to merge 2 commits into from

Conversation

supercaracal
Copy link
Contributor

@supercaracal supercaracal commented Dec 23, 2018

Hi,

Redis::Distributed give us partitioning feature by client side consistent hashing.
There are some gems which depend on the above feature. e.g. redis-store, ActiveSupport, resque

I'd say that we might be felt pain or exhausting by simultaneous maintaining both Redis and Redis::Distributed as public interfaces. So I refactored the Redis::Distributed along with several additional Redis API supports (e.g. Streams) and several compatibility-breaking minor changes.

Before the changes

Redis::Distributed
  |
  +--- Redis
  |
  +--- HashRing
require 'redis/distributed'

Redis::Distributed.new(nodes, tag: tag, ring: ring, timeout: 10)

After the changes

Redis
  |
  +--- Client
  |
  +--- Cluster
  |      |
  |      +--- Client
  |
  +--- Distributed::Partitioner <- Added
         |
         +--- Client
         |
         +--- Distributed::HashRing <- Namespace changed
Redis.new(distributed: { nodes: nodes, tag: tag, ring: ring }, timeout: 10)

But we leave Redis::Distributed's old interface for backward compatibility.

About several commands that can operate on multiple keys

The current implementation doesn't allow multiple keys without tag. After refactoring, the multiple keys are allowed only if they effect single node.

Compatibility-breaking changes

  1. Redis::Distributed#[] has been removed.
  2. Redis::Distributed#[]= has been removed.
  3. Redis::HashRing#ring has been removed.
  4. Redis::HashRing#sorted_keys has been removed.
  5. Redis::HashRing#replicas has been removed.
  6. Redis::Distributed#on_each_node has been removed in protected scope.
  7. Redis::Distributed#node_index_for has been removed in protected scope.
  8. Redis::Distributed#key_tag has been removed in protected scope.
  9. Redis::Distributed#ensure_same_node has been removed in protected scope.
  10. Redis::Distributed#select has been become raising Redis::Distributed::CannotDistribute error.
  11. The instance type of Redis::Distributed#nodes's return value has been changed Array<Redis> into Array<Redis::Client>.
  12. The nested depth of Redis::Distributed#script(:exists)'s return value has been become 1 from 2.
  13. The raising error has been changed NotImplementedError into Redis::Distributed::CannotDistribute when Redis::Distributed#monitor is called.
  14. The raising error has been changed NoMethodError into Redis::CommandError when any Redis::Distributed#not_yet_implemented_commands are called.

ref: #219
ref: rails/rails#31134
ref: redis-store/redis-store#277
ref: #687
ref: redis-store/redis-store#282
ref: #653 (comment)
ref: #716

@supercaracal supercaracal force-pushed the refactor-distributed branch 4 times, most recently from 60cb738 to 827a999 Compare December 24, 2018 15:00
attr_reader :nodes

# Find the closest index in HashRing with value <= the given value
def self.binary_search(ary, value)
Copy link
Contributor Author

@supercaracal supercaracal Dec 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this is subtly different from Array#bsearch_index.

end

def db=(_db)
raise CannotDistribute, 'select'
Copy link
Contributor Author

@supercaracal supercaracal Dec 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error handling is on purpose because HashRing is broken by SELECT command. The checksum calculation depends on the client id. It includes the db number if the id option isn't specified.

@supercaracal supercaracal force-pushed the refactor-distributed branch 2 times, most recently from 0805783 to a42ab18 Compare December 25, 2018 14:17
end
end

def test_brpop_raises
assert_raises(Redis::Distributed::CannotDistribute) do
r.brpop(%w[foo bar])
target_version('3.2.0') do
Copy link
Contributor Author

@supercaracal supercaracal Dec 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About BRPOP command:

  • v3.0 The number of last key position is expected to -2 but was 1.
  • v5.0

@supercaracal
Copy link
Contributor Author

supercaracal commented Dec 25, 2018

I wonder if the client side sharding feature is still used by someone. I think it is alone not high available. So it seems that it should be combined replication with Sentinel or Keepalived and so on in production environment. But now, we're able to use Redis Cluster.

Data store or cache?

There might be demands as a cache store.

@supercaracal supercaracal force-pushed the refactor-distributed branch 2 times, most recently from cd2b24f to fc5cac9 Compare January 6, 2019 08:35
rd.set('foo', '1')
assert_equal '1', rd.get('foo')

hr = Redis::HashRing.new
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used by some gems. e.g. redis-store

end

def test_migrate
r.set("foo", "s1")

assert_raise Redis::Distributed::CannotDistribute do
r.migrate("foo", {})
r.migrate('foo', host: '127.0.0.1', port: PORT)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -6,6 +6,14 @@ class TestDistributedCommandsOnValueTypes < Test::Unit::TestCase
include Helper::Distributed
include Lint::ValueTypes

def test_move
assert_raise(Redis::Distributed::CannotDistribute) { super }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is raised from SELECT.

@@ -27,7 +27,7 @@ def test_info

def test_info_commandstats
target_version "2.5.7" do
r.nodes.each { |n| n.config(:resetstat) }
r.nodes.each { |n| n.call(%i[config resetstat]) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a compatibility-breaking change. The return value's instance type is changed Redis into Redis::Client.

assert_raise Redis::Distributed::CannotDistribute do
r.multi { @foo = 1 }
r.multi { :dummy }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@byroot
Copy link
Collaborator

byroot commented May 6, 2019

I realize I should have said that months ago, but I'm afraid +798 −1,238 is way too big of a change for me to efficiently review, as I'm not that familiar with the codebase myself yet, and even less so with Redis::Cluster.

Is there any way this could be split in more reasonable chunks ? e.g ~300 line max changes. That would make my life much much easier.

@supercaracal
Copy link
Contributor Author

supercaracal commented May 6, 2019

Sure. Thank you for your response. I will try to split this PR. 😄

@supercaracal supercaracal deleted the refactor-distributed branch May 2, 2024 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants