redis_counters 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG.md +38 -0
  4. data/Gemfile +3 -0
  5. data/Makefile +14 -0
  6. data/README.md +320 -0
  7. data/Rakefile +15 -0
  8. data/lib/redis_counters.rb +21 -0
  9. data/lib/redis_counters/base_counter.rb +84 -0
  10. data/lib/redis_counters/bucket.rb +59 -0
  11. data/lib/redis_counters/cluster.rb +22 -0
  12. data/lib/redis_counters/clusterize_and_partitionize.rb +194 -0
  13. data/lib/redis_counters/hash_counter.rb +70 -0
  14. data/lib/redis_counters/partition.rb +16 -0
  15. data/lib/redis_counters/unique_hash_counter.rb +51 -0
  16. data/lib/redis_counters/unique_values_lists/base.rb +57 -0
  17. data/lib/redis_counters/unique_values_lists/blocking.rb +167 -0
  18. data/lib/redis_counters/unique_values_lists/expirable.rb +155 -0
  19. data/lib/redis_counters/unique_values_lists/non_blocking.rb +91 -0
  20. data/lib/redis_counters/version.rb +3 -0
  21. data/redis_counters.gemspec +31 -0
  22. data/spec/redis_counters/base_spec.rb +29 -0
  23. data/spec/redis_counters/hash_counter_spec.rb +462 -0
  24. data/spec/redis_counters/unique_hash_counter_spec.rb +83 -0
  25. data/spec/redis_counters/unique_values_lists/blocking_spec.rb +94 -0
  26. data/spec/redis_counters/unique_values_lists/expirable_spec.rb +6 -0
  27. data/spec/redis_counters/unique_values_lists/non_blicking_spec.rb +6 -0
  28. data/spec/spec_helper.rb +24 -0
  29. data/spec/support/unique_values_lists/common.rb +563 -0
  30. data/spec/support/unique_values_lists/expirable.rb +162 -0
  31. data/spec/support/unique_values_lists/set.rb +119 -0
  32. data/tasks/audit.rake +6 -0
  33. data/tasks/cane.rake +12 -0
  34. data/tasks/changelog.rake +7 -0
  35. data/tasks/coverage.rake +21 -0
  36. data/tasks/support.rb +24 -0
  37. metadata +242 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7686748e5af2550c7895c4d3b893a4464e12495f
