freno-client 0.3.0 → 0.8.1

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
- SHA1:
3
- metadata.gz: ad998991ce9e268582944b14bcad1f95b3f256ae
4
- data.tar.gz: 858a1e5eab82112e90e44559cc64f14a57eef065
2
+ SHA256:
3
+ metadata.gz: 3f680a8adbf86c47eff50bcc58ae7b79993ef3f2f3c57ad99c9512937f92ba80
4
+ data.tar.gz: 50cf87848d1284d171457112ed5a297fd063532f78ef0399e5d669b1bc3ded33
5
5
  SHA512:
6
- metadata.gz: d550f31be8158f7ec916d3b76475c8ec15e04e6039cc34473049ad276097397df72c1139bfd421175b1792eba997763fffadcd9023d4860eef6096f23040d024
7
- data.tar.gz: c6c3826a47449958d2030fc8f8904eeba9cee2aeb72890fa6803d0c287542c13814df6934b0b47d03cb418544cef7a856623591700b4d149ca5ba4df0edd261b
6
+ metadata.gz: 6037e296652844e51cb469d3d6ca6dbb55e2baaa08c8c726b2885ad1b3ba6dd21857a85562d5b1491fb6b6cb86859ddc5fec0420f05089bb0c871c6ac69713cb
7
+ data.tar.gz: 352a0abcea1e4fb28d2e920812a1d95a8051b62e52ff8a1cbaf05340aff3409150ad74d6292461908e4c7a9408dce7507910ff6ae4448f7cf62a9f1c048cd0f9
data/.gitignore CHANGED
@@ -8,6 +8,5 @@
8
8
  /pkg/
9
9
  /spec/reports/
10
10
  /tmp/
11
- /bin/
12
11
  /vendor/gems
13
12
  .ruby-version
