binary42-remix-stash 0.9.1 → 0.9.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.
- data/{README.mdown → README.markdown} +2 -0
- data/VERSION +1 -1
- data/{examples/bench.rb → benchmarks/get_set.rb} +2 -2
- data/benchmarks/payload.rb +63 -0
- data/lib/remix/stash/protocol.rb +18 -115
- data/lib/remix/stash.rb +12 -3
- data/remix-stash.gemspec +6 -6
- data/spec/stash_spec.rb +15 -0
- metadata +6 -6
@@ -8,6 +8,8 @@ It does require memcached 1.4+ but you should be running that anyway (if you are
|
|
8
8
|
|
9
9
|
# TODO
|
10
10
|
|
11
|
+
* write extras, keys, and values directly to avoid packing overhead
|
12
|
+
* gracefully handle connection interruptions
|
11
13
|
* optimize option merging with cache
|
12
14
|
* make clusters selectable per stash
|
13
15
|
* implement the rest of the memcached 1.4 binary API
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.2
|
@@ -2,9 +2,9 @@ require 'benchmark'
|
|
2
2
|
require File.dirname(__FILE__) + '/../harness'
|
3
3
|
require File.dirname(__FILE__) + '/../harness_cache'
|
4
4
|
|
5
|
-
LARGE_NUMBER =
|
5
|
+
LARGE_NUMBER = 200_000
|
6
6
|
|
7
|
-
Benchmark.
|
7
|
+
Benchmark.bmbm do |b|
|
8
8
|
b.report('get/set stash') do
|
9
9
|
LARGE_NUMBER.times {|n|
|
10
10
|
stash[:abcxyz123] = n
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require File.dirname(__FILE__) + '/../harness'
|
3
|
+
require File.dirname(__FILE__) + '/../harness_cache'
|
4
|
+
|
5
|
+
LARGE_NUMBER = 20_000
|
6
|
+
|
7
|
+
large_value = 'a' * 100_000
|
8
|
+
med_value = 'b' * 2_000
|
9
|
+
small_value = 'c' * 100
|
10
|
+
tiny_value = 'd'
|
11
|
+
|
12
|
+
KEY = 'abc123xyz'
|
13
|
+
|
14
|
+
Benchmark.bmbm do |b|
|
15
|
+
b.report('100k stash') do
|
16
|
+
LARGE_NUMBER.times {
|
17
|
+
stash.write(KEY, large_value)
|
18
|
+
stash.read(KEY)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
b.report('100k cache') do
|
22
|
+
LARGE_NUMBER.times {
|
23
|
+
Cache.set(KEY, large_value, 0, true)
|
24
|
+
Cache.get(KEY, true)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
b.report('2k stash') do
|
28
|
+
LARGE_NUMBER.times {
|
29
|
+
stash.write(KEY, med_value)
|
30
|
+
stash.read(KEY)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
b.report('2k cache') do
|
34
|
+
LARGE_NUMBER.times {
|
35
|
+
Cache.set(KEY, med_value, 0, true)
|
36
|
+
Cache.get(KEY, true)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
b.report('100b stash') do
|
40
|
+
LARGE_NUMBER.times {
|
41
|
+
stash.write(KEY, small_value)
|
42
|
+
stash.read(KEY)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
b.report('100b cache') do
|
46
|
+
LARGE_NUMBER.times {
|
47
|
+
Cache.set(KEY, small_value, 0, true)
|
48
|
+
Cache.get(KEY, true)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
b.report('1b stash') do
|
52
|
+
LARGE_NUMBER.times {
|
53
|
+
stash.write(KEY, tiny_value)
|
54
|
+
stash.read(KEY)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
b.report('1b cache') do
|
58
|
+
LARGE_NUMBER.times {
|
59
|
+
Cache.set(KEY, tiny_value, 0, true)
|
60
|
+
Cache.get(KEY, true)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
data/lib/remix/stash/protocol.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Stash::Protocol
|
2
2
|
extend self
|
3
3
|
|
4
|
-
HEADER_FORMAT = "
|
4
|
+
HEADER_FORMAT = "CCnCCnNNQ"
|
5
5
|
|
6
6
|
# Magic codes
|
7
7
|
REQUEST = 0x80
|
@@ -50,47 +50,18 @@ module Stash::Protocol
|
|
50
50
|
# Extras
|
51
51
|
COUNTER_FAULT_EXPIRATION = 0xFFFFFFFF
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# Opcode (1) : 0x02
|
57
|
-
# Key length (2,3) : 0x0005
|
58
|
-
# Extra length (4) : 0x08
|
59
|
-
# Data type (5) : 0x00
|
60
|
-
# Reserved (6,7) : 0x0000
|
61
|
-
# Total body (8-11) : 0x00000012
|
62
|
-
# Opaque (12-15): 0x00000000
|
63
|
-
# CAS (16-23): 0x0000000000000000
|
64
|
-
# Extras :
|
65
|
-
# Flags (24-27): 0xdeadbeef
|
66
|
-
# Expiry (28-31): 0x00000e10
|
67
|
-
# Key (32-36): The textual string "Hello"
|
68
|
-
# Value (37-41): The textual string "World"
|
69
|
-
header = [REQUEST, ADD, key.size, 8, 0, 0, data.size + key.size + 8, '', '', 0, 0, key, data].pack(HEADER_FORMAT + 'NNa*a*')
|
53
|
+
ADD_PACKET = HEADER_FORMAT + 'NNa*a*'
|
54
|
+
def add(io, key, data, ttl = 0)
|
55
|
+
header = [REQUEST, ADD, key.size, 8, 0, 0, data.size + key.size + 8, 0, 0, 0, ttl, key, data].pack(ADD_PACKET)
|
70
56
|
io.write(header)
|
71
57
|
resp = read_resp(io)
|
72
58
|
resp[:status] == NO_ERROR
|
73
59
|
end
|
74
60
|
|
61
|
+
DECR_PACKET = HEADER_FORMAT + 'NNQNa*'
|
75
62
|
def decr(io, key, step)
|
76
|
-
# Field (offset) (value)
|
77
|
-
# Magic (0) : 0x80
|
78
|
-
# Opcode (1) : 0x06
|
79
|
-
# Key length (2,3) : 0x0007
|
80
|
-
# Extra length (4) : 0x14
|
81
|
-
# Data type (5) : 0x00
|
82
|
-
# Reserved (6,7) : 0x0000
|
83
|
-
# Total body (8-11) : 0x0000001b
|
84
|
-
# Opaque (12-15): 0x00000000
|
85
|
-
# CAS (16-23): 0x0000000000000000
|
86
|
-
# Extras :
|
87
|
-
# delta (24-31): 0x0000000000000001
|
88
|
-
# initial (32-39): 0x0000000000000000
|
89
|
-
# exipration (40-43): 0x00000e10
|
90
|
-
# Key : Textual string "counter"
|
91
|
-
# Value : None
|
92
63
|
low, high = split64(step)
|
93
|
-
header = [REQUEST, DECREMENT, key.size, 20, 0, 0, key.size + 20,
|
64
|
+
header = [REQUEST, DECREMENT, key.size, 20, 0, 0, key.size + 20, 0, 0, high, low, 0, COUNTER_FAULT_EXPIRATION, key].pack(DECR_PACKET)
|
94
65
|
io.write(header)
|
95
66
|
resp = read_resp(io)
|
96
67
|
if resp[:status] == NO_ERROR
|
@@ -98,60 +69,25 @@ module Stash::Protocol
|
|
98
69
|
end
|
99
70
|
end
|
100
71
|
|
72
|
+
DELETE_PACKET = HEADER_FORMAT + 'a*'
|
101
73
|
def delete(io, key, ttl = 0)
|
102
|
-
|
103
|
-
# Magic (0) : 0x80
|
104
|
-
# Opcode (1) : 0x04
|
105
|
-
# Key length (2,3) : 0x0005
|
106
|
-
# Extra length (4) : 0x00
|
107
|
-
# Data type (5) : 0x00
|
108
|
-
# Reserved (6,7) : 0x0000
|
109
|
-
# Total body (8-11) : 0x00000005
|
110
|
-
# Opaque (12-15): 0x00000000
|
111
|
-
# CAS (16-23): 0x0000000000000000
|
112
|
-
# Extras : None
|
113
|
-
# Key : The textual string "Hello"
|
114
|
-
# Value : None
|
115
|
-
header = [REQUEST, DELETE, key.size, 0, 0, 0, key.size, '', '', key].pack(HEADER_FORMAT + 'a*')
|
74
|
+
header = [REQUEST, DELETE, key.size, 0, 0, 0, key.size, 0, 0, key].pack(DELETE_PACKET)
|
116
75
|
io.write(header)
|
117
76
|
resp = read_resp(io)
|
118
77
|
resp[:status] == NO_ERROR
|
119
78
|
end
|
120
79
|
|
80
|
+
FLUSH_PACKET = HEADER_FORMAT + 'N'
|
121
81
|
def flush(io)
|
122
|
-
|
123
|
-
# Magic (0) : 0x80
|
124
|
-
# Opcode (1) : 0x08
|
125
|
-
# Key length (2,3) : 0x0000
|
126
|
-
# Extra length (4) : 0x04
|
127
|
-
# Data type (5) : 0x00
|
128
|
-
# Reserved (6,7) : 0x0000
|
129
|
-
# Total body (8-11) : 0x00000004
|
130
|
-
# Opaque (12-15): 0x00000000
|
131
|
-
# CAS (16-23): 0x0000000000000000
|
132
|
-
# Extras :
|
133
|
-
# Expiry (24-27): 0x000e10
|
134
|
-
header = [REQUEST, FLUSH, 0, 4, 0, 0, 4, '', '', 0].pack(HEADER_FORMAT + 'N')
|
82
|
+
header = [REQUEST, FLUSH, 0, 4, 0, 0, 4, 0, 0, 0].pack(FLUSH_PACKET)
|
135
83
|
io.write(header)
|
136
84
|
resp = read_resp(io)
|
137
85
|
resp[:status] == NO_ERROR
|
138
86
|
end
|
139
87
|
|
88
|
+
GET_PACKET = HEADER_FORMAT + 'a*'
|
140
89
|
def get(io, key)
|
141
|
-
|
142
|
-
# Magic (0) : 0x80
|
143
|
-
# Opcode (1) : 0x00
|
144
|
-
# Key length (2,3) : 0x0005
|
145
|
-
# Extra length (4) : 0x00
|
146
|
-
# Data type (5) : 0x00
|
147
|
-
# Reserved (6,7) : 0x0000
|
148
|
-
# Total body (8-11) : 0x00000005
|
149
|
-
# Opaque (12-15): 0x00000000
|
150
|
-
# CAS (16-23): 0x0000000000000000
|
151
|
-
# Extras : None
|
152
|
-
# Key (24-29): The textual string: "Hello"
|
153
|
-
# Value : None
|
154
|
-
header = [REQUEST, GET, key.size, 0, 0, 0, key.size, '', '', key].pack(HEADER_FORMAT + 'a*')
|
90
|
+
header = [REQUEST, GET, key.size, 0, 0, 0, key.size, 0, 0, key].pack(GET_PACKET)
|
155
91
|
io.write(header)
|
156
92
|
resp = read_resp(io)
|
157
93
|
resp[:status] == NO_ERROR ? parse_get(resp[:body])[:value] : nil
|
@@ -161,25 +97,10 @@ module Stash::Protocol
|
|
161
97
|
load_ruby_value(get(io, key))
|
162
98
|
end
|
163
99
|
|
100
|
+
INCR_PACKET = HEADER_FORMAT + 'NNQNa*'
|
164
101
|
def incr(io, key, step)
|
165
|
-
# Field (offset) (value)
|
166
|
-
# Magic (0) : 0x80
|
167
|
-
# Opcode (1) : 0x05
|
168
|
-
# Key length (2,3) : 0x0007
|
169
|
-
# Extra length (4) : 0x14
|
170
|
-
# Data type (5) : 0x00
|
171
|
-
# Reserved (6,7) : 0x0000
|
172
|
-
# Total body (8-11) : 0x0000001b
|
173
|
-
# Opaque (12-15): 0x00000000
|
174
|
-
# CAS (16-23): 0x0000000000000000
|
175
|
-
# Extras :
|
176
|
-
# delta (24-31): 0x0000000000000001
|
177
|
-
# initial (32-39): 0x0000000000000000
|
178
|
-
# exipration (40-43): 0x00000e10
|
179
|
-
# Key : Textual string "counter"
|
180
|
-
# Value : None
|
181
102
|
low, high = split64(step)
|
182
|
-
header = [REQUEST, INCREMENT, key.size, 20, 0, 0, key.size + 20,
|
103
|
+
header = [REQUEST, INCREMENT, key.size, 20, 0, 0, key.size + 20, 0, 0, high, low, 0, COUNTER_FAULT_EXPIRATION, key].pack(INCR_PACKET)
|
183
104
|
io.write(header)
|
184
105
|
resp = read_resp(io)
|
185
106
|
if resp[:status] == NO_ERROR
|
@@ -187,24 +108,10 @@ module Stash::Protocol
|
|
187
108
|
end
|
188
109
|
end
|
189
110
|
|
111
|
+
SET_PACKET = HEADER_FORMAT + 'NNa*a*'
|
190
112
|
def set(io, key, data, ttl = 0)
|
191
|
-
|
192
|
-
|
193
|
-
# Opcode (1) : 0x01
|
194
|
-
# Key length (2,3) : 0x0005
|
195
|
-
# Extra length (4) : 0x08
|
196
|
-
# Data type (5) : 0x00
|
197
|
-
# Reserved (6,7) : 0x0000
|
198
|
-
# Total body (8-11) : 0x00000012
|
199
|
-
# Opaque (12-15): 0x00000000
|
200
|
-
# CAS (16-23): 0x0000000000000000
|
201
|
-
# Extras :
|
202
|
-
# Flags (24-27): 0xdeadbeef
|
203
|
-
# Expiry (28-31): 0x00000e10
|
204
|
-
# Key (32-36): The textual string "Hello"
|
205
|
-
# Value (37-41): The textual string "World"
|
206
|
-
header = [REQUEST, SET, key.size, 8, 0, 0, data.size + key.size + 8, '', '', 0, 0, key, data].pack(HEADER_FORMAT + 'NNa*a*')
|
207
|
-
io.write(header)
|
113
|
+
header = [REQUEST, SET, key.size, 8, 0, 0, data.size + key.size + 8, 0, 0, 0, ttl, key, data].pack(SET_PACKET)
|
114
|
+
io << header
|
208
115
|
resp = read_resp(io)
|
209
116
|
resp[:status] == NO_ERROR
|
210
117
|
end
|
@@ -213,10 +120,6 @@ module Stash::Protocol
|
|
213
120
|
set(io, key, Marshal.dump(value), ttl)
|
214
121
|
end
|
215
122
|
|
216
|
-
def method_missing(message, *a)
|
217
|
-
fail [:NOT_IMPLEMENTED, self, message, *a].inspect
|
218
|
-
end
|
219
|
-
|
220
123
|
private
|
221
124
|
|
222
125
|
def load_ruby_value(data)
|
@@ -242,7 +145,7 @@ private
|
|
242
145
|
resp = { :magic => magic, :opcode => opcode, :key_length => key_length,
|
243
146
|
:extra => extra, :type => type, :status => status,
|
244
147
|
:body_length => body_length, :opaque => opaque, :cas => cas }
|
245
|
-
resp[:body] = io.read(
|
148
|
+
resp[:body] = io.read(body_length) if body_length > 0
|
246
149
|
resp
|
247
150
|
end
|
248
151
|
|
data/lib/remix/stash.rb
CHANGED
@@ -33,6 +33,13 @@ class Stash
|
|
33
33
|
@opts = name == :root ? {:coherency => :action, :ttl => 0} : {}
|
34
34
|
end
|
35
35
|
|
36
|
+
def add(*keys)
|
37
|
+
opts = default_opts(keys)
|
38
|
+
value = keys.pop
|
39
|
+
key = canonical_key(keys)
|
40
|
+
cluster.select(key) {|io| Protocol.add(io, key, value, opts[:ttl])}
|
41
|
+
end
|
42
|
+
|
36
43
|
def clear(*keys)
|
37
44
|
if keys.empty?
|
38
45
|
if @name == :root
|
@@ -159,8 +166,9 @@ class Stash
|
|
159
166
|
|
160
167
|
private
|
161
168
|
|
169
|
+
KEY_SEPARATOR = '/'
|
162
170
|
def canonical_key(keys)
|
163
|
-
"#{implicit_scope}#{keys.join(
|
171
|
+
"#{implicit_scope}#{keys.join(KEY_SEPARATOR)}#{vector}"
|
164
172
|
end
|
165
173
|
|
166
174
|
def cluster
|
@@ -175,12 +183,13 @@ private
|
|
175
183
|
params.last.is_a?(Hash) ? default.merge(params.pop) : default
|
176
184
|
end
|
177
185
|
|
186
|
+
EMPTY_SCOPE = ''
|
178
187
|
def implicit_scope
|
179
188
|
if @scope
|
180
189
|
scope = @scope.call(self)
|
181
|
-
scope ? "#{scope}/" :
|
190
|
+
scope ? "#{scope}/" : EMPTY_SCOPE
|
182
191
|
else
|
183
|
-
|
192
|
+
EMPTY_SCOPE
|
184
193
|
end
|
185
194
|
end
|
186
195
|
|
data/remix-stash.gemspec
CHANGED
@@ -5,23 +5,24 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{remix-stash}
|
8
|
-
s.version = "0.9.
|
8
|
+
s.version = "0.9.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Brian Mitchell"]
|
12
|
-
s.date = %q{2009-09-
|
12
|
+
s.date = %q{2009-09-09}
|
13
13
|
s.email = %q{binary42@gmail.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
16
|
-
"README.
|
16
|
+
"README.markdown"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
19
|
".gitignore",
|
20
20
|
"LICENSE",
|
21
|
-
"README.
|
21
|
+
"README.markdown",
|
22
22
|
"Rakefile",
|
23
23
|
"VERSION",
|
24
|
-
"
|
24
|
+
"benchmarks/get_set.rb",
|
25
|
+
"benchmarks/payload.rb",
|
25
26
|
"examples/eval.rb",
|
26
27
|
"examples/gate.rb",
|
27
28
|
"examples/getset.rb",
|
@@ -49,7 +50,6 @@ Gem::Specification.new do |s|
|
|
49
50
|
"spec/extension_spec.rb",
|
50
51
|
"spec/spec.rb",
|
51
52
|
"spec/stash_spec.rb",
|
52
|
-
"examples/bench.rb",
|
53
53
|
"examples/eval.rb",
|
54
54
|
"examples/gate.rb",
|
55
55
|
"examples/getset.rb",
|
data/spec/stash_spec.rb
CHANGED
@@ -105,6 +105,21 @@ class StashSpec < Spec
|
|
105
105
|
|
106
106
|
end
|
107
107
|
|
108
|
+
context '#add' do
|
109
|
+
|
110
|
+
should 'allow keys to be set with new values' do
|
111
|
+
assert stash.add('f', '42')
|
112
|
+
assert_equal '42', stash.read('f')
|
113
|
+
end
|
114
|
+
|
115
|
+
should 'not overwrite existing values' do
|
116
|
+
stash['f'] = '43'
|
117
|
+
assert !stash.add('f', '42')
|
118
|
+
assert_equal '43', stash['f']
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
108
123
|
context '#clear' do
|
109
124
|
|
110
125
|
setup do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: binary42-remix-stash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Mitchell
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-09-
|
12
|
+
date: 2009-09-09 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -21,14 +21,15 @@ extensions: []
|
|
21
21
|
|
22
22
|
extra_rdoc_files:
|
23
23
|
- LICENSE
|
24
|
-
- README.
|
24
|
+
- README.markdown
|
25
25
|
files:
|
26
26
|
- .gitignore
|
27
27
|
- LICENSE
|
28
|
-
- README.
|
28
|
+
- README.markdown
|
29
29
|
- Rakefile
|
30
30
|
- VERSION
|
31
|
-
-
|
31
|
+
- benchmarks/get_set.rb
|
32
|
+
- benchmarks/payload.rb
|
32
33
|
- examples/eval.rb
|
33
34
|
- examples/gate.rb
|
34
35
|
- examples/getset.rb
|
@@ -75,7 +76,6 @@ test_files:
|
|
75
76
|
- spec/extension_spec.rb
|
76
77
|
- spec/spec.rb
|
77
78
|
- spec/stash_spec.rb
|
78
|
-
- examples/bench.rb
|
79
79
|
- examples/eval.rb
|
80
80
|
- examples/gate.rb
|
81
81
|
- examples/getset.rb
|