ar-octopus 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.mkdn +49 -0
- data/Rakefile +106 -0
- data/VERSION +1 -0
- data/doc/api.textile +98 -0
- data/doc/features.textile +74 -0
- data/doc/libraries.textile +70 -0
- data/doc/shards.yml +76 -0
- data/lib/octopus.rb +30 -0
- data/lib/octopus/controller.rb +2 -0
- data/lib/octopus/migration.rb +41 -0
- data/lib/octopus/model.rb +57 -0
- data/lib/octopus/proxy.rb +195 -0
- data/spec/config/shards.yml +76 -0
- data/spec/database_connection.rb +6 -0
- data/spec/database_models.rb +23 -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/octopus/controller_spec.rb +7 -0
- data/spec/octopus/migration_spec.rb +79 -0
- data/spec/octopus/model_spec.rb +122 -0
- data/spec/octopus/octopus_spec.rb +15 -0
- data/spec/octopus/proxy_spec.rb +112 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +27 -0
- metadata +123 -0
data/README.mkdn
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
<h1> Octopus - Easy Database Sharding for ActiveRecord</h1>
|
2
|
+
|
3
|
+
<p> Octopus is a better way to do Database Sharding in ActiveRecord. Sharding allows multiple databases in the same rails application. While there are several projects that implement Sharding (e.g. DbCharmer, DataFabric), each project has its own limitations. The main goal of octopus project is to provide a nice and clean way of doing Database Sharding.</p>
|
4
|
+
|
5
|
+
<h2>Feature list: </h2>
|
6
|
+
<p> The design of the api is made to be simple as possible. Octopus is focusing in the end user, giving the power of multiple databases, but with reliable code and flexibility. Octopus is focused on Rails 3, but will be soon compatible with Rails 2.x.</p>
|
7
|
+
|
8
|
+
<p> Octopus supports: </p>
|
9
|
+
|
10
|
+
- Sharding (with multiple shards, and grouped shards).
|
11
|
+
- Replication (Master/slave support, with multiple slaves).
|
12
|
+
- Moving data between shards with migrations.
|
13
|
+
- Tools to manage database configurations. (soon)
|
14
|
+
|
15
|
+
<p> To see the complete list of features and syntax, please check out our <a href="http://wiki.github.com/tchandy/octopus/"> Wiki</a>
|
16
|
+
|
17
|
+
<h2>Thanks</h2>
|
18
|
+
|
19
|
+
This project is sponsored by the <a href="http://www.rubysoc.org">Ruby Summer of Code</a>,
|
20
|
+
and my mentors <a href="http://github.com/mperham">Mike Perham</a> and <a href="http://github.com/amitagarwal">Amit Agarwal</a>.
|
21
|
+
|
22
|
+
|
23
|
+
<h3>If you are writing code that smells using octopus, the EVIL Octopus is going behind you, be careful.</h3>
|
24
|
+
<pre>
|
25
|
+
_________________
|
26
|
+
___ | I'm so EVIL! |
|
27
|
+
.-' `'. |______________ |
|
28
|
+
/ \ /
|
29
|
+
| ; /
|
30
|
+
| | / ___.--,
|
31
|
+
_.._ |0) ~ (0) | _.---'`__.-( (_.
|
32
|
+
__.--'`_.. '.__.\ '--. \_.-' ,.--'` `""`
|
33
|
+
( ,.--'` ',__ /./; ;, '.__.'` __
|
34
|
+
_`) ) .---.__.' / | |\ \__..--"" """--.,_
|
35
|
+
`---' .'.''-._.-'`_./ /\ '. \ _.-~~~````~~~-._`-.__.'
|
36
|
+
| | .' _.-' | | \ \ '. `~---`
|
37
|
+
\ \/ .' \ \ '. '-._)
|
38
|
+
\/ / \ \ `=.__`~-.
|
39
|
+
/ /\ `) ) / / `"".`\
|
40
|
+
, _.-'.'\ \ / / ( ( / /
|
41
|
+
`--~` ) ) .-'.' '.'. | (
|
42
|
+
(/` ( (` ) ) '-;
|
43
|
+
` '-; (-'
|
44
|
+
</pre>
|
45
|
+
|
46
|
+
|
47
|
+
<h2>Copyright</h2>
|
48
|
+
|
49
|
+
Copyright (c) 2010 Thiago Pradi, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ar-octopus"
|
9
|
+
gem.summary = "Easy Database Sharding for ActiveRecord"
|
10
|
+
gem.description = "This gem allows you to use sharded databases with ActiveRecord. this also provides a interface for replication and for running migrations with multiples shards."
|
11
|
+
gem.email = "tchandy@gmail.com"
|
12
|
+
gem.homepage = "http://github.com/tchandy/octopus"
|
13
|
+
gem.authors = ["Thiago Pradi", "Mike Perham", "Amit Agarwal"]
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
gem.version = "0.0.1"
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "octopus #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :db do
|
49
|
+
desc 'Build the MySQL test databases'
|
50
|
+
task :build_databases do
|
51
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
52
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
53
|
+
(1..5).each do |idx|
|
54
|
+
%x( echo "create DATABASE octopus_shard#{idx} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{mysql_user})
|
55
|
+
end
|
56
|
+
|
57
|
+
%x( createdb -E UTF8 -U #{postgres_user} octopus_shard1 )
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'Drop the MySQL test databases'
|
61
|
+
task :drop_databases do
|
62
|
+
mysql_user = ENV['MYSQL_USER'] || "root"
|
63
|
+
postgres_user = ENV['POSTGRES_USER'] || "postgres"
|
64
|
+
(1..5).each do |idx|
|
65
|
+
%x( mysqladmin --user=#{mysql_user} -f drop octopus_shard#{idx} )
|
66
|
+
end
|
67
|
+
|
68
|
+
%x( dropdb -U #{postgres_user} octopus_shard1 )
|
69
|
+
end
|
70
|
+
|
71
|
+
desc 'Create tables on mysql databases'
|
72
|
+
task :create_tables do
|
73
|
+
Dir.chdir(File.expand_path(File.dirname(__FILE__) + "/spec"))
|
74
|
+
require "database_connection"
|
75
|
+
require "octopus"
|
76
|
+
[:master, :brazil, :canada, :russia, :alone_shard, :postgresql_shard].each do |shard_symbol|
|
77
|
+
ActiveRecord::Base.using(shard_symbol).connection.create_table(:users) do |u|
|
78
|
+
u.string :name
|
79
|
+
end
|
80
|
+
|
81
|
+
ActiveRecord::Base.using(shard_symbol).connection.create_table(:clients) do |u|
|
82
|
+
u.string :country
|
83
|
+
u.string :name
|
84
|
+
end
|
85
|
+
|
86
|
+
ActiveRecord::Base.using(shard_symbol).connection.create_table(:cats) do |u|
|
87
|
+
u.string :name
|
88
|
+
end
|
89
|
+
|
90
|
+
ActiveRecord::Base.using(shard_symbol).connection.create_table(:items) do |u|
|
91
|
+
u.string :name
|
92
|
+
u.integer :client_id
|
93
|
+
end
|
94
|
+
|
95
|
+
ActiveRecord::Base.using(shard_symbol).connection.create_table(:schema_migrations) do |u|
|
96
|
+
u.string :version, :unique => true, :null => false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc 'Prepare the MySQL test databases'
|
102
|
+
task :prepare => [:drop_databases, :build_databases, :create_tables]
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/doc/api.textile
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
h1. Api Design
|
2
|
+
|
3
|
+
* The API design should be simple as possible, so they will just have one method to change the shards, the method using, which will be different in the context that him get called. In controller, they will send all queries in the action or controller to a specified shard. So, if you want to get all of your application sharded, you need to do a before_filter in the application controller.
|
4
|
+
* The other method will be sharded_by, which selects the shard using a attribute of the model. This method is just accessible from class method on ActiveRecord::Base.
|
5
|
+
* The using method also could be called from models, as a scope, or receiving a block, that will run all the methods inside a specific shard.
|
6
|
+
* Also, the using method could be called from migrations. For default, migrations should run only in the master database, if you want to run in a different database, you should specify where you wish that migration run. like this:
|
7
|
+
<pre>
|
8
|
+
# This will run in all, master and slaves
|
9
|
+
class AddNameFieldToUser < ActiveRecord::Migration
|
10
|
+
using(:all)
|
11
|
+
#or
|
12
|
+
using_all
|
13
|
+
|
14
|
+
def self.up
|
15
|
+
add_column :users, :name, :string
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
remove_column :users, :name, :string
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# This will run just in all slaves
|
25
|
+
class AddNameFieldToUser < ActiveRecord::Migration
|
26
|
+
using(:slaves)
|
27
|
+
or
|
28
|
+
using_slaves()
|
29
|
+
|
30
|
+
def self.up
|
31
|
+
add_column :users, :name, :string
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.down
|
35
|
+
remove_column :users, :name, :string
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This will run in all shards, slaves or not
|
40
|
+
class AddNameFieldToUser < ActiveRecord::Migration
|
41
|
+
using(:shards)
|
42
|
+
|
43
|
+
def self.up
|
44
|
+
add_column :users, :name, :string
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.down
|
48
|
+
remove_column :users, :name, :string
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This will run in specific shard
|
53
|
+
class AddNameFieldToUser < ActiveRecord::Migration
|
54
|
+
using(:canada)
|
55
|
+
|
56
|
+
def self.up
|
57
|
+
add_column :users, :name, :string
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.down
|
61
|
+
remove_column :users, :name, :string
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# This will run in the specified shards, this accepts two or more shards.
|
66
|
+
class AddNameFieldToUser < ActiveRecord::Migration
|
67
|
+
using(:canada, :brazil)
|
68
|
+
|
69
|
+
def self.up
|
70
|
+
add_column :users, :name, :string
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.down
|
74
|
+
remove_column :users, :name, :string
|
75
|
+
end
|
76
|
+
end
|
77
|
+
</pre>
|
78
|
+
* The default behavior on finds will be: if you find a object from a shard, like:
|
79
|
+
|
80
|
+
# This will find all users in brazil shard and iterate over it, saving all of them in the brazil shard
|
81
|
+
<pre>
|
82
|
+
User.using(:brazil).find(:all).each do |user|
|
83
|
+
user.name = "Brazil"
|
84
|
+
user.save()
|
85
|
+
end
|
86
|
+
</pre>
|
87
|
+
|
88
|
+
# This will find all users in brazil shard and iterate over it, saving all of them in the canada shard
|
89
|
+
<pre>
|
90
|
+
User.using(:brazil).find(:all).each do |user|
|
91
|
+
user.name = "Brazil"
|
92
|
+
user.using(:canada).save()
|
93
|
+
end
|
94
|
+
</pre>
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
h1. Octopus Features List:
|
2
|
+
|
3
|
+
* Support for replicated databases, with one master and multiple slaves. Multi-master support could be added later. The writes will be sended to master and the reads to slave, but this could be modified.
|
4
|
+
* Support database sharding, with a nice and clean syntax like:
|
5
|
+
<pre>
|
6
|
+
User.using(:awesome_shard).all() or User.using_awesome_shard.all()
|
7
|
+
|
8
|
+
class User < ActiveRecord::Base
|
9
|
+
sharded_by :code
|
10
|
+
|
11
|
+
def awesome_method
|
12
|
+
#All queries inside the block will go to awesome_shard
|
13
|
+
using(:awesome_shard) do
|
14
|
+
Foo.all
|
15
|
+
Bar.all
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ApplicationController < ActionController::Base
|
21
|
+
around_filter :select_shard
|
22
|
+
|
23
|
+
def select_shard(&block)
|
24
|
+
using(current_user.city) do
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
</pre>
|
30
|
+
* Running migrations between shards, example:
|
31
|
+
<pre>
|
32
|
+
class MyAwesomeMigration < ActiveRecord::Migration
|
33
|
+
using :awesome_shard
|
34
|
+
|
35
|
+
def self.up
|
36
|
+
create_table :users do |t|
|
37
|
+
t.string :name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.down
|
42
|
+
drop_table :users
|
43
|
+
end
|
44
|
+
end
|
45
|
+
</pre>
|
46
|
+
* The sharding config will be separated from the database.yml file, given a more cleaner and nice configuration file.
|
47
|
+
* An initial generator will be integrated in to the project, to help people using octopus, this will generate the config file: db/shards.yml, and a initializer to help the configuration. The example of this configuration is on this directory.
|
48
|
+
* Like the others implementation, the sharding will be selected using a Proxy class for ActiveRecord, where the class will require the connection, and the query will be executed in the selected shard.
|
49
|
+
* After this essentials feature, I will add a task to capistrano that allows you to generate the configuration and start the replication with one line command, reading the db/shards.yml configuration and running commands on the servers.
|
50
|
+
* In replication, all slaves will be trated as shards, but you will have to specify what should be used as slave. For default, replication will balance your read queries between the slaves, and your writes queries will goes to master. if you don't want this feature, just set the load_balancing to false, and specify what queries you want to goes to slaves, and what slave. with replication, you will have methods like:
|
51
|
+
<pre>
|
52
|
+
# This sends the queries to a random shard (support just read queries, not writes)
|
53
|
+
User.using_slaves().all()
|
54
|
+
# This sends all queries to master(read/write)
|
55
|
+
User.using_master().all()
|
56
|
+
# Same thing, but all users queries will be sent to master
|
57
|
+
class User < ActiveRecord::Base
|
58
|
+
using_master()
|
59
|
+
end
|
60
|
+
# Same thing, but all read queries will be sent to slaves
|
61
|
+
class User < ActiveRecord::Base
|
62
|
+
using_slaves()
|
63
|
+
end
|
64
|
+
|
65
|
+
#or
|
66
|
+
|
67
|
+
class User < ActiveRecord::Base
|
68
|
+
using(:master)
|
69
|
+
end
|
70
|
+
|
71
|
+
#Tip: you cannot name a shard as master or slaves, they are reserved words used for replication.
|
72
|
+
</pre>
|
73
|
+
|
74
|
+
* If you have multiple slaves, the load balancing will be did using round robin algorithm, sending the queries to the databases available. (This isn't the better algorithm, but it's easy to implement and works)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
h1. Masochism
|
2
|
+
|
3
|
+
p. Features:
|
4
|
+
* Support Multiple Database Support
|
5
|
+
* The config is stored in database.yml
|
6
|
+
* You could have a master and multiples slaves, but you couldn't change on the fly the shard. Ex: User.using(:awesome_shard)
|
7
|
+
|
8
|
+
p. Pros:
|
9
|
+
* Easy to use
|
10
|
+
* Test Coverage
|
11
|
+
|
12
|
+
p. Cons:
|
13
|
+
* Outdated (Lastest commit in January 12, 2009)
|
14
|
+
* Don't support running migrations on different shards.
|
15
|
+
* Don't support changing the shard on the fly
|
16
|
+
|
17
|
+
|
18
|
+
h1. DataFabric
|
19
|
+
|
20
|
+
p. Features:
|
21
|
+
* Support Multiple Database Support
|
22
|
+
* The config is stored in database.yml
|
23
|
+
* You could have data that are just sharded, not replicated.
|
24
|
+
* Support on the fly sharding selecting, with blocks
|
25
|
+
|
26
|
+
p. Pros:
|
27
|
+
* Easy to use and config
|
28
|
+
* Test Coverage
|
29
|
+
* Support just sharded, not replicated data
|
30
|
+
|
31
|
+
p. Cons:
|
32
|
+
* Don't support running migrations between shards.
|
33
|
+
* Don't support changing the sharding on the model, example: User.using(:awesome_shard)
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
h1. DbCharmer
|
38
|
+
|
39
|
+
p. Features:
|
40
|
+
* Support Multiple Database Support
|
41
|
+
* The config is stored in database.yml
|
42
|
+
* You could have a master and multiples slaves
|
43
|
+
* You could change the shard on the fly, with this syntax: User.switch_connection_to(:awesome_shard)
|
44
|
+
* You could run migrations over shards
|
45
|
+
* You could specify configurations of shards using ruby code
|
46
|
+
|
47
|
+
|
48
|
+
p. Pros:
|
49
|
+
* Support replication and sharding
|
50
|
+
* Support migrations between shards
|
51
|
+
* Supports on the Fly changing on the model
|
52
|
+
|
53
|
+
p. Cons:
|
54
|
+
* Didn't have test coverage in the plugin project, the tests are in another project.
|
55
|
+
* Weird and complicated syntax.
|
56
|
+
* Code are much more complicated than in the others project.
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
h1. DataMapper Sharding
|
61
|
+
|
62
|
+
p. Features:
|
63
|
+
* Support Multiple Database Support
|
64
|
+
* Syntax: DataMapper.setup(:external, 'mysql://someother_host/dm_core_test'); repository(:external) { Person.first }
|
65
|
+
|
66
|
+
h1. Multi-DB (http://github.com/schoefmax/multi_db)
|
67
|
+
|
68
|
+
p. Features:
|
69
|
+
* Support replication, with multiple slaves
|
70
|
+
* Load balancing between slaves
|
data/doc/shards.yml
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# The master database is the database settend in config/database.yml
|
2
|
+
# This supports both sharding and replication, you could select the shards, and for default just replicated
|
3
|
+
# database will be used, the master database is in database.yml and the slave will only be awesome_slave.
|
4
|
+
# Setting Load balancing for default will be true, octopus will send all your read queries to slaves, and writes queries to
|
5
|
+
# master. if you just want some queries to send to slaves, set it to false, and use the sintax User.using(:slave).
|
6
|
+
production:
|
7
|
+
replicated: true
|
8
|
+
|
9
|
+
shards:
|
10
|
+
awesome_slave:
|
11
|
+
adapter: mysql
|
12
|
+
username: user
|
13
|
+
password: pass
|
14
|
+
database: awesome_slave
|
15
|
+
host: 192.321.321.21
|
16
|
+
|
17
|
+
not_slave:
|
18
|
+
adapter: mysql
|
19
|
+
username: user
|
20
|
+
password: pass
|
21
|
+
database: awesome_slave
|
22
|
+
host: 192.321.321.18
|
23
|
+
|
24
|
+
# This is another example, not replicated.
|
25
|
+
production:
|
26
|
+
shards:
|
27
|
+
us:
|
28
|
+
adapter: mysql
|
29
|
+
username: user
|
30
|
+
password: pass
|
31
|
+
database: shard1
|
32
|
+
host: 192.321.321.19
|
33
|
+
canada:
|
34
|
+
adapter: mysql
|
35
|
+
username: user
|
36
|
+
password: pass
|
37
|
+
database: shard1
|
38
|
+
host: 192.321.321.17
|
39
|
+
brazil:
|
40
|
+
adapter: mysql
|
41
|
+
username: user
|
42
|
+
password: pass
|
43
|
+
database: shard1
|
44
|
+
host: 192.321.321.90
|
45
|
+
|
46
|
+
# This is a example using a group of shards:
|
47
|
+
production:
|
48
|
+
shards:
|
49
|
+
history_shards:
|
50
|
+
aug2009:
|
51
|
+
adapter: mysql
|
52
|
+
username: user
|
53
|
+
password: pass
|
54
|
+
database: aug2009
|
55
|
+
host: 192.321.321.21
|
56
|
+
oct2009:
|
57
|
+
adapter: mysql
|
58
|
+
username: user
|
59
|
+
password: pass
|
60
|
+
database: oct2009
|
61
|
+
host: 192.321.321.18
|
62
|
+
|
63
|
+
country_shards:
|
64
|
+
brazil:
|
65
|
+
adapter: mysql
|
66
|
+
username: user
|
67
|
+
password: pass
|
68
|
+
database: brazil
|
69
|
+
host: 192.321.321.21
|
70
|
+
slave: true
|
71
|
+
canada:
|
72
|
+
adapter: mysql
|
73
|
+
username: user
|
74
|
+
password: pass
|
75
|
+
database: canada
|
76
|
+
host: 192.321.321.18
|