redis_counters 1.3.0

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.
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