master_slave_adapter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ tags
2
+ test/*
3
+ pkg/*
4
+ .rvmrc
5
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # 0.2.0 (April 2, 2012)
2
+
3
+ * Add support for ActiveRecord's query cache
4
+
5
+ # 0.1.10 (March 06, 2012)
6
+
7
+ * Delegate #visitor to master connection
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2011 Maurício Linhares,
2
+ Torsten Curdt,
3
+ Kim Altintop,
4
+ Omid Aladini,
5
+ SoundCloud Ltd
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc 'Run specs'
7
+ RSpec::Core::RakeTask.new
8
+
9
+ desc 'Default: Run specs'
10
+ task :default => :spec
data/Readme.md ADDED
@@ -0,0 +1,181 @@
1
+ # Replication Aware Master Slave Adapter [![Build Status](https://secure.travis-ci.org/soundcloud/large-hadron-migrator.png)][6]
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
181
+ [6]: http://travis-ci.org/soundcloud/master_slave_adapter
data/TODO.txt ADDED
@@ -0,0 +1,8 @@
1
+ Read only mode
2
+ --------------
3
+
4
+ - Add better fallback to connection_for_read
5
+
6
+ - write tests
7
+ - extract adapter specific code
8
+ - make everything nice
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1 @@
1
+ require 'master_slave_adapter'
@@ -0,0 +1 @@
1
+ require 'master_slave_adapter'