relix 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +176 -0
- data/lib/relix.rb +8 -0
- data/lib/relix/version.rb +3 -0
- metadata +75 -0
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Relix
|
2
|
+
|
3
|
+
A Redis-backed indexing layer that can be used with any (or no) backend data storage.
|
4
|
+
|
5
|
+
## Rationale
|
6
|
+
|
7
|
+
With the rise in popularity of non-relational databases, and the regular use of relational databases in non-relational ways, data indexing has become an aspect of data storage that you can't simply assume is handled for you. More and more applications are storing their data in databases that treat that stored data as opaque, and thus there's no query engine sitting on top of the data making sure that it can be quickly and flexibly looked up.
|
8
|
+
|
9
|
+
Relix is a layer that can be added on to any model to make all the normal types of querying you want to do: equality, less than/greater than, in set, range, limit, etc., quick and painless. Relix depends on Redis to be awesome at what it does - blazingly fast operations on basic data types - and layers on top of that pluggable indexing of your data for fast lookup.
|
10
|
+
|
11
|
+
## Philosophy
|
12
|
+
|
13
|
+
* Performance is paramount - be FAST.
|
14
|
+
* Leverage Redis and its strengths to the hilt. Never do in Relix what could be done in Redis.
|
15
|
+
* Be extremely tolerant to failure.
|
16
|
+
** Since we can't guarantee atomicity with the backing datastore, index early and clean up later.
|
17
|
+
** Make continuous index repair easy since the chaos monkey could attack at any time.
|
18
|
+
* Be pluggable; keep the core simple and allow easy extensibility
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
If you're using bundler, add Relix to your Gemfile:
|
23
|
+
|
24
|
+
gem 'relix'
|
25
|
+
|
26
|
+
Otherwise gem install:
|
27
|
+
|
28
|
+
$ gem install relix
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
To index something in a model, include the Relix module, declare the primary key (required), and declare any additional indexes you want:
|
33
|
+
|
34
|
+
class Transaction
|
35
|
+
include Relix
|
36
|
+
|
37
|
+
attr_accessor :key, :account_key, :created_at
|
38
|
+
|
39
|
+
relix do
|
40
|
+
primary_key :key
|
41
|
+
multi :account_key, order: :created_at
|
42
|
+
unique :by_created_at, on: :key, order: :created_at
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(key, account_key, created_at)
|
46
|
+
@key = key
|
47
|
+
@account_key = account_key
|
48
|
+
@created_at = created_at
|
49
|
+
|
50
|
+
# Trigger the actual indexing
|
51
|
+
index!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Transaction.new(1, 1, Time.parse('2011-09-30'))
|
56
|
+
Transaction.new(2, 2, Time.parse('2011-09-29'))
|
57
|
+
Transaction.new(3, 2, Time.parse('2011-10-01'))
|
58
|
+
Transaction.new(4, 2, Time.parse('2011-08-30'))
|
59
|
+
|
60
|
+
Note the #index! call to trigger actual indexing.
|
61
|
+
|
62
|
+
Now that your indexes are declared, you can use an index to do a lookups:
|
63
|
+
|
64
|
+
p Transaction.lookup{|q| q[:account_key].eq(1) } # => [1]
|
65
|
+
p Transaction.lookup{|q| q[:account_key].eq(2) } # => [4,2,3]
|
66
|
+
|
67
|
+
The result is always an array of primary keys. You can also use a bare lookup to return all records:
|
68
|
+
|
69
|
+
p Transaction.lookup # => [1,2,3,4]
|
70
|
+
|
71
|
+
# Also useful for counting:
|
72
|
+
p Transaction.lookup.size # => 4
|
73
|
+
|
74
|
+
Some indexes can be ordered by default:
|
75
|
+
|
76
|
+
p Transaction.lookup{|q| q[:account_key].eq(2)} # => [4,2,3]
|
77
|
+
|
78
|
+
Which can be combined with offset and limit:
|
79
|
+
|
80
|
+
p Transaction.lookup{|q| q[:account_key].eq(2, limit: 1)} # => [4]
|
81
|
+
p Transaction.lookup{|q| q[:account_key].eq(2, limit: 1, offset: 1)} # => [2]
|
82
|
+
p Transaction.lookup{|q| q[:account_key].eq(2, limit: 1, offset: 2)} # => [3]
|
83
|
+
|
84
|
+
Since the :primary_key index is ordered by insertion order, we've also declared a :by_created_at index on key that gives us the records ordered by the #created_at attribute:
|
85
|
+
|
86
|
+
p Transaction.lookup{|q| q[:by_created_at].all} # => [4,2,1,3]
|
87
|
+
|
88
|
+
## Querying
|
89
|
+
|
90
|
+
Relix uses a simple query language based on method chaining. A "root" query is passed in to the lookup block, and then query terms are chained off of it:
|
91
|
+
|
92
|
+
class Person
|
93
|
+
include Relix
|
94
|
+
relix do
|
95
|
+
primary_key :key
|
96
|
+
multi :name, order: :birthdate
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
people = Person.lookup{|q| q[:name].eq("Bob Smith")}
|
101
|
+
|
102
|
+
Basically you just specify an index and an operation against that index. In addition, you can specify options for the query, such as limit and offset, if supported by the index type. Relix only supports querying by a single index at a time.
|
103
|
+
|
104
|
+
Any ordered index can also be offset and limited:
|
105
|
+
|
106
|
+
people = Person.lookup{|q| q[:name].eq("Bob Smith", offset: 5, limit: 5)}
|
107
|
+
|
108
|
+
In addition, rather than an offset, an indexed primary key can be specified as a starting place using from:
|
109
|
+
|
110
|
+
person_id = Person.lookup{|q| q[:name].eq("Bob Smith")[2]}
|
111
|
+
people = Person.lookup{|q| q[:name].eq("Bob Smith", from: person_id)}
|
112
|
+
|
113
|
+
The from option is exclusive - it does not return or count the key you pass to it.
|
114
|
+
|
115
|
+
|
116
|
+
## Indexing
|
117
|
+
|
118
|
+
### Inheritance
|
119
|
+
|
120
|
+
Indexes are inherited up the Ruby ancestor chain, so you can for instance set the primary_key in a base class and then not have to re-declare it in each subclass.
|
121
|
+
|
122
|
+
|
123
|
+
### Multiple Value Indexes
|
124
|
+
|
125
|
+
Indexes can be built over multiple attributes:
|
126
|
+
|
127
|
+
relix do
|
128
|
+
multi :storage_state_by_account, on: %w(storage_state account_id)
|
129
|
+
end
|
130
|
+
|
131
|
+
When there are multiple attributes, they are specified in a hash:
|
132
|
+
|
133
|
+
lookup do |q|
|
134
|
+
q[:storage_state_by_account].eq(
|
135
|
+
{storage_state: 'cached', account_id: 'bob'}, limit: 10)
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
## Index Types
|
140
|
+
|
141
|
+
### PrimaryKeyIndex
|
142
|
+
|
143
|
+
The primary key index is the only index that is required on a model. Under the covers it is stored very similarly to a UniqueIndex, and it is stably sorted in insertion order. It is declared using #primary_key within the relix block:
|
144
|
+
|
145
|
+
relix do
|
146
|
+
primary_key :id
|
147
|
+
end
|
148
|
+
|
149
|
+
**Supported Operators**: eq, all
|
150
|
+
**Ordering**: insertion
|
151
|
+
|
152
|
+
|
153
|
+
### MultiIndex
|
154
|
+
|
155
|
+
Multi indexes allow multiple matching primary keys per indexed value, and are ideal for one to many relationships. They can include an ordering, and are declared using #multi in the relix block:
|
156
|
+
|
157
|
+
relix do
|
158
|
+
multi :account_id, order: :created_at
|
159
|
+
end
|
160
|
+
|
161
|
+
**Supported Operators**: eq
|
162
|
+
**Ordering**: can be ordered on any numeric attribute (default is the to_i of the indexed value)
|
163
|
+
|
164
|
+
|
165
|
+
### UniqueIndex
|
166
|
+
|
167
|
+
Unique indexes will raise an error if the same value is indexed twice for a different primary key. They also provide super fast lookups. They are declared using #unique in the relix block:
|
168
|
+
|
169
|
+
relix do
|
170
|
+
unique :email
|
171
|
+
end
|
172
|
+
|
173
|
+
Unique indexes ignore nil values - they will not be indexed and an error is not raised if there is more than one object with a value of nil. A multi-value unique index will be completely skipped if any value in it is nil.
|
174
|
+
|
175
|
+
**Supported Operators**: eq, all
|
176
|
+
**Ordering**: can be ordered on any numeric attribute (default is the to_i of the indexed value)
|
data/lib/relix.rb
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: relix
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathaniel Talbott
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hiredis
|
16
|
+
requirement: &70323249027760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.3.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70323249027760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: redis
|
27
|
+
requirement: &70323249027180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.2
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70323249027180
|
36
|
+
description: ! 'Relix is a layer that can be added on to any model to make all the
|
37
|
+
normal types of querying you want to do: equality, less than/greater than, in set,
|
38
|
+
range, limit, etc., quick and painless. Relix depends on Redis to be awesome at
|
39
|
+
what it does - blazingly fast operations on basic data types - and layers on top
|
40
|
+
of that pluggable indexing of your data for fast lookup.'
|
41
|
+
email:
|
42
|
+
- nathaniel@talbott.ws
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/relix.rb
|
49
|
+
- lib/relix/version.rb
|
50
|
+
homepage: http://github.com/ntalbott/relix
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.8.6
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.11
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: A Redis-backed indexing layer that can be used with any (or no) backend data
|
74
|
+
storage.
|
75
|
+
test_files: []
|