Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexander Staubo
committed
May 18, 2011
0 parents
commit 9af101f
Showing
13 changed files
with
454 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/pkg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
source 'http://rubygems.org/' | ||
|
||
gem 'activesupport', '>= 2.2' | ||
gem 'activerecord', '>= 2.2' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2011 [name of plugin creator] | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
Multidb | ||
======= | ||
|
||
A simple, no-nonsense ActiveRecord extension which allows the application to switch | ||
between multiple database connections, such as in a master/slave environment. For example: | ||
|
||
Multidb.use(:slave) do | ||
@posts = Post.all | ||
end | ||
|
||
The extension was developed in order to support PostgreSQL 9.0's new hot standby | ||
support in a production environment. | ||
|
||
Randomized balancing of multiple connections within a group is supported. In the | ||
future, some kind of automatic balancing of read/write queries might be implemented. | ||
|
||
Testet with Rails 2.3.11. No guarantees about Rails 3. | ||
|
||
|
||
Comparison to other ActiveRecord extensions | ||
=========================================== | ||
|
||
Unlike other, more full-featured extensions such as Octopus and Seamless Database Pool, | ||
Multidb strives to be: | ||
|
||
* Implemented using a minimal amount of | ||
monkeypatching magic. The only part of ActiveRecord that is overriden is | ||
`ActiveRecord::Base#connection`. | ||
|
||
* Non-invasive. Very small amounts of configuration and changes to the client | ||
application are required. | ||
|
||
* Orthogonal. Unlike Octopus, for example, connections follow context: | ||
|
||
Multidb.use(:master) do | ||
@post = Post.find(1) | ||
Multidb.use(:slave) do | ||
@post.authors # This will use the slave | ||
end | ||
end | ||
|
||
* Low-overhead. Since `connection` is called on every single | ||
database operation, it needs to be fast. Which it is: Multidb's implementation of | ||
`connection` incurs only a single hash lookup in `Thread.current`. | ||
|
||
However, Multidb also has fewer features. At the moment it will _not_ automatically | ||
split reads and writes between database backends. | ||
|
||
|
||
Getting started | ||
=============== | ||
|
||
In Rails 2.x applications without a `Gemfile`, add this to `environment.rb`: | ||
|
||
config.gem 'ar-multidb' | ||
|
||
In Bundler-based on Rails apps, add this to your `Gemfile`: | ||
|
||
gem 'ar-multidb', :require => 'multidb' | ||
|
||
You may also install it as a plugin: | ||
|
||
script/plugin install git://github.com/alexstaubo/multidb.git | ||
|
||
All that is needed is to set up your `database.yml` file: | ||
|
||
production: | ||
adapter: postgresql | ||
database: myapp_production | ||
username: ohoh | ||
password: mymy | ||
host: db1 | ||
multidb: | ||
databases: | ||
slave: | ||
host: db-slave | ||
|
||
Each database entry may be a hash or an array. So this also works: | ||
|
||
production: | ||
adapter: postgresql | ||
database: myapp_production | ||
username: ohoh | ||
password: mymy | ||
host: db1 | ||
multidb: | ||
databases: | ||
slave: | ||
- host: db-slave1 | ||
- host: db-slave2 | ||
The database hashes follow the same format as the top-level adapter configuration. In | ||
other words, each database connection may override the adapter, database name, username | ||
and so on. | ||
|
||
To use the connection, modify your code by wrapping database access logic in blocks: | ||
|
||
Multidb.use(:slave) do | ||
@posts = Post.all | ||
end | ||
|
||
To wrap entire controller requests, for example: | ||
|
||
class PostsController < ApplicationController | ||
around_filter :run_using_slave | ||
|
||
def run_using_slave(&block) | ||
Multidb.use(:slave, &block) | ||
end | ||
end | ||
|
||
You can also set the current connection for the remainder of the thread's execution: | ||
|
||
Multidb.use(:slave) | ||
# Do work | ||
Multidb.use(:master) | ||
|
||
Note that the symbol `:default` will (unless you override it) refer to the default | ||
top-level ActiveRecord configuration. | ||
|
||
|
||
Legal | ||
===== | ||
|
||
Copyright (c) 2011 Alexander Staubo. Released under the MIT license. See the file LICENSE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# encoding: utf-8 | ||
|
||
require 'rubygems' | ||
require 'rake' | ||
require 'rake/rdoctask' | ||
|
||
begin | ||
require 'jeweler' | ||
Jeweler::Tasks.new do |gem| | ||
gem.name = 'ar-multidb' | ||
gem.summary = gem.description = %Q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.} | ||
gem.email = "alex@bengler.no" | ||
gem.homepage = "http://github.com/alexstaubo/multidb" | ||
gem.authors = ["Alexander Staubo"] | ||
gem.has_rdoc = true | ||
gem.require_paths = ["lib"] | ||
gem.files = FileList[%W( | ||
README.markdown | ||
VERSION | ||
LICENSE* | ||
lib/**/* | ||
)] | ||
gem.add_dependency 'activesupport', '>= 2.2' | ||
gem.add_dependency 'activerecord', '>= 2.2' | ||
end | ||
Jeweler::GemcutterTasks.new | ||
rescue LoadError | ||
$stderr << "Warning: Gem-building tasks are not included as Jeweler (or a dependency) not available. Install it with: `gem install jeweler`.\n" | ||
end | ||
|
||
Rake::RDocTask.new do |rdoc| | ||
version = File.exist?('VERSION') ? File.read('VERSION') : "" | ||
|
||
rdoc.rdoc_dir = 'rdoc' | ||
rdoc.title = "ruby-hdfs #{version}" | ||
rdoc.rdoc_files.include('README*') | ||
rdoc.rdoc_files.include('lib/**/*.rb') | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Generated by jeweler | ||
# DO NOT EDIT THIS FILE DIRECTLY | ||
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' | ||
# -*- encoding: utf-8 -*- | ||
|
||
Gem::Specification.new do |s| | ||
s.name = %q{ar-multidb} | ||
s.version = "0.1.0" | ||
|
||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= | ||
s.authors = ["Alexander Staubo"] | ||
s.date = %q{2011-05-18} | ||
s.description = %q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.} | ||
s.email = %q{alex@bengler.no} | ||
s.extra_rdoc_files = [ | ||
"LICENSE", | ||
"README.markdown" | ||
] | ||
s.files = [ | ||
"LICENSE", | ||
"README.markdown", | ||
"VERSION", | ||
"lib/multidb.rb", | ||
"lib/multidb/balancer.rb", | ||
"lib/multidb/configuration.rb", | ||
"lib/multidb/model_extensions.rb" | ||
] | ||
s.homepage = %q{http://github.com/alexstaubo/multidb} | ||
s.require_paths = ["lib"] | ||
s.rubygems_version = %q{1.5.0} | ||
s.summary = %q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.} | ||
|
||
if s.respond_to? :specification_version then | ||
s.specification_version = 3 | ||
|
||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then | ||
s.add_runtime_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_runtime_dependency(%q<activerecord>, [">= 2.2"]) | ||
s.add_runtime_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_runtime_dependency(%q<activerecord>, [">= 2.2"]) | ||
else | ||
s.add_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_dependency(%q<activerecord>, [">= 2.2"]) | ||
s.add_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_dependency(%q<activerecord>, [">= 2.2"]) | ||
end | ||
else | ||
s.add_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_dependency(%q<activerecord>, [">= 2.2"]) | ||
s.add_dependency(%q<activesupport>, [">= 2.2"]) | ||
s.add_dependency(%q<activerecord>, [">= 2.2"]) | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Init file for running as Rails plugin. | ||
require 'multidb' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
require 'multidb/configuration' | ||
require 'multidb/model_extensions' | ||
require 'multidb/balancer' | ||
|
||
module Multidb | ||
class << self | ||
|
||
def install! | ||
configure! | ||
if @configuration and @configuration.raw_configuration[:databases].any? | ||
ActiveRecord::Base.class_eval do | ||
include Multidb::ModelExtensions | ||
end | ||
@balancer = Balancer.new(@configuration) | ||
end | ||
end | ||
|
||
attr_reader :balancer | ||
|
||
delegate :use, :get, :to => :balancer | ||
|
||
end | ||
end | ||
|
||
Multidb.install! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
module Multidb | ||
|
||
class Candidate | ||
def initialize(config) | ||
adapter = config[:adapter] | ||
begin | ||
require "active_record/connection_adapters/#{adapter}_adapter" | ||
rescue LoadError | ||
raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$!})" | ||
end | ||
@connection_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new( | ||
ActiveRecord::Base::ConnectionSpecification.new(config, "#{adapter}_connection")) | ||
end | ||
|
||
def connection | ||
@connection_pool.connection | ||
end | ||
end | ||
|
||
class Balancer | ||
|
||
def initialize(configuration) | ||
@candidates = {}.with_indifferent_access | ||
@configuration = configuration | ||
@configuration.raw_configuration[:databases].each_pair do |name, config| | ||
configs = config.is_a?(Array) ? config : [config] | ||
configs.each do |config| | ||
candidate = Candidate.new(@configuration.default_adapter.merge(config)) | ||
@candidates[name] ||= [] | ||
@candidates[name].push(candidate) | ||
end | ||
end | ||
unless @candidates.include?(:default) | ||
@candidates[:default] = [Candidate.new(@configuration.default_adapter)] | ||
end | ||
end | ||
|
||
def get(name, &block) | ||
candidates = @candidates[name] || [] | ||
raise ArgumentError, "No such database connection '#{name}'" if candidates.blank? | ||
candidate = candidates.sample | ||
block_given? ? yield(candidate) : candidate | ||
end | ||
|
||
def use(name, &block) | ||
result = nil | ||
get(name) do |candidate| | ||
connection = candidate.connection | ||
if block_given? | ||
previous_connection, Thread.current[:multidb_connection] = | ||
Thread.current[:multidb_connection], connection | ||
begin | ||
result = yield | ||
ensure | ||
Thread.current[:multidb_connection] = previous_connection | ||
end | ||
result | ||
else | ||
result = Thread.current[:multidb_connection] = connection | ||
end | ||
end | ||
result | ||
end | ||
|
||
def current_connection | ||
Thread.current[:multidb_connection] ||= ActiveRecord::Base.connection_pool.connection | ||
end | ||
|
||
class << self | ||
def use(name, &block) | ||
Multidb.balancer.use(name, &block) | ||
end | ||
|
||
def current_connection | ||
Multidb.balancer.current_connection | ||
end | ||
end | ||
|
||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
module Multidb | ||
|
||
class << self | ||
|
||
def configure! | ||
activerecord_config = ActiveRecord::Base.connection_pool.connection.instance_variable_get(:@config).dup.with_indifferent_access | ||
default_adapter, configuration_hash = activerecord_config, activerecord_config.delete(:multidb) | ||
@configuration = Configuration.new(default_adapter, configuration_hash) | ||
end | ||
|
||
attr_reader :configuration | ||
|
||
end | ||
|
||
class Configuration | ||
def initialize(default_adapter, configuration_hash) | ||
@default_adapter = default_adapter | ||
@raw_configuration = configuration_hash | ||
end | ||
|
||
attr_reader :default_adapter | ||
attr_reader :raw_configuration | ||
end | ||
|
||
end |
Oops, something went wrong.