4
+ data.tar.gz: da77d21dac0066c38745affc6301055191f10dc6
5
+ SHA512:
6
+ metadata.gz: ee6ce001671fc800cb85327c64e39196197708da7f32d07a5ef3fbd7b9a1937fb2526e207a8c5c5ef8902973db5e7e2d827434b09401982f7af1cfe917203815
7
+ data.tar.gz: 3b2fa28eb9a5c12efb45fae68c96da2ac03ad68e95ad59264ca152f3bc080371a79efc61c4d5890653de54c217f10193a56663e4ea4951dc4357d7d2edfd3ce2
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
19
+ .rbx/
20
+ gemfiles/
21
+ /coverage
@@ -0,0 +1,38 @@
1
+
2
+ #### [Current]
3
+
4
+
5
+ #### v1.2.0
6
+ * 2014-11-10 [be642ce](../../commit/be642ce) - __(Artem Napolskih)__ Release 1.2.0
7
+ * 2014-11-10 [6257dc2](../../commit/6257dc2) - __(Artem Napolskih)__ feature(hash_counter): added float mode
8
+ * 2014-10-17 [48deb40](../../commit/48deb40) - __(Artem Napolskih)__ feature(unique_values_list): introduced a list of unique values to expiry of the members
9
+ * 2014-10-17 [e1206b7](../../commit/e1206b7) - __(Artem Napolskih)__ feature(unique_values_list): introduce has_value? method
10
+ * 2014-10-17 [cccc2be](../../commit/cccc2be) - __(Artem Napolskih)__ feature(unique_values_list): rename Standard and Fast unique values list to Blocking and NonBlocking respectively
11
+ * 2014-10-17 [7521fd8](../../commit/7521fd8) - __(Artem Napolskih)__ chore(specs): reorganize unique lists specs
12
+ * 2014-04-23 [94fada3](../../commit/94fada3) - __(Artem Napolskih)__ chore(all): fix circleci badge
13
+ * 2013-11-20 [0ae5531](../../commit/0ae5531) - __(Artem Napolskih)__ Release 1.1.0
14
+
15
+ #### v1.1.0
16
+ * 2013-11-20 [01f12f9](../../commit/01f12f9) - __(Artem Napolskih)__ Release 1.1.0
17
+ * 2013-11-20 [3897988](../../commit/3897988) - __(Artem Napolskih)__ refactor(all): back delete_all! methods
18
+ * 2013-11-15 [2d1c733](../../commit/2d1c733) - __(Artem Napolskih)__ refactor(all): extract Cluster and Partition classes
19
+ * 2013-10-22 [020cc8e](../../commit/020cc8e) - __(Napolskih)__ refactor(all): shared code of clustering and partitioning extracted into module - group_keys -> cluster_keys - shared code of clustering and partitioning extracted into module
20
+
21
+ #### v1.0.1
22
+ * 2013-10-21 [9b5e13b](../../commit/9b5e13b) - __(Artem Napolskih)__ Release 1.0.1
23
+ * 2013-10-21 [bfa328a](../../commit/bfa328a) - __(Artem Napolskih)__ feature(hash_counter): method date when called with a block, now returns the total number of lines
24
+ * 2013-10-21 [6d04c95](../../commit/6d04c95) - __(Artem Napolskih)__ Release 1.0.0
25
+
26
+ #### v1.0.0
27
+ * 2013-10-21 [b5f5344](../../commit/b5f5344) - __(Artem Napolskih)__ Release 1.0.0
28
+ * 2013-10-10 [b29eab7](../../commit/b29eab7) - __(Artem Napolskih)__ feature(unique_values_lists): added methods to read and delete data
29
+ * 2013-10-10 [67c5200](../../commit/67c5200) - __(Artem Napolskih)__ feature(unique_hash_counter): introduce unique list postfix delimiter
30
+ * 2013-10-08 [8a9a2a9](../../commit/8a9a2a9) - __(Artem Napolskih)__ feature(hash_counter): added methods to read and delete data
31
+ * 2013-09-13 [f8d037c](../../commit/f8d037c) - __(Artem Napolskih)__ docs(all): documenting
32
+
33
+ #### v1.0.0beta1
34
+ * 2013-09-13 [43016ec](../../commit/43016ec) - __(Artem Napolskih)__ Release 1.0.0beta1
35
+ * 2013-09-12 [0a176fa](../../commit/0a176fa) - __(Artem Napolskih)__ feature(all) introduce key and value delimiters
36
+ * 2013-09-12 [addbb40](../../commit/addbb40) - __(Artem Napolskih)__ feature(unique_values_list): fast (non blocking) unique values list added
37
+ * 2013-08-23 [897418d](../../commit/897418d) - __(Artem Napolskih)__ Initial commit
38
+ * 2013-08-21 [21ed53b](../../commit/21ed53b) - __(napolskih)__ dummy commit
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,14 @@
1
+ BUNDLE = bundle
2
+ BUNDLE_OPTIONS = -j 4
3
+ RSPEC = ${BUNDLE} exec rspec
4
+
5
+ all: test
6
+
7
+ test: bundler/install
8
+ ${RSPEC} 2>&1
9
+
10
+ bundler/install:
11
+ if ! gem list bundler -i > /dev/null; then \
12
+ gem install bundler; \
13
+ fi
14
+ ${BUNDLE} install ${BUNDLE_OPTIONS}
@@ -0,0 +1,320 @@
1
+ # RedisCounters
2
+
3
+ [![Dolly](http://dolly.railsc.ru/badges/abak-press/redis_counters/master)](http://dolly.railsc.ru/projects/36/builds/latest/?ref=master)
4
+ [![Code Climate](https://codeclimate.com/repos/522e9b497e00a46a0d01227c/badges/ae868ca76e52852ebc5a/gpa.png)](https://codeclimate.com/repos/522e9b497e00a46a0d01227c/feed)
5
+ [![Test Coverage](https://codeclimate.com/repos/522e9b497e00a46a0d01227c/badges/ae868ca76e52852ebc5a/coverage.svg)](https://codeclimate.com/repos/522e9b497e00a46a0d01227c/feed)
6
+
7
+ Набор структур данных на базе Redis.
8
+
9
+ ## RedisCounters::HashCounter
10
+
11
+ Счетчик на основе Hash, с ~~преферансом и тайками-близняшками~~ партиционированием и кластеризацией значений.
12
+
13
+ Обязательные параметры: counter_name, field_name или group_keys.
14
+
15
+ ### Сложность
16
+ + инкремент - O(1).
17
+
18
+ ### Примеры использования
19
+
20
+ Простой счетчик значений.
21
+ ```ruby
22
+ counter = RedisCounters::HashCounter.new(redis, {
23
+ :counter_name => :simple_counter,
24
+ :field_name => :pages
25
+ })
26
+
27
+ 5.times { counter.increment }
28
+
29
+ redis:
30
+ simple_counter = {
31
+ pages => 5
32
+ }
33
+
34
+ > counter.partitions
35
+ => [{}]
36
+
37
+ > counter.data
38
+ => [{:value=>5}]
39
+ ```
40
+
41
+ Счетчик посещенных страниц компании с партиционированием по дате.
42
+ ```ruby
43
+ counter = RedisCounters::HashCounter.new(redis, {
44
+ :counter_name => :pages_by_day,
45
+ :group_keys => [:company_id],
46
+ :partition_keys => [:date]
47
+ })
48
+
49
+ 2.times { counter.increment(:company_id => 1, :date => '2013-08-01') }
50
+ 3.times { counter.increment(:company_id => 2, :date => '2013-08-01') }
51
+ 1.times { counter.increment(:company_id => 3, :date => '2013-08-02') }
52
+
53
+ redis:
54
+ pages_by_day:2013-08-01 = {
55
+ 1 => 2
56
+ 2 => 3
57
+ }
58
+ pages_by_day:2013-08-02 = {
59
+ 3 => 1
60
+ }
61
+
62
+ > counter.partitions
63
+ => [{:date=>"2013-08-01"}, {:date=>"2013-08-02"}]
64
+
65
+ > counter.data
66
+ => [{:company_id=>"1", :value=>2},
67
+ {:company_id=>"2", :value=>3},
68
+ {:company_id=>"3", :value=>1}]
69
+
70
+ > counter.delete_partitions!(:date => '2013-08-01')
71
+ => [1]
72
+
73
+ > counter.partitions
74
+ => [{:date=>"2013-08-02"}]
75
+
76
+ > counter.data
77
+ => [{:company_id=>"3", :value=>1}]
78
+
79
+ > counter.delete_all!
80
+ => [1]
81
+
82
+ > counter.data
83
+ => []
84
+ ```
85
+
86
+ Тоже самое, но партиция задается с помощью proc.
87
+ ```ruby
88
+ counter = RedisCounters::HashCounter.new(redis, {
89
+ :counter_name => :pages_by_day,
90
+ :group_keys => [:company_id],
91
+ :partition_keys => proc { |params| params.fetch(:date) }
92
+ })
93
+ ```
94
+
95
+ Счетчик посещенных страниц с группировкой по городу посетителя и партиционированием по дате и компании.
96
+ ```ruby
97
+ counter = RedisCounters::HashCounter.new(redis, {
98
+ :counter_name => :pages_by_day_city,
99
+ :group_keys => [:company_id, :city_id],
100
+ :partition_keys => [:date, :company_id]
101
+ })
102
+
103
+ 2.times { counter.increment(:date => '2013-08-01', :company_id => 1, :city_id => 11) }
104
+ 1.times { counter.increment(:date => '2013-08-01', :company_id => 1, :city_id => 12) }
105
+ 4.times { counter.increment(:date => '2013-08-01', :company_id => 2, :city_id => 10) }
106
+ 3.times { counter.increment(:date => '2013-08-02', :company_id => 1, :city_id => 15) }
107
+
108
+ redis:
109
+ pages_by_day_city:2013-08-01:1 = {
110
+ 1:11 => 2,
111
+ 1:12 => 1
112
+ }
113
+
114
+ pages_by_day_city:2013-08-01:2 = {
115
+ 2:10 => 4
116
+ }
117
+
118
+ pages_by_day_city:2013-08-02:1 = {
119
+ 1:15 => 3
120
+ }
121
+
122
+ > counter.partitions
123
+ => [{:date=>"2013-08-02", :company_id=>"1"},
124
+ {:date=>"2013-08-01", :company_id=>"1"},
125
+ {:date=>"2013-08-01", :company_id=>"2"}]
126
+
127
+ > counter.partitions(:date => '2013-08-01')
128
+ => [{:date=>"2013-08-01", :company_id=>"1"},
129
+ {:date=>"2013-08-01", :company_id=>"2"}]
130
+
131
+ > counter.data
132
+ => [{:company_id => 1, :city_id=>"15", :value=>3},
133
+ {:company_id => 1, :city_id=>"11", :value=>2},
134
+ {:company_id => 1, :city_id=>"12", :value=>1},
135
+ {:company_id => 2, :city_id=>"10", :value=>4}]
136
+
137
+ > counter.data(:date => '2013-08-01')
138
+ => [{:company_id => 1, :city_id=>"11", :value=>2},
139
+ {:company_id => 1, :city_id=>"12", :value=>1},
140
+ {:company_id => 2, :city_id=>"10", :value=>4}]
141
+
142
+ > counter.data(:date => '2013-08-01') { |batch| puts batch }
143
+ {:company_id => 1, :city_id=>"11", :value=>2}
144
+ {:company_id => 1, :city_id=>"12", :value=>1}
145
+ {:company_id => 2, :city_id=>"10", :value=>4}
146
+ ```
147
+
148
+ ## RedisCounters::UniqueValuesLists::Blocking
149
+
150
+ Список уникальных значений, с возможностью кластеризации и партиционирования значений.
151
+
152
+ Особенности:
153
+ - Использует механизм оптимистичных блокировок.
154
+ - Помимо списка значений, ведет так же, список партиций, для каждого кластера.
155
+ - Полностью транзакционен - сторонний блок, выполняемый после добавления уникального элемента,
156
+ выполняется в той же транзакции, в которой добавляется уникальный элемент.
157
+
158
+ Вероятно, в условиях большой конкурентности, обладает не лучшей производительносью из-за частых блокировок.
159
+
160
+ Обязательные параметры: counter_name и value_keys.
161
+
162
+ ### Сложность
163
+ + добавление элемента - от O(1), при отсутствии партиционирования, до O(N), где N - кол-во партиций.
164
+
165
+ ### Примеры использования
166
+
167
+ Простой список уникальных пользователей.
168
+ ```ruby
169
+ counter = RedisCounters::UniqueValuesLists::Blocking.new(redis, {
170
+ :counter_name => :users,
171
+ :value_keys => [:user_id]
172
+ })
173
+
174
+ counter.increment(:user_id => 1)
175
+ counter.increment(:user_id => 2)
176
+ counter.increment(:user_id => 1)
177
+
178
+ redis:
179
+ users = ['1', '2']
180
+ ```
181
+
182
+ Список уникальных пользователей, посетивших компаниию, за месяц, партиционированный по суткам.
183
+ ```ruby
184
+ counter = RedisCounters::UniqueValuesLists::Blocking.new(redis, {
185
+ :counter_name => :company_users_by_month,
186
+ :value_keys => [:company_id, :user_id],
187
+ :cluster_keys => [:start_month_date],
188
+ :partition_keys => [:date]
189
+ })
190
+
191
+ 2.times { counter.add(:company_id => 1, :user_id => 11, :date => '2013-08-10', :start_month_date => '2013-08-01') }
192
+ 3.times { counter.add(:company_id => 1, :user_id => 22, :date => '2013-08-10', :start_month_date => '2013-08-01') }
193
+ 3.times { counter.add(:company_id => 1, :user_id => 22, :date => '2013-09-05', :start_month_date => '2013-09-01') }
194
+ 3.times { counter.add(:company_id => 2, :user_id => 11, :date => '2013-08-10', :start_month_date => '2013-08-01') }
195
+ 1.times { counter.add(:company_id => 2, :user_id => 22, :date => '2013-08-11', :start_month_date => '2013-08-01') }
196
+
197
+ redis:
198
+ company_users_by_month:2013-08-01:partitions = ['2013-08-10', '2013-08-11']
199
+ company_users_by_month:2013-08-01:2013-08-10 = ['1:11', '1:22', '2:11']
200
+ company_users_by_month:2013-08-01:2013-08-11 = ['2:22']
201
+
202
+ company_users_by_month:2013-09-01:partitions = ['2013-09-05']
203
+ company_users_by_month:2013-09-01:2013-09-05 = ['1:22']
204
+ ```
205
+
206
+ ## RedisCounters::UniqueValuesLists::NonBlocking
207
+
208
+ Быстрый список уникальных значений, с возможностью кластеризации и партиционирования значений.
209
+
210
+ Скорость работы достигается за счет следующих особенностей:
211
+ - Использует 2х объема памяти для хранения элементов,
212
+ при использовании партиционирования.
213
+ Eсли партиционирование не используется, то расход памяти такой-же как у UniqueValuesLists::Blocking.
214
+ - Не транзакционен - сторонний блок, выполняемый после добавления уникального элемента,
215
+ выполняется за пределами транзакции, в которой добавляется уникальный элемент.
216
+ - Не ведется список партиций.
217
+
218
+ Обязательные параметры: counter_name и value_keys.
219
+
220
+ ### Сложность
221
+ + добавление элемента - O(1)
222
+
223
+
224
+ ## RedisCounters::UniqueValuesLists::Expirable
225
+
226
+ Список уникальных значений, с возможностью expire отдельных элементов.
227
+
228
+ На основе сортированного множества.
229
+ http://redis4you.com/code.php?id=010
230
+
231
+ На основе механизма оптимистических блокировок.
232
+ смотри Optimistic locking using check-and-set:
233
+ http://redis.io/topics/transactions
234
+
235
+ Особенности:
236
+ - Expire - таймаут, можно установить как на уровне счетчика,
237
+ так и на уровне отдельного занчения;
238
+ - Очистка возможна как в автоматическогом режиме так в и ручном;
239
+ - Значения сохраняет в партициях;
240
+ - Ведет список партиций;
241
+ - Полностью транзакционен.
242
+
243
+ Обязательные параметры: counter_name и value_keys.
244
+ Таймаут задается параметром :expire. По умолчанию :never.
245
+ :clean_expired - режим автоочистки. По умолчанию true.
246
+
247
+ ### Примеры использования
248
+
249
+ ```ruby
250
+ counter = RedisCounters::UniqueValuesLists::Expirable.new(redis,
251
+ :counter_name => :sessions,
252
+ :value_keys => [:session_id],
253
+ :expire => 10.minutes
254
+ )
255
+
256
+ counter << session_id: 1
257
+ counter << session_id: 2
258
+ counter << session_id: 3, expire: :never
259
+
260
+ counter.data
261
+ > [{session_id: 1}, {session_id: 2}, {session_id: 3}]
262
+
263
+ # after 10 minutes
264
+
265
+ counter.data
266
+ > [{session_id: 3}]
267
+
268
+ counter.has_value?(session_id: 1)
269
+ false
270
+ ```
271
+
272
+ ## RedisCounters::UniqueHashCounter
273
+
274
+ Сборная конструкция на основе предыдущих.
275
+ HashCounter, с возможностью подсчета только у уникальных событий.
276
+
277
+ ### Сложность
278
+ аналогично сложности, используемого уникального списка.
279
+
280
+ ### Примеры использования
281
+
282
+ Счетчик уникальных пользователей, посетивших компаниию, за месяц, кластеризованный по суткам.
283
+ ```ruby
284
+ counter = RedisCounters::UniqueHashCounter.new(redis, {
285
+ :counter_name => :company_users_by_month,
286
+ :group_keys => [:company_id],
287
+ :partition_keys => [:date],
288
+ :unique_list => {
289
+ :list_class => RedisCounters::UniqueValuesLists::Blocking
290
+ :value_keys => [:company_id, :user_id],
291
+ :cluster_keys => [:start_month_date],
292
+ :partition_keys => [:date]
293
+ }
294
+ })
295
+
296
+ 2.times { counter.increment(:company_id => 1, :user_id => 11, :date => '2013-08-10', :start_month_date => '2013-08-01') }
297
+ 3.times { counter.increment(:company_id => 1, :user_id => 22, :date => '2013-08-10', :start_month_date => '2013-08-01') }
298
+ 3.times { counter.increment(:company_id => 1, :user_id => 22, :date => '2013-09-05', :start_month_date => '2013-09-01') }
299
+ 3.times { counter.increment(:company_id => 2, :user_id => 11, :date => '2013-08-10', :start_month_date => '2013-08-01') }
300
+ 1.times { counter.increment(:company_id => 2, :user_id => 22, :date => '2013-08-11', :start_month_date => '2013-08-01') }
301
+
302
+ redis:
303
+ company_users_by_month:2013-08-10 = {
304
+ 1 = 2,
305
+ 2 = 1
306
+ }
307
+ company_users_by_month:2013-08-11 = {
308
+ 2 = 1
309
+ }
310
+ company_users_by_month:2013-09-05 = {
311
+ 1 = 1
312
+ }
313
+
314
+ company_users_by_month_uq:2013-08-01:partitions = ['2013-08-10', '2013-08-11']
315
+ company_users_by_month_uq:2013-08-01:2013-08-10 = ['1:11', '1:22', '2:11']
316
+ company_users_by_month_uq:2013-08-01:2013-08-11 = ['2:22']
317
+
318
+ company_users_by_month_uq:2013-09-01:partitions = ['2013-09-05']
319
+ company_users_by_month_uq:2013-09-01:2013-09-05 = ['1:22']
320
+ ```
@@ -0,0 +1,15 @@
1
+ # coding: utf-8
2
+ require 'bundler/gem_tasks'
3
+
4
+ # load everything from tasks/ directory
5
+ Dir[File.join(File.dirname(__FILE__), 'tasks', '*.{rb,rake}')].each { |f| load(f) }
6
+
7
+ task :release => [:check, :changelog]
8
+
9
+ desc 'Check quality'
10
+ task :check => [:audit, :quality, :coverage]
11
+
12
+ require 'rspec/core/rake_task'
13
+
14
+ # setup `spec` task
15
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ require 'redis_counters/version'
3
+ require 'redis_counters/base_counter'
4
+ require 'redis_counters/hash_counter'
5
+ require 'redis_counters/unique_hash_counter'
6
+ require 'redis_counters/unique_values_lists/base'
7
+ require 'redis_counters/unique_values_lists/blocking'
8
+ require 'redis_counters/unique_values_lists/non_blocking'
9
+ require 'redis_counters/unique_values_lists/expirable'
10
+
11
+ require 'active_support'
12
+ require 'active_support/core_ext'
13
+
14
+ module RedisCounters
15
+
16
+ def create_counter(redis, opts)
17
+ BaseCounter.create(redis, opts)
18
+ end
19
+
20
+ module_function :create_counter
21
+ end