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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4dc4867024ed5527a9b0a499e4daff0c9c4bac5e
4
- data.tar.gz: 170743d2e7f735b39fbcb477657643918c66cd32
3
+ metadata.gz: 942671e3f14754da500b275f0da2354cef9d7d0e
4
+ data.tar.gz: bfcd3be83e4101f627d10c0718afe41e5dae5b44
5
5
  SHA512:
6
- metadata.gz: 8da212514ba19441a154323990544bb18ce0a3a39ff133f227bab84790e48b747f254b53d9a59c2aa93cfad0f0367fd03a44d3bcbe94a17874e7f2b084bec468
7
- data.tar.gz: 731c52cd786a65d0a124edf94e1f721b69ec78cee0549d499b2cc35a5ac2210aa7ae87d8b22a3734829271ea538ed6b3cfb197fd1ebc2d4ce0f961d16e86afda
6
+ metadata.gz: f51e87da7797bbb503ebe60e1e719d498f2cc8020e09578a6b9d2ed2c770c7d879ca2f32d7b7ab4b224c76f0c5ba00c8dc48bc91cf806dd24be6299e5424e209
7
+ data.tar.gz: b1cacfad8294f8371f0c28ec6a76f0608ce9c0c23361cb256e9ba0b083bde43459ad2c18e575a166c6b3f258edbb562fc1ec7520f4653b5c30bcefdb9cef59ce
@@ -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.3.9 - 2013-03-20
5
- [Full Changelog](https://github.com/taskrabbit/makara/compare/v0.3.9...v0.3.10)
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 stickyness by keeping track of a context (sha). In a multi-instance environment it persists a context in a cache. If Rails is present it will automatically use Rails.cache. You can provide any kind of store as long as it responds to the methods required in [lib/makara/cache.rb](lib/makara/cache.rb).
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
- ```ruby
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
- To handle persistence of context across requests in a Rack app, makara provides a middleware. It lays a cookie named `_mkra_ctxt` which contains the current master context. If the next request is executed before the cookie expires, master will be used. If something occurs which naturally requires master on the second request, the context is changed and stored again.
70
+ #### Releasing stuck connections (clearing context)
73
71
 
74
- #### Changing Context
72
+ If you need to clear the current context, releasing any stuck connections, all you have to do is:
75
73
 
76
- If you need to change the makara context, releasing any stuck connections, all you have to do is:
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
- ctx = Makara::Context.generate # or any unique sha
80
- Makara::Context.set_current ctx
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
- ### Forcing Master
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
- write_to_cache = true # or false
93
- proxy.stick_to_master!(write_to_cache)
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
- ### Skipping the Stickiness
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
- * Cookie based cache store?
287
+ * Support for providing context as query param
290
288
  * More real world examples
@@ -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
@@ -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
@@ -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'
@@ -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
- @store = store
7
+ Makara::Logging::Logger.log deprecation_warning, :warn
16
8
  end
17
9
 
18
- def read(key)
19
- store.try(:read, key)
20
- end
10
+ private
21
11
 
22
- def write(key, value, ttl)
23
- store.try(:write, key, value, :expires_in => ttl.to_i)
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
@@ -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
@@ -1,63 +1,134 @@
1
1
  require 'digest/md5'
2
2
 
3
- # Keeps track of the current and previous context (hexdigests)
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
- class << self
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
- def generate(seed = nil)
11
- seed ||= "#{Time.now.to_i}#{Thread.current.object_id}#{rand(99999)}"
12
- Digest::MD5.hexdigest(seed)
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
- def get_previous
16
- fetch(:makara_context_previous) { generate }
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
- def set_previous(context)
20
- previously_sticky.clear
21
- set(:makara_context_previous,context)
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
- def get_current
25
- fetch(:makara_context_current) { generate }
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 set_current(context)
29
- set(:makara_context_current,context)
107
+ def stuck?(proxy_id)
108
+ current.staged?(proxy_id) || current.stuck?(proxy_id)
30
109
  end
31
110
 
32
- def previously_stuck?(config_id)
33
- previously_sticky.fetch(config_id) do
34
- stuck?(Makara::Context.get_previous, config_id)
111
+ def next
112
+ if current.commit
113
+ current.stored_data
35
114
  end
36
115
  end
37
116
 
38
- # Called by `Proxy#stick_to_master!` to stick subsequent requests to
39
- # master. They'll see the current context as their previous context
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 stuck?(context, config_id)
46
- !!Makara::Cache.read(cache_key_for(context, config_id))
121
+ def release_all
122
+ current.release_all
47
123
  end
48
124
 
49
125
  protected
50
-
51
- def previously_sticky
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