xbar 0.0.1 → 0.4.0
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/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
|