xbar 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Appraisals +25 -0
- data/README.mkdn +215 -0
- data/Rakefile +337 -1
- data/examples/README +5 -0
- data/examples/config/simple.json +22 -0
- data/examples/example1.rb +34 -0
- data/examples/migrations/1_create_users.rb +10 -0
- data/examples/setup.rb +43 -0
- data/gemfiles/rails3.gemfile +8 -0
- data/gemfiles/rails3.gemfile.lock +74 -0
- data/gemfiles/rails31.gemfile +8 -0
- data/gemfiles/rails31.gemfile.lock +83 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/gemfiles/rails32.gemfile.lock +117 -0
- data/gemfiles/rails4.gemfile +9 -0
- data/gemfiles/rails4.gemfile.lock +134 -0
- data/lib/migrations/1_create_usage_statistics.rb +23 -0
- data/lib/xbar/association.rb +49 -0
- data/lib/xbar/association_collection.rb +69 -0
- data/lib/xbar/colors.rb +32 -0
- data/lib/xbar/has_and_belongs_to_many_association.rb +17 -0
- data/lib/xbar/logger.rb +14 -0
- data/lib/xbar/mapper.rb +304 -0
- data/lib/xbar/migration.rb +76 -0
- data/lib/xbar/model.rb +165 -0
- data/lib/xbar/proxy.rb +249 -0
- data/lib/xbar/rails2/association.rb +133 -0
- data/lib/xbar/rails2/persistence.rb +39 -0
- data/lib/xbar/rails3/arel.rb +13 -0
- data/lib/xbar/rails3/association.rb +112 -0
- data/lib/xbar/rails3/persistence.rb +37 -0
- data/lib/xbar/rails3.1/singular_association.rb +34 -0
- data/lib/xbar/scope_proxy.rb +55 -0
- data/lib/xbar/shard.rb +95 -0
- data/lib/xbar/version.rb +2 -2
- data/lib/xbar.rb +121 -2
- data/run +27 -0
- data/spec/config/acme.json +53 -0
- data/spec/config/connection.rb +2 -0
- data/spec/config/default.json +160 -0
- data/spec/config/duplicate_shard.json +21 -0
- data/spec/config/missing_key.json +20 -0
- data/spec/config/new_shards.json +29 -0
- data/spec/config/no_master_shard.json +19 -0
- data/spec/config/not_entire_sharded.json +23 -0
- data/spec/config/octopus.json +27 -0
- data/spec/config/octopus_rails.json +25 -0
- data/spec/config/production_fully_replicated.json +21 -0
- data/spec/config/production_raise_error.json +17 -0
- data/spec/config/simple.json +22 -0
- data/spec/config/single_adapter.json +20 -0
- data/spec/console.rb +15 -0
- data/spec/migrations/10_create_users_using_replication.rb +12 -0
- data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
- data/spec/migrations/12_create_users_using_block.rb +23 -0
- data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
- data/spec/migrations/1_create_users_on_master.rb +9 -0
- data/spec/migrations/2_create_users_on_canada.rb +11 -0
- data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
- data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/database_models.rb +78 -0
- data/spec/support/xbar_helper.rb +42 -0
- data/spec/xbar/association_spec.rb +660 -0
- data/spec/xbar/controller_spec.rb +40 -0
- data/spec/xbar/logger_spec.rb +22 -0
- data/spec/xbar/mapper_spec.rb +283 -0
- data/spec/xbar/migration_spec.rb +110 -0
- data/spec/xbar/model_spec.rb +434 -0
- data/spec/xbar/proxy_spec.rb +124 -0
- data/spec/xbar/replication_spec.rb +94 -0
- data/spec/xbar/scope_proxy_spec.rb +22 -0
- data/spec/xbar/shard_spec.rb +36 -0
- data/xbar.gemspec +13 -3
- metadata +231 -10
data/Appraisals
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# appraise "rails2" do
|
2
|
+
# gem 'activerecord', '~> 2.3'
|
3
|
+
# gem 'actionpack', '~> 2.3'
|
4
|
+
# end
|
5
|
+
|
6
|
+
appraise "rails3" do
|
7
|
+
gem 'activerecord', '~> 3.0.11'
|
8
|
+
gem 'actionpack', '~> 3.0.11'
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise "rails31" do
|
12
|
+
gem 'activerecord', '~> 3.1.1'
|
13
|
+
gem 'actionpack', '~> 3.1.1'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
appraise "rails32" do
|
18
|
+
gem 'rails', '3.2.1'
|
19
|
+
end
|
20
|
+
|
21
|
+
appraise "rails4" do
|
22
|
+
gem 'rails', :path => '/opt/rails/rails-edge'
|
23
|
+
gem 'journey', :git => 'https://github.com/rails/journey.git'
|
24
|
+
gem 'arel', :git => 'https://github.com/rails/arel.git'
|
25
|
+
end
|
data/README.mkdn
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# XBar - MongoDB Style Sharding for ActiveRecord
|
2
|
+
|
3
|
+
## Supported versions
|
4
|
+
*Caution* -- only ActiveRecord 3.2 is currently supported. This
|
5
|
+
will soon be improved.
|
6
|
+
|
7
|
+
## General Design
|
8
|
+
|
9
|
+
The XBar project is derived from *Octopus*. Octopus showed that the
|
10
|
+
implementation technique of using a proxy for the
|
11
|
+
`ActiveRecord::ConnectionAdapters::AbstractAdapter` object instances
|
12
|
+
is possible. This proxy implements a sort of *late binding*,
|
13
|
+
returning a real abstract adapter object that depends on the current
|
14
|
+
state of the proxy, especially on the value of the `current_shard`.
|
15
|
+
Many of the tricky pieces of code, especially those for managing
|
16
|
+
associations, come from Octopus.
|
17
|
+
|
18
|
+
## Changes from Octopus
|
19
|
+
|
20
|
+
The Octopus implementation has been modified to support efficient
|
21
|
+
dynamic reconfiguration. The configuration is given in a JSON
|
22
|
+
document, rather than YAML. This makes it convenient to update the
|
23
|
+
application over the network using the standard JSON over HTTP
|
24
|
+
technique. The JSON document supports multiple application
|
25
|
+
environments. These align with Rails environments when Rails is
|
26
|
+
present. Unlike Octopus, this document format does not allow multiple
|
27
|
+
'Octopus' evironments. Instead, there is an `xbar_env` which
|
28
|
+
essentially specifies a *different* JSON file to use.
|
29
|
+
|
30
|
+
Another change is that shards that are themselves a collection of
|
31
|
+
mirrors is supported. Mirroring does not take place among shards, it
|
32
|
+
takes place among the mirrors that constitute a shard. Replication
|
33
|
+
among the mirrors is expected to be handling by some database specific
|
34
|
+
technique, such as native *MySQL* replication.
|
35
|
+
|
36
|
+
The Octopus `Proxy` class has been split into two classes `Proxy` and
|
37
|
+
`Shard`, and into a module `Mapper`. `Mapper` contains the in-memory
|
38
|
+
representation of the current configuration. Each `Proxy` instance no
|
39
|
+
longer has its own copy of the configuration. There is exactly one
|
40
|
+
`Mapper` module for the entire application, necessarily true, since
|
41
|
+
its a *module*. As before there is one `Proxy` instance per thread,
|
42
|
+
which allows per-thread state to be stored in the instance. A `Proxy`
|
43
|
+
instance references a collection of `Shard` instances. Each `Shard`
|
44
|
+
instance manages a set of replicas for that shard.
|
45
|
+
|
46
|
+
The concept of a *group* as used in Octopus is mostly gone. Vestiges
|
47
|
+
of it exist in the migration code. Thus a migration can still specify
|
48
|
+
that it should take place on multiple shards. In the general case, a
|
49
|
+
group concept would fork writes to send them to multiple shards. This
|
50
|
+
is only practical if the writes are performed in parallel in separate
|
51
|
+
threads. The current `Proxy` would become a group manager and we'd
|
52
|
+
have a hierarchy something like this:
|
53
|
+
|
54
|
+
Proxy (set of Groups) -> Group (set of Shards) -> Shard (set of replicas)
|
55
|
+
|
56
|
+
This complexity is too great for the first version of `XBar`.
|
57
|
+
|
58
|
+
## Concepts
|
59
|
+
|
60
|
+
### Mapper
|
61
|
+
|
62
|
+
The mapper is a module that maintains the state of the database servers, shards,
|
63
|
+
replication, and databases. It is configured via a JSON document. The JSON
|
64
|
+
document can be in the local file system, or it can be delivered over HTTP. A
|
65
|
+
new JSON document can be installed at any time. The mapper does not maintain
|
66
|
+
any per-thread state. All the proxies get their mapping information directly
|
67
|
+
from the mapper. Some information is cached in each proxy, optimized for use in
|
68
|
+
the proxy. Thus when the JSON document is changed, the mapper will notify each
|
69
|
+
proxy to rebuild its state.
|
70
|
+
|
71
|
+
### Proxy
|
72
|
+
|
73
|
+
An instance of the `Proxy` object exists per thread. Thus thread-local state
|
74
|
+
can be kept directly in the proxy, and the proxy can refer to global state in
|
75
|
+
the mapper (and in the `XBar` module itself). Each proxy registers with the
|
76
|
+
mapper when it is created so that the mapper can notify the proxy of changes to
|
77
|
+
the global configuration.
|
78
|
+
|
79
|
+
A proxy is only responsible for managing shards, that is, except for some
|
80
|
+
initialization code, it has no knowledge of replicas within a shard. It has a
|
81
|
+
list, the `shard_list`, that maps shard names to `Shard` objects. For each SQL
|
82
|
+
statement, block of statements, or transaction, the proxy choose a shard and
|
83
|
+
deletegates the operation to the shard. The shard, in turn, chooses the particular
|
84
|
+
replica to use.
|
85
|
+
|
86
|
+
### Shard
|
87
|
+
|
88
|
+
The term *shard* is used as it is used in MongoDB. A shard acts like an
|
89
|
+
independent database. In our case, the replication within the shard is handled
|
90
|
+
by some external means such as native MySQL replication. Nevertheless, XBar
|
91
|
+
knows about the replicas within the shard, and it knows which replica is the
|
92
|
+
replication master. This allows XBar to direct transactions to the master shard,
|
93
|
+
and reads to the (eventually-consistent) replicas within the shard.
|
94
|
+
|
95
|
+
The shard is a per-thread object, referenced only from a proxy. Thus the
|
96
|
+
overall structure is tree-like. One mapper references a collection of proxies
|
97
|
+
(one per thread), and each proxy references a collection of shards. Each shard
|
98
|
+
object references a list of `ActiveRecord::ConnectionAdapters::ConnectionPool`
|
99
|
+
instances that the shard uses to select connections to replicas within the
|
100
|
+
shard.
|
101
|
+
|
102
|
+
### Environments
|
103
|
+
|
104
|
+
XBar has three concepts all called *environments*. First, there is the
|
105
|
+
*rails_env* that is inherited from Rails when this gem is included in a Rails
|
106
|
+
application. Second, the *xbar_env* is the name of the configuration file that
|
107
|
+
is currently in effect. In the case where the configuration was loaded via a
|
108
|
+
JSON document over HTTP, a name for the *xbar_env* is generated in some other
|
109
|
+
way (TBD). Finally, there is the *app_env*, that functions much like
|
110
|
+
*rails_env* when Rails is not present. When Rails is present, *app_env* is read
|
111
|
+
only and always has the same value as *rails_env*. When Rails is not present,
|
112
|
+
*app_env* can be selected by the user. In either case, its value should be name
|
113
|
+
of an environment stanza in the current configuration file.
|
114
|
+
|
115
|
+
## Getting Started
|
116
|
+
|
117
|
+
The *examples* directory at the top level of the source distribution
|
118
|
+
has some stand-alone examples that show how to set up and use `XBar`.
|
119
|
+
|
120
|
+
## Using XBar
|
121
|
+
|
122
|
+
XBar adds a method to each `ActiveRecord` Class and object: the using
|
123
|
+
method is used to select the shard like this:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
User.where(:name => "Thiago").limit(3).using(:slave_one)
|
127
|
+
```
|
128
|
+
|
129
|
+
XBar also supports queries within a block. When you pass a block to
|
130
|
+
the using method, all queries inside the block will be sent to the
|
131
|
+
specified shard.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
XBar.using(:slave_two) do
|
135
|
+
User.create(:name => "Mike")
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
Each model instance knows which shard it came from so this will work
|
140
|
+
automatically:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# This will find the user in the shard1
|
144
|
+
@user = User.using(:shard1).find_by_name("Joao")
|
145
|
+
|
146
|
+
# This will find the user in the master database
|
147
|
+
@user2 = User.find_by_name("Jose")
|
148
|
+
|
149
|
+
#Sets the name
|
150
|
+
@user.name = "Mike"
|
151
|
+
|
152
|
+
# Save the user in the correct shard, shard1.
|
153
|
+
@user.save
|
154
|
+
```
|
155
|
+
|
156
|
+
### Migrations
|
157
|
+
|
158
|
+
In migrations, you also have access to the using method. The syntax is
|
159
|
+
basically the same. This migration will run in the brazil and canada
|
160
|
+
shards.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class CreateUsersOnBothShards < ActiveRecord::Migration
|
164
|
+
using(:brazil, :canada)
|
165
|
+
|
166
|
+
def self.up
|
167
|
+
User.create!(:name => "Both")
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.down
|
171
|
+
User.delete_all
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
### Rails Controllers
|
177
|
+
|
178
|
+
If you want to send a specified action, or all actions from a
|
179
|
+
controller, to a specific shard, use this syntax:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
class ApplicationController < ActionController::Base
|
183
|
+
around_filter :select_shard
|
184
|
+
|
185
|
+
def select_shard(&block)
|
186
|
+
XBar.using(:brazil, &block)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
## Exception with Idle Connections
|
192
|
+
|
193
|
+
Sometimes, when a connection isn't used for much time,
|
194
|
+
this will makes ActiveRecord raising an exception. if you have this
|
195
|
+
kind of applications, please, add the following line to your
|
196
|
+
configuration:
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
verify_connection: true
|
200
|
+
```
|
201
|
+
|
202
|
+
This will tell XBar to verify the connection before sending the query.
|
203
|
+
|
204
|
+
## Mixing XBar with the Rails Multiple Database Mechanism
|
205
|
+
|
206
|
+
If you want to set a custom connection to a specific model, use this
|
207
|
+
syntax:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
#This class sets its own connection
|
211
|
+
# establish_connection will not work, use xbar_establish_connection instead.
|
212
|
+
class CustomConnection < ActiveRecord::Base
|
213
|
+
xbar_establish_connection(:adapter => "mysql", :database => "xbar_shard2")
|
214
|
+
end
|
215
|
+
```
|
data/Rakefile
CHANGED
@@ -1 +1,337 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
# require 'metric_fu'
|
4
|
+
require 'appraisal'
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
# MetricFu::Mapperuration.run do |config|
|
9
|
+
# config.metrics = [:churn,:flay, :flog, :reek, :roodi, :saikuro]
|
10
|
+
# config.graphs = [:flog, :flay, :reek, :roodi]
|
11
|
+
# config.flay = { :dirs_to_flay => ['spec', 'lib'] }
|
12
|
+
# config.flog = { :dirs_to_flog => ['spec', 'lib'] }
|
13
|
+
# config.reek = { :dirs_to_reek => ['spec', 'lib'] }
|
14
|
+
# config.roodi = { :dirs_to_roodi => ['spec', 'lib'] }
|
15
|
+
# config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10 }
|
16
|
+
# end
|
17
|
+
|
18
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
22
|
+
end
|
23
|
+
|
24
|
+
namespace :test do
|
25
|
+
|
26
|
+
desc 'Create usage_statistics table'
|
27
|
+
task :add_stats do
|
28
|
+
require 'active_record'
|
29
|
+
require "xbar"
|
30
|
+
XBar.directory = File.expand_path("../spec", __FILE__)
|
31
|
+
XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
|
32
|
+
|
33
|
+
class CreateUsageStatistics < ActiveRecord::Migration
|
34
|
+
using :master
|
35
|
+
def up
|
36
|
+
create_table(:usage_statistics) do |t|
|
37
|
+
t.string :shard_name
|
38
|
+
t.string :method
|
39
|
+
t.string :adapter
|
40
|
+
t.string :username
|
41
|
+
t.string :thread_id
|
42
|
+
t.integer :port
|
43
|
+
t.string :host
|
44
|
+
t.string :database_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
CreateUsageStatistics.migrate(:up)
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'Drop usage_statistics table'
|
52
|
+
task :drop_stats do
|
53
|
+
require 'active_record'
|
54
|
+
require "xbar"
|
55
|
+
XBar.directory = File.expand_path("../spec", __FILE__)
|
56
|
+
XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
|
57
|
+
|
58
|
+
class CreateUsageStatistics < ActiveRecord::Migration
|
59
|
+
using :master
|
60
|
+
def down
|
61
|
+
drop_table(:usage_statistics)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
CreateUsageStatistics.migrate(:down)
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'Drop the databases for tests'
|
68
|
+
task :drop do
|
69
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
70
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
71
|
+
|
72
|
+
require 'active_record'
|
73
|
+
require "xbar"
|
74
|
+
XBar.directory = File.expand_path("../spec", __FILE__)
|
75
|
+
XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
|
76
|
+
|
77
|
+
%w(mysql_m london_s canada_1 brazil_1 china_1 china_2).each do |key|
|
78
|
+
config = XBar::Mapper.connections[key].spec.config
|
79
|
+
opts = "-h#{config[:host]} -P#{config[:port]} -u#{config[:username]}"
|
80
|
+
opts += " -p#{config[:password]}" if config[:password] &&
|
81
|
+
config[:password] != ""
|
82
|
+
|
83
|
+
db = config[:database]
|
84
|
+
cmd = "mysqladmin #{opts} -f drop #{db}"
|
85
|
+
%x( #{cmd} )
|
86
|
+
end
|
87
|
+
|
88
|
+
%x{ mysqladmin -uroot -f drop rogue }
|
89
|
+
|
90
|
+
%w(moscow_s russia_1 russia_2 russia_3).each do |key|
|
91
|
+
config = XBar::Mapper.connections[key].spec.config
|
92
|
+
%x( dropdb -U #{postgres_user} #{config[:database]} )
|
93
|
+
end
|
94
|
+
|
95
|
+
%x( rm -f /tmp/paris.sqlite3 )
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
desc 'Create databases for the tests'
|
100
|
+
task :create do
|
101
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
102
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
103
|
+
|
104
|
+
require 'active_record'
|
105
|
+
require "xbar"
|
106
|
+
XBar.directory = File.expand_path("../spec", __FILE__)
|
107
|
+
XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
|
108
|
+
|
109
|
+
%w(brazil_1 canada_1 mysql_m london_s china_1 china_2).each do |key|
|
110
|
+
config = XBar::Mapper.connections[key].spec.config
|
111
|
+
opts = "-h#{config[:host]} -P#{config[:port]} -u#{config[:username]}"
|
112
|
+
opts += " -p#{config[:password]}" if config[:password ] &&
|
113
|
+
config[:password] != ""
|
114
|
+
|
115
|
+
db = config[:database]
|
116
|
+
sql = "create DATABASE #{db} DEFAULT CHARACTER SET utf8 " +
|
117
|
+
"DEFAULT COLLATE utf8_unicode_ci"
|
118
|
+
%x( echo #{sql} | mysql #{opts} )
|
119
|
+
end
|
120
|
+
|
121
|
+
%x{ mysqladmin -uroot create rogue }
|
122
|
+
|
123
|
+
%w(moscow_s russia_1 russia_2 russia_3).each do |key|
|
124
|
+
config = XBar::Mapper.connections[key].spec.config
|
125
|
+
%x( createdb -E UTF8 -U #{postgres_user} -T template0 #{config[:database]} )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
desc 'Build test tables'
|
130
|
+
task :tables do
|
131
|
+
|
132
|
+
require 'active_record'
|
133
|
+
require "xbar"
|
134
|
+
XBar.directory = File.expand_path("../spec", __FILE__)
|
135
|
+
XBar::Mapper.reset(xbar_env: 'default', app_env: 'test')
|
136
|
+
|
137
|
+
class BlankModel < ActiveRecord::Base; end;
|
138
|
+
|
139
|
+
%w(master london paris moscow canada brazil russia_east
|
140
|
+
russia_west russia_central china_east china_west).each do |shard|
|
141
|
+
|
142
|
+
BlankModel.using(shard).connection.
|
143
|
+
initialize_schema_migrations_table
|
144
|
+
|
145
|
+
## Find the best way to build tables on the master of each shard. ##
|
146
|
+
## We should use migrations instead of this error-prone way. ##
|
147
|
+
|
148
|
+
BlankModel.using(shard).connection.create_table(:users) do |u|
|
149
|
+
u.string :name
|
150
|
+
u.integer :number
|
151
|
+
u.boolean :admin
|
152
|
+
end
|
153
|
+
|
154
|
+
BlankModel.using(shard).connection.create_table(:clients) do |u|
|
155
|
+
u.string :country
|
156
|
+
u.string :name
|
157
|
+
end
|
158
|
+
|
159
|
+
BlankModel.using(shard).connection.create_table(:cats) do |u|
|
160
|
+
u.string :name
|
161
|
+
end
|
162
|
+
|
163
|
+
BlankModel.using(shard).connection.create_table(:items) do |u|
|
164
|
+
u.string :name
|
165
|
+
u.integer :client_id
|
166
|
+
end
|
167
|
+
|
168
|
+
BlankModel.using(shard).connection.create_table(:computers) do |u|
|
169
|
+
u.string :name
|
170
|
+
end
|
171
|
+
|
172
|
+
BlankModel.using(shard).connection.create_table(:keyboards) do |u|
|
173
|
+
u.string :name
|
174
|
+
u.integer :computer_id
|
175
|
+
end
|
176
|
+
|
177
|
+
BlankModel.using(shard).connection.create_table(:roles) do |u|
|
178
|
+
u.string :name
|
179
|
+
end
|
180
|
+
|
181
|
+
BlankModel.using(shard).connection.create_table(:permissions) do |u|
|
182
|
+
u.string :name
|
183
|
+
end
|
184
|
+
|
185
|
+
BlankModel.using(shard).connection.create_table(:permissions_roles, :id => false) do |u|
|
186
|
+
u.integer :role_id
|
187
|
+
u.integer :permission_id
|
188
|
+
end
|
189
|
+
|
190
|
+
BlankModel.using(shard).connection.create_table(:assignments) do |u|
|
191
|
+
u.integer :programmer_id
|
192
|
+
u.integer :project_id
|
193
|
+
end
|
194
|
+
|
195
|
+
BlankModel.using(shard).connection.create_table(:programmers) do |u|
|
196
|
+
u.string :name
|
197
|
+
end
|
198
|
+
|
199
|
+
BlankModel.using(shard).connection.create_table(:projects) do |u|
|
200
|
+
u.string :name
|
201
|
+
end
|
202
|
+
|
203
|
+
BlankModel.using(shard).connection.create_table(:comments) do |u|
|
204
|
+
u.string :name
|
205
|
+
u.string :commentable_type
|
206
|
+
u.integer :commentable_id
|
207
|
+
end
|
208
|
+
|
209
|
+
BlankModel.using(shard).connection.create_table(:parts) do |u|
|
210
|
+
u.string :name
|
211
|
+
u.integer :item_id
|
212
|
+
end
|
213
|
+
|
214
|
+
BlankModel.using(shard).connection.create_table(:yummy) do |u|
|
215
|
+
u.string :name
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
desc 'Prepare the test databases'
|
221
|
+
task :prepare => [:drop, :create, :tables]
|
222
|
+
end
|
223
|
+
|
224
|
+
namespace :db do
|
225
|
+
desc 'Build the databases for tests'
|
226
|
+
|
227
|
+
task :build_databases do
|
228
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
229
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
230
|
+
(1..5).each do |idx|
|
231
|
+
%x( echo "create DATABASE xbar_shard#{idx} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{mysql_user})
|
232
|
+
end
|
233
|
+
|
234
|
+
%x( createdb -E UTF8 -U #{postgres_user} -T template0 xbar_shard1 )
|
235
|
+
end
|
236
|
+
|
237
|
+
desc 'Drop the tests databases'
|
238
|
+
task :drop_databases do
|
239
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
240
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
241
|
+
(1..5).each do |idx|
|
242
|
+
%x( mysqladmin --user=#{mysql_user} -f drop xbar_shard#{idx} )
|
243
|
+
end
|
244
|
+
|
245
|
+
%x( dropdb -U #{postgres_user} xbar_shard1 )
|
246
|
+
%x( rm -f /tmp/database.sqlite3 )
|
247
|
+
end
|
248
|
+
|
249
|
+
desc 'Create tables on tests databases'
|
250
|
+
task :create_tables do
|
251
|
+
Dir.chdir(File.expand_path(File.dirname(__FILE__) + "/spec"))
|
252
|
+
# require 'active_support/core_ext/class/inheritable_attributes'
|
253
|
+
require 'active_record'
|
254
|
+
require "xbar"
|
255
|
+
[:master, :brazil, :canada, :russia, :alone_shard, :postgresql_shard, :sqlite_shard].each do |shard_symbol|
|
256
|
+
# Rails 3.1 needs to do some introspection around the base class, which requires
|
257
|
+
# the model be a descendent of ActiveRecord::Base.
|
258
|
+
class BlankModel < ActiveRecord::Base; end;
|
259
|
+
|
260
|
+
BlankModel.using(shard_symbol).connection.initialize_schema_migrations_table()
|
261
|
+
|
262
|
+
BlankModel.using(shard_symbol).connection.create_table(:users) do |u|
|
263
|
+
u.string :name
|
264
|
+
u.integer :number
|
265
|
+
u.boolean :admin
|
266
|
+
end
|
267
|
+
|
268
|
+
BlankModel.using(shard_symbol).connection.create_table(:clients) do |u|
|
269
|
+
u.string :country
|
270
|
+
u.string :name
|
271
|
+
end
|
272
|
+
|
273
|
+
BlankModel.using(shard_symbol).connection.create_table(:cats) do |u|
|
274
|
+
u.string :name
|
275
|
+
end
|
276
|
+
|
277
|
+
BlankModel.using(shard_symbol).connection.create_table(:items) do |u|
|
278
|
+
u.string :name
|
279
|
+
u.integer :client_id
|
280
|
+
end
|
281
|
+
|
282
|
+
BlankModel.using(shard_symbol).connection.create_table(:computers) do |u|
|
283
|
+
u.string :name
|
284
|
+
end
|
285
|
+
|
286
|
+
BlankModel.using(shard_symbol).connection.create_table(:keyboards) do |u|
|
287
|
+
u.string :name
|
288
|
+
u.integer :computer_id
|
289
|
+
end
|
290
|
+
|
291
|
+
BlankModel.using(shard_symbol).connection.create_table(:roles) do |u|
|
292
|
+
u.string :name
|
293
|
+
end
|
294
|
+
|
295
|
+
BlankModel.using(shard_symbol).connection.create_table(:permissions) do |u|
|
296
|
+
u.string :name
|
297
|
+
end
|
298
|
+
|
299
|
+
BlankModel.using(shard_symbol).connection.create_table(:permissions_roles, :id => false) do |u|
|
300
|
+
u.integer :role_id
|
301
|
+
u.integer :permission_id
|
302
|
+
end
|
303
|
+
|
304
|
+
BlankModel.using(shard_symbol).connection.create_table(:assignments) do |u|
|
305
|
+
u.integer :programmer_id
|
306
|
+
u.integer :project_id
|
307
|
+
end
|
308
|
+
|
309
|
+
BlankModel.using(shard_symbol).connection.create_table(:programmers) do |u|
|
310
|
+
u.string :name
|
311
|
+
end
|
312
|
+
|
313
|
+
BlankModel.using(shard_symbol).connection.create_table(:projects) do |u|
|
314
|
+
u.string :name
|
315
|
+
end
|
316
|
+
|
317
|
+
BlankModel.using(shard_symbol).connection.create_table(:comments) do |u|
|
318
|
+
u.string :name
|
319
|
+
u.string :commentable_type
|
320
|
+
u.integer :commentable_id
|
321
|
+
end
|
322
|
+
|
323
|
+
BlankModel.using(shard_symbol).connection.create_table(:parts) do |u|
|
324
|
+
u.string :name
|
325
|
+
u.integer :item_id
|
326
|
+
end
|
327
|
+
|
328
|
+
BlankModel.using(shard_symbol).connection.create_table(:yummy) do |u|
|
329
|
+
u.string :name
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
desc 'Prepare the test databases'
|
335
|
+
task :prepare => [:drop_databases, :build_databases, :create_tables]
|
336
|
+
end
|
337
|
+
|
data/examples/README
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"__COMMENT": "Simple SQLite French Environment",
|
3
|
+
|
4
|
+
"connections": {
|
5
|
+
"paris_m": { "adapter": "sqlite3", "database": "/tmp/paris.sqlite3"},
|
6
|
+
"france_1": { "adapter": "sqlite3", "database": "/tmp/france_1.sqlite3"},
|
7
|
+
"france_2": { "adapter": "sqlite3", "database": "/tmp/france_2.sqlite3"},
|
8
|
+
"france_3": { "adapter": "sqlite3", "database": "/tmp/france_3.sqlite3"}
|
9
|
+
},
|
10
|
+
|
11
|
+
"environments": {
|
12
|
+
"test": {
|
13
|
+
"shards": {
|
14
|
+
"master": "paris_m",
|
15
|
+
"france": ["france_1", "france_2", "france_3"],
|
16
|
+
"france_nord": "france_1",
|
17
|
+
"france_central": "france_2",
|
18
|
+
"france_sud": "france_3"
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'setup'))
|
2
|
+
|
3
|
+
module Examples
|
4
|
+
Setup.start('simple', 'test', 1)
|
5
|
+
|
6
|
+
# Define the model to let us access the 'users' table through ActiveRecord.
|
7
|
+
class User < ActiveRecord::Base; end
|
8
|
+
|
9
|
+
# Everything is now set up. Remember that the three french shards don't
|
10
|
+
# really replicate. XBar is set up to *think* that they do. We know
|
11
|
+
# better, and can check that reads and writes go to the shards that
|
12
|
+
# we think that they should. They curious results below wouldn't happen
|
13
|
+
# if replication is really taking place (modulo the eventual-consistency
|
14
|
+
# problem).
|
15
|
+
|
16
|
+
User.using(:france_central).create!([{name: "central1"}, {name: "central2"}])
|
17
|
+
XBar.using(:france_sud) do
|
18
|
+
User.create!([{name: "sud1"}, {name: "sud2"}, {name: "sud3"}])
|
19
|
+
end
|
20
|
+
User.using(:france).create!(name: "nord1")
|
21
|
+
|
22
|
+
f1 = User.using(:france).all.size # 2
|
23
|
+
f2 = User.using(:france).all.size # 3
|
24
|
+
f3 = User.using(:france).all.size # 2
|
25
|
+
f4 = User.using(:france).all.size # 3
|
26
|
+
n1 = User.using(:france_nord).all.size # 1
|
27
|
+
c1 = User.using(:france_central).all.size # 2
|
28
|
+
s1 = User.using(:france_sud).all.size # 3
|
29
|
+
|
30
|
+
puts [f1, f2, f3, f4, n1, c1, s1].to_s # [2, 3, 2, 3, 1, 2, 3]
|
31
|
+
|
32
|
+
Setup.stop(1)
|
33
|
+
end
|
34
|
+
|
data/examples/setup.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_record'
|
3
|
+
require 'xbar'
|
4
|
+
|
5
|
+
# This file demonstrates a self-contained use of XBar. The only support
|
6
|
+
# is a subdirectory 'config' that holds a JSON configuration file, and
|
7
|
+
# a subdirectory 'migrations' that holds the one migration that we plan
|
8
|
+
# to use.
|
9
|
+
|
10
|
+
module Examples
|
11
|
+
module Setup
|
12
|
+
|
13
|
+
MIGRATIONS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'migrations'))
|
14
|
+
|
15
|
+
def self.start(xbar_env, app_env, version = nil)
|
16
|
+
|
17
|
+
# This must agree with what's in the 'simple' JSON config file. Make
|
18
|
+
# sure that we're starting with a clean slate.
|
19
|
+
%x{ rm -f /tmp/paris.sqlite3 /tmp/france_1.sqlite3 \
|
20
|
+
/tmp/france_2.sqlite3 /tmp/france_3.sqlite3 }
|
21
|
+
|
22
|
+
# This directory should have a subdirectory called 'config' which
|
23
|
+
# actually holds the config files.
|
24
|
+
XBar.directory = File.expand_path(File.dirname(__FILE__))
|
25
|
+
|
26
|
+
# Initialize the mapper with the 'test' environment from the 'simple'
|
27
|
+
# configuration file.
|
28
|
+
XBar::Mapper.reset(xbar_env: xbar_env, app_env: app_env)
|
29
|
+
|
30
|
+
# Use a migration to create initial table(s) to work with.
|
31
|
+
if version
|
32
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, version)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.stop(version = nil)
|
37
|
+
if version
|
38
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, version)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|