diff --git a/README.md b/README.md index 54bd3b6..5ea9d3b 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,30 @@ Rails.application.configure do end ``` +### Named shard destinations + +By default, the node key used for sharding is the name of the database in `database.yml`. + +It is possible to add names for the shards in the cluster config. This will allow you to shuffle or remove shards without breaking consistent hashing. + +```ruby +Rails.application.configure do + config.solid_cache.connects_to = { + shards: { + cache_primary_shard1: { writing: :cache_primary_shard1 }, + cache_primary_shard2: { writing: :cache_primary_shard2 }, + cache_secondary_shard1: { writing: :cache_secondary_shard1 }, + cache_secondary_shard2: { writing: :cache_secondary_shard2 }, + } + } + + primary_cluster = { shards: { cache_primary_shard1: :node1, cache_primary_shard2: :node2 } } + secondary_cluster = { shards: { cache_primary_shard1: :node3, cache_primary_shard2: :node4 } } + config.cache_store = [ :solid_cache_store, clusters: [ primary_cluster, secondary_cluster ] ] +end +``` + + ### Enabling encryption Add this to an initializer: diff --git a/lib/solid_cache.rb b/lib/solid_cache.rb index 2ad9b02..884447e 100644 --- a/lib/solid_cache.rb +++ b/lib/solid_cache.rb @@ -17,14 +17,6 @@ def self.shard_config(shard) all_shards_config && all_shards_config[shard] end - def self.shard_destinations - @shard_databases ||= each_shard.map.to_h do - config = Record.connection_db_config - destination = [ config.try(:host), config.try(:port), config.try(:database) ].compact.join("-") - [ Record.current_shard, destination ] - end - end - def self.each_shard return to_enum(:each_shard) unless block_given? diff --git a/lib/solid_cache/cluster/connection_handling.rb b/lib/solid_cache/cluster/connection_handling.rb index d7198e9..2d6bd43 100644 --- a/lib/solid_cache/cluster/connection_handling.rb +++ b/lib/solid_cache/cluster/connection_handling.rb @@ -7,17 +7,27 @@ module ConnectionHandling def initialize(options = {}) super(options) - @shard_options = options.delete(:shards) @async_writes = options.delete(:async_writes) + @shard_options = options.delete(:shards) + + if [Hash, Array, NilClass].none? { |klass| @shard_options.is_a? klass } + raise ArgumentError, "`shards` is a `#{shards.class.name}`, it should be one of Array, Hash or nil" + end + + # Done lazily as the cache maybe created before ActionRecord initialization + @shards_initialized = false end def shards - # Load lazily as the cache maybe created before the SolidCache connections are initialized - @shards ||= @shard_options || SolidCache.all_shard_keys || [nil] + initialize_shards unless shards_initialized? + + @shards end - def destination_shards - @destination_shards ||= shards.compact.to_h { |shard| [ SolidCache.shard_destinations[shard], shard ] } + def nodes + initialize_shards unless shards_initialized? + + @nodes end def writing_all_shards @@ -59,6 +69,29 @@ def reading_shard(normalized_key:) end private + attr_reader :consistent_hash + + def shards_initialized? + @shards_initialized + end + + def initialize_shards + case @shard_options + when Array, NilClass + @shards = @shard_options || SolidCache.all_shard_keys || [] + @nodes = @shards.to_h { |shard| [ shard, shard ] } + when Hash + @shards = @shard_options.keys + @nodes = @shard_options.inverse + end + + if @shards.count > 1 + @consistent_hash = MaglevHash.new(@nodes.keys) + end + + @shards_initialized = true + end + def with_shard(shard) if shard Record.connected_to(shard: shard) { yield } @@ -82,15 +115,10 @@ def in_shards(list) end def shard_for_normalized_key(normalized_key) - return shards.first if shards.count == 1 - - destination = consistent_hash.node(normalized_key) - destination_shards[destination] - end + return shards.first if shards.count <= 1 - def consistent_hash - return nil if shards.count == 1 - @consistent_hash ||= MaglevHash.new(destination_shards.keys) + node = consistent_hash.node(normalized_key) + nodes[node] end def async_if_required diff --git a/test/unit/solid_cache_test.rb b/test/unit/solid_cache_test.rb index 826725a..e04b414 100644 --- a/test/unit/solid_cache_test.rb +++ b/test/unit/solid_cache_test.rb @@ -27,19 +27,6 @@ class SolidCacheTest < ActiveSupport::TestCase shards = SolidCache.each_shard.map { SolidCache::Record.current_shard } assert_equal [ :default, :default2, :primary_shard_one, :primary_shard_two, :secondary_shard_one, :secondary_shard_two ], shards end - - test "shard_databases" do - configs = SolidCache::Record.configurations - shard_names = [:default, :default2, :primary_shard_one, :primary_shard_two, :secondary_shard_one, :secondary_shard_two] - expected = shard_names.to_h do |shard_name| - database_name = SolidCache.connects_to.dig(:shards, shard_name, :writing).to_s - config = configs.configs_for(env_name: Rails.env, name: database_name) - destination = [ config.try(:host), config.try(:port), config.try(:database) ].compact.join("-") - [ shard_name, destination ] - end - - assert_equal expected, SolidCache.shard_destinations - end end class SolidCacheFailsafeTest < ActiveSupport::TestCase