makara 0.3.10 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -4
- data/README.md +22 -24
- data/gemfiles/ar50.gemfile +1 -1
- data/gemfiles/ar51.gemfile +1 -1
- data/lib/makara.rb +1 -0
- data/lib/makara/cache.rb +4 -40
- data/lib/makara/config_parser.rb +12 -2
- data/lib/makara/context.rb +108 -38
- data/lib/makara/cookie.rb +52 -0
- data/lib/makara/error_handler.rb +2 -2
- data/lib/makara/logging/logger.rb +1 -1
- data/lib/makara/middleware.rb +12 -75
- data/lib/makara/pool.rb +4 -11
- data/lib/makara/proxy.rb +20 -25
- data/lib/makara/version.rb +2 -2
- data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +0 -4
- data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +1 -1
- data/spec/cache_spec.rb +2 -74
- data/spec/config_parser_spec.rb +21 -1
- data/spec/context_spec.rb +163 -100
- data/spec/cookie_spec.rb +72 -0
- data/spec/middleware_spec.rb +26 -55
- data/spec/proxy_spec.rb +51 -36
- data/spec/spec_helper.rb +2 -9
- data/spec/support/helpers.rb +6 -2
- data/spec/support/mock_objects.rb +1 -1
- data/spec/support/proxy_extensions.rb +1 -1
- metadata +5 -4
- data/lib/makara/cache/memory_store.rb +0 -31
- data/lib/makara/cache/noop_store.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 942671e3f14754da500b275f0da2354cef9d7d0e
|
4
|
+
data.tar.gz: bfcd3be83e4101f627d10c0718afe41e5dae5b44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f51e87da7797bbb503ebe60e1e719d498f2cc8020e09578a6b9d2ed2c770c7d879ca2f32d7b7ab4b224c76f0c5ba00c8dc48bc91cf806dd24be6299e5424e209
|
7
|
+
data.tar.gz: b1cacfad8294f8371f0c28ec6a76f0608ce9c0c23361cb256e9ba0b083bde43459ad2c18e575a166c6b3f258edbb562fc1ec7520f4653b5c30bcefdb9cef59ce
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
-
## v0.
|
5
|
-
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.
|
4
|
+
## v0.4.0 - 2018-04-01
|
5
|
+
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.10...v0.4.0)
|
6
|
+
|
7
|
+
This release is a major change to how we remember state between requests. A redis store is no longer needed. Everything is in the cookies.
|
8
|
+
- Implement stickiness for the duration of `master_ttl` via cookies [#194](https://github.com/taskrabbit/makara/pull/194) Rosa Gutierrez
|
9
|
+
|
10
|
+
|
11
|
+
## v0.3.10 - 2018-03-20
|
12
|
+
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.9...v0.3.10)
|
6
13
|
|
7
14
|
Fixed
|
8
15
|
- Send nextval queries to master and show queries to replicas for Postgres [#173](https://github.com/taskrabbit/makara/pull/173) Andrew Kane
|
@@ -18,14 +25,14 @@ Documentation and Test
|
|
18
25
|
- Travis Upgrade [#199](https://github.com/taskrabbit/makara/pull/199) Brian Leonard
|
19
26
|
|
20
27
|
## v0.3.9 - 2017-08-14
|
21
|
-
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.8...v0.3.9)
|
28
|
+
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.8...v0.3.9)
|
22
29
|
|
23
30
|
Changed
|
24
31
|
- Add postgis support [#118](https://github.com/taskrabbit/makara/pull/118) Kevin Bacha
|
25
32
|
|
26
33
|
## v0.3.8 - 2017-07-11
|
27
34
|
|
28
|
-
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.7...v0.3.8)
|
35
|
+
[Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.7...v0.3.8)
|
29
36
|
|
30
37
|
Changed
|
31
38
|
- Rails 5.1 compatibility [#150](https://github.com/taskrabbit/makara/pull/150) Jeremy Daer
|
data/README.md
CHANGED
@@ -61,40 +61,44 @@ This implementation will send any request not like "SELECT..." to a master conne
|
|
61
61
|
|
62
62
|
Makara comes with a config parser which will handle providing subconfigs to the `connection_for` method. Check out the ActiveRecord database.yml example below for more info.
|
63
63
|
|
64
|
-
### Context
|
64
|
+
### Stickiness Context
|
65
65
|
|
66
|
-
Makara handles
|
66
|
+
Makara handles stickiness by keeping track of which proxies are stuck at any given moment. The context is basically a mapping of proxy ids to the timestamp until which they are stuck.
|
67
67
|
|
68
|
-
|
69
|
-
Makara::Cache.store = MyRedisCacheStore.new
|
70
|
-
```
|
68
|
+
To handle persistence of context across requests in a Rack app, makara provides a middleware. It lays a cookie named `_mkra_stck` which contains the current context. If the next request is executed before the cookie expires, that given context will be used. If something occurs which naturally requires master on the second request, the context is updated and stored again.
|
71
69
|
|
72
|
-
|
70
|
+
#### Releasing stuck connections (clearing context)
|
73
71
|
|
74
|
-
|
72
|
+
If you need to clear the current context, releasing any stuck connections, all you have to do is:
|
75
73
|
|
76
|
-
|
74
|
+
```ruby
|
75
|
+
Makara::Context.release_all
|
76
|
+
```
|
77
77
|
|
78
|
+
You can also clear stuck connections for a specific proxy:
|
78
79
|
```ruby
|
79
|
-
|
80
|
-
Makara::Context.
|
80
|
+
Makara::Context.release(proxy_id)
|
81
|
+
Makara::Context.release('mysql_main')
|
82
|
+
Makara::Context.release('redis')
|
83
|
+
...
|
81
84
|
```
|
82
85
|
|
83
86
|
A context is local to the curent thread of execution. This will allow you to stick to master safely in a single thread
|
84
87
|
in systems such as sidekiq, for instance.
|
85
88
|
|
86
89
|
|
87
|
-
|
90
|
+
#### Forcing Master
|
88
91
|
|
89
92
|
If you need to force master in your app then you can simply invoke stick_to_master! on your connection:
|
90
93
|
|
91
94
|
```ruby
|
92
|
-
|
93
|
-
proxy.stick_to_master!(
|
95
|
+
persist = true # or false, it's true by default
|
96
|
+
proxy.stick_to_master!(persist)
|
94
97
|
```
|
95
98
|
|
99
|
+
It'll keep the proxy stuck to master for the current request, and if `persist = true` (default), it'll be also stored in the context for subsequent requests, keeping the proxy stuck up to the duration of `master_ttl` configured for the proxy.
|
96
100
|
|
97
|
-
|
101
|
+
#### Skipping the Stickiness
|
98
102
|
|
99
103
|
If you're using the `sticky: true` configuration and you find yourself in a situation where you need to write information through the proxy but you don't want the context to be stuck to master, you should use a `without_sticking` block:
|
100
104
|
|
@@ -145,6 +149,8 @@ production:
|
|
145
149
|
# add a makara subconfig
|
146
150
|
makara:
|
147
151
|
|
152
|
+
# optional id to identify the proxy with this configuration for stickiness
|
153
|
+
id: mysql
|
148
154
|
# the following are default values
|
149
155
|
blacklist_duration: 5
|
150
156
|
master_ttl: 5
|
@@ -167,6 +173,7 @@ Let's break this down a little bit. At the top level of your config you have the
|
|
167
173
|
Following the adapter choice is all the standard configurations (host, port, retry, database, username, password, etc). With all the standard configurations provided, you can now provide the makara subconfig.
|
168
174
|
|
169
175
|
The makara subconfig sets up the proxy with a few of its own options, then provides the connection list. The makara options are:
|
176
|
+
* id - an identifier for the proxy, used for sticky behaviour and context. The default is to use a MD5 hash of the configuration contents, so if you are setting `sticky` to true, it's a good idea to also set an `id`. Otherwise any stuck connections will be cleared if the configuration changes (as the default MD5 hash id would change as well)
|
170
177
|
* blacklist_duration - the number of seconds a node is blacklisted when a connection failure occurs
|
171
178
|
* disable_blacklist - do not blacklist node at any error, useful in case of one master
|
172
179
|
* sticky - if a node should be stuck to once it's used during a specific context
|
@@ -275,16 +282,7 @@ def handle_request_after_third_party_record_creation
|
|
275
282
|
end
|
276
283
|
```
|
277
284
|
|
278
|
-
Similarly, if you have a third party service which will conduct a generic request against your Rack app, you can force master via a query param:
|
279
|
-
|
280
|
-
```ruby
|
281
|
-
def send_url_to_third_party
|
282
|
-
context = Makara::Context.get_current
|
283
|
-
ThirdParty.read_from_here!("http://mysite.com/path/to/resource?_mkra_ctxt=#{context}")
|
284
|
-
end
|
285
|
-
```
|
286
|
-
|
287
285
|
## Todo
|
288
286
|
|
289
|
-
*
|
287
|
+
* Support for providing context as query param
|
290
288
|
* More real world examples
|
data/gemfiles/ar50.gemfile
CHANGED
@@ -9,7 +9,7 @@ gem 'activerecord', '~> 5.0.0'
|
|
9
9
|
gem 'rspec'
|
10
10
|
gem 'rack'
|
11
11
|
gem 'timecop'
|
12
|
-
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'mysql2', '~> 0.4.10', :platform => :ruby
|
13
13
|
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
14
14
|
gem 'pg', '0.21.0', :platform => :ruby
|
15
15
|
gem 'activerecord-jdbcpostgresql-adapter', :platform => :jruby
|
data/gemfiles/ar51.gemfile
CHANGED
@@ -9,7 +9,7 @@ gem 'activerecord', '~> 5.1.0'
|
|
9
9
|
gem 'rspec'
|
10
10
|
gem 'rack'
|
11
11
|
gem 'timecop'
|
12
|
-
gem 'mysql2', :platform => :ruby
|
12
|
+
gem 'mysql2', '~> 0.4.10', :platform => :ruby
|
13
13
|
gem 'activerecord-jdbcmysql-adapter', :platform => :jruby
|
14
14
|
gem 'pg', '0.21.0', :platform => :ruby
|
15
15
|
gem 'activerecord-jdbcpostgresql-adapter', :platform => :jruby
|
data/lib/makara.rb
CHANGED
@@ -6,6 +6,7 @@ module Makara
|
|
6
6
|
autoload :ConfigParser, 'makara/config_parser'
|
7
7
|
autoload :ConnectionWrapper, 'makara/connection_wrapper'
|
8
8
|
autoload :Context, 'makara/context'
|
9
|
+
autoload :Cookie, 'makara/cookie'
|
9
10
|
autoload :ErrorHandler, 'makara/error_handler'
|
10
11
|
autoload :Middleware, 'makara/middleware'
|
11
12
|
autoload :Pool, 'makara/pool'
|
data/lib/makara/cache.rb
CHANGED
@@ -1,53 +1,17 @@
|
|
1
|
-
require 'active_support/core_ext/object/try'
|
2
|
-
|
3
|
-
# The Makara Cache should have access to your centralized cache store.
|
4
|
-
# It serves the purpose of storing the Makara::Context across requests, servers, etc.
|
5
|
-
|
6
1
|
module Makara
|
7
2
|
module Cache
|
8
3
|
|
9
|
-
autoload :MemoryStore, 'makara/cache/memory_store'
|
10
|
-
autoload :NoopStore, 'makara/cache/noop_store'
|
11
|
-
|
12
4
|
class << self
|
13
5
|
|
14
6
|
def store=(store)
|
15
|
-
|
7
|
+
Makara::Logging::Logger.log deprecation_warning, :warn
|
16
8
|
end
|
17
9
|
|
18
|
-
|
19
|
-
store.try(:read, key)
|
20
|
-
end
|
10
|
+
private
|
21
11
|
|
22
|
-
def
|
23
|
-
|
12
|
+
def deprecation_warning
|
13
|
+
"Makara's context is no longer persisted in a backend cache, a cookie store is used by default.\nSetting the Makara::Cache.store won't have any effects."
|
24
14
|
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def store
|
29
|
-
case @store
|
30
|
-
when :noop, :null
|
31
|
-
@store = Makara::Cache::NoopStore.new
|
32
|
-
when :memory
|
33
|
-
@store = Makara::Cache::MemoryStore.new
|
34
|
-
else
|
35
|
-
if defined?(Rails)
|
36
|
-
|
37
|
-
# in AR3 RAILS_CACHE may not be loaded if the full env is not present
|
38
|
-
# Rails.cache will throw an error because of it.
|
39
|
-
if ActiveRecord::VERSION::MAJOR < 4
|
40
|
-
@store ||= Rails.cache if defined?(RAILS_CACHE)
|
41
|
-
else
|
42
|
-
@store ||= Rails.cache if defined?(Rails)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
@store
|
48
|
-
end
|
49
|
-
|
50
15
|
end
|
51
|
-
|
52
16
|
end
|
53
17
|
end
|
data/lib/makara/config_parser.rb
CHANGED
@@ -127,7 +127,7 @@ module Makara
|
|
127
127
|
# NOTE: Does not use ENV['DATABASE_URL']
|
128
128
|
def self.merge_and_resolve_default_url_config(config)
|
129
129
|
if ENV['DATABASE_URL']
|
130
|
-
Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
|
130
|
+
Makara::Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
|
131
131
|
end
|
132
132
|
return config unless config.key?(:url)
|
133
133
|
url = config[:url]
|
@@ -144,7 +144,7 @@ module Makara
|
|
144
144
|
@config = config.symbolize_keys
|
145
145
|
@makara_config = DEFAULTS.merge(@config[:makara] || {})
|
146
146
|
@makara_config = @makara_config.symbolize_keys
|
147
|
-
@id = @makara_config[:id]
|
147
|
+
@id = sanitize_id(@makara_config[:id])
|
148
148
|
end
|
149
149
|
|
150
150
|
|
@@ -197,5 +197,15 @@ module Makara
|
|
197
197
|
|
198
198
|
end
|
199
199
|
|
200
|
+
|
201
|
+
def sanitize_id(id)
|
202
|
+
return if id.nil? || id.empty?
|
203
|
+
|
204
|
+
id.gsub(/[\|:]/, '').tap do |sanitized_id|
|
205
|
+
if sanitized_id.size != id.size
|
206
|
+
Makara::Logging::Logger.log "Proxy id '#{id}' changed to '#{sanitized_id}'", :warn
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
200
210
|
end
|
201
211
|
end
|
data/lib/makara/context.rb
CHANGED
@@ -1,63 +1,134 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
|
-
# Keeps track of the current
|
4
|
-
# If a new context is needed it can be generated via Makara::Context.generate
|
5
|
-
|
3
|
+
# Keeps track of the current stickiness state for different Makara proxies
|
6
4
|
module Makara
|
7
5
|
class Context
|
8
|
-
|
6
|
+
attr_accessor :stored_data, :staged_data
|
7
|
+
attr_reader :current_timestamp
|
8
|
+
|
9
|
+
def initialize(context_data)
|
10
|
+
@stored_data = context_data
|
11
|
+
@staged_data = {}
|
12
|
+
@dirty = @was_dirty = false
|
13
|
+
|
14
|
+
freeze_time
|
15
|
+
end
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
|
17
|
+
def stage(proxy_id, ttl)
|
18
|
+
staged_data[proxy_id] = [staged_data[proxy_id].to_f, ttl.to_f].max
|
19
|
+
end
|
20
|
+
|
21
|
+
def stuck?(proxy_id)
|
22
|
+
stored_data[proxy_id] && !expired?(stored_data[proxy_id])
|
23
|
+
end
|
24
|
+
|
25
|
+
def staged?(proxy_id)
|
26
|
+
staged_data.key?(proxy_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def release(proxy_id)
|
30
|
+
@dirty ||= !!stored_data.delete(proxy_id)
|
31
|
+
staged_data.delete(proxy_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def release_all
|
35
|
+
if self.stored_data.any?
|
36
|
+
self.stored_data = {}
|
37
|
+
# We need to track a change made to the current stored data
|
38
|
+
# so we can commit it later
|
39
|
+
@dirty = true
|
13
40
|
end
|
41
|
+
self.staged_data = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Stores the staged data with an expiration time based on the current time,
|
45
|
+
# and clears any expired entries. Returns true if any changes were made to
|
46
|
+
# the current store
|
47
|
+
def commit
|
48
|
+
freeze_time
|
49
|
+
release_expired
|
50
|
+
store_staged_data
|
51
|
+
clean
|
14
52
|
|
15
|
-
|
16
|
-
|
53
|
+
was_dirty?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def freeze_time
|
59
|
+
@current_timestamp = Time.now.to_f
|
60
|
+
end
|
61
|
+
|
62
|
+
# Indicates whether there have been changes to the context that need
|
63
|
+
# to be persisted when the request finishes
|
64
|
+
def dirty?
|
65
|
+
@dirty
|
66
|
+
end
|
67
|
+
|
68
|
+
def was_dirty?
|
69
|
+
@was_dirty
|
70
|
+
end
|
71
|
+
|
72
|
+
def expired?(timestamp)
|
73
|
+
timestamp <= current_timestamp
|
74
|
+
end
|
75
|
+
|
76
|
+
def release_expired
|
77
|
+
previous_size = stored_data.size
|
78
|
+
stored_data.delete_if { |_, timestamp| expired?(timestamp) }
|
79
|
+
@dirty ||= previous_size != stored_data.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def store_staged_data
|
83
|
+
staged_data.each do |proxy_id, ttl|
|
84
|
+
if ttl > 0 && self.stored_data[proxy_id].to_f < current_timestamp + ttl
|
85
|
+
self.stored_data[proxy_id] = current_timestamp + ttl
|
86
|
+
@dirty = true
|
87
|
+
end
|
17
88
|
end
|
89
|
+
end
|
18
90
|
|
19
|
-
|
20
|
-
|
21
|
-
|
91
|
+
def clean
|
92
|
+
@was_dirty = dirty?
|
93
|
+
@dirty = false
|
94
|
+
@staged_data = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
class << self
|
98
|
+
def set_current(context_data)
|
99
|
+
set(:makara_current_context, new(context_data))
|
22
100
|
end
|
23
101
|
|
24
|
-
|
25
|
-
|
102
|
+
# Called by `Proxy#stick_to_master!` to use master in subsequent requests
|
103
|
+
def stick(proxy_id, ttl)
|
104
|
+
current.stage(proxy_id, ttl)
|
26
105
|
end
|
27
106
|
|
28
|
-
def
|
29
|
-
|
107
|
+
def stuck?(proxy_id)
|
108
|
+
current.staged?(proxy_id) || current.stuck?(proxy_id)
|
30
109
|
end
|
31
110
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
111
|
+
def next
|
112
|
+
if current.commit
|
113
|
+
current.stored_data
|
35
114
|
end
|
36
115
|
end
|
37
116
|
|
38
|
-
|
39
|
-
|
40
|
-
# when they're asking whether they should be stuck to master.
|
41
|
-
def stick(context, config_id, ttl)
|
42
|
-
Makara::Cache.write(cache_key_for(context, config_id), '1', ttl)
|
117
|
+
def release(proxy_id)
|
118
|
+
current.release(proxy_id)
|
43
119
|
end
|
44
120
|
|
45
|
-
def
|
46
|
-
|
121
|
+
def release_all
|
122
|
+
current.release_all
|
47
123
|
end
|
48
124
|
|
49
125
|
protected
|
50
|
-
|
51
|
-
|
52
|
-
fetch(:makara_previously_sticky) { Hash.new }
|
53
|
-
end
|
54
|
-
|
55
|
-
def cache_key_for(context, config_id)
|
56
|
-
"makara::#{context}-#{config_id}"
|
126
|
+
def current
|
127
|
+
fetch(:makara_current_context) { new({}) }
|
57
128
|
end
|
58
129
|
|
59
130
|
def fetch(key)
|
60
|
-
get(key) || set(key,yield)
|
131
|
+
get(key) || set(key, yield)
|
61
132
|
end
|
62
133
|
|
63
134
|
if Thread.current.respond_to?(:thread_variable_get)
|
@@ -65,19 +136,18 @@ module Makara
|
|
65
136
|
Thread.current.thread_variable_get(key)
|
66
137
|
end
|
67
138
|
|
68
|
-
def set(key,value)
|
69
|
-
Thread.current.thread_variable_set(key,value)
|
139
|
+
def set(key, value)
|
140
|
+
Thread.current.thread_variable_set(key, value)
|
70
141
|
end
|
71
142
|
else
|
72
143
|
def get(key)
|
73
144
|
Thread.current[key]
|
74
145
|
end
|
75
146
|
|
76
|
-
def set(key,value)
|
147
|
+
def set(key, value)
|
77
148
|
Thread.current[key]=value
|
78
149
|
end
|
79
150
|
end
|
80
|
-
|
81
151
|
end
|
82
152
|
end
|
83
153
|
end
|