redis-ick 0.0.3 → 0.0.4

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
2
  SHA1:
3
- metadata.gz: 985bcc31664985af1016a12b0b45e015f6307b4c
4
- data.tar.gz: f45617138cd637aeeafac5e6cc31fe3e103dc814
3
+ metadata.gz: 67d1648e28624b1ed5c0a9f475412deb102f2b5b
4
+ data.tar.gz: 97754295aead271f4748adef41f93eee3e081b11
5
5
  SHA512:
6
- metadata.gz: ed52b22601623951ad500d91680910db9411829cf3792cbff8f435bd17536c2d5324285159f262b926f9d3be719b33d9293ed675f6ee10219a4d522d731356be
7
- data.tar.gz: 6950898f2e07b6e678b1add4238cb66264c1f11740707fcd1dc0065b76d9e89a758c7c1eab36c2c31f7ae3dd5cf7ff2d8c2a37596136433be8e6b3ab8cf13575
6
+ metadata.gz: 020f83530d8dc2579d4343d32fdceec98dc1575f9137d2e0678ec0484a36fbfad14c3222cc1bddffa3548e8aa20bcffb0dd25b6ffc2b47c90bf76428c7405e70
7
+ data.tar.gz: e000d81edb08fefa4204c167d0adb3aae8f773892222681de2498e947e41a1af57370c83d3c29e2b5be504b3d2541c69836c8f1dc41b43c5d2301dabd1eb3b81
data/.rubocop.yml CHANGED
@@ -1,6 +1,147 @@
1
- inherit_from: .rubocop_todo.yml
1
+
2
2
  AllCops:
3
3
  Include:
4
4
  - Rakefile
5
5
  - Gemfile
6
6
  - '*.gemspec'
7
+
8
+ # Broadly speaking, test code gets a pass for most of the Metrics family.
9
+ #
10
+ # IMO test code is not the place get pedantic about class length,
11
+ # method complexity, etc. One should be encouraged to add more tests
12
+ # with minimal friction, not forced to make a hard choice between
13
+ # cutting tests or splitting up my test suites.
14
+ #
15
+ Metrics/ClassLength:
16
+ Max: 400
17
+ Exclude:
18
+ - 'test/**/*.rb'
19
+
20
+ # I like this Metric in principle, but I don't like the default max of
21
+ # 15.
22
+ #
23
+ # Also, as per Metrics/ClassLength IMO this kind of limit should not
24
+ # apply to test code (I get up to 318 over there).
25
+ #
26
+ Metrics/AbcSize:
27
+ Max: 30
28
+ Exclude:
29
+ - 'test/**/*.rb'
30
+
31
+ # I like this Metric in principle, but I don't like the default max of
32
+ # 10.
33
+ #
34
+ # Also, as per Metrics/ClassLength IMO this kind of limit should not
35
+ # apply to test code.
36
+ #
37
+ Metrics/MethodLength:
38
+ Max: 50
39
+ Exclude:
40
+ - 'test/**/*.rb'
41
+
42
+ # I put extra spaces in a lot of expressions for a lot of different
43
+ # reasons, including especially readability.
44
+ #
45
+ # I reject these cops.
46
+ #
47
+ Layout:
48
+ Enabled: false
49
+
50
+ # I like a lot of the Lint tests, but not these.
51
+ #
52
+ Lint/AmbiguousBlockAssociation: # obnoxiously rejects idiomatic Ruby
53
+ Enabled: false
54
+
55
+ # Ick#ickreserve returns an Array of Arrays.
56
+ #
57
+ # Performance/HashEachMethods complains that:
58
+ #
59
+ # ick.ickreserve('key',num).each do |message,score|
60
+ # ...
61
+ # end
62
+ #
63
+ # would be more efficient as a each_key loop.
64
+ #
65
+ # This cop inappropriately requires us to change working code to
66
+ # broken code. This is a known issue which the authors of Rubocop do
67
+ # not intend to change:
68
+ #
69
+ # https://github.com/bbatsov/rubocop/issues/4732
70
+ #
71
+ # My reaction is to blacklist this cop.
72
+ #
73
+ Performance/HashEachMethods:
74
+ Enabled: false
75
+
76
+ # This does no more than insist I type "format" instead of "sprintf",
77
+ # where the two are aliases.
78
+ #
79
+ Style/FormatString:
80
+ Enabled: false
81
+
82
+ # There is nothing wrong with Ruby 1.9 Hash syntax.
83
+ #
84
+ Style/HashSyntax:
85
+ Enabled: false
86
+
87
+ # No. Indeed, postfix if can often drive a line over 80 columns wide.
88
+ #
89
+ Style/IfUnlessModifier:
90
+ Enabled: false
91
+
92
+ # No. There is nothing wrong with "if !foo".
93
+ #
94
+ # As far as I'm concerned, "unless" is in poor taste because it means
95
+ # I have to think in English in two different logical senses - and
96
+ # English is a poor language for logical senses.
97
+ #
98
+ Style/NegatedIf:
99
+ Enabled: false
100
+
101
+ # "Do not use semicolons to terminate expressions."
102
+ #
103
+ # That's great when I terminate a single-line expression with a
104
+ # redundant semicolo because I forget I'm not using C.
105
+ #
106
+ # But when I'm using a semicolon to separate two expressions there is
107
+ # no other choice. So this really ought to be Style/OneExprPerLine,
108
+ # which I reject.
109
+ #
110
+ Style/Semicolon:
111
+ Enabled: false
112
+
113
+ # No.
114
+ #
115
+ # Some lines must have '\"'. It is ugly to use a mix of '"' and '\''
116
+ # in LoCs which are close to one another. Therefore, banning '"' if
117
+ # '"' is not strictly necessary drives visual inconsistency.
118
+ #
119
+ Style/StringLiterals:
120
+ Enabled: false
121
+
122
+ # This is the same kind of obnoxious pedantry which drove Hungarian
123
+ # Notation.
124
+ #
125
+ # The [] literal syntax is perfectly servicable and there is no point
126
+ # _tightly_ coupling it to the content of the array. That's why we
127
+ # have context-free grammers!
128
+ #
129
+ Style/SymbolArray:
130
+ Enabled: false
131
+
132
+ # Shockingly, this cop requires us to *OMIT*, not *INCLUDE* parens in
133
+ # ternery conditons.
134
+ #
135
+ # IMO this one is actively harmful in that it discourages attention to
136
+ # clarity and avoiding some nasty precedence surprises.
137
+ #
138
+ Style/TernaryParentheses:
139
+ Enabled: false
140
+
141
+ # I am a huge fan of using trailing commas when I break an argument
142
+ # list down one-per line.
143
+ #
144
+ # As such, I reject this test.
145
+ #
146
+ Style/TrailingCommaInLiteral:
147
+ Enabled: false
data/.travis.yml CHANGED
@@ -8,5 +8,7 @@ services:
8
8
  rvm:
9
9
  - 2.1.6
10
10
  script:
11
- - rubocop
11
+ - rubocop --version
12
+ - bundle exec rubocop --version
13
+ - bundle exec rubocop
12
14
  - bundle exec env REDIS_URL=redis://localhost:6379 rake test
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
4
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 ProsperWorks
3
+ Copyright (c) 2015-2017 ProsperWorks
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,45 +1,264 @@
1
- # Redis::Ick ![TravisCI](https://travis-ci.org/ProsperWorks/redis-ick.svg?branch=master)
1
+ # Redis::Ick An Indexing Queue ![TravisCI](https://travis-ci.org/ProsperWorks/redis-ick.svg?branch=master)
2
2
 
3
- Ick: An Indexing QUeue.
3
+ Redis::Ick implements a priority queue in Redis which supports:
4
4
 
5
- Redis::Ick implements a priority queue in Redis with two-phase commit
6
- and write-folding aka write-combining semantics.
5
+ * multiple producers
6
+ * write-folding
7
+ * two-phase commit for a single consumer
7
8
 
8
- Icks are well-suited for dirty lists in data sync systems.
9
+ Ick solves a variety of race condition and starvation issues which can
10
+ arise between the producers and the consumer.
9
11
 
10
- A Redis-based queue-like data structure for message passing in a
11
- many-producer/single-consumer pattern.
12
+ Icks are similar to (and built from) Redis sorted sets. They are
13
+ well-suited for dirty lists in data sync systems.
12
14
 
13
- Ick is compatible with Redis Cluster and RedisLabs Enterprise Cluster.
14
- Each Ick has a master key and any other Redis keys it uses use a
15
- prescriptive hash based on the master key.
15
+ Ick has been live in production at ProsperWorks since 2015-10-21. We
16
+ use them at the heart of our PG-to-ES and PG-to-Neo4j pipelines, for
17
+ data migration and repair, and a variety of other crawler systems.
16
18
 
17
- Ick offers write-folding semantics in which re-adding a member already
18
- in queue does not increase the size of the queue. It may, or may not,
19
- rearrange that member's position within the queue.
19
+ ## Background: The Original Pattern
20
20
 
21
- Ick is batchy on the consumer side with reliable delivery semantics
22
- using a two-phase protocol: it supports for reserving batches and
23
- later committing all, some, or none of them.
21
+ Long before Ick, our indexer queue was a simple Redis sorted set which
22
+ used current time for scores. It looked like:
24
23
 
25
- Note that members held in the reserve buffer by the consumer do *not*
26
- write-fold against members being added by producers.
24
+ # in any process whenever a document is dirtied
25
+ redis.zadd(queue_key,Time.now.to_f,document_id)
27
26
 
28
- Ick offers atomicity among producer and consumer operations by virtue
29
- of leveraging Lua-in-Redis.
27
+ # in the indexer process
28
+ batch = redis.zrangebyrank(queue_key,0,batch_size) # critical section start
29
+ process_batch_slowly(batch)
30
+ # burn down the queue only if the batch succeeded
31
+ redis.zrem(queue_key,*members_of(batch)) # critical section end
30
32
 
