redlics 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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