redlics 0.1.1 → 0.1.2

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: 775adf037d5749d709c27978f19bbafb34f70252
4
- data.tar.gz: 6e343a76a1d11bbb9444f4e9e26c318c2a27c57a
3
+ metadata.gz: 440bd913d49adfede251b58856a638d008194561
4
+ data.tar.gz: 7b0379761bbf94c4bc0cfe1417eabe7410c037ac
5
5
  SHA512:
6
- metadata.gz: efa81ea222976f6fe1c47c4712e92ac69adb27fe94de99d786ca7a897dec65077afd3db499cf257b0432b591f950a5b4f2e1541b0d3c9f1251ddfa7c7511f671
7
- data.tar.gz: 7823550d20d1f845e5c72afb693075c9ded6ba6af4cfff29cfc12794f661acd7bf8b18ef8ea4ea6646accfd5c0d0d3502fc49bc6ca8983cb3c8aa79b02f8d545
6
+ metadata.gz: 0aceaf45e7877871a26988f04316ab0f02bf4922ea0fa3c0efe850f92d9a887439dd53932431e4760682507cfee415f476b70bdb5d42fb47d76b1c7d21a67825
7
+ data.tar.gz: 46633fd477c57d1295710d5127e0cfcdb8e52232e566f67b61f5576ce1b7d9081b07cda2607f5c1e38c9289c9171f61c21328dc894e00891225ce12c52f9ac92
data/README.md CHANGED
@@ -9,6 +9,19 @@
9
9
 
10
10
  Redis analytics with tracks (using bitmaps) and counts (using buckets) encoding numbers in Redis keys and values.
11
11
 