@@ -0,0 +1,179 @@
1
+ require: rubocop-performance
2
+
3
+ AllCops:
4
+ DisabledByDefault: true
5
+ TargetRubyVersion: 2.5
6
+
7
+ Bundler/DuplicatedGem:
8
+ Enabled: true
9
+ Bundler/OrderedGems:
10
+ Enabled: true
11
+
12
+ Layout/BlockAlignment:
13
+ Enabled: true
14
+ Layout/BlockEndNewline:
15
+ Enabled: true
16
+ Layout/ConditionPosition:
17
+ Enabled: true
18
+ Layout/DefEndAlignment:
19
+ Enabled: true
20
+ Layout/EndOfLine:
21
+ Enabled: true
22
+ Layout/IndentationStyle:
23
+ Enabled: true
24
+ Layout/InitialIndentation:
25
+ Enabled: true
26
+ Layout/SpaceAfterColon:
27
+ Enabled: true
28
+ Layout/SpaceAfterComma:
29
+ Enabled: true
30
+ Layout/SpaceAfterMethodName:
31
+ Enabled: true
32
+ Layout/SpaceAfterNot:
33
+ Enabled: true
34
+ Layout/SpaceAfterSemicolon:
35
+ Enabled: true
36
+ Layout/SpaceAroundBlockParameters:
37
+ Enabled: true
38
+ Layout/SpaceAroundEqualsInParameterDefault:
39
+ Enabled: true
40
+ Layout/SpaceInsideArrayPercentLiteral:
41
+ Enabled: true
42
+ Layout/SpaceInsideParens:
43
+ Enabled: true
44
+ Layout/SpaceInsideRangeLiteral:
45
+ Enabled: true
46
+ Layout/TrailingEmptyLines:
47
+ Enabled: true
48
+ Layout/TrailingWhitespace:
49
+ Enabled: true
50
+
51
+ Lint/CircularArgumentReference:
52
+ Enabled: true
53
+ Lint/Debugger:
54
+ Enabled: true
55
+ Lint/DeprecatedClassMethods:
56
+ Enabled: true
57
+ Lint/DuplicateHashKey:
58
+ Enabled: true
59
+ Lint/DuplicateMethods:
60
+ Enabled: true
61
+ Lint/EachWithObjectArgument:
62
+ Enabled: true
63
+ Lint/ElseLayout:
64
+ Enabled: true
65
+ Lint/EmptyEnsure:
66
+ Enabled: true
67
+ Lint/EmptyInterpolation:
68
+ Enabled: true
69
+ Lint/EnsureReturn:
70
+ Enabled: true
71
+ Lint/FlipFlop:
72
+ Enabled: true
73
+ Lint/FloatOutOfRange:
74
+ Enabled: true
75
+ Lint/FormatParameterMismatch:
76
+ Enabled: true
77
+ Lint/LiteralInInterpolation:
78
+ Enabled: true
79
+ Lint/Loop:
80
+ Enabled: true
81
+ Lint/NextWithoutAccumulator:
82
+ Enabled: true
83
+ Lint/RandOne:
84
+ Enabled: true
85
+ Lint/RedundantSplatExpansion:
86
+ Enabled: true
87
+ Lint/RedundantStringCoercion:
88
+ Enabled: true
89
+ Lint/RequireParentheses:
90
+ Enabled: true
91
+ Lint/RescueException:
92
+ Enabled: true
93
+ Lint/UnderscorePrefixedVariableName:
94
+ Enabled: true
95
+ Lint/UnreachableCode:
96
+ Enabled: true
97
+ Lint/UselessComparison:
98
+ Enabled: true
99
+ Lint/UselessSetterCall:
100
+ Enabled: true
101
+ Lint/Void:
102
+ Enabled: true
103
+
104
+ Naming/AsciiIdentifiers:
105
+ Enabled: true
106
+ Naming/ClassAndModuleCamelCase:
107
+ Enabled: true
108
+ Naming/FileName:
109
+ Enabled: true
110
+ Naming/MethodName:
111
+ Enabled: true
112
+
113
+ Performance/Count:
114
+ Enabled: true
115
+ Performance/Detect:
116
+ Enabled: true
117
+ Performance/DoubleStartEndWith:
118
+ Enabled: true
119
+ Performance/EndWith:
120
+ Enabled: true
121
+ Performance/FlatMap:
122
+ Enabled: true
123
+ Performance/RedundantMerge:
124
+ Enabled: true
125
+ MaxKeyValuePairs: 1
126
+ Performance/ReverseEach:
127
+ Enabled: true
128
+ Performance/Size:
129
+ Enabled: true
130
+ Performance/StartWith:
131
+ Enabled: true
132
+
133
+ Security/Eval:
134
+ Enabled: true
135
+
136
+ Style/ArrayJoin:
137
+ Enabled: true
138
+ Style/BeginBlock:
139
+ Enabled: true
140
+ Style/BlockComments:
141
+ Enabled: true
142
+ Style/CaseEquality:
143
+ Enabled: true
144
+ Style/CharacterLiteral:
145
+ Enabled: true
146
+ Style/ClassMethods:
147
+ Enabled: true
148
+ Style/DefWithParentheses:
149
+ Enabled: true
150
+ Style/EndBlock:
151
+ Enabled: true
152
+ Style/For:
153
+ Enabled: true
154
+ Style/HashSyntax:
155
+ Enabled: true
156
+ EnforcedStyle: ruby19
157
+ Style/LambdaCall:
158
+ Enabled: true
159
+ Style/MethodCallWithoutArgsParentheses:
160
+ Enabled: true
161
+ Style/MethodDefParentheses:
162
+ Enabled: true
163
+ Style/MultilineIfThen:
164
+ Enabled: true
165
+ Style/NilComparison:
166
+ Enabled: true
167
+ Style/Not:
168
+ Enabled: true
169
+ Style/OneLineConditional:
170
+ Enabled: true
171
+ Style/RedundantSortBy:
172
+ Enabled: true
173
+ Style/Sample:
174
+ Enabled: true
175
+ Style/StabbyLambdaParentheses:
176
+ Enabled: true
177
+ Style/StringLiterals:
178
+ Enabled: true
179
+ EnforcedStyle: double_quotes
@@ -1,5 +1,10 @@
1
- sudo: false
2
1
  language: ruby
3
2
  rvm:
