redtastic 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.gitignore +10 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +17 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +84 -0
- data/README.md +275 -0
- data/lib/redtastic/connection.rb +14 -0
- data/lib/redtastic/model.rb +285 -0
- data/lib/redtastic/script_manager.rb +32 -0
- data/lib/redtastic/scripts/data_points_for_keys.lua +37 -0
- data/lib/redtastic/scripts/hmfind.lua +14 -0
- data/lib/redtastic/scripts/hmincrby.lua +9 -0
- data/lib/redtastic/scripts/msadd.lua +3 -0
- data/lib/redtastic/scripts/msismember.lua +7 -0
- data/lib/redtastic/scripts/msrem.lua +3 -0
- data/lib/redtastic/scripts/msunion.lua +28 -0
- data/lib/redtastic/scripts/sum.lua +13 -0
- data/lib/redtastic/scripts/union_data_points_for_keys.lua +51 -0
- data/lib/redtastic/version.rb +3 -0
- data/redtastic.gemspec +27 -0
- data/spec/connection_spec.rb +42 -0
- data/spec/model_spec.rb +776 -0
- data/spec/sample_scripts/sample.lua +1 -0
- data/spec/sample_scripts/sample_with_args.lua +1 -0
- data/spec/script_manager_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MGFlYTFiMjY1ZmY5NDI3ZjQ5YWJmM2UzMTMyYzFhNGY1NThlNmFiZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZmM3MzRmN2UyZWRmOGYzOTkwM2I4NDg1NzcxNWRiNDZlODQ2NmI3MA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MjJlMTI2OWQ4YjZiMjg2ZDBhZWYwMGI1NmNiNTI0MGI4NjViMjc2YTc5OWVk
|
10
|
+
ZGJiYjAzODJmN2E2Y2UxNGJiYWM2ZGNkYWYxNTAwZjFjOGZhZmRkMmQxNmE4
|
11
|
+
Njg3NGNkOWQwNjQwNDlhNjA0MmRjNDU2YTFhNzg0NjM2NDA3ZTA=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NTIzMTBjOWY5NWNlNGJkMjUxMGNmYjk2NjQ2YzU5OTRmZTQ4OWEwNDRmODkx
|
14
|
+
NmRkNWZkOTE1MTJiMmVmMmM3MjNiMTNjNDAzNzhhZWQyMDA5YzhiYWMzNTA4
|
15
|
+
MTExZTI4OGRkYjQzNGU3ZGIyYzc4MDZkYzEyOTgxOTI0NzVhOTc=
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
redtastic (0.2.2)
|
5
|
+
activesupport
|
6
|
+
redis
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.0.2)
|
12
|
+
i18n (~> 0.6, >= 0.6.4)
|
13
|
+
minitest (~> 4.2)
|
14
|
+
multi_json (~> 1.3)
|
15
|
+
thread_safe (~> 0.1)
|
16
|
+
tzinfo (~> 0.3.37)
|
17
|
+
ast (1.1.0)
|
18
|
+
atomic (1.1.14)
|
19
|
+
coderay (1.1.0)
|
20
|
+
coveralls (0.7.0)
|
21
|
+
multi_json (~> 1.3)
|
22
|
+
rest-client
|
23
|
+
simplecov (>= 0.7)
|
24
|
+
term-ansicolor
|
25
|
+
thor
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
docile (1.1.1)
|
28
|
+
dotenv (0.9.0)
|
29
|
+
git (1.2.6)
|
30
|
+
i18n (0.6.9)
|
31
|
+
method_source (0.8.2)
|
32
|
+
mime-types (2.0)
|
33
|
+
minitest (4.7.5)
|
34
|
+
multi_json (1.8.4)
|
35
|
+
parser (2.1.4)
|
36
|
+
ast (~> 1.1)
|
37
|
+
slop (~> 3.4, >= 3.4.5)
|
38
|
+
powerpack (0.0.9)
|
39
|
+
pry (0.9.12.4)
|
40
|
+
coderay (~> 1.0)
|
41
|
+
method_source (~> 0.8)
|
42
|
+
slop (~> 3.4)
|
43
|
+
rainbow (1.99.1)
|
44
|
+
redis (3.0.7)
|
45
|
+
rest-client (1.6.7)
|
46
|
+
mime-types (>= 1.16)
|
47
|
+
rspec (2.14.1)
|
48
|
+
rspec-core (~> 2.14.0)
|
49
|
+
rspec-expectations (~> 2.14.0)
|
50
|
+
rspec-mocks (~> 2.14.0)
|
51
|
+
rspec-core (2.14.7)
|
52
|
+
rspec-expectations (2.14.4)
|
53
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
54
|
+
rspec-mocks (2.14.4)
|
55
|
+
rubocop (0.16.0)
|
56
|
+
parser (~> 2.1)
|
57
|
+
powerpack (~> 0.0.6)
|
58
|
+
rainbow (>= 1.1.4)
|
59
|
+
simplecov (0.8.2)
|
60
|
+
docile (~> 1.1.0)
|
61
|
+
multi_json
|
62
|
+
simplecov-html (~> 0.8.0)
|
63
|
+
simplecov-html (0.8.0)
|
64
|
+
slop (3.4.7)
|
65
|
+
term-ansicolor (1.2.2)
|
66
|
+
tins (~> 0.8)
|
67
|
+
thor (0.18.1)
|
68
|
+
thread_safe (0.1.3)
|
69
|
+
atomic
|
70
|
+
tins (0.13.1)
|
71
|
+
tzinfo (0.3.38)
|
72
|
+
|
73
|
+
PLATFORMS
|
74
|
+
ruby
|
75
|
+
|
76
|
+
DEPENDENCIES
|
77
|
+
coveralls
|
78
|
+
dotenv
|
79
|
+
git
|
80
|
+
pry
|
81
|
+
redtastic!
|
82
|
+
rspec
|
83
|
+
rubocop
|
84
|
+
simplecov
|
data/README.md
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
Redtastic [![Build Status](https://travis-ci.org/bellycard/redtastic.png?branch=master)](https://travis-ci.org/bellycard/redtastic) [![Coverage Status](https://coveralls.io/repos/bellycard/redtastic/badge.png?branch=master)](https://coveralls.io/r/bellycard/redtastic?branch=master)
|
2
|
+
========
|
3
|
+
|
4
|
+
Redtastic! Why? Because using Redis for analytics is fantastic!
|
5
|
+
|
6
|
+
Redtastic provides a interface for storing, retriveing, and aggregating time intervalled data. Applications of Redtastic include developing snappy dashboards containing daily / monthly / yearly counts over time. Additionally Redtastic allows for the "mashing-up" of different statistics, allowing the drilling down of data into specific subgroups (such as "Number of unique customers who are also male, android users...etc").
|
7
|
+
|
8
|
+
Redtastic is backed by [Redis](http://redis.io) - so it's super fast.
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
```
|
14
|
+
$ gem install redtastic
|
15
|
+
```
|
16
|
+
|
17
|
+
or in your **Gemfile**
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
gem 'redtastic'
|
21
|
+
```
|
22
|
+
|
23
|
+
and run:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ bundle install
|
27
|
+
```
|
28
|
+
|
29
|
+
Then initialize Redtastic in your application & connect it with a redis instance:
|
30
|
+
|
31
|
+
``` ruby
|
32
|
+
$redis = Redis.new
|
33
|
+
Redtastic::Connection.establish_connection($redis, 'namespace')
|
34
|
+
```
|
35
|
+
\* *specifying a namespace is optional and is used to avoid key collisions if multiple applications are using the same instance of Redis.*
|
36
|
+
|
37
|
+
Usage
|
38
|
+
-----
|
39
|
+
|
40
|
+
### Defining a Redtastic Model
|
41
|
+
|
42
|
+
First, create a Redtastic Model:
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
class Checkins < Redtastic::Model
|
46
|
+
type :counter
|
47
|
+
resolution :days
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
The class must inherit from Redtastic::Model and provide some attributes:
|
52
|
+
* **type:** *Required*. The data type of the model. Valid values include:
|
53
|
+
* :counter
|
54
|
+
* :unique (more on types [below](https://github.com/bellycard/Redtastic#Redtastic-types)).
|
55
|
+
* **resolution:** *Optional*. The degree of fidelity you would like to store this model at. Valid values include:
|
56
|
+
* :days
|
57
|
+
* :weeks
|
58
|
+
* :months
|
59
|
+
* :years
|
60
|
+
* nil
|
61
|
+
|
62
|
+
\* *Note that methods requesting results at a higher resolution than that of the model will not work. Using a lower resolution will result in less memory utilization and will generally see faster query response times.*
|
63
|
+
|
64
|
+
### Using Redtastic Models
|
65
|
+
|
66
|
+
Incrementing / decrementing counters:
|
67
|
+
|
68
|
+
``` ruby
|
69
|
+
Checkins.increment(id: 1, timestamp: '2014-01-01')
|
70
|
+
Checkins.decrement(id: 1, timestamp: '2014-01-01')
|
71
|
+
```
|
72
|
+
|
73
|
+
Find a value of a counter for a single / day / week / month / year:
|
74
|
+
|
75
|
+
``` ruby
|
76
|
+
Checkins.find(id: 1, year: 2014, month: 1, day: 5) # Day
|
77
|
+
Checkins.find(id: 1, year: 2014, week: 2) # Week
|
78
|
+
Checkins.find(id: 1, year: 2014, month: 1) # Month
|
79
|
+
Checkins.find(id: 1, year: 2014) # Year
|
80
|
+
```
|
81
|
+
|
82
|
+
Find the aggregate total over a dataspan:
|
83
|
+
|
84
|
+
``` ruby
|
85
|
+
Checkins.aggregate(id: 1, start_date: '2014-01-01', end_date: '2014-01-05')
|
86
|
+
```
|
87
|
+
|
88
|
+
Get the aggregate total + data points for each date at the specified interval:
|
89
|
+
|
90
|
+
``` ruby
|
91
|
+
Checkins.aggregate(id: 1, start_date: '2014-01-01', end_date: '2014-01-05', interval: :days)
|
92
|
+
```
|
93
|
+
|
94
|
+
### Multiple Ids
|
95
|
+
|
96
|
+
The above methods also have support for multiple ids.
|
97
|
+
|
98
|
+
Incrementing / decrementing multiple ids in one request:
|
99
|
+
|
100
|
+
``` ruby
|
101
|
+
Checkins.increment(id: [1001, 1002, 2003], timestamp: '2014-01-01')
|
102
|
+
Checkins.decrement(id: [1001, 1002, 2003], timestamp: '2014-01-01')
|
103
|
+
```
|
104
|
+
|
105
|
+
Find for mutiple ids at once:
|
106
|
+
|
107
|
+
``` ruby
|
108
|
+
Checkins.find(id: [1001, 1002, 2003], year: 2014, month: 1, day: 5)
|
109
|
+
```
|
110
|
+
|
111
|
+
Aggregations across mutiple ids can be quite powerful:
|
112
|
+
|
113
|
+
``` ruby
|
114
|
+
Checkins.aggregate(id: [1001, 1002, 2003], start_date: '2014-01-01', end_date: '2014-01-05')
|
115
|
+
```
|
116
|
+
|
117
|
+
As well as aggregating across mutiple ids w/ data points at the specified interval:
|
118
|
+
|
119
|
+
``` ruby
|
120
|
+
Checkins.aggregate(id: [1001, 1002, 2003], start_date: '2013-01-01', end_date: '2014-01-05', interval: :days)
|
121
|
+
```
|
122
|
+
|
123
|
+
### Redtastic Types
|
124
|
+
|
125
|
+
#### Counters
|
126
|
+
|
127
|
+
Counters are just what they appear to be - counters of things. Examples of using counters is shown in the previous two sections.
|
128
|
+
|
129
|
+
#### Unique Counters
|
130
|
+
|
131
|
+
Unique counters are used when an event with the same unique_id should not be counted twice. A general example of this could be a counter for the number of users that visited a place. In this case the "id" parameter would represent the id of the place and the unique_id would be the users id.
|
132
|
+
|
133
|
+
**Examples**
|
134
|
+
|
135
|
+
Incrementing / Decrementing (adding / removing a unique_id from a set for a particular id / time):
|
136
|
+
``` ruby
|
137
|
+
Customers.increment(id: 1, timestamp: '2014-01-05', unique_id: 1000)
|
138
|
+
Customers.decrement(id: 1, timestamp: '2014-01-05', unique_id: 1000)
|
139
|
+
```
|
140
|
+
|
141
|
+
Find:
|
142
|
+
``` ruby
|
143
|
+
Customers.find(id: 1, year: 2014, month: 1, day: 5, unique_id: 1000) # Returns true or false
|
144
|
+
```
|
145
|
+
|
146
|
+
Find the aggregate total over a datespan (this would only return the *unique* aggregate total):
|
147
|
+
``` ruby
|
148
|
+
Customers.aggregate(id: 1, start_date: '2014-01-01', end_date: '2014-01-05')
|
149
|
+
```
|
150
|
+
|
151
|
+
Find the aggregate total + data points for each point at a specified interval (again, this returns not only the unique aggregate total, but also the unique total for each interval data point ~ being each day / week / month / year...etc)
|
152
|
+
``` ruby
|
153
|
+
Customers.aggregate(id: 1, start_date: '2014-01-01', end_date: '2014-01-05', interval: :days)
|
154
|
+
```
|
155
|
+
|
156
|
+
Unique counters also support querying mutiple ids. For example, we can find the unique aggregate totals across multiple ids by doing:
|
157
|
+
``` ruby
|
158
|
+
Customers.aggregate(id: [1,2,3], start_date: '2014-01-01', end_date: '2014-01-05', interval: :days)
|
159
|
+
```
|
160
|
+
|
161
|
+
#### Attributes
|
162
|
+
|
163
|
+
Attributes are unique counters that are not associated with an id, and can be thought of as a type of "global" group. This can be mashed up with other unique counters that are associated with ids to give the same result as if they were associated with that id. The main advantages to using this technique are:
|
164
|
+
|
165
|
+
* Save a tremendous amount of memory by not storing this data, for ever resolution interval, for every id
|
166
|
+
* Easier to update / maintain / rebuilding data is much quicker ( instead of having to update at every interval / id, you can just update it once at the "global" level)
|
167
|
+
|
168
|
+
This is best explained with the example below.
|
169
|
+
|
170
|
+
Say you have a unique counter "Customers", and a unique couner "Males". Instead of storing them both at the id & daily level we can get the number of males / day / id with the following:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class Customers < Redtastic::Model
|
174
|
+
type :unique
|
175
|
+
resolution :days
|
176
|
+
end
|
177
|
+
|
178
|
+
class Males < Redtastic::Model
|
179
|
+
type :unique
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
then to mash up Customers against Males, just use the attributes parameter:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
Customers.aggregate(id: 1, start_date: '2014-01-01', end_date: '204-01-09', attrbiutes: [:males])
|
187
|
+
```
|
188
|
+
|
189
|
+
You can even mash up multiple attributes. Suppose I want to see all the customers who are Male and Android Users. First add the AndroidUsers class:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class AndroidUsers < Redtastic::Model
|
193
|
+
type :unique
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
then just add that into the query:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
Customers.aggregate(id: 1, start_date: '2014-01-01', end_date: '2014-01-09', attributes: [:males, :android_users])
|
201
|
+
```
|
202
|
+
|
203
|
+
and just like every other example, attributes can be used in aggregations accross multiple ids:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
Customers.aggregate(id: [1,2,3], start_date: '2014-01-01', end_date: '2014-01-09', attributes: [:males, :android_users])
|
207
|
+
```
|
208
|
+
|
209
|
+
All the methods available to unique counters can be used for unique counters acting as global attributes, with a few simplifications. Obviously, if it does not have a resolution and is not associated with an id, then there is no need to pass those parameters into any of those.
|
210
|
+
|
211
|
+
For example, adding / removing a unique_id to a global attribute set:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
Males.increment(unique_id: 1000)
|
215
|
+
Males.decrement(unique_id: 1000)
|
216
|
+
```
|
217
|
+
|
218
|
+
or seeing if a unique_id is in a global set:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
Males.find(unique_id: 1000)
|
222
|
+
```
|
223
|
+
|
224
|
+
### Misc
|
225
|
+
|
226
|
+
#### Script Manager
|
227
|
+
|
228
|
+
Redtastic also provides access to the *Redtastic::ScriptManager* class which it uses internally to pre-load & provide an interface to running Lua Scripts on Redis. Although it is used by Redtastic to run its own scripts, anybody can use it to run their own custom scripts defined in their application:
|
229
|
+
|
230
|
+
Create a script: *./lib/scripts/hello.lua*
|
231
|
+
``` lua
|
232
|
+
return 'hello'
|
233
|
+
```
|
234
|
+
Tell ScriptManager to pre-load your scripts (after initializing Redtastic)
|
235
|
+
``` ruby
|
236
|
+
Redtastic::ScriptManager.load_scripts('./lib/scripts')
|
237
|
+
```
|
238
|
+
|
239
|
+
Now you can easily use your script anywhere in your application:
|
240
|
+
``` ruby
|
241
|
+
puts Redtastic::ScriptManager.hello # prints 'hello'
|
242
|
+
```
|
243
|
+
|
244
|
+
with every script having the ability to accept parameters for the KEYS & ARGV arrays:
|
245
|
+
```ruby
|
246
|
+
keys = []
|
247
|
+
argv = []
|
248
|
+
Redtastic::ScriptManager.hello(keys, argv)
|
249
|
+
```
|
250
|
+
|
251
|
+
Performance
|
252
|
+
-----------
|
253
|
+
|
254
|
+
Contributing
|
255
|
+
------------
|
256
|
+
|
257
|
+
1. Fork it
|
258
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
259
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
260
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
261
|
+
5. Create new Pull Request
|
262
|
+
|
263
|
+
TODOS
|
264
|
+
-----
|
265
|
+
* Set elapsed expiration times for each resolution of a model (ie. keys of resolution days expire in 1 year, months expire in 2 years...etc).
|
266
|
+
* For large, multi-id aggregations, set batch size & do aggregations in serveral batches rather than all in one lua run to prevent long running lua scripts from blocking any other redis operation.
|
267
|
+
* Support for hourly resolutions
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
|
274
|
+
|
275
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Redtastic
|
2
|
+
class Connection
|
3
|
+
class << self
|
4
|
+
attr_accessor :namespace
|
5
|
+
attr_accessor :redis
|
6
|
+
|
7
|
+
def establish_connection(connection, namespace = nil)
|
8
|
+
@redis = connection
|
9
|
+
@namespace = namespace
|
10
|
+
Redtastic::ScriptManager.load_scripts(File.join(File.dirname(__FILE__),'/scripts'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|