31
- Ick offers starvation-free semantics when scores are approximately the
32
- current time. When Ick performs write-folding, it always preserves
33
- the *lowest* score seen for a given message. Thus, in both the
34
- producer set and the consumer set, entries never move further away
35
- from the poppy end.
33
+ **Big Advantage**: Failover. Because we defer ZREM until after
34
+ success, when we fail in process_batch_slowly() (such as via an
35
+ exception or SIGKILL), all document_ids in the batch are still in
36
+ Redis. When the indexer process resumes, those document_ids will run
37
+ again.
36
38
 
37
- Ick supports only a single consumer: there is only one buffer for the
38
- two-phase pop protocol. If you need more than one consumer, shard
39
- messages across multiple Icks each of which routes to one consumer.
39
+ **Big Advantage**: Write Folding. Because we use Redis sorted sets,
40
+ when a document is dirtied twice in quick succession, we only get 1
41
+ entry in the queue. We change the timestamp but we do not end up
42
+ with 2 entries in the queue. Thus, the queue grows only in the number
43
+ of dirty _documents_ per unit time, not in the number of dirty
44
+ _operations_ per unit time. In a sense, the more we fall behind the
45
+ slower we fall.
40
46
 
41
- incept: 2015-10-01
42
- arch: https://goo.gl/V1g9I8
47
+ **Big Problem**: The Forgotten Dirtiness Problem. If some document is
48
+ dirtied a second time after the start of process_batch_slowly(), when
49
+ process_batch_slowly() end we will drop that document from the queue.
50
+ Thus, the document will be dirty but no longer in the queue!
51
+
52
+ **Small Problem**: The Hot Data Starvation Problem. Because we score
53
+ by time-of-dirtiness and we use ZRANGEBYRANK starting at 0, each batch
54
+ is the _coldest_ dirty documents. Most of the time this is a good
55
+ proxy for what we really care about: the _oldest_ dirty documents.
56
+ But when a document is re-dirtied, its old timestamp is replaced with
57
+ a new timestamp. In effect, it jumps from the cold end of the queue
58
+ to the hot end of the queue. If the queue is big enough that it is
59
+ always larger than one batch and a document is hot enough that it gets
60
+ updated in queue more often than our batches, the document will never
61
+ be popped out into a batch.
62
+
63
+ ## Background: The Intermediate Pattern
64
+
65
+ A year before Ick, August 2014, Gerald made a huge improvement which
66
+ mostly mitigated the Forgotten Dirtiness Problem:
67
+
68
+ # in any process: whenever a document is dirtied
69
+ redis.zadd(queue_key,Time.now.to_f,document_id)
70
+
71
+ # in the indexer process:
72
+ batch1 = redis.zrangebyrank(queue_key,0,batch_size)
73
+ process_batch_slowly(batch)
74
+ # burn down the queue only if the batch succeeded
75
+ batch2 = redis.zrangebyrank(queue_key,0,batch_size) # critical section start
76
+ unchanged_keys = batch1.keys - keys_whose_score_changed_in(batch1,batch2)
77
+ redis.zrem(queue_key,*members_of(unchanged_keys)) # critical section end
78
+
79
+ Gerald changed it so a second snapshot of the cold end of the queue is
80
+ taken after process_batch_slowly(). Only documents whose timestamps
81
+ did not change between the two snapshots are removed from the queue.
82
+
83
+ Notice how the critical section no longer includes
84
+ process_batch_slowly(). Instead it only spans two Redis ops and some
85
+ local set arithmetic which.
86
+
87
+ The critical section and the Forgotten Dirtiness Problem which it
88
+ causes is still there, but is much smaller. In practice we have
89
+ process_batch_slowly() taking minutes, but even in extreme situations
90
+ this critical section never took more than 3 seconds.
91
+
92
+ ## Proposal: The Ick Pattern
93
+
94
+ In October 2015, while reviewing the Forgotten Dirtiness problem, we
95
+ identified the Hot Data Starvation Problem. We developed Ick and
96
+ switched to this almost familiar pattern:
97
+
98
+ # in any process: whenever a document is dirtied
99
+ Ick.ickadd(redis,queue_key,Time.now.to_f,document_id)
100
+
101
+ # in the indexer process:
102
+ batch = Ick.ickreserve(redis,queue_key,batch_size)
103
+ process_batch_slowly(batch)
104
+ # burn down the queue only if the batch succeeded
105
+ Ick.ickcommit(redis,queue_key,*members_of(batch)) # critical section gone
106
+
107
+
108
+ Ick solves for failover via a two phase commit protocol between
109
+ **ickreserve** and **ickcommit**. If there is a failure during
110
+ process_batch_slowly(batch), the next time time we call **ickreserve**
111
+ we will just get the same batch - it will have resided unchanged in
112
+ the consumer set until we get happy and call **ickcommit**.
113
+
114
+ Ick solves the Forgotten Dirtiness Problem by virtue of
115
+ **ickreserve**’s implicit atomicity and the fact that **ickcommit** is
116
+ only ever called from the indexer and producers do not mutate the
117
+ consumer set.
118
+
119
+ Ick solves the Hot Data Starvation Problem by a subtle change in
120
+ ICKADD. Unlike ZADD, which overwrites the old score when a message is
121
+ re-added, or ZADD NX which always preserves the old score, ICKADD
122
+ always takes the _min_ of the old and new scores. Thus, Ick tracks
123
+ the first-known ditry time for a message even when there is time skew
124
+ in the producers. The longer entries stay in the consumer set, the
125
+ more they implicitly percolate toward the cold end regardless of how
126
+ many updates they receive. Ditto in the consumer set. Provided that
127
+ all producers make a best effort to use only current or future
128
+ timestamps when they call ICKADD, the ICKRESERVE batch will always
129
+ include the oldest entries and there will be no starvation.
130
+
131
+ Apology: I know that [Two-Phase
132
+ Commit](https://en.wikipedia.org/wiki/Two-phase_commit_protocol) has a
133
+ different technical meaning than what Ick does. Unfortunately I can't
134
+ find a better name for this very common failsafe queue pattern. I
135
+ suppose we could think of the Redis sorted set as the coordinator and
136
+ the consumer process as the (single) participant node and, generously,
137
+ Two-Phase Commit might be taken to describe Ick.
138
+
139
+
140
+ ## What is Ick?
141
+
142
+ An Ick is a collection of three Redis keys which all live on the same
143
+ [Redis hash slot](https://redis.io/topics/cluster-spec):
144
+
145
+ * version flag, a string
146
+ * producer set, a sorted set into which we flag keys as dirty with timestamps
147
+ * consumer set, a sorted set from which the indexer pulls batches to index
148
+
149
+ ### Ick defines 5 operations on this data via Lua on Redis:
150
+
151
+ * **ickdel**: removes all keys associated with a given Ick structure
152
+ * **ickstats**: returns a hash of stats including version and size
153
+ * **ickadd**: add a batch of members with scores to the producer set
154
+ * implements write-folding: a message can only appear once in the producer set
155
+ * when a member is re-added, it takes the lowest of 2 scores
156
+ * **ickreserve**: moves members from the producer set to the consumer
157
+ set until the consumer set is size N or the producer set is empty
158
+ * implements write-folding: a message can only appear once in the consumer set
159
+ * when a member-is re-added it takes the lowest of 2 scores
160
+ * returns the results as an array
161
+ * **ickcommit**: deletes members from the consumer set
162
+
163
+ Reminder: With few exceptions, all Redis commands are atomic and
164
+ transactional. This includes any Lua scripts such as those which
165
+ implement Ick. This atomicity guarantee is important to the
166
+ correctness of Ick, but because it is inherent in Redis/Lua, does not
167
+ appear explicitly in any of the Ick sources.
168
+
169
+ ## Fabulous Diagram
170
+
171
+ Here is a coarse dataflow for members moving through an Ick.
172
+
173
+ app
174
+ |
175
+ +-- **ickadd** --> producer set
176
+ |
177
+ +-- **ickreserve** --> consumer set
178
+ |
179
+ +-- **ickcommit** --> forgotten
180
+
181
+ ## Miscellanea
182
+
183
+ ### Ready for Redis Cluster
184
+
185
+ Even though one Ick uses three Redis keys, Ick is compatible with
186
+ Redis Cluster. At ProsperWorks we use it with RedisLabs Enterprise
187
+ Cluster.
188
+
189
+ Ick does some very tricky things to compute the producer set and
190
+ consumer set keys from the master key in a way which puts them all on
191
+ the same slot in both Redis Cluster and with RLEC's default
192
+ prescriptive hashing algorithm.
193
+
194
+ See [redis-key_hash](https://github.com/ProsperWorks/redis-key_hash)
195
+ for how test this.
196
+
197
+ ### Scalability
198
+
199
+ Ick supports only a single consumer: there is only one consumer set.
200
+
201
+ If your application need more than one consumer for throughput or
202
+ other reasons, you should shard across multiple Icks, each with one
203
+ consumer loop each.
204
+
205
+ This is exactly how we use Icks at ProsperWorks. Our application code
206
+ does not simply push to an individual Ick. We push to a bit of code
207
+ which knows that one "channel" is really N Icks. To select an Ick,
208
+ that code does a stable hash of our document_ids, modulo N.
209
+
210
+ This way, each Ick is able to dedupe across only its dedicated subset
211
+ of all messages.
212
+
213
+ We considered a more complicated Ick which supported multiple
214
+ consumers, but a lot of new problems come up once we take that step:
215
+ can one message be in multiple consumer sets? If not, what happens
216
+ when one consumer halts? How do we prevent the cold end of the
217
+ producer set from getting clogged up with messages destined for the
218
+ idle consumer?
219
+
220
+ We prefer handling those issues in higher-level code. Thus, Ick by
221
+ itself does not attempt to solve scalability.
222
+
223
+
224
+ ### Some Surprises Which Can Be Gotchas in Test
225
+
226
+ Because ICKADD uses write-folding semantics over the producer set,
227
+ ICKADD might or might not grow the total size of the queue.
228
+
229
+ ICKRESERVE is not a read-only operation. It can mutate both the
230
+ producer set and the consumer set. Because ICKRESERVE uses
231
+ write-folding semantics between the producer set and the consumer set,
232
+ ICKRESERVE(N) might:
233
+
234
+ * shrink the producer set by N and grow the consumer set by N
235
+ * shrink the producer set by 0 and grow the consumer set by 0
236
+ * shrink the producer set by N and grow the consumer set by 0
237
+ * or anything in between
238
+
239
+ Because Ick always uses the min when multiple scores are present for
240
+ one message, ICKADD can rearrange the order of the producer set and
241
+ ICKRESERVE can rearrange the order of the consumer set in surprising
242
+ ways.
243
+
244
+ ICKADD write-folds in the producer set but not in the consumer set.
245
+ Thus, one message can appear in both the producer set and the consumer
246
+ set. At first this seems wrong and inefficient, but in fact it is a
247
+ desirable property. When a message is in both sets, it means it was
248
+ included in a batch by ICKRESERVE, then added by ICKADD, but has yet
249
+ to be ICKCOMMITed. The interpretation for this is that the consumer
250
+ is actively engaged in updating the downstream systems. But that
251
+ means, at the Ick it is indeterminate whether the message is still
252
+ dirty or has been cleaned. That is, being in both queues corresponds
253
+ exactly to a message being in the critical section where a race
254
+ condition is possible. Thus, we _want_ it to still be dirty and to
255
+ appear in a future batch.
256
+
257
+ None of these surprises is a bug in Ick: they are all consistent with
258
+ the design and the intent. But they are surprises nonetheless and can
259
+ (and have) led to bugs in code which makes an unwarranted assumption.
260
+
261
+ ## Installation
43
262
 
44
263
  ```ruby
45
264
  gem 'redis-ick'
@@ -67,6 +286,7 @@ Usage example for producers:
67
286
  ick = Ick.new(redis)
68
287
  ick.ickadd("mykey",12.8,"foo")
69
288
  ick.ickadd("mykey",123.4,"baz")
289
+ ick.ickadd("mykey",Time.now.to_f,"bang") # Time.now recommended for scores
70
290
 
71
291
  Usage example for consumer:
72
292
 
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
10
  task :default => :test
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "redis/ick"
3
+ require 'bundler/setup'
4
+ require 'redis/ick'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
8
8
 
9
9
  # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
10
+ # require 'pry'
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
data/lib/redis/ick.rb CHANGED
@@ -2,9 +2,11 @@ require 'redis/ick/version'
2
2
  require 'redis/script_manager'
3
3
 
4
4
  class Redis
5
+
6
+ # Binds Lua code to provide the Ick operations in Redis.
7
+ #
5
8
  class Ick
6
9
 
7
- # TODO: rubocop
8
10
  # TODO: rdoc
9
11
 
10
12
  # Creates an Ick accessor.
@@ -18,14 +20,16 @@ class Redis
18
20
  if !redis.is_a?(Redis)
19
21
  raise ArgumentError, "not a Redis: #{redis}"
20
22
  end
21
- if statsd && !statsd.respond_to?(:increment)
22
- raise ArgumentError, "no statsd.increment"
23
- end
24
- if statsd && !statsd.respond_to?(:timing)
25
- raise ArgumentError, "no statsd.timeing"
26
- end
27
- if statsd && !statsd.respond_to?(:time)
28
- raise ArgumentError, "no statsd.time"
23
+ if statsd
24
+ if !statsd.respond_to?(:increment)
25
+ raise ArgumentError, 'no statsd.increment'
26
+ end
27
+ if !statsd.respond_to?(:timing)
28
+ raise ArgumentError, 'no statsd.timeing'
29
+ end
30
+ if !statsd.respond_to?(:time)
31
+ raise ArgumentError, 'no statsd.time'
32
+ end
29
33
  end
30
34
  @redis = redis
31
35
  @statsd = statsd
@@ -61,10 +65,10 @@ class Redis
61
65
  def _statsd_time(metric)
62
66
  if statsd
63
67
  statsd.time(metric) do
64
- return block_given? ? yield : nil
68
+ block_given? ? yield : nil
65
69
  end
66
70
  else
67
- return block_given? ? yield : nil
71
+ block_given? ? yield : nil
68
72
  end
69
73
  end
70
74
 
@@ -258,16 +262,16 @@ class Redis
258
262
  class << raw_ickreserve_results
259
263
  alias_method :original_value, :value
260
264
  def value
261
- original_value.each_slice(2).map { |p|
265
+ original_value.each_slice(2).map do |p|
262
266
  [ p[0], ::Redis::Ick._floatify(p[1]) ]
263
- }
267
+ end
264
268
  end
265
269
  end
266
270
  raw_ickreserve_results
267
271
  else
268
- results = raw_ickreserve_results.each_slice(2).map { |p|
272
+ results = raw_ickreserve_results.each_slice(2).map do |p|
269
273
  [ p[0], ::Redis::Ick._floatify(p[1]) ]
270
- }
274
+ end
271
275
  _statsd_timing('profile.ick.ickreserve.num_results',results.size)
272
276
  results
273
277
  end
@@ -472,6 +476,7 @@ class Redis
472
476
  local ick_cset_size = redis.call('ZCARD',ick_cset_key)
473
477
  local ick_stats = {
474
478
  'key', ick_key,
479
+ 'keys', { ick_key, ick_pset_key, ick_cset_key },
475
480
  'ver', ick_ver,
476
481
  'cset_size', ick_cset_size,
477
482
  'pset_size', ick_pset_size,
@@ -588,7 +593,8 @@ class Redis
588
593
  if ick_cset_size and target_cset_size <= ick_cset_size then
589
594
  break
590
595
  end
591
- local first_in_pset = redis.call('ZRANGE',ick_pset_key,0,0,'WITHSCORES')
596
+ local first_in_pset =
597
+ redis.call('ZRANGE',ick_pset_key,0,0,'WITHSCORES')
592
598
  if 0 == table.getn(first_in_pset) then
593
599
  break
594
600
  end
@@ -7,15 +7,28 @@ class Redis
7
7
  #
8
8
  # 0.0.2 - Broke out into Prosperworks/redis-ick, make public.
9
9
  #
10
- # 0.0.3 - Got .travis.yml working with a live redis-server, some
11
- # cleanup, runtime dependency on redis-script_manager for
12
- # Ick._eval, added Rubocop.
10
+ # 0.0.3 - Got .travis.yml working with a live redis-server.
11
+ #
12
+ # Runtime dependency on redis-script_manager for
13
+ # Ick._eval.
14
+ #
15
+ # Initial Rubocop integration.
16
+ #
17
+ # Misc cleanup.
18
+ #
19
+ # 0.0.4 - Imported text from original design doc to README.md, polish.
20
+ #
21
+ # Rubocop polish and defiance.
22
+ #
23
+ # Development dependency on redis-key_hash to test
24
+ # prescriptive hash claims. Identified limits of
25
+ # prescriptive hash robustness.
13
26
  #
14
27
  # 0.1.0 - (future) Big README.md and Rdoc update, solicit feedback
15
28
  # from select external beta users.
16
29
  #
17
30
  # 0.2.0 - (future) Incorporate feedback, announce.
18
31
  #
19
- VERSION = '0.0.3'
32
+ VERSION = '0.0.4'.freeze
20
33
  end
21
34
  end
data/redis-ick.gemspec CHANGED
@@ -1,16 +1,15 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "redis/ick/version"
3
+ require 'redis/ick/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
 
8
- spec.name = "redis-ick"
7
+ spec.name = 'redis-ick'
9
8
  spec.version = Redis::Ick::VERSION
10
9
  spec.platform = Gem::Platform::RUBY
11
10
 
12
- spec.authors = ["jhwillett"]
13
- spec.email = ["jhw@prosperworks.com"]
11
+ spec.authors = ['jhwillett']
12
+ spec.email = ['jhw@prosperworks.com']
14
13
 
15
14
  spec.summary = 'Redis queues with two-phase commit and write-folding.'
16
15
  spec.homepage = 'https://github.com/ProsperWorks/redis-ick'
@@ -18,15 +17,18 @@ Gem::Specification.new do |spec|
18
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
18
  f.match(%r{^(test|spec|features)/})
20
19
  end
21
- spec.bindir = "exe"
20
+ spec.bindir = 'exe'
22
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
22
+ spec.require_paths = ['lib']
24
23
 
25
24
  spec.add_development_dependency 'bundler', '~> 1.14'
26
25
  spec.add_development_dependency 'rake', '~> 10.0'
27
26
  spec.add_development_dependency 'minitest', '~> 5.0'
28
- spec.add_development_dependency 'redis', '~> 3.2'
27
+ spec.add_development_dependency 'redis-key_hash', '~> 0.0.4'
28
+ spec.add_development_dependency 'redis-namespace', '~> 1.5'
29
+ spec.add_development_dependency 'rubocop', '~> 0.50.0'
29
30
 
31
+ spec.add_dependency 'redis', '~> 3.2'
30
32
  spec.add_runtime_dependency 'redis-script_manager', '~> 0.0.2'
31
33
 
32
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-ick
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - jhwillett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-29 00:00:00.000000000 Z
11
+ date: 2017-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redis-key_hash
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.0.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: redis-namespace
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.50.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.50.0
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: redis
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +101,7 @@ dependencies:
59
101
  - - "~>"
60
102
  - !ruby/object:Gem::Version
61
103
  version: '3.2'
62
- type: :development
104
+ type: :runtime
63
105
  prerelease: false
64
106
  version_requirements: !ruby/object:Gem::Requirement
65
107
  requirements:
@@ -89,7 +131,6 @@ extra_rdoc_files: []
89
131
  files:
90
132
  - ".gitignore"
91
133
  - ".rubocop.yml"
92
- - ".rubocop_todo.yml"
93
134
  - ".travis.yml"
94
135
  - Gemfile
95
136
  - LICENSE
data/.rubocop_todo.yml DELETED
@@ -1,296 +0,0 @@
1
- # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2017-08-29 15:15:59 -0700 using RuboCop version 0.49.1.
4
- # The point is for the user to remove these configuration records
5
- # one by one as the offenses are removed from the code base.
6
- # Note that changes in the inspected code, or installation of new
7
- # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 1
10
- # Cop supports --auto-correct.
11
- Layout/EmptyLineAfterMagicComment:
12
- Exclude:
13
- - 'redis-ick.gemspec'
14
-
15
- # Offense count: 6
16
- # Cop supports --auto-correct.
17
- # Configuration parameters: AllowAdjacentOneLineDefs, NumberOfEmptyLines.
18
- Layout/EmptyLineBetweenDefs:
19
- Exclude:
20
- - 'test/redis/ick_test.rb'
21
-
22
- # Offense count: 1
23
- # Cop supports --auto-correct.
24
- Layout/EmptyLines:
25
- Exclude:
26
- - 'lib/redis/ick.rb'
27
-
28
- # Offense count: 2
29
- # Cop supports --auto-correct.
30
- # Configuration parameters: EnforcedStyle, SupportedStyles.
31
- # SupportedStyles: empty_lines, no_empty_lines
32
- Layout/EmptyLinesAroundBlockBody:
33
- Exclude:
34
- - 'redis-ick.gemspec'
35
-
36
- # Offense count: 4
37
- # Cop supports --auto-correct.
38
- # Configuration parameters: EnforcedStyle, SupportedStyles.
39
- # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines
40
- Layout/EmptyLinesAroundClassBody:
41
- Exclude:
42
- - 'lib/redis/ick.rb'
43
- - 'test/redis/ick_test.rb'
44
-
45
- # Offense count: 15
46
- # Cop supports --auto-correct.
47
- # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
48
- Layout/ExtraSpacing:
49
- Exclude:
50
- - 'test/redis/ick_test.rb'
51
-
52
- # Offense count: 1
53
- # Cop supports --auto-correct.
54
- Layout/LeadingCommentSpace:
55
- Exclude:
56
- - 'test/redis/ick_test.rb'
57
-
58
- # Offense count: 276
59
- # Cop supports --auto-correct.
60
- Layout/SpaceAfterComma:
61
- Exclude:
62
- - 'lib/redis/ick.rb'
63
- - 'test/redis/ick_test.rb'
64
-
65
- # Offense count: 2
66
- # Cop supports --auto-correct.
67
- Layout/SpaceAfterNot:
68
- Exclude:
69
- - 'lib/redis/ick.rb'
70
-
71
- # Offense count: 1
72
- # Cop supports --auto-correct.
73
- # Configuration parameters: EnforcedStyleInsidePipes, SupportedStylesInsidePipes.
74
- # SupportedStylesInsidePipes: space, no_space
75
- Layout/SpaceAroundBlockParameters:
76
- Exclude:
77
- - 'lib/redis/ick.rb'
78
-
79
- # Offense count: 1
80
- # Cop supports --auto-correct.
81
- # Configuration parameters: SupportedStyles.
82
- # SupportedStyles: space, no_space
83
- Layout/SpaceAroundEqualsInParameterDefault:
84
- EnforcedStyle: no_space
85
-
86
- # Offense count: 5
87
- # Cop supports --auto-correct.
88
- # Configuration parameters: AllowForAlignment.
89
- Layout/SpaceAroundOperators:
90
- Exclude:
91
- - 'test/redis/ick_test.rb'
92
-
93
- # Offense count: 2
94
- # Cop supports --auto-correct.
95
- # Configuration parameters: EnforcedStyle, SupportedStyles.
96
- # SupportedStyles: space, no_space
97
- Layout/SpaceBeforeBlockBraces:
98
- Exclude:
99
- - 'lib/redis/ick.rb'
100
- - 'test/redis/ick_test.rb'
101
-
102
- # Offense count: 4
103
- # Cop supports --auto-correct.
104
- # Configuration parameters: AllowForAlignment.
105
- Layout/SpaceBeforeFirstArg:
106
- Exclude:
107
- - 'test/redis/ick_test.rb'
108
-
109
- # Offense count: 4
110
- # Cop supports --auto-correct.
111
- # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
112
- # SupportedStyles: space, no_space
113
- # SupportedStylesForEmptyBraces: space, no_space
114
- Layout/SpaceInsideBlockBraces:
115
- Exclude:
116
- - 'Gemfile'
117
- - 'lib/redis/ick.rb'
118
- - 'test/redis/ick_test.rb'
119
-
120
- # Offense count: 10
121
- # Cop supports --auto-correct.
122
- Layout/SpaceInsideBrackets:
123
- Exclude:
124
- - 'lib/redis/ick.rb'
125
- - 'test/redis/ick_test.rb'
126
-
127
- # Offense count: 1
128
- Lint/AmbiguousBlockAssociation:
129
- Exclude:
130
- - 'test/redis/ick_test.rb'
131
-
132
- # Offense count: 18
133
- Metrics/AbcSize:
134
- Max: 318
135
-
136
- # Offense count: 2
137
- # Configuration parameters: CountComments.
138
- Metrics/ClassLength:
139
- Max: 609
140
-
141
- # Offense count: 1
142
- Metrics/CyclomaticComplexity:
143
- Max: 8
144
-
145
- # Offense count: 3
146
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
147
- # URISchemes: http, https
148
- Metrics/LineLength:
149
- Max: 81
150
-
151
- # Offense count: 19
152
- # Configuration parameters: CountComments.
153
- Metrics/MethodLength:
154
- Max: 175
155
-
156
- # Offense count: 1
157
- Metrics/PerceivedComplexity:
158
- Max: 8
159
-
160
- # Offense count: 1
161
- # Cop supports --auto-correct.
162
- Performance/TimesMap:
163
- Exclude:
164
- - 'test/redis/ick_test.rb'
165
-
166
- # Offense count: 2
167
- # Cop supports --auto-correct.
168
- # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
169
- # SupportedStyles: line_count_based, semantic, braces_for_chaining
170
- # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
171
- # FunctionalMethods: let, let!, subject, watch
172
- # IgnoredMethods: lambda, proc, it
173
- Style/BlockDelimiters:
174
- Exclude:
175
- - 'lib/redis/ick.rb'
176
-
177
- # Offense count: 1
178
- # Configuration parameters: EnforcedStyle, SupportedStyles.
179
- # SupportedStyles: nested, compact
180
- Style/ClassAndModuleChildren:
181
- Exclude:
182
- - 'test/redis/ick_test.rb'
183
-
184
- # Offense count: 2
185
- Style/ClassVars:
186
- Exclude:
187
- - 'test/redis/ick_test.rb'
188
-
189
- # Offense count: 1
190
- Style/Documentation:
191
- Exclude:
192
- - 'spec/**/*'
193
- - 'test/**/*'
194
- - 'lib/redis/ick.rb'
195
-
196
- # Offense count: 1
197
- # Cop supports --auto-correct.
198
- # Configuration parameters: EnforcedStyle, SupportedStyles.
199
- # SupportedStyles: format, sprintf, percent
200
- Style/FormatString:
201
- Exclude:
202
- - 'test/redis/ick_test.rb'
203
-
204
- # Offense count: 1
205
- # Configuration parameters: MinBodyLength.
206
- Style/GuardClause:
207
- Exclude:
208
- - 'lib/redis/ick.rb'
209
-
210
- # Offense count: 3
211
- # Cop supports --auto-correct.
212
- # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
213
- # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
214
- Style/HashSyntax:
215
- Exclude:
216
- - 'Rakefile'
217
- - 'test/redis/ick_test.rb'
218
-
219
- # Offense count: 3
220
- # Cop supports --auto-correct.
221
- # Configuration parameters: MaxLineLength.
222
- Style/IfUnlessModifier:
223
- Exclude:
224
- - 'lib/redis/ick.rb'
225
-
226
- # Offense count: 6
227
- # Configuration parameters: SupportedStyles.
228
- # SupportedStyles: snake_case, camelCase
229
- Style/MethodName:
230
- EnforcedStyle: snake_case
231
-
232
- # Offense count: 1
233
- # Cop supports --auto-correct.
234
- Style/MutableConstant:
235
- Exclude:
236
- - 'lib/redis/ick/version.rb'
237
-
238
- # Offense count: 15
239
- # Cop supports --auto-correct.
240
- # Configuration parameters: EnforcedStyle, SupportedStyles.
241
- # SupportedStyles: both, prefix, postfix
242
- Style/NegatedIf:
243
- Exclude:
244
- - 'lib/redis/ick.rb'
245
- - 'test/redis/ick_test.rb'
246
-
247
- # Offense count: 1
248
- # Cop supports --auto-correct.
249
- # Configuration parameters: AllowMultipleReturnValues.
250
- Style/RedundantReturn:
251
- Exclude:
252
- - 'lib/redis/ick.rb'
253
-
254
- # Offense count: 1
255
- # Cop supports --auto-correct.
256
- # Configuration parameters: AllowAsExpressionSeparator.
257
- Style/Semicolon:
258
- Exclude:
259
- - 'test/redis/ick_test.rb'
260
-
261
- # Offense count: 28
262
- # Cop supports --auto-correct.
263
- # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline.
264
- # SupportedStyles: single_quotes, double_quotes
265
- Style/StringLiterals:
266
- Exclude:
267
- - 'Gemfile'
268
- - 'Rakefile'
269
- - 'bin/console'
270
- - 'lib/redis/ick.rb'
271
- - 'redis-ick.gemspec'
272
- - 'test/redis/ick_test.rb'
273
- - 'test/test_helper.rb'
274
-
275
- # Offense count: 1
276
- # Cop supports --auto-correct.
277
- # Configuration parameters: MinSize, SupportedStyles.
278
- # SupportedStyles: percent, brackets
279
- Style/SymbolArray:
280
- EnforcedStyle: brackets
281
-
282
- # Offense count: 1
283
- # Cop supports --auto-correct.
284
- # Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
285
- # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
286
- Style/TernaryParentheses:
287
- Exclude:
288
- - 'lib/redis/ick.rb'
289
-
290
- # Offense count: 11
291
- # Cop supports --auto-correct.
292
- # Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline.
293
- # SupportedStylesForMultiline: comma, consistent_comma, no_comma
294
- Style/TrailingCommaInLiteral:
295
- Exclude:
296
- - 'test/redis/ick_test.rb'