ar-octopus 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.mkdn +80 -55
- data/lib/octopus.rb +20 -6
- data/lib/octopus/{rails3/abstract_adapter.rb → abstract_adapter.rb} +1 -4
- data/lib/octopus/association.rb +5 -99
- data/lib/octopus/association_shard_tracking.rb +105 -0
- data/lib/octopus/collection_association.rb +9 -0
- data/lib/octopus/collection_proxy.rb +14 -0
- data/lib/octopus/has_and_belongs_to_many_association.rb +2 -12
- data/lib/octopus/load_balancing.rb +3 -0
- data/lib/octopus/load_balancing/round_robin.rb +15 -0
- data/lib/octopus/{rails3/log_subscriber.rb → log_subscriber.rb} +0 -0
- data/lib/octopus/model.rb +74 -85
- data/lib/octopus/{rails3/persistence.rb → persistence.rb} +0 -0
- data/lib/octopus/proxy.rb +166 -29
- data/lib/octopus/relation_proxy.rb +39 -0
- data/lib/octopus/scope_proxy.rb +7 -10
- data/lib/octopus/shard_tracking.rb +45 -0
- data/lib/octopus/shard_tracking/attribute.rb +24 -0
- data/lib/octopus/shard_tracking/dynamic.rb +7 -0
- data/lib/octopus/singular_association.rb +7 -0
- data/lib/octopus/slave_group.rb +11 -0
- data/lib/octopus/version.rb +1 -1
- data/spec/config/shards.yml +53 -0
- data/spec/octopus/{association_spec.rb → association_shard_tracking_spec.rb} +1 -1
- data/spec/octopus/collection_proxy_spec.rb +15 -0
- data/spec/octopus/model_spec.rb +2 -2
- data/spec/octopus/octopus_spec.rb +34 -0
- data/spec/octopus/relation_proxy_spec.rb +77 -0
- data/spec/octopus/replicated_slave_grouped_spec.rb +64 -0
- data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
- data/spec/support/octopus_helper.rb +1 -0
- metadata +26 -9
- data/lib/octopus/association_collection.rb +0 -49
- data/lib/octopus/rails3/singular_association.rb +0 -34
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzBiMGIyMGY1ZGEyOWRlMGY0MjAyNjljYWQ4ZTI2NmFhZmE0NzIzZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDM0Yzg3ODhlMjNjODEzYjcyYTFmOTFmYmFhNmNjOTk2NmI0Y2YwZg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZjFlODczZWIwYTM3MWNiZWE3ZjYwMTRiZTRhOTlhODMwZmVkZmU5ZWM1MTMy
|
10
|
+
ODRlNzllMjgwY2Q4NzY3NjhiMzBhNTk4ZGRhNDNiYWRmZWE0NDBlM2UxZDE3
|
11
|
+
ZTYyNjYyZmM1MTIwNTdmODkwNzM1MmMwYzU2NTY0MDI2Mzg5NzU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmM5NGFiZWE5MGE3ZWM2N2M0OGFkOGNjOWJiZmZlOWVhOTA5YjYzNmQ1Yzlh
|
14
|
+
ZDlkMWNiNDMwYjQ3ZWEzYzY5MDFkOTAxOTNhZTk0NTk0YTkyYjc4NGQzYTM4
|
15
|
+
ZjE2Yjg5ZmEzYmMxOTRjODgxMDc4YjA3Y2VjNDE1NjZhNTEwODE=
|
data/README.mkdn
CHANGED
@@ -21,13 +21,17 @@ When using replication, all writes queries will be sent to master, and read quer
|
|
21
21
|
When using sharding, you need to specify which shard to send the query. Octopus supports selecting the shard inside a controller, or manually in each object. More could be found at <a href="http://wiki.github.com/tchandy/octopus/sharding"> Wiki</a>
|
22
22
|
|
23
23
|
### Replication + Sharding
|
24
|
-
|
24
|
+
When using replication and sharding concurrently, you must specify a shard, and can optionally specify a <a href="https://github.com/tchandy/octopus/wiki/Slave-Groups">slave group</a>.
|
25
|
+
All write queries will be sent to each shard's master. If the slave group is specified read queries will be sent to slaves in it, or else to shard's master.
|
26
|
+
More info could be found at <a href="https://github.com/tchandy/octopus/wiki/Slave-Groups"> Wiki</a>
|
25
27
|
|
26
28
|
## Install
|
27
29
|
|
28
30
|
Add this line to Gemfile:
|
29
31
|
|
30
|
-
|
32
|
+
```
|
33
|
+
gem 'ar-octopus'
|
34
|
+
```
|
31
35
|
|
32
36
|
Currently, Octopus doesn't support Rails 2. If you need support for rails 2, please use the version 0.5.0.
|
33
37
|
|
@@ -42,7 +46,9 @@ information for each shard is stored within that shard's database.
|
|
42
46
|
If you are upgrading from < 0.5.0 run the `copy_schema_versions` rake task to copy the schema version information in the
|
43
47
|
master database to each of the shards:
|
44
48
|
|
45
|
-
|
49
|
+
```bash
|
50
|
+
rake octopus:copy_schema_versions
|
51
|
+
```
|
46
52
|
|
47
53
|
Once the task completes migrations will operate normally and schema information will be stored in each shard database
|
48
54
|
going forward.
|
@@ -55,62 +61,73 @@ First, you need to create a config file, shards.yml, inside your config/ directo
|
|
55
61
|
|
56
62
|
Octopus adds a method to each AR Class and object: the using method is used to select the shard like this:
|
57
63
|
|
58
|
-
|
64
|
+
```ruby
|
65
|
+
User.where(:name => "Thiago").limit(3).using(:slave_one)
|
66
|
+
```
|
59
67
|
|
60
68
|
Octopus also supports queries within a block. When you pass a block to the using method, all queries inside the block will be sent to the specified shard.
|
61
69
|
|
62
|
-
|
63
|
-
|
64
|
-
|
70
|
+
```ruby
|
71
|
+
Octopus.using(:slave_two) do
|
72
|
+
User.create(:name => "Mike")
|
73
|
+
end
|
74
|
+
```
|
65
75
|
|
66
76
|
Each model instance knows which shard it came from so this will work automatically:
|
67
77
|
|
68
|
-
|
69
|
-
|
78
|
+
```ruby
|
79
|
+
# This will find the user in the shard1
|
80
|
+
@user = User.using(:shard1).find_by_name("Joao")
|
70
81
|
|
71
|
-
|
72
|
-
|
82
|
+
# This will find the user in the master database
|
83
|
+
@user2 = User.find_by_name("Jose")
|
73
84
|
|
74
|
-
|
75
|
-
|
85
|
+
#Sets the name
|
86
|
+
@user.name = "Mike"
|
76
87
|
|
77
|
-
|
78
|
-
|
88
|
+
# Save the user in the correct shard, shard1.
|
89
|
+
@user.save
|
90
|
+
```
|
79
91
|
|
80
92
|
### Migrations
|
81
93
|
|
82
94
|
In migrations, you also have access to the using method. The syntax is basically the same. This migration will run in the brazil and canada shards.
|
83
95
|
|
84
|
-
|
85
|
-
|
96
|
+
```ruby
|
97
|
+
class CreateUsersOnBothShards < ActiveRecord::Migration
|
98
|
+
using(:brazil, :canada)
|
86
99
|
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
def self.up
|
101
|
+
User.create!(:name => "Both")
|
102
|
+
end
|
90
103
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
104
|
+
def self.down
|
105
|
+
User.delete_all
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
95
109
|
|
96
110
|
You also could send a migration to a group of shards. This migration will be sent to all shards that belongs to history_shards group, specified in shards.yml:
|
97
111
|
|
98
|
-
|
99
|
-
|
112
|
+
```ruby
|
113
|
+
class CreateUsersOnMultiplesGroups < ActiveRecord::Migration
|
114
|
+
using_group(:history_shards)
|
100
115
|
|
101
|
-
|
102
|
-
|
103
|
-
|
116
|
+
def self.up
|
117
|
+
User.create!(:name => "MultipleGroup")
|
118
|
+
end
|
104
119
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
120
|
+
def self.down
|
121
|
+
User.delete_all
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
109
125
|
|
110
126
|
You can specify a `default_migration_group` for migrations, so that modifications to each individual migration file are not needed:
|
111
|
-
|
112
|
-
|
113
|
-
|
127
|
+
```yaml
|
128
|
+
octopus:
|
129
|
+
default_migration_group: europe_databases
|
130
|
+
```
|
114
131
|
|
115
132
|
There is no need for a corresponding `default_migration_shard` - simply define that database to be your master. You might want this setting if all of your databases have identical schemas, but are not replicated.
|
116
133
|
|
@@ -118,13 +135,15 @@ There is no need for a corresponding `default_migration_shard` - simply define t
|
|
118
135
|
|
119
136
|
If you want to send a specified action, or all actions from a controller, to a specific shard, use this syntax:
|
120
137
|
|
121
|
-
|
122
|
-
|
138
|
+
```ruby
|
139
|
+
class ApplicationController < ActionController::Base
|
140
|
+
around_filter :select_shard
|
123
141
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
142
|
+
def select_shard(&block)
|
143
|
+
Octopus.using(:brazil, &block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
128
147
|
|
129
148
|
To see the complete list of features and syntax, please check out our <a href="http://wiki.github.com/tchandy/octopus/"> Wiki</a>
|
130
149
|
Want to see sample rails applications using octopus features? please check it out: <a href="http://github.com/tchandy/octopus_sharding_example">Sharding Example</a> and <a href="http://github.com/tchandy/octopus_replication_example">Replication Example</a>. Also, we have an example that shows how to use Octopus without Rails: <a href="http://github.com/tchandy/octopus_sinatra"> Octopus + Sinatra Example</a>.
|
@@ -132,27 +151,33 @@ Want to see sample rails applications using octopus features? please check it ou
|
|
132
151
|
## Mixing Octopus with the Rails multiple database model
|
133
152
|
If you want to set a custom connection to a specific model, use the syntax `octopus_establish_connection` syntax:
|
134
153
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
154
|
+
```ruby
|
155
|
+
#This class sets its own connection
|
156
|
+
class CustomConnection < ActiveRecord::Base
|
157
|
+
octopus_establish_connection(:adapter => "mysql", :database => "octopus_shard2")
|
158
|
+
end
|
159
|
+
```
|
139
160
|
|
140
161
|
## Contributing with Octopus
|
141
162
|
Contributors are welcome! To run the test suite, you need mysql, postgresql and sqlite3 installed. This is what you need to setup your Octopus development environment:
|
142
163
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
164
|
+
```bash
|
165
|
+
git clone http://github.com/tchandy/octopus.git
|
166
|
+
cd octopus
|
167
|
+
bundle install
|
168
|
+
bundle exec rake db:prepare
|
169
|
+
bundle exec rake appraisal:install
|
170
|
+
bundle exec rake spec
|
171
|
+
```
|
149
172
|
|
150
173
|
This command will run the spec suite for all rails versions supported.
|
151
174
|
To run our integrations tests inside sample_app, you need to following commands:
|
152
175
|
|
153
|
-
|
154
|
-
|
155
|
-
|
176
|
+
```bash
|
177
|
+
cd sample_app
|
178
|
+
bundle install
|
179
|
+
cucumber
|
180
|
+
```
|
156
181
|
|
157
182
|
If you are having issues running the octopus spec suite, verify your database users and passwords match those inside the config files and your permissions are correct.
|
158
183
|
|
data/lib/octopus.rb
CHANGED
@@ -90,18 +90,30 @@ module Octopus
|
|
90
90
|
yield
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
def self.fully_replicated(&block)
|
95
|
+
old_fully_replicated = Thread.current["octopus.fully_replicated"]
|
96
|
+
Thread.current["octopus.fully_replicated"] = true
|
97
|
+
yield
|
98
|
+
ensure
|
99
|
+
Thread.current["octopus.fully_replicated"] = old_fully_replicated
|
100
|
+
end
|
93
101
|
end
|
94
102
|
|
103
|
+
require "octopus/shard_tracking"
|
104
|
+
require "octopus/shard_tracking/attribute"
|
105
|
+
require "octopus/shard_tracking/dynamic"
|
95
106
|
|
96
107
|
require "octopus/model"
|
97
108
|
require "octopus/migration"
|
98
|
-
require "octopus/association_collection"
|
99
|
-
require "octopus/has_and_belongs_to_many_association"
|
100
109
|
require "octopus/association"
|
101
|
-
require "octopus/
|
102
|
-
require "octopus/
|
103
|
-
require "octopus/
|
104
|
-
require "octopus/
|
110
|
+
require "octopus/collection_association"
|
111
|
+
require "octopus/has_and_belongs_to_many_association"
|
112
|
+
require "octopus/association_shard_tracking"
|
113
|
+
require "octopus/persistence"
|
114
|
+
require "octopus/log_subscriber"
|
115
|
+
require "octopus/abstract_adapter"
|
116
|
+
require "octopus/singular_association"
|
105
117
|
|
106
118
|
if defined?(::Rails)
|
107
119
|
require "octopus/railtie"
|
@@ -109,4 +121,6 @@ end
|
|
109
121
|
|
110
122
|
|
111
123
|
require "octopus/proxy"
|
124
|
+
require "octopus/collection_proxy"
|
125
|
+
require "octopus/relation_proxy"
|
112
126
|
require "octopus/scope_proxy"
|
@@ -3,10 +3,7 @@ module Octopus
|
|
3
3
|
module AbstractAdapter
|
4
4
|
module OctopusShard
|
5
5
|
|
6
|
-
parent = ActiveSupport::BasicObject
|
7
|
-
if Octopus.rails4?
|
8
|
-
parent = ActiveSupport::ProxyObject
|
9
|
-
end
|
6
|
+
parent = Octopus.rails3? ? ActiveSupport::BasicObject : ActiveSupport::ProxyObject
|
10
7
|
|
11
8
|
class InstrumenterDecorator < parent
|
12
9
|
def initialize(adapter, instrumenter)
|
data/lib/octopus/association.rb
CHANGED
@@ -1,105 +1,11 @@
|
|
1
1
|
module Octopus::Association
|
2
|
-
def self.
|
3
|
-
base.send(:include,
|
2
|
+
def self.included(base)
|
3
|
+
base.send(:include, Octopus::ShardTracking::Dynamic)
|
4
4
|
end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
METHODS= %w[
|
9
|
-
all
|
10
|
-
average
|
11
|
-
count
|
12
|
-
empty?
|
13
|
-
exists?
|
14
|
-
find
|
15
|
-
find_by_sql
|
16
|
-
first
|
17
|
-
last
|
18
|
-
maximum
|
19
|
-
minimum
|
20
|
-
pluck
|
21
|
-
scoping
|
22
|
-
size
|
23
|
-
sum
|
24
|
-
to_a
|
25
|
-
]
|
26
|
-
|
27
|
-
METHODS.each do |m|
|
28
|
-
define_method m.to_sym do |*args,&block|
|
29
|
-
if self.respond_to?(:proxy_association) and self.proxy_association
|
30
|
-
self.proxy_association.owner.run_on_shard { super(*args, &block) }
|
31
|
-
else
|
32
|
-
super(*args, &block)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
module InstanceMethods
|
40
|
-
def set_connection_on_association(record)
|
41
|
-
return unless ::Octopus.enabled?
|
42
|
-
return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
|
43
|
-
if !record.current_shard.nil? && !self.current_shard.nil? && record.current_shard != self.current_shard
|
44
|
-
raise "Association Error: Records are from different shards"
|
45
|
-
end
|
46
|
-
|
47
|
-
record.current_shard = self.class.connection.current_shard = self.current_shard if should_set_current_shard?
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
if Octopus.rails4?
|
52
|
-
def has_many(association_id, scope=nil, options={}, &extension)
|
53
|
-
if options == {} && scope.is_a?(Hash)
|
54
|
-
default_octopus_opts(scope)
|
55
|
-
else
|
56
|
-
default_octopus_opts(options)
|
57
|
-
end
|
58
|
-
super
|
59
|
-
end
|
60
|
-
else
|
61
|
-
def has_many(association_id, options={}, &extension)
|
62
|
-
default_octopus_opts(options)
|
63
|
-
super
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
if Octopus.rails4?
|
69
|
-
def has_and_belongs_to_many(association_id, scope=nil, options={}, &extension)
|
70
|
-
if options == {} && scope.is_a?(Hash)
|
71
|
-
default_octopus_opts(scope)
|
72
|
-
else
|
73
|
-
default_octopus_opts(options)
|
74
|
-
end
|
75
|
-
super
|
76
|
-
end
|
77
|
-
else
|
78
|
-
def has_and_belongs_to_many(association_id, options={}, &extension)
|
79
|
-
default_octopus_opts(options)
|
80
|
-
super
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def default_octopus_opts(options)
|
85
|
-
if options[:before_add].is_a?(Array)
|
86
|
-
options[:before_add] << :set_connection_on_association
|
87
|
-
elsif options[:before_add].is_a?(Symbol)
|
88
|
-
options[:before_add] = [:set_connection_on_association, options[:before_add]]
|
89
|
-
else
|
90
|
-
options[:before_add] = :set_connection_on_association
|
91
|
-
end
|
92
|
-
|
93
|
-
if options[:before_remove].is_a?(Array)
|
94
|
-
options[:before_remove] << :set_connection_on_association
|
95
|
-
elsif options[:before_remove].is_a?(Symbol)
|
96
|
-
options[:before_remove] = [:set_connection_on_association, options[:before_remove]]
|
97
|
-
else
|
98
|
-
options[:before_remove] = :set_connection_on_association
|
99
|
-
end
|
100
|
-
|
101
|
-
options[:extend] = [ Octopus::Association::QueryOnCurrentShard, options[:extend] ].flatten.compact
|
6
|
+
def current_shard
|
7
|
+
owner.current_shard
|
102
8
|
end
|
103
9
|
end
|
104
10
|
|
105
|
-
ActiveRecord::
|
11
|
+
ActiveRecord::Associations::Association.send(:include, Octopus::Association)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Octopus::AssociationShardTracking
|
2
|
+
def self.extended(base)
|
3
|
+
base.send(:include, InstanceMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
module QueryOnCurrentShard
|
7
|
+
|
8
|
+
METHODS= %w[
|
9
|
+
all
|
10
|
+
average
|
11
|
+
count
|
12
|
+
empty?
|
13
|
+
exists?
|
14
|
+
find
|
15
|
+
find_by_sql
|
16
|
+
first
|
17
|
+
last
|
18
|
+
maximum
|
19
|
+
minimum
|
20
|
+
pluck
|
21
|
+
scoping
|
22
|
+
size
|
23
|
+
sum
|
24
|
+
to_a
|
25
|
+
]
|
26
|
+
|
27
|
+
METHODS.each do |m|
|
28
|
+
define_method m.to_sym do |*args,&block|
|
29
|
+
if self.respond_to?(:proxy_association) and self.proxy_association
|
30
|
+
self.proxy_association.owner.run_on_shard { super(*args, &block) }
|
31
|
+
else
|
32
|
+
super(*args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
def set_connection_on_association(record)
|
41
|
+
return unless ::Octopus.enabled?
|
42
|
+
return if !self.class.connection.respond_to?(:current_shard) || !self.respond_to?(:current_shard)
|
43
|
+
if !record.current_shard.nil? && !self.current_shard.nil? && record.current_shard != self.current_shard
|
44
|
+
raise "Association Error: Records are from different shards"
|
45
|
+
end
|
46
|
+
|
47
|
+
record.current_shard = self.class.connection.current_shard = self.current_shard if should_set_current_shard?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if Octopus.rails4?
|
52
|
+
def has_many(association_id, scope=nil, options={}, &extension)
|
53
|
+
if options == {} && scope.is_a?(Hash)
|
54
|
+
default_octopus_opts(scope)
|
55
|
+
else
|
56
|
+
default_octopus_opts(options)
|
57
|
+
end
|
58
|
+
super
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def has_many(association_id, options={}, &extension)
|
62
|
+
default_octopus_opts(options)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
if Octopus.rails4?
|
69
|
+
def has_and_belongs_to_many(association_id, scope=nil, options={}, &extension)
|
70
|
+
if options == {} && scope.is_a?(Hash)
|
71
|
+
default_octopus_opts(scope)
|
72
|
+
else
|
73
|
+
default_octopus_opts(options)
|
74
|
+
end
|
75
|
+
super
|
76
|
+
end
|
77
|
+
else
|
78
|
+
def has_and_belongs_to_many(association_id, options={}, &extension)
|
79
|
+
default_octopus_opts(options)
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_octopus_opts(options)
|
85
|
+
if options[:before_add].is_a?(Array)
|
86
|
+
options[:before_add] << :set_connection_on_association
|
87
|
+
elsif options[:before_add].is_a?(Symbol)
|
88
|
+
options[:before_add] = [:set_connection_on_association, options[:before_add]]
|
89
|
+
else
|
90
|
+
options[:before_add] = :set_connection_on_association
|
91
|
+
end
|
92
|
+
|
93
|
+
if options[:before_remove].is_a?(Array)
|
94
|
+
options[:before_remove] << :set_connection_on_association
|
95
|
+
elsif options[:before_remove].is_a?(Symbol)
|
96
|
+
options[:before_remove] = [:set_connection_on_association, options[:before_remove]]
|
97
|
+
else
|
98
|
+
options[:before_remove] = :set_connection_on_association
|
99
|
+
end
|
100
|
+
|
101
|
+
options[:extend] = [ Octopus::AssociationShardTracking::QueryOnCurrentShard, options[:extend] ].flatten.compact
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
ActiveRecord::Base.extend(Octopus::AssociationShardTracking)
|