my_stuff-multidb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +13 -0
- data/README.rdoc +65 -0
- data/bin/my_stuff-multidb-unmangle +27 -0
- data/lib/my_stuff/multidb.rb +180 -0
- data/lib/my_stuff/multidb/base.rb +43 -0
- data/lib/my_stuff/multidb/sharded.rb +29 -0
- data/lib/my_stuff/multidb/unsharded.rb +29 -0
- metadata +61 -0
data/COPYING
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2011-present, Fred Emmott <copyright@fredemmott.co.uk>
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
This provides an API that's relatively convenient for:
|
4
|
+
* Reading from slaves
|
5
|
+
* Using multiple shards
|
6
|
+
|
7
|
+
= Usage
|
8
|
+
|
9
|
+
module MySpecProvider
|
10
|
+
def spec_for_new
|
11
|
+
{ :adapter => 'mysql', :host => ...}
|
12
|
+
end
|
13
|
+
|
14
|
+
def spec_for_master shard_id
|
15
|
+
...
|
16
|
+
end
|
17
|
+
|
18
|
+
def spec_for_slave shard_id
|
19
|
+
...
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module MyDB
|
24
|
+
class Widget < ActiveRecord::Base; end
|
25
|
+
include MyStuff::MultiDB::Sharded
|
26
|
+
extend MySpecProvider
|
27
|
+
end
|
28
|
+
|
29
|
+
MyDB.with_master_for_new do |db,spec|
|
30
|
+
db::Widget.create(...)
|
31
|
+
end
|
32
|
+
|
33
|
+
MyDB.with_slave_for(shard_id) do |db|
|
34
|
+
db::Widget.find(record_id);
|
35
|
+
...
|
36
|
+
end
|
37
|
+
|
38
|
+
Another option, if you only do primary key lookups, is to encode the shard
|
39
|
+
ID into the record id.
|
40
|
+
|
41
|
+
For more, see:
|
42
|
+
|
43
|
+
* ./examples/run
|
44
|
+
* comments at top of lib/mystuff/multidb.rb
|
45
|
+
|
46
|
+
= How It Works
|
47
|
+
|
48
|
+
For every spec, it defines a new sub-module of MyStuff::MultiDB, which
|
49
|
+
encodes the database details, and creates new subclasses of your
|
50
|
+
ActiveRecord::Base classes, within these modules. For example:
|
51
|
+
|
52
|
+
$ bin/my_stuff-multidb-unmangle MyStuff::MultiDB::MYSTUFF_MULTIDB_DB_216c6f63616c686f7374213333303621::MyDB::Widget
|
53
|
+
MyStuff::MultiDB::<localhost:3306/>::MyDB::Widget
|
54
|
+
$
|
55
|
+
|
56
|
+
There's also lots of deep voodoo making the ActiveRecord stack use the right
|
57
|
+
connection details :)
|
58
|
+
|
59
|
+
= Caveats
|
60
|
+
|
61
|
+
You can not do cross-shard operations simply, eg joins.
|
62
|
+
|
63
|
+
= Copying
|
64
|
+
|
65
|
+
See the COPYING file.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright 2011-present Fred Emmott. See COPYING file.
|
3
|
+
|
4
|
+
_FILE = File.readlink(__FILE__) rescue __FILE__
|
5
|
+
_DIR = File.expand_path(File.dirname(_FILE))
|
6
|
+
$LOAD_PATH.push(File.expand_path(_DIR + '/../lib'))
|
7
|
+
|
8
|
+
require 'my_stuff/multidb'
|
9
|
+
|
10
|
+
def filter line
|
11
|
+
line.gsub(/MYSTUFF_MULTIDB_DB_[a-z0-9]+/){ |mangled|
|
12
|
+
data = MyStuff::MultiDB.unmangle(mangled)
|
13
|
+
"<%s:%d/%s>" % [
|
14
|
+
data[:host],
|
15
|
+
data[:port],
|
16
|
+
data[:database],
|
17
|
+
]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
if ARGV.empty?
|
22
|
+
STDIN.each_line do |line|
|
23
|
+
puts filter(line.strip)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
puts filter(ARGV.first)
|
27
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# Copyright 2011-present Fred Emmott. See COPYING file.
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module MyStuff
|
6
|
+
# =Example
|
7
|
+
#
|
8
|
+
# module Foo
|
9
|
+
# class SomeTable < ActiveRecord::Base
|
10
|
+
# end
|
11
|
+
# include MyStuff::MultiDB::Unsharded
|
12
|
+
# end
|
13
|
+
# module Bar
|
14
|
+
# class SomeTable < ActiveRecord::Base
|
15
|
+
# end
|
16
|
+
# include MyStuff::MultiDB::Sharded
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Foo.with_master do |db|
|
20
|
+
# p db::SomeTable.where(:some_column = 'bar')
|
21
|
+
# end
|
22
|
+
# Foo.with_slave do |db|
|
23
|
+
# p db::SomeTable.where(:some_column = 'bar')
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Bar.with_master_for(id) do |db|
|
27
|
+
# p db::SomeTable.where(:some_column = 'bar')
|
28
|
+
# end
|
29
|
+
# Bar.with_master_for_new do |db|
|
30
|
+
# db::SomeTable.new do
|
31
|
+
# ...
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# Bar.with_slave_for(id) do |db|
|
35
|
+
# p db::SomeTable.where(:some_column = 'bar')
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# See MyStuff::MultiDB::Unsharded and MyStuff::MultiDB::Sharded
|
39
|
+
#
|
40
|
+
# = Details
|
41
|
+
#
|
42
|
+
# When you call <tt>with_*</tt>, it:
|
43
|
+
# * Looks for a class called MyStuff::MultiDB::MANGLED_DATABASE_NAME
|
44
|
+
# * If it doesn't exist, it creates a new ActiveRecord::Base subclass
|
45
|
+
# * It then looks for a module within that class with the same name as your
|
46
|
+
# module
|
47
|
+
# * If it's not there:
|
48
|
+
# * It creates the module
|
49
|
+
# * It creates a subclass of each of your ActiveRecord definitions within this
|
50
|
+
# module
|
51
|
+
# * It delegates connection handling to the database class
|
52
|
+
# * It then returns the database class
|
53
|
+
#
|
54
|
+
# So, you end up with an ActiveRecord class like:
|
55
|
+
# MyStuff::MultiDB::MANGLED_DATABASE_NAME::YourModule::YourClass
|
56
|
+
module MultiDB
|
57
|
+
def self.open_connections
|
58
|
+
Hash.new.tap do |result|
|
59
|
+
constants.each do |name|
|
60
|
+
if name.to_s.starts_with? 'MYSTUFF_MULTIDB_DB'
|
61
|
+
klass = const_get(name, false)
|
62
|
+
checked_out = klass.connection_pool.instance_eval { @checked_out.size }
|
63
|
+
result[name] = checked_out if checked_out > 0
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Takes a module name and converts it to connection details.
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# mangled:: <tt>MyStuff::MultiDB::MYSTUFF_MULTIDB_DB_747970686f6e2e66726564656d6d6f74742e636f2e756b2c333330362c747474::ServiceLocator::Tier</tt>
|
73
|
+
# unmanagled:: <tt>MyStuff::MultiDB::<mysql://typhon.fredemmott.co.uk:3306/ttt>::ServiceLocator::Tier</tt>
|
74
|
+
#
|
75
|
+
# The initscript cat-log and tail-log commands will do this unmanggling for you.
|
76
|
+
def self.unmangle name
|
77
|
+
db = name.sub /^:?MYSTUFF_MULTIDB_DB_/, ''
|
78
|
+
# Format: "MYSTUFF_MULTIDB_DB_" + hex("host,port,database")
|
79
|
+
junk, host, port, database = db.each_char.each_slice(2).reduce(String.new){ |m,*nibbles| m += "%c" % nibbles.join.hex }.split('!')
|
80
|
+
{
|
81
|
+
:host => host,
|
82
|
+
:port => port.to_i,
|
83
|
+
:database => database,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.included othermod # :nodoc:
|
88
|
+
class <<othermod
|
89
|
+
def for_spec spec
|
90
|
+
MyStuff::MultiDB.for_spec(spec, self)
|
91
|
+
end
|
92
|
+
def with_slave *args; raise NotImplementedError.new "Available in MyStuff::MultiDB::Unsharded"; end
|
93
|
+
def with_master *args; raise NotImplementedError.new "Available in MyStuff::MultiDB::Unsharded"; end
|
94
|
+
def with_master_for *args; raise NotImplementedError.new "Available in MyStuff::MultiDB::Sharded"; end
|
95
|
+
def with_master_for_new; raise NotImplementedError.new "Available in MyStuff::MultiDB::Sharded"; end
|
96
|
+
def with_slave_for *args; raise NotImplementedError.new "Available in MyStuff::MultiDB::Sharded"; end
|
97
|
+
def sharded?; raise NotImplementedError.new; end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Fetch/create the magic classes.
|
102
|
+
def self.for_spec spec, mod # :nodoc:
|
103
|
+
db_key = ("MYSTUFF_MULTIDB_DB_" + ("!%s!%s!%s" % [
|
104
|
+
spec[:host],
|
105
|
+
spec[:port],
|
106
|
+
spec[:database],
|
107
|
+
]
|
108
|
+
).each_byte.map{ |x| "%x" % x }.join).to_sym
|
109
|
+
|
110
|
+
# db: class representing the logical database
|
111
|
+
if self.const_defined? db_key
|
112
|
+
db = self.const_get(db_key)
|
113
|
+
else
|
114
|
+
db = Class.new ActiveRecord::Base
|
115
|
+
def db.abstract_class?; true; end
|
116
|
+
self.const_set(db_key, db)
|
117
|
+
db.establish_connection(spec)
|
118
|
+
end
|
119
|
+
|
120
|
+
mod_key = mod.name.split(':').last.to_sym
|
121
|
+
# db_mod: class representing the module from the AR definition
|
122
|
+
if db.const_defined? mod_key, false
|
123
|
+
db_mod = db.const_get(mod_key, false)
|
124
|
+
else
|
125
|
+
db_mod = Module.new
|
126
|
+
db.const_set(mod_key, db_mod)
|
127
|
+
db_mod.send(:define_singleton_method, :magic_database) { db }
|
128
|
+
db_mod.send(:define_singleton_method, :muggle) { mod }
|
129
|
+
|
130
|
+
# klass: a specific table's AR class
|
131
|
+
def db_mod.const_missing name
|
132
|
+
klass = muggle.const_get(name)
|
133
|
+
klass_sym = klass.name.split(':').last.to_sym
|
134
|
+
|
135
|
+
# subklass: klass tied to a specific DB
|
136
|
+
subklass = Class.new(klass)
|
137
|
+
const_set klass_sym, subklass
|
138
|
+
define_singleton_method(klass_sym) { subklass }
|
139
|
+
|
140
|
+
subklass.send :include, MyStuff::MultiDB::Base
|
141
|
+
|
142
|
+
# Make associations work.
|
143
|
+
klass.reflect_on_all_associations.each do |reflection|
|
144
|
+
subklass.send(
|
145
|
+
reflection.macro, # eg :has_one
|
146
|
+
reflection.name, # eg :some_table
|
147
|
+
reflection.options
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
return subklass
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
return db_mod
|
156
|
+
end
|
157
|
+
|
158
|
+
protected
|
159
|
+
def self.with_db db, id, writable # :nodoc:
|
160
|
+
if writable == :writable
|
161
|
+
if id == :new
|
162
|
+
spec = db.spec_for_new
|
163
|
+
else
|
164
|
+
spec = db.spec_for_master(id)
|
165
|
+
end
|
166
|
+
elsif writable == :read_only
|
167
|
+
spec = db.spec_for_slave(id)
|
168
|
+
end
|
169
|
+
klass = db.for_spec(spec)
|
170
|
+
|
171
|
+
klass.magic_database.connection_pool.with_connection do
|
172
|
+
yield klass, spec
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
require 'my_stuff/multidb/base'
|
179
|
+
require 'my_stuff/multidb/sharded'
|
180
|
+
require 'my_stuff/multidb/unsharded'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Copyright 2011-present Fred Emmott. See COPYING file.
|
2
|
+
|
3
|
+
module MyStuff
|
4
|
+
module MultiDB
|
5
|
+
module Base # :nodoc:
|
6
|
+
def connection
|
7
|
+
self.class.connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def magic_database
|
16
|
+
@magic_database ||= self.name.split('::').tap(&:pop).join('::').constantize.magic_database
|
17
|
+
end
|
18
|
+
|
19
|
+
def arel_engine
|
20
|
+
magic_database.arel_engine
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection
|
24
|
+
magic_database.connection
|
25
|
+
end
|
26
|
+
|
27
|
+
def abstract_class?; true; end
|
28
|
+
|
29
|
+
def model_name
|
30
|
+
# Rails form_for wants this
|
31
|
+
ActiveModel::Name.new(
|
32
|
+
self.name.split('::').last.tap{|s| def s.name; self; end}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def inherited(child)
|
37
|
+
def child.abstract_class?; false; end
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright 2011-present Fred Emmott. See COPYING file.
|
2
|
+
|
3
|
+
module MyStuff
|
4
|
+
module MultiDB
|
5
|
+
module Sharded
|
6
|
+
def self.included othermod # :nodoc:
|
7
|
+
othermod.send :include, MyStuff::MultiDB
|
8
|
+
class <<othermod
|
9
|
+
def sharded?; true; end;
|
10
|
+
def with_master_for id
|
11
|
+
MyStuff::MultiDB.with_db(
|
12
|
+
self, id, :writable
|
13
|
+
) { |*args| yield *args}
|
14
|
+
end
|
15
|
+
def with_master_for_new
|
16
|
+
MyStuff::MultiDB.with_db(
|
17
|
+
self, :new, :read_only
|
18
|
+
) { |*args| yield *args}
|
19
|
+
end
|
20
|
+
def with_slave_for id
|
21
|
+
MyStuff::MultiDB.with_db(
|
22
|
+
self, id, :read_only
|
23
|
+
) { |*args| yield *args}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright 2011-present Fred Emmott. See COPYING file.
|
2
|
+
|
3
|
+
module MyStuff
|
4
|
+
module MultiDB
|
5
|
+
# Mixin for databases with no sharding (eg ServiceLocator, which defines the sharding).
|
6
|
+
#
|
7
|
+
# See MyStuff::MultiDB for an example.
|
8
|
+
#
|
9
|
+
# Including this defines <tt>with_master</tt> and <tt>with_slave</tt>.
|
10
|
+
module Unsharded
|
11
|
+
def self.included othermod # :nodoc:
|
12
|
+
othermod.send :include, MyStuff::MultiDB
|
13
|
+
class <<othermod
|
14
|
+
def sharded?; false; end;
|
15
|
+
def with_master
|
16
|
+
MyStuff::MultiDB.with_db(
|
17
|
+
self, :unsharded, :writable
|
18
|
+
) { |*args| yield *args }
|
19
|
+
end
|
20
|
+
def with_slave
|
21
|
+
MyStuff::MultiDB.with_db(
|
22
|
+
self, :unsharded, :read_only
|
23
|
+
) { |*args| yield *args}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: my_stuff-multidb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Fred Emmott
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-17 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email:
|
18
|
+
- mail@fredemmott.co.uk
|
19
|
+
executables:
|
20
|
+
- my_stuff-multidb-unmangle
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- COPYING
|
27
|
+
- README.rdoc
|
28
|
+
- bin/my_stuff-multidb-unmangle
|
29
|
+
- lib/my_stuff/multidb/sharded.rb
|
30
|
+
- lib/my_stuff/multidb/base.rb
|
31
|
+
- lib/my_stuff/multidb/unsharded.rb
|
32
|
+
- lib/my_stuff/multidb.rb
|
33
|
+
homepage: https://github.com/fredemmott/my_stuff-multidb
|
34
|
+
licenses:
|
35
|
+
- ISC
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.8.6
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: ActiveRecord sharding/slaves library
|
60
|
+
test_files: []
|
61
|
+
|