redis-ick 0.0.3 → 0.0.4

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: 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'