master_slave_adapter_soundcloud 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Readme.md +180 -0
  2. data/VERSION +1 -1
  3. data/master_slave_adapter.gemspec +1 -1
  4. metadata +4 -4
  5. data/README +0 -43
data/Readme.md ADDED
@@ -0,0 +1,180 @@
1
+ # Replication Aware Master Slave Adapter
2
+
3
+ Improved version of the [master_slave_adapter plugin][1], packaged as a gem.
4
+
5
+ ## Features
6
+
7
+ 1. automatic selection of master or slave connection: `with_consistency`
8
+ 2. manual selection of master or slave connection: `with_master`, `with_slave`
9
+ 3. transaction callbacks: `on_commit`, `on_rollback`
10
+ 4. also:
11
+ * support for multiple slaves
12
+ * (partial) support for [database_cleaner][2]
13
+
14
+ ### Automatic Selection of Master or Slave
15
+
16
+ * _note that this feature currently only works with MySQL_
17
+ * _see also this [blog post][3] for a more detailed explanation_
18
+
19
+ The adapter will run all reads against a slave database, unless a) the read is inside an open transaction or b) the
20
+ adapter determines that the slave lags behind the master _relative to the last write_. For this to work, an initial
21
+ initial consistency requirement ("`Clock`") must be passed to the adapter. Based on this clock value, the adapter
22
+ determines if a (randomly chosen) slave meets this requirement. If not, all statements are executed against master,
23
+ otherwise, the slave connection is used until either a transaction is opened or a write occurs. After a successful write
24
+ or transaction, the adapter determines a new consistency requirement, which is returned and can be used for subsequent
25
+ operations. Note that after a write or transaction, the adapter keeps using the master connection.
26
+
27
+ As an example, a Rails application could run the following function as an `around_filter`:
28
+
29
+ ```ruby
30
+ def with_consistency_filter
31
+ if logged_in?
32
+ # it's a good idea to use this feature on a per-user basis
33
+ cache_key = [ CACHE_NAMESPACE, current_user.id.to_s ].join(":")
34
+
35
+ clock = cached_clock(cache_key) ||
36
+ ActiveRecord::Base.connection.master_clock
37
+
38
+ new_clock = ActiveRecord::Base.with_consistency(clock) do
39
+ # inside the controller, ActiveRecord models can be used just as normal.
40
+ # The adapter will take care of choosing the right connection.
41
+ yield
42
+ end
43
+
44
+ [ new_clock, clock ].compact.max.tap do |c|
45
+ cache_clock!(cache_key, c)
46
+ end if new_clock != clock
47
+ else
48
+ # anonymous users will have to wait until the slaves have caught up
49
+ with_slave { yield }
50
+ end
51
+ end
52
+ ```
53
+
54
+ Note that we use the current `master_clock` as a reference point. This will give the user a recent view of the data,
55
+ possibly reading from master, and if no write occurs inside the `with_consistency` block, we have a reasonable value to
56
+ cache and reuse on subsequent requests. Alternatively, we could have used
57
+ `ActiveRecord::ConnectionAdapters::MasterSlaveAdapter::Clock.zero` to indicate no particular consistency requirement.
58
+ Since `with_consistency` blocks can be nested, the controller code could later decide to require a more recent view on
59
+ the data.
60
+
61
+ ### Manual Selection of Master or Slave
62
+
63
+ The original functionality of the adapter has been preserved:
64
+
65
+ ```ruby
66
+ ActiveRecord::Base.with_master do
67
+ # everything inside here will go to master
68
+ end
69
+
70
+ ActiveRecord::Base.with_slave do
71
+ # everything inside here will go to one of the slaves
72
+ # opening a transaction or writing will switch to master
73
+ # for the rest of the block
74
+ end
75
+ ```
76
+
77
+ `with_master`, `with_slave` as well as `with_consistency` can be nested deliberately.
78
+
79
+ ### Transaction Callbacks
80
+
81
+ This feature was originally developed at [SoundCloud][4] for the standard `MysqlAdapter`. It allows arbitrary blocks of
82
+ code to be deferred for execution until the next transaction completes (or rolls back).
83
+
84
+ ```irb
85
+ irb> ActiveRecord::Base.on_commit { puts "COMMITTED!" }
86
+ irb> ActiveRecord::Base.on_rollback { puts "ROLLED BACK!" }
87
+ irb> ActiveRecord::Base.connection.transaction do
88
+ irb* # ...
89
+ irb> end
90
+ COMMITTED!
91
+ => nil
92
+ irb> ActiveRecord::Base.connection.transaction do
93
+ irb* # ...
94
+ irb* raise "failed operation"
95
+ irb> end
96
+ ROLLED BACK!
97
+ # stack trace omitted
98
+ => nil
99
+ ```
100
+
101
+ Note that a transaction callback will be fired only *once*, so you might want to do:
102
+
103
+ ```ruby
104
+ class MyModel
105
+ after_save do
106
+ connection.on_commit do
107
+ # ...
108
+ end
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### Support for Multiple Slaves
114
+
115
+ The adapter keeps a list of slave connections (see *Configuration*) and chooses randomly between them. The selection is
116
+ made at the beginning of a `with_slave` or `with_consistency` block and doesn't change until the block returns. Hence, a
117
+ nested `with_slave` or `with_consistency` might run against a different slave.
118
+
119
+ ### Database Cleaner
120
+
121
+ At [SoundCloud][4], we're using [database_cleaner][2]'s 'truncation strategy' to wipe the database between [cucumber][5]
122
+ 'feature's. As our cucumber suite proved valuable while testing the `with_consistency` feature, we had to support
123
+ `truncate_table` as an `ActiveRecord::Base.connection` instance method. We might add other strategies if there's enough
124
+ interest.
125
+
126
+ ## Configuration
127
+
128
+ Example configuration for the development environment in `database.yml`:
129
+
130
+ ```yaml
131
+ development:
132
+ adapter: master_slave # use master_slave adapter
133
+ connection_adapter: mysql # actual adapter to use (only mysql is supported atm)
134
+ disable_connection_test: false # when an instance is checked out from the connection pool,
135
+ # we check if the connections are still alive, reconnecting if necessary
136
+ # these values are picked up as defaults in the 'master' and 'slaves' sections:
137
+ database: aweapp_development
138
+ username: aweappuser
139
+ password: s3cr3t
140
+
141
+ master:
142
+ host: masterhost
143
+ username: readwrite_user # override default value
144
+
145
+ slaves:
146
+ - host: slave01
147
+ - host: slave02
148
+ ```
149
+
150
+ ## Installation
151
+
152
+ Using plain rubygems:
153
+
154
+ ```sh
155
+ $ gem install master_slave_adapter_soundcloud
156
+ ```
157
+
158
+ Using bundler:
159
+
160
+ ```sh
161
+ $ cat >> Gemfile
162
+ gem 'master_slave_adapter_soundcloud', '~> 0.1', :require => 'master_slave_adaper'
163
+ ^D
164
+ $ bundle install
165
+ ```
166
+
167
+ ## Credits
168
+
169
+ * Maurício Lenhares - _original master_slave_adapter plugin_
170
+ * Torsten Curdt - _with_consistency, maintainership & open source licenses_
171
+ * Sean Treadway - _chief everything & transaction callbacks_
172
+ * Kim Altintop - _strong lax monoidal endofunctors_
173
+ * Omid Aladini - _chief operator & everything else_
174
+
175
+
176
+ [1]: https://github.com/mauricio/master_slave_adapter
177
+ [2]: https://github.com/bmabey/database_cleaner
178
+ [3]: http://www.yourdailygeekery.com/2011/06/14/master-slave-consistency.html
179
+ [4]: http://backstage.soundcloud.com
180
+ [5]: http://cukes.info
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.require_path = 'lib'
19
19
 
20
20
  s.required_ruby_version = '>= 1.9.2'
21
- s.required_rubygems_version = '1.3.7'
21
+ s.required_rubygems_version = '>= 1.3.7'
22
22
  s.add_development_dependency 'rspec'
23
23
 
24
24
  s.add_dependency 'activerecord', '= 2.3.9'
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mauricio Linhares
@@ -61,8 +61,8 @@ files:
61
61
  - .gitignore
62
62
  - Gemfile
63
63
  - LICENSE
64
- - README
65
64
  - Rakefile
65
+ - Readme.md
66
66
  - VERSION
67
67
  - lib/active_record/connection_adapters/master_slave_adapter.rb
68
68
  - lib/master_slave_adapter.rb
@@ -90,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
90
  required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  none: false
92
92
  requirements:
93
- - - "="
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  segments:
96
96
  - 1
data/README DELETED
@@ -1,43 +0,0 @@
1
- master_slave_adapter
2
- ====
3
-
4
- This simple plugin acts as a common ActiveRecord adapter and allows you to
5
- setup a master-slave environment using any database you like (and is supported
6
- by ActiveRecord).
7
-
8
- This plugin works by handling two connections, one to a master database,
9
- that will receive all non-"SELECT" statements, and another to a slave database
10
- that that is going to receive all SELECT statements. It also tries to do as
11
- little black magic as possible, it works just like any other ActiveRecord database
12
- adapter and performs no monkeypatching at all, so it's easy and simple to use
13
- and understand.
14
-
15
- The master database connection will also receive SELECT calls if a transaction
16
- is active at the moment or if a command is executed inside a "with_master" block:
17
-
18
- ActiveRecord::Base.with_master do # :with_master instructs the adapter
19
- @users = User.all # to use the master connection inside the block
20
- end
21
-
22
- To use this adapter you just have to install the plugin:
23
-
24
- ruby script/plugin install git://github.com/tcurdt/master_slave_adapter_mauricio.git
25
-
26
- And then configure it at your database.yml file:
27
-
28
- development:
29
- database: sample_development
30
- username: root
31
- adapter: master_slave # the adapter must be set to "master_slave"
32
- host: 10.21.34.80
33
- master_slave_adapter: mysql # here's where you'll place the real database adapter name
34
- disable_connection_test: true # this will disable the connection test before use,
35
- # can possibly improve the performance but you could also
36
- # hit stale connections, default is false
37
- eager_load_connections: true # connections are lazy loaded by default, you can load gem eagerly setting this to true
38
- master: # and here's where you'll add the master database configuration
39
- database: talkies_development # you shouldn't specify an "adapter" here, the
40
- username: root # value at "master_slave_adapter" is going to be used
41
- host: 10.21.34.82
42
- adapter: postgresql # you can use another adapter for the master connection if needed
43
- # if you don't set it the "master_slave_adapter" property will be used