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 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

.disableObject

Disable sharding for all models configured to do so



78
79
80
# File 'lib/dynashard.rb', line 78

def self.disable
  @enabled = false
end

.enableObject

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

Returns:

  • (Boolean)


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_contextObject

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