Module: Dynashard
- Defined in:
- lib/dynashard.rb,
lib/dynashard/model.rb,
lib/dynashard/validations.rb,
lib/dynashard/associations.rb,
lib/dynashard/connection_handler.rb
Overview
Dynashard - Dynamic sharding for ActiveRecord
This package provides database sharding functionality for ActiveRecord models.
Sharding is disabled by default and is enabled with Dynashard.enable
. This allows sharding behavior to be enabled globally or only for specific environments so for example production environments could be sharded while development environments could use a single database.
Models may be configured to determine the appropriate shard (database connection) to use based on context defined prior to performing queries.
class Widget < ActiveRecord::Base
shard :by => :user
end
class WidgetController < ApplicationController
around_filter :set_shard_context
def index
# Widgets will be loaded using the connection for the current user's shard
@widgets = Widget.find(:all)
end
private
def set_shard_context
Dynashard.with_context(:user => current_user.shard) do
yield
end
end
end
Associated models may be configured to use different shards determined by the association’s owner.
class Company < ActiveRecord::Base
shard :associated, :using => :shard
has_many :customers
def shard
# logic to find the company's shard
end
end
class Customer < ActiveRecord::Base
belongs_to :company
shard :by => :company
end
> c = Company.find(:first)
=> #<Company id:1>
Company is loaded using the default ActiveRecord connection.
> c.customers
=> [#<Dynashard::Shard0::Customer id: 1>, #<Dynashard::Shard0::Customer id: 2>]
Customers are loaded using the connection for the Company's shard. Associated models
are returned as shard-specific subclasses of the association class.
> c.customers.create(:name => 'Always right')
=> #<Dynashard::Shard0::Customer id: 3>
New associations are saved on the Company's shard.
TODO: add gotcha section, eg:
- uniqueness validations should be scoped by whatever is sharding
Defined Under Namespace
Modules: ActiveRecordExtensions, ConnectionHandlerExtensions, ProxyExtensions, ValidationExtensions
Class Method Summary collapse
-
.class_for(spec) ⇒ Object
Return a class with an established connection to a database shard.
-
.disable ⇒ Object
Disable sharding for all models configured to do so.
-
.enable ⇒ Object
Enable sharding for all models configured to do so.
-
.enabled? ⇒ Boolean
Return true if sharding is globally enabled.
-
.reflection_for(owner, reflection) ⇒ Object
Return a reflection with a sharded class.
-
.shard_context ⇒ Object
Return a threadsafe(?) current mapping of shard context to connection spec.
-
.sharded_model_class(shard_klass, base_klass) ⇒ Object
Return a model subclass configured to use a specific shard.
-
.with_context(new_context, &block) ⇒ Object
Execute a block within a given sharding context.
Class Method Details
.class_for(spec) ⇒ Object
Return a class with an established connection to a database shard
106 107 108 109 110 |
# File 'lib/dynashard.rb', line 106 def self.class_for(spec) @class_cache ||= {} @class_cache[spec] ||= new_shard_class(spec) @class_cache[spec] end |
.disable ⇒ Object
Disable sharding for all models configured to do so
78 79 80 |
# File 'lib/dynashard.rb', line 78 def self.disable @enabled = false end |
.enable ⇒ Object
Enable sharding for all models configured to do so
73 74 75 |
# File 'lib/dynashard.rb', line 73 def self.enable @enabled = true end |
.enabled? ⇒ Boolean
Return true if sharding is globally enabled
83 84 85 |
# File 'lib/dynashard.rb', line 83 def self.enabled? @enabled == true end |
.reflection_for(owner, reflection) ⇒ Object
Return a reflection with a sharded class
113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/dynashard.rb', line 113 def self.reflection_for(owner, reflection) reflection_copy = reflection.dup shard_klass = if owner.class.respond_to?(:dynashard_klass) owner.class.dynashard_klass else Dynashard.class_for(owner.send(owner.class.dynashard_association_using)) end klass = sharded_model_class(shard_klass, reflection.klass) reflection_copy.instance_variable_set('@klass', klass) reflection_copy.instance_variable_set('@class_name', klass.name) reflection_copy end |
.shard_context ⇒ Object
Return a threadsafe(?) current mapping of shard context to connection spec
101 102 103 |
# File 'lib/dynashard.rb', line 101 def self.shard_context Thread.current[:shard_context] ||= {} end |
.sharded_model_class(shard_klass, base_klass) ⇒ Object
Return a model subclass configured to use a specific shard
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/dynashard.rb', line 128 def self.sharded_model_class(shard_klass, base_klass) class_name = "#{shard_klass.name}::#{base_klass.name}" unless shard_klass.constants.include?(base_klass.name.to_sym) class_eval <<EOE class #{class_name} < #{base_klass.name} @@dynashard_klass = #{shard_klass.name} def self.dynashard_model?() true end def self.dynashard_klass() @@dynashard_klass end def self.connection dynashard_klass.connection end def self.dynashard_context superclass.dynashard_context end end EOE klass = class_name.constantize klass.connection_handler.dynashard_pool_alias(klass.name, shard_klass.name) end class_name.constantize end |
.with_context(new_context, &block) ⇒ Object
Execute a block within a given sharding context
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/dynashard.rb', line 88 def self.with_context(new_context, &block) orig_context = shard_context.dup shard_context.merge! new_context result = nil begin result = yield ensure shard_context.replace orig_context end result end |