12
+ ## Features
13
+
14
+ * Tracking with bitmaps
15
+ * Counting with buckets
16
+ * High configurable
17
+ * Encode/decode numbers in Redis keys and values
18
+ * Very less memory consumption in Redis
19
+ * Support of time frames
20
+ * Uses Lua script for better performance
21
+ * Plot option for tracks and counts
22
+ * Keeps Redis clean
23
+ * and many more, see the [documentation](http://www.rubydoc.info/gems/redlics)
24
+
12
25
  ## Installation
13
26
 
14
27
  Add this line to your application's Gemfile:
@@ -25,22 +38,349 @@ Or install it yourself as:
25
38
 
26
39
  $ gem install redlics
27
40
 
28
- ## Features
41
+ ## Usage
29
42
 
30
- * Tracking with bitmaps
31
- * Counting with buckets
32
- * High configurable
33
- * Encode/decode numbers in Redis keys and values
34
- * Very less memory consumption in Redis
35
- * Support of time frames
36
- * Uses Lua script for better performance
37
- * Plot option for tracks and counts
38
- * Keeps Redis clean
39
- * and many more
43
+ ### Configuration
40
44
 
41
- ## Usage
45
+ The following configuration is the default configuration of Redlics. Store the configration code and load it at the beginning of Redlics use.
46
+ Rails users can create a file `redlics.rb` in `config/initializers` to load the own Redlics configuration.
47
+
48
+ ```ruby
49
+ Redlics.configure do |config|
50
+ config.pool_size = 5 # Default connection pool size is 5
51
+ config.pool_timeout = 5 # Default connection pool timeout is 5
52
+ config.namespace = 'rl' # Default Redis namespace is 'rl', short name saves memory
53
+ config.redis = { url: 'redis://127.0.0.1:6379' } # Default Redis configuration, see: https://github.com/redis/redis-rb/blob/master/lib/redis.rb
54
+ config.silent = false # Silent Redis errors, default is false
55
+ config.separator = ':' # Default Redis namespace separator, default is ':'
56
+ config.bucket = true # Bucketize counter object ids, default is true
57
+ config.bucket_size = 1000 # Bucket size, best performance with bucket size 1000. See hash-max-ziplist-entries
58
+ config.auto_clean = true # Auto remove operation keys from Redis
59
+ config.encode = { # Encode event ids or object ids
60
+ events: true,
61
+ ids: true
62
+ }
63
+ config.granularities = {
64
+ minutely: { step: 1.minute, pattern: '%Y%m%d%H%m' },
65
+ hourly: { step: 1.hour, pattern: '%Y%m%d%H' },
66
+ daily: { step: 1.day, pattern: '%Y%m%d' },
67
+ weekly: { step: 1.week, pattern: '%GW%V' },
68
+ monthly: { step: 1.month, pattern: '%Y%m' },
69
+ yearly: { step: 1.year, pattern: '%Y' }
70
+ }
71
+ config.counter_expirations = { minutely: 1.day, hourly: 1.week, daily: 3.months, weekly: 1.year, monthly: 1.year, yearly: 1.year }
72
+ config.counter_granularity = :daily..:yearly
73
+ config.tracker_expirations = { minutely: 1.day, hourly: 1.week, daily: 3.months, weekly: 1.year, monthly: 1.year, yearly: 1.year },
74
+ config.tracker_granularity = :daily..:yearly
75
+ config.operation_expiration = 1.day
76
+ end
77
+ ```
78
+
79
+ #### Buckets
80
+
81
+ If Redlics is configured to use buckets please configure Redis to allow an ideal size of list entries.
82
+
83
+ ```ruby
84
+ # Redlics config
85
+ config.bucket = true
86
+ config.bucket_size = 1000
87
+ ```
88
+
89
+ The Redis configuration can be found in file `redis.conf`. The default bucket size is 1000 and is an ideal size. Any higher size and
90
+ the HSET commands would cause noticeable CPU activity. The Redis setting `hash-max-ziplist-entries` configures the maximum number
91
+ of entries a hash can have while still being encoded efficiently.
92
+
93
+ ```
94
+ # /etc/redis/redis.conf
95
+ hash-max-ziplist-entries 1024
96
+ hash-max-ziplist-value 64
97
+ ```
98
+
99
+ Read more:
100
+ * [Special encoding of small aggregate data types](http://redis.io/topics/memory-optimization)
101
+ * [Storing hundreds of millions of simple key-value pairs in Redis](http://instagram-engineering.tumblr.com/post/12202313862/storing-hundreds-of-millions-of-simple-key-value)
102
+
103
+ ##### Example
104
+
105
+ * **Id:** 1234
106
+ * **Bucket size**: 1000
107
+
108
+ Results in:
109
+
110
+ * **Bucket nr.:** 1 (part of Redis key)
111
+ * **Bucket entry nr.:** 234 (part of Redis value)
112
+
113
+ #### Encoding
114
+
115
+ If Redlics is configured to encode events and object ids, all numbers are encoded to save memory.
116
+
117
+ ```ruby
118
+ config.encode = {
119
+ events: true,
120
+ ids: true
121
+ }
122
+ ```
123
+
124
+ ##### Examples
125
+
126
+ Byte size reduction of id `1234` from 4 bytes to 2 bytes.
127
+
128
+ * Ids encoding
129
+
130
+ ```ruby
131
+ Redlics::Key.encode(1234)
132
+ # => "2+"
133
+ ```
134
+
135
+ * Event encoding
136
+
137
+ Encodes numbers in event names separated by the defined separator in the configuration.
138
+ **Event name:** `products:1234`, **encoded event:** `products:!k`.
139
+
140
+ ### Counting
141
+
142
+ Counting an event can be done by call count with **arguments**, **hash parameters** or a **block**.
143
+
144
+ ```ruby
145
+ # By arguments
146
+ Redlics.count('products:list')
147
+
148
+ # By hash parameters
149
+ Redlics.count(event: 'products:list', id: 1234)
150
+
151
+ # By block
152
+ Redlics.count do |c|
153
+ c.event = 'products:list'
154
+ c.id = 1234
155
+
156
+ # Count this event in the past
157
+ c.past = 3.days.ago
158
+
159
+ # Count granularity for this event: Symbol, String, Array or Range
160
+ c.granularity = :daily..:monthly
161
+ # c.granularity = :daily
162
+ # c.granularity = [:daily, :monthly]
163
+
164
+ # Expire (delete) count for this event for specific granularities after defined period.
165
+ c.expiration_for = { daily: 6.days, monthly: 2.months }
166
+ end
167
+ ```
168
+
169
+ **Parameters**
170
+
171
+ * **event:** event name **(required)**.
172
+ * **id:** object id (optional), e.g. user id
173
+ * **past:** time object (optional), if not set `Time.now` is used.
174
+ * **granularity:** granularities defined in configuration (optional), if not set `config.counter_granularity` is used.
175
+ * **expiration_for:** expire count for given granularities (optional), if not set `config.counter_expirations` is used.
176
+
177
+ ### Tracking
178
+
179
+ Tracking an event can be done by call track with **arguments**, **hash parameters** or a **block**.
180
+
181
+ ```ruby
182
+ # By arguments
183
+ Redlics.track('products:list', 1234)
184
+
185
+ # By hash parameters
186
+ Redlics.track(event: 'products:list', id: 1234)
42
187
 
43
- Coming soon...
188
+ # By block
189
+ Redlics.track do |t|
190
+ t.event = 'products:list'
191
+ t.id = 1234
192
+
193
+ # Track this event in the past
194
+ t.past = 3.days.ago
195
+
196
+ # Track granularity for this event: Symbol, String, Array or Range
197
+ t.granularity = :daily..:monthly
198
+ # t.granularity = :daily
199
+ # t.granularity = [:daily, :monthly]
200
+
201
+ # Expire (delete) tracking for this event for specific granularities after defined period.
202
+ t.expiration_for = { daily: 6.days, monthly: 2.months }
203
+ end
204
+ ```
205
+
206
+ **Parameters**
207
+
208
+ * **event:** event name **(required)**.
209
+ * **id:** object id **(required)**, e.g. user id
210
+ * **past:** time object (optional), if not set `Time.now` is used.
211
+ * **granularity:** granularities defined in configuration (optional), if not set `config.counter_granularity` is used.
212
+ * **expiration_for:** expire track for given granularities (optional), if not set `config.counter_expirations` is used.
213
+
214
+ ### Analyze
215
+
216
+ To analyze recorded data an analyzable query object must be defined first.
217
+
218
+ ```ruby
219
+ a1 = Redlics.analyze('products:list', :today)
220
+
221
+ # Examples
222
+ a2 = Redlics.analyze('products:list', :today, granularity: :minutely)
223
+ a3 = Redlics.analyze('products:list', :today, id: 1234)
224
+ ```
225
+
226
+ **Parameters**
227
+
228
+ * **event:** event name **(required)**.
229
+ * **time:** time object **(required)**, can be:
230
+ * **a symbol:** predefined in Redlics::TimeFrame.init_with_symbol
231
+ * e.g. *:hour, :day, :week, :month, :year, :today, :yesterday, :this_week, :last_week, :this_month, :last_month, :this_year, :last_year*
232
+ * **a hash:** with keys `from` and `to`
233
+ * e.g. `{ from: 30.days.ago, to: Time.now}`
234
+ * **a range:** defined as a range
235
+ * e.g. `30.days.ago..Time.now`
236
+ * **a time:** simple time object
237
+ * e.g. `Time.new(2016, 1, 12)` or `1.day.ago.to_time`
238
+ * **Options:**
239
+ * **id:** object id, e.g. user id
240
+ * **granularity:** one granularitiy defined in configuration (optional), if not set first element of `config.counter_granularity` is used.
241
+
242
+ Analyzable query objects can be used to analyze **counts** and **tracks**.
243
+ Queries are not *"realized"* until an action is performed:
244
+
245
+ #### Counts
246
+
247
+ ```ruby
248
+ # Check how many counts has been recorded.
249
+ a1.counts
250
+
251
+ # Use this method to get plot-friendly data for graphs.
252
+ a1.plot_counts
253
+
254
+ # See what's under the hood. No Redis access.
255
+ a1.realize_counts!
256
+ ```
257
+
258
+ #### Tracks
259
+
260
+ ```ruby
261
+ # Check how many unique tracks has been recorded.
262
+ a1.tracks
263
+
264
+ # Check if given id exists in the tracks result.
265
+ a1.exists?
266
+
267
+ # Use this method to get plot-friendly data for graphs.
268
+ a1.plot_tracks
269
+
270
+ # See what's under the hood. No Redis access.
271
+ a1.realize_tracks!
272
+ ```
273
+
274
+ #### Reset
275
+
276
+ Reset is required to keep clean redis operation results. To calculate counts and tracks operations are stored in Redis.
277
+ It is possible to delete this operation result keys in Redis manually or let the Ruby garbage collector clean redis before the
278
+ analyzable query objects are destructed (configuration `config.auto_clean`). The third way is hard coded and uses an expiration
279
+ time in Redis for that given operation result keys. The expiration time for operations can be configured with `config.operation_expiration`.`
280
+
281
+ ```ruby
282
+ a1.reset!
283
+ ```
284
+
285
+ Partial resets are also possible by pass a `space` argument as symbol:
286
+
287
+ ```ruby
288
+ # :counter, :tracker, :counts, :tracks, :exists,
289
+ # :plot_counts, :plot_tracks, :realize_counts, :realize_tracks
290
+ a1.reset!(:counter)
291
+ a1.reset!(:tracker)
292
+ ```
293
+
294
+ **It is recommended to do a reset if the analyzable query object is no more needed!**
295
+
296
+ The analyzable query objects can also be created and used in a block.
297
+
298
+ ```ruby
299
+ Redlics.analyze('products:list', :today) do |a|
300
+ a.tracks
301
+ # ...
302
+ a.reset!
303
+ end
304
+ ```
305
+
306
+ ### Operators
307
+
308
+ Analyzable query objects can be calculated also using operators (for tracking data). The following operators are available:
309
+
310
+ * **AND** (`&`)
311
+ * **OR** (`|`),
312
+ * **NOT** (`~`, `-`)
313
+ * **XOR** (`^`)
314
+ * **PLUS** (`+`)
315
+ * **MINUS** (`-`)
316
+
317
+ Assuming users has been tracked for the actions `products:list, products:featured, logged_in`, then it is
318
+ possible to use operators to check users that:
319
+
320
+ * has viewed the products list
321
+ * and the featured products list
322
+ * but not logged in today
323
+
324
+ ```ruby
325
+ # Create analyzable query objects
326
+ a1 = Redlics.analyze('products:list', :today)
327
+ a2 = Redlics.analyze('products:featured', :today)
328
+ a3 = Redlics.analyze('logged_in', :today)
329
+
330
+ # The operation
331
+ o = (( a1 & a2) - a3)
332
+
333
+ # To check how many users are in this result set.
334
+ o.tracks
335
+
336
+ # To check if a user is in this result set.
337
+ o.exists?(1234)
338
+
339
+ # Clean up complete operation results.
340
+ o.reset!(:tree)
341
+ ```
342
+
343
+ ### Tips and hints
344
+
345
+ #### Granularities
346
+
347
+ * You should be aware that there is a close relation between counting, tracking and querying in regards to granularities.
348
+ * When querying, make sure to tracking in the same granularity.
349
+ * If you are tracking in the range of `:daily..:monthly` then you can only query in that range (or you will get wrong results).
350
+ * Another possible error you should be aware of is when querying for a time frame that is not correlated with the granularity.
351
+
352
+ #### Buckets
353
+
354
+ * Use buckets if you have many counters to save memory.
355
+ * 1000 is the ideal bucket size.
356
+
357
+ #### Encoding
358
+
359
+ * Use event and ids encoding if you have many counters to save memory.
360
+
361
+ #### Namespaces
362
+
363
+ Keys in Redis look like this:
364
+
365
+ ```ruby
366
+ # Tracker
367
+ 'rl:t:products:list:2016'
368
+
369
+ # Counter without buckets (unencoded)
370
+ 'rl:c:products:list:2016:1234'
371
+
372
+ # Counter without buckets (encoded)
373
+ 'rl:c:products:list:2016:!k'
374
+
375
+ # Counter with buckets (unencoded, 234 is value of key)
376
+ 'rl:c:products:list:2016:1' => '234'
377
+
378
+ # Counter with buckets (encoded, 3k is value of key)
379
+ 'rl:c:products:list:2016:2' => '3k'
380
+
381
+ # Operation
382
+ 'rl:o:f56fa42d-1e85-4e2f-b8c8-a0f9b5bee5d0'
383
+ ```
44
384
 
45
385
  ## Contributors
46
386
 
@@ -12,7 +12,7 @@ module Redlics
12
12
  # Initialization with default configuration.
13
13
  #
14
14
  # Configure Redis:
15
- # etc/redis/redis.conf
15
+ # /etc/redis/redis.conf
16
16
  # hash-max-ziplist-entries 1024
17
17
  # hash-max-ziplist-value 64
18
18
  #
@@ -15,7 +15,7 @@ module Redlics
15
15
  # @return [Array] list of counted granularities
16
16
  def count(*args, &block)
17
17
  return count_with_block(&block) if block_given?
18
- return count_with_hash if args.first.is_a?(Hash)
18
+ return count_with_hash(args.first) if args.first.is_a?(Hash)
19
19
  count_with_args(*args)
20
20
  end
21
21
 
@@ -45,7 +45,7 @@ module Redlics
45
45
  # @return [Array] list of counted granularities
46
46
  def count_with_block
47
47
  yield options = OpenStruct.new
48
- count_with_hash(options)
48
+ count_with_hash(options.to_h)
49
49
  end
50
50
 
51
51
 
@@ -38,9 +38,9 @@ module Redlics
38
38
  def check(granularities)
39
39
  keys = Redlics.config.granularities.keys
40
40
  checked = if granularities.is_a?(Range)
41
- keys[keys.index(granularities.first)..keys.index(granularities.last)]
41
+ keys[keys.index(granularities.first.to_sym)..keys.index(granularities.last.to_sym)]
42
42
  else
43
- [granularities].flatten & Redlics.config.granularities.keys
43
+ [granularities.to_sym].flatten & Redlics.config.granularities.keys
44
44
  end
45
45
  checked.any? ? checked : nil
46
46
  end
data/lib/redlics/key.rb CHANGED
@@ -17,6 +17,7 @@ module Redlics
17
17
  # @return [Array] bucketized key name
18
18
  def name(context, event, granularity, past, options = {})
19
19
  past ||= Time.now
20
+ event ||= 'nil'
20
21
  granularity = Granularity.validate(context, granularity).first
21
22
  event = encode_event(event) if Redlics.config.encode[:events]
22
23
  key = "#{context[:short]}#{Redlics.config.separator}#{event}#{Redlics.config.separator}#{time_format(granularity, past)}"
@@ -67,7 +68,7 @@ module Redlics
67
68
  number = (number.size % 2) != 0 ? "0#{number}" : number
68
69
  token = 0
69
70
  while token <= number.size - 1
70
- encoded += encode_map[number[token..token+1].to_i.to_s.to_sym].to_s
71
+ encoded += encode_map[number[token..token+1].to_i.to_s].to_s
71
72
  token += 2
72
73
  end
73
74
  encoded
@@ -83,7 +84,7 @@ module Redlics
83
84
  string = string.to_s
84
85
  token = 0
85
86
  while token <= string.size - 1
86
- number = decode_map[string[token].to_s.to_sym].to_s
87
+ number = decode_map[string[token]].to_s
87
88
  decoded += number.size == 1 ? "0#{number}" : number
88
89
  token += 1
89
90
  end
@@ -15,7 +15,7 @@ module Redlics
15
15
  # @return [Array] list of tracked granularities
16
16
  def track(*args, &block)
17
17
  return track_with_block(&block) if block_given?
18
- return track_with_hash if args.first.is_a?(Hash)
18
+ return track_with_hash(args.first) if args.first.is_a?(Hash)
19
19
  track_with_args(*args)
20
20
  end
21
21
 
@@ -43,7 +43,7 @@ module Redlics
43
43
  # @return [Array] list of tracked granularities
44
44
  def track_with_block
45
45
  yield options = OpenStruct.new
46
- track_with_hash(options)
46
+ track_with_hash(options.to_h)
47
47
  end
48
48
 
49
49
 
@@ -1,4 +1,4 @@
1
1
  # Redlics version.
2
2
  module Redlics
3
- VERSION = '0.1.1'
3
+ VERSION = '0.1.2'
4
4
  end
data/lib/redlics.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'msgpack'
1
2
  require 'redlics/version'
2
3
  require 'redlics/config'
3
4
  require 'redlics/exception'
data/redlics.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'redis', '~> 3.2'
27
27
  spec.add_dependency 'redis-namespace', '~> 1.5'
28
28
  spec.add_dependency 'activesupport', '~> 4.2'
29
- spec.add_dependency 'msgpack', '~> 0.7'
29
+ spec.add_dependency 'msgpack', '~> 0.5.12'
30
30
 
31
31
  spec.add_development_dependency 'rake', '~> 11.0'
32
32
  spec.add_development_dependency 'minitest', '~> 5.8'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redlics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Egon Zemmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-15 00:00:00.000000000 Z
11
+ date: 2016-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.7'
75
+ version: 0.5.12
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.7'
82
+ version: 0.5.12
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement