freno-client 0.3.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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