4
- - 2.4.1
5
- before_install: gem install bundler -v 1.14.6
3
+ - 2.5
4
+ - 2.6
5
+ - 2.7
6
+ - ruby-head
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: ruby-head
10
+ script: bin/test
@@ -11,9 +11,9 @@ Please note that this project is released with a [Contributor Code of Conduct][c
11
11
 
12
12
  ## Submitting a pull request
13
13
 
14
- 0. [Fork][fork] and clone the freno-clientsitory
15
- 0. Configure and install the dependencies: `script/bootstrap`
16
- 0. Make sure the tests pass on your machine: `rake`
14
+ 0. [Fork][fork] and clone the freno-client repository
15
+ 0. Configure and install the dependencies: `bin/setup`
16
+ 0. Make sure the tests pass on your machine: `bin/test`
17
17
  0. Create a new branch: `git checkout -b my-branch-name`
18
18
  0. Make your change, add tests, and make sure the tests still pass
19
19
  0. Push to your fork and [submit a pull request][pr]
data/Gemfile CHANGED
@@ -1,3 +1,14 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem "rake"
7
+ end
8
+
9
+ group :test do
10
+ gem "minitest", ">= 5"
11
+ gem "mocha"
12
+ gem "rubocop", "~> 0.85.1", require: false
13
+ gem "rubocop-performance", require: false
14
+ end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Freno::Client
1
+ # Freno Client [![Build Status](https://travis-ci.org/github/freno-client.svg?branch=master)](https://travis-ci.org/github/github/freno-client)
2
2
 
3
- A ruby client for [Freno](https://github.com/github/freno): the cooperative, highly available throttler service.
3
+ A ruby client and throttling library for [Freno](https://github.com/github/freno): the cooperative, highly available throttler service.
4
4
 
5
5
  ## Current status
6
6
 
@@ -29,7 +29,7 @@ Or install it yourself as:
29
29
  To start using the client, give it a faraday instance pointing to Freno's base URL.
30
30
 
31
31
  ```ruby
32
- require 'freno/client'
32
+ require "freno/client"
33
33
 
34
34
  FRENO_URL = "http://freno.domain.com:8111"
35
35
  faraday = Faraday.new(FRENO_URL)
@@ -175,10 +175,187 @@ freno = Freno::Client.new(faraday) do |client|
175
175
  end
176
176
  ```
177
177
 
178
+ ### Throttler objects
179
+
180
+ Apart from the operations above, freno-client comes with `Freno::Throttler`, a ruby library for throttling. You can use it in the following way:
181
+
182
+ ```ruby
183
+ require "freno/throttler"
184
+
185
+ client = Freno::Client.new(faraday)
186
+ throttler = Freno::Throttler.new(client: client, app: :my_app)
187
+ context = :my_cluster
188
+
189
+ bid_data_set.each_slice(SLICE_SIZE) do |slice|
190
+ throttler.throttle(context) do
191
+ update(slice)
192
+ end
193
+ end
194
+ ```
195
+
196
+ In the above example, `Freno::Throttler#throttle(context, &block)` will check freno to determine whether is OK to proceed with the given block. If so, the block will be executed immediately, otherwise the throttler will sleep and try
197
+ again.
198
+
199
+ #### Throttler configuration
200
+
201
+ ```ruby
202
+ module Freno
203
+ class Throttler
204
+
205
+ DEFAULT_WAIT_SECONDS = 0.5
206
+ DEFAULT_MAX_WAIT_SECONDS = 10
207
+
208
+ def initialize(client: nil,
209
+ app: nil,
210
+ mapper: Mapper::Identity,
211
+ instrumenter: Instrumenter::Noop,
212
+ circuit_breaker: CircuitBreaker::Noop,
213
+ wait_seconds: DEFAULT_WAIT_SECONDS,
214
+ max_wait_seconds: DEFAULT_MAX_WAIT_SECONDS)
215
+
216
+
217
+ @client = client
218
+ @app = app
219
+ @mapper = mapper
220
+ @instrumenter = instrumenter
221
+ @circuit_breaker = circuit_breaker
222
+ @wait_seconds = wait_seconds
223
+ @max_wait_seconds = max_wait_seconds
224
+
225
+ yield self if block_given?
226
+
227
+ validate_args
228
+ end
229
+
230
+ ...
231
+ end
232
+ end
233
+ ```
234
+
235
+ A Throttler instance will make calls to freno on behalf of the given `app`,
236
+ using the given `client` (an instance of `Freno::Client`).
237
+
238
+ You optionally provide the time you want the throttler to sleep in case the check to freno fails, this is `wait_seconds`.
239
+
240
+ If replication lags badly, you can control until when you want to keep sleeping
241
+ and retrying the check by setting `max_wait_seconds`. When that times out, the throttle will raise a `Freno::Throttler::WaitedTooLong` error.
242
+
243
+ #### Instrumenting the throttler
244
+
245
+ You can also configure the throttler with an `instrumenter` collaborator to subscribe to events happening during the `throttle` call.
246
+
247
+ An instrumenter is an object that responds to `instrument(event_name, payload = {})` to receive events from the throttler. One could use `ActiveSupport::Notifications` as an instrumenter and subscribe to "freno.*" events somewhere else in the application, or implement one like the following to push some metrics to a stats system.
248
+
249
+ ```ruby
250
+ class StatsInstrumenter
251
+
252
+ attr_reader :stats
253
+
254
+ def initialize(stats:)
255
+ @stats = stats
256
+ end
257
+
258
+ def instrument(event_name, payload)
259
+ method = event_name.sub("throttler.", "")
260
+ send(method, payload) if respond_to?(method)
261
+ end
262
+
263
+ def called(payload)
264
+ increment("throttler.called", tags: extract_tags(payload))
265
+ end
266
+
267
+ def waited(payload)
268
+ stats.histogram("throttler.waited", payload[:waited], tags: extract_tags(payload))
269
+ end
270
+
271
+ ...
272
+
273
+ def circuit_open(payload)
274
+ stats.increment("throttler.circuit_open", tags: extract_tags(payload))
275
+ end
276
+
277
+ private
278
+
279
+ def extract_tags(payload)
280
+ cluster_names = payload[:store_names] || []
281
+ cluster_tags = cluster_names.map{ |cluster_name| "cluster:#{cluster_name}" }
282
+ end
283
+ end
284
+ ```
285
+
286
+ #### Adding resiliency
287
+
288
+ The throttler can also receive a `circuit_breaker` object to implement resiliency.
289
+
290
+ With that information it receives, the circuit breaker determines whether or not to allow the next request. A circuit is said to be open when the next request is not allowed; and it's said to be closed when the next request is allowed
291
+
292
+ If the throttler waited too long, or an unexpected error happened; the circuit breaker will receive a `failure`. If in contrast it succeeded, the circuit breaker will receive a `success` message.
293
+
294
+ Once the circuit is open, the throttler will not try to throttle calls, an instead throw a `Freno::Throttler::CircuitOpen`
295
+
296
+ The following is a simple per-process circuit breaker implementation:
297
+
298
+ ```ruby
299
+ class MemoryCircuitBreaker
300
+
301
+ DEFAULT_CIRCUIT_RETRY_INTERVAL = 10
302
+
303
+ def initialize(circuit_retry_interval: DEFAULT_CIRCUIT_RETRY_INTERVAL)
304
+ @circuit_closed = true
305
+ @last_failure = nil
306
+ @circuit_retry_interval = circuit_retry_interval
307
+ end
308
+
309
+ def allow_request?
310
+ @circuit_closed || (Time.now - @last_failure) > @circuit_retry_interval
311
+ end
312
+
313
+ def success
314
+ @circuit_closed = true
315
+ end
316
+
317
+ def failure
318
+ @last_failure = Time.now
319
+ @circuit_closed = false
320
+ end
321
+ end
322
+ ```
323
+
324
+ #### Flexible throttling strategies
325
+
326
+ The throttler uses a `mapper` to determine, based on the context provided to `#throttle`, the clusters which replication delay needs to be checked.
327
+
328
+ By default the throttler uses `Mapper::Identity`, which expect the context to be the store name(s) to check:
329
+
330
+ ```ruby
331
+ # will check my_cluster's health
332
+ throttler.throttle(:my_cluster) { ... }
333
+ # will check the health of cluster_a and cluster_b and throttle if any of them is not OK.
334
+ throttler.throttle([:cluster_a, :cluster_b]) { ... }
335
+ ```
336
+
337
+ You can create your own mapper, which is just an callable object (like a Proc, or any other object that responds to `call(context)`). The following is a mapper that knows how to throttle access to certain tables and shards.
338
+
339
+
340
+ ```ruby
341
+ class ShardMapper
342
+ def call(context = {})
343
+ context.map do |table, shards|
344
+ DatabaseStructure.cluster_for(table, shards)
345
+ end
346
+ end
347
+ end
348
+
349
+ throttler = Freno::Throttler.new(client: freno, app: :my_app, mapper: ShardMapper.new)
350
+
351
+ throttler.throttle(:users => [1,2,3], :repositories => 5) do
352
+ perform_writes
353
+ end
354
+ ```
178
355
 
179
356
  ## Development
180
357
 
181
- After checking out the repo, run `script/bootstrap` to install dependencies. Then, run `script/test` to run the tests. You can also run `script/console` for an interactive prompt that will allow you to experiment.
358
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
182
359
 
183
360
  ## Contributing
184
361
 
@@ -189,13 +366,11 @@ This repository is open to [contributions](CONTRIBUTING.md). Contributors are ex
189
366
  If you are the current maintainer of this gem:
190
367
 
191
368
  1. Create a branch for the release: `git checkout -b cut-release-vx.y.z`
192
- 1. Make sure your local dependencies are up to date: `script/bootstrap`
193
- 1. Ensure that tests are green: `bundle exec rake test`
369
+ 1. Make sure your local dependencies are up to date: `bin/setup`
370
+ 1. Ensure that tests are green: `bin/test`
194
371
  1. Bump gem version in `lib/freno/client/version.rb`
195
372
  1. Merge a PR to github/freno-client containing the changes in the version file
196
- 1. Tag and push: `git tag vx.xx.xx; git push --tags`
197
- 1. Build the gem: `gem build freno-client`
198
- 1. Push to rubygems.org: `gem push freno-client-x.y.z.gem`
373
+ 1. Run `bin/release`
199
374
 
200
375
  ## License
201
376