redis-script_manager 0.0.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 +7 -0
- data/.gitignore +50 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +21 -0
- data/README.md +125 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/redis/script_manager.rb +334 -0
- data/lib/redis/script_manager/version.rb +17 -0
- data/redis-script_manager.gemspec +30 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e5a3988633cfd5315a4591a70edc873c902fe829
|
4
|
+
data.tar.gz: 9e279ed9af52df46d347c69a7f51ce21bf3cfc47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4408f0dd1575e24f23cd5888d44ce7d539d47955cd9c19100237e886ead48b0b4fd49d3d6f84e79478c307e47d0ee69e5d77ca468029704b18bc30357adddf51
|
7
|
+
data.tar.gz: 754d3250762ec2fbbd0e058b403ba0797aa4d1899a0ba1bc04d79430574ed7d71e882242fbd81f39134a43b1f6b0aa88b58ae7b883e23c175cb3cb87fc54e307
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
# Gemfile.lock
|
46
|
+
# .ruby-version
|
47
|
+
# .ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
redis-script_manager (0.0.2)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.10.3)
|
10
|
+
rake (10.5.0)
|
11
|
+
redis (3.3.3)
|
12
|
+
redis-namespace (1.5.3)
|
13
|
+
redis (~> 3.0, >= 3.0.4)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
ruby
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
bundler (~> 1.14)
|
20
|
+
minitest (~> 5.0)
|
21
|
+
rake (~> 10.0)
|
22
|
+
redis (~> 3.2)
|
23
|
+
redis-namespace (~> 1.5)
|
24
|
+
redis-script_manager!
|
25
|
+
|
26
|
+
BUNDLED WITH
|
27
|
+
1.15.3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 ProsperWorks
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# Redis::ScriptManager
|
2
|
+
|
3
|
+
Redis::ScriptManager executes your Lua scripts in your Redis
|
4
|
+
infrastructure while managing trade-offs between bandwidth and
|
5
|
+
statefulness.
|
6
|
+
|
7
|
+
## Other Packages With Related Functionality
|
8
|
+
|
9
|
+
### resque-scheduler
|
10
|
+
|
11
|
+
https://github.com/resque/resque-scheduler
|
12
|
+
|
13
|
+
Resque::Scheduler::Lock::Resiliant manages its scripts with
|
14
|
+
load-on-init and EVALSHA-else-SCRIPT-LOAD-and-re-EVALSHA.
|
15
|
+
|
16
|
+
This is robust if there are no SCRIPT FLUSH or no pipelines, but it
|
17
|
+
will fail if a SCRIPT FLUSH can happen after init, then we run in a
|
18
|
+
pipeline.
|
19
|
+
|
20
|
+
The Lua scripts in resque-scheduler are only a few 100s of bytes in
|
21
|
+
size, so there is little to be gained by avoiding simply using EVAL.
|
22
|
+
|
23
|
+
Warning: resque-scheduler embeds "#{timeout}" in its Lua scripts. One
|
24
|
+
is unlikely to change resque-scheduler lock timeouts frequently, but
|
25
|
+
if so it could be possible to fill the Redis script cache with tons of
|
26
|
+
abandoned scripts.
|
27
|
+
|
28
|
+
### wolverine
|
29
|
+
|
30
|
+
https://github.com/Shopify/wolverine
|
31
|
+
http://shopify.github.io/wolverine/
|
32
|
+
|
33
|
+
Wolverine does load-on-init and EVALSHA-only. Assuming you have only
|
34
|
+
one Redis connection and SCRIPT FLUSH is never called, this is fine
|
35
|
+
even in a pipeline.
|
36
|
+
|
37
|
+
If you juggle multiple Redis connections or are worried about
|
38
|
+
inconsistent script cache contents, Wolverine might have some gaps.
|
39
|
+
|
40
|
+
Wolverine also has nice support for keeping your Lua scripts in a
|
41
|
+
repository of related .lua files, with support for common code
|
42
|
+
folding.
|
43
|
+
|
44
|
+
### redis-lua
|
45
|
+
|
46
|
+
https://github.com/chanks/redis-lua
|
47
|
+
|
48
|
+
Redis-lua does EVALSHA-else-SCRIPT-LOAD-and-re-EVALSHA when not in a
|
49
|
+
pipeline, simple EVAL otherwise.
|
50
|
+
|
51
|
+
This is great: this is always correct regardless of how many Redises
|
52
|
+
you are talking to, and calls outside of a pipeline will tend toward
|
53
|
+
minimal bandwith as the script cache gets warmed up.
|
54
|
+
|
55
|
+
However, if most of your scripting is done within a pipeline,
|
56
|
+
bandwidth use will stay high.
|
57
|
+
|
58
|
+
Early iterations of redis-script_manager were inspired by redis-lua.
|
59
|
+
|
60
|
+
### redis-rb-scripting
|
61
|
+
|
62
|
+
https://github.com/codekitchen/redis-rb-scripting
|
63
|
+
|
64
|
+
redis-rb-scripting does load-on-init plus EVALSHA-else-EVAL. It is
|
65
|
+
correct when the script cache is inconsistent but does not tend to
|
66
|
+
repopulate the cache in these cases.
|
67
|
+
|
68
|
+
redis-rb-scripting has no special provision for pipelines. Called in
|
69
|
+
a pipeline it will fail to recover from a cold cache.
|
70
|
+
|
71
|
+
Like wolverine, redis-rb-scripting also supports a repository of .lua
|
72
|
+
files, but without the common code folding.
|
73
|
+
|
74
|
+
### led
|
75
|
+
|
76
|
+
https://github.com/ciconia/led
|
77
|
+
|
78
|
+
Like redis-rb-scripting, led does load-on-init plus EVALSHA-else-EVAL.
|
79
|
+
It is correct when the script cache is inconsistent but does not tend
|
80
|
+
to repopulate the cache in these cases. It has no special provision
|
81
|
+
for pipelines. Called in a pipeline it will fail to recover from a
|
82
|
+
cold cache.
|
83
|
+
|
84
|
+
Like wolverine, redis-rb-scripting also supports a repository of .lua
|
85
|
+
files with common code folding.
|
86
|
+
|
87
|
+
|
88
|
+
## Installation
|
89
|
+
|
90
|
+
Add this line to your application's Gemfile:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
gem 'redis-script_manager'
|
94
|
+
```
|
95
|
+
|
96
|
+
And then execute:
|
97
|
+
|
98
|
+
$ bundle
|
99
|
+
|
100
|
+
Or install it yourself as:
|
101
|
+
|
102
|
+
$ gem install redis-script_manager
|
103
|
+
|
104
|
+
## Usage
|
105
|
+
|
106
|
+
TODO: Write usage instructions here
|
107
|
+
|
108
|
+
## Development
|
109
|
+
|
110
|
+
After checking out the repo, run `bin/setup` to install
|
111
|
+
dependencies. Then, run `rake test` to run the tests. You can also run
|
112
|
+
`bin/console` for an interactive prompt that will allow you to
|
113
|
+
experiment.
|
114
|
+
|
115
|
+
To install this gem onto your local machine, run `bundle exec rake
|
116
|
+
install`. To release a new version, update the version number in
|
117
|
+
`version.rb`, and then run `bundle exec rake release`, which will
|
118
|
+
create a git tag for the version, push git commits and tags, and push
|
119
|
+
the `.gem` file to [rubygems.org](https://rubygems.org).
|
120
|
+
|
121
|
+
## Contributing
|
122
|
+
|
123
|
+
Bug reports and pull requests are welcome on GitHub at
|
124
|
+
https://github.com/[USERNAME]/redis-script_manager.
|
125
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "redis/script_manager"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
require "redis/script_manager/version"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class ScriptManager
|
5
|
+
|
6
|
+
# TODO: rubocop
|
7
|
+
|
8
|
+
# TODO: rdoc
|
9
|
+
|
10
|
+
# Efficiently evaluates a Lua script on Redis.
|
11
|
+
#
|
12
|
+
# Makes a best effort to moderate bandwidth by leveraging EVALSHA
|
13
|
+
# and managing the SCRIPT LOAD state.
|
14
|
+
#
|
15
|
+
# @param redis a Redis to call, must respond to :eval, :evalsha,
|
16
|
+
# and :script
|
17
|
+
#
|
18
|
+
# @param lua a String, the Lua script to execute on redis
|
19
|
+
#
|
20
|
+
# @param keys a list of String, the keys which will bind to the KEYS
|
21
|
+
# list in the lua script.
|
22
|
+
#
|
23
|
+
# @param args a list of arguments which will bind to ARGV list in
|
24
|
+
# the lua script.
|
25
|
+
#
|
26
|
+
# @return the return result of evaluating lua against keys and args
|
27
|
+
# on redis
|
28
|
+
#
|
29
|
+
# TODO: Should this just monkey-patch over Redis.eval?
|
30
|
+
#
|
31
|
+
def self.eval_gently(redis,lua,keys=[],args=[])
|
32
|
+
[:eval,:evalsha,:script,:client].each do |method|
|
33
|
+
if !redis.respond_to?(method) || !redis.respond_to?(:script)
|
34
|
+
raise ArgumentError, "bogus redis #{redis}, no #{method}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if !lua.is_a?(String)
|
38
|
+
raise ArgumentError, "bogus lua #{lua}"
|
39
|
+
end
|
40
|
+
if !keys.is_a?(Array)
|
41
|
+
raise ArgumentError, "bogus keys #{keys}: non-array"
|
42
|
+
end
|
43
|
+
keys_classes = keys.map(&:class).uniq
|
44
|
+
if [] != keys_classes - [String,Symbol]
|
45
|
+
raise ArgumentError, "bogus keys #{keys}: bad types in #{keys_classes}"
|
46
|
+
end
|
47
|
+
if !args.is_a?(Array)
|
48
|
+
raise ArgumentError, "bogus args #{args}"
|
49
|
+
end
|
50
|
+
if keys.size < 1
|
51
|
+
raise ArgumentError, 'Twemproxy intolerant of 0 keys in EVAL or EVALSHA'
|
52
|
+
end
|
53
|
+
#
|
54
|
+
# Per http://redis.io/commands/eval:
|
55
|
+
#
|
56
|
+
# Redis does not need to recompile the script every time as it
|
57
|
+
# uses an internal caching mechanism, however paying the cost of
|
58
|
+
# the additional bandwidth may not be optimal in many contexts."
|
59
|
+
#
|
60
|
+
# So, where the bandwidth imposed by uploading a short script is
|
61
|
+
# small, we will just use EVAL. Also, in MONITOR streams it can
|
62
|
+
# be nice to see the Lua instead of a bunch of noisy shas.
|
63
|
+
#
|
64
|
+
# However, where the script is long we try to conserve bandwidth
|
65
|
+
# by trying EVALSHA first. In case our script has never been
|
66
|
+
# uploaded before, or if the Redis suffered a FLUSHDB or SCRIPT
|
67
|
+
# FLUSH, we catch NOSCRIPT error, recover with SCRIPT LOAD, and
|
68
|
+
# repeat the EVALSHA.
|
69
|
+
#
|
70
|
+
# Caveat: if all of this is wrapped in a Redis pipeline, then the
|
71
|
+
# first EVALSHA returns a Redis::Future, not a result. We won't
|
72
|
+
# know whether it throws an error until after the pipeline - and
|
73
|
+
# after we've committed to the rest of our stream of commands.
|
74
|
+
# Thus, we can't recover from a NOSCRIPT error.
|
75
|
+
#
|
76
|
+
# To be safe in this pipelined-but-not-in-script-database edge
|
77
|
+
# case, we stick with simple EVAL when we detect we are running
|
78
|
+
# within a pipeline.
|
79
|
+
#
|
80
|
+
if configuration.do_minify_lua
|
81
|
+
lua = self.minify_lua(lua)
|
82
|
+
end
|
83
|
+
if lua.size < configuration.max_tiny_lua
|
84
|
+
_statsd_increment("eval")
|
85
|
+
return redis.eval(lua,keys,args)
|
86
|
+
end
|
87
|
+
if configuration.do_preload
|
88
|
+
#
|
89
|
+
# I tested RLEC in ali-staging with this script:
|
90
|
+
#
|
91
|
+
# sha = Redis.current.script(:load,'return redis.call("get",KEYS[1])')
|
92
|
+
# 100.times.map { |n| "foo-#{n}" }.each do |key|
|
93
|
+
# set = Redis.current.set(key,key)
|
94
|
+
# get = Redis.current.evalsha(sha,[key])
|
95
|
+
# puts "%7s %7s %7s" % [key,get,set]
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# Sure enough, all 100 EVALSHA worked. Since ali-staging is a
|
99
|
+
# full-weight sharded RLEC, this tells me that SCRIPT LOAD
|
100
|
+
# propagates to all Redis shards in RLEC.
|
101
|
+
#
|
102
|
+
# Thus, we can trust that any script need only be sent down a
|
103
|
+
# particular Redis connection once. Thereafter we can assume
|
104
|
+
# EVALSHA will work.
|
105
|
+
#
|
106
|
+
# I do not know if the same thing will happen in Redis Cluster,
|
107
|
+
# and I am just about certain that the more primitive
|
108
|
+
# Twemproxy-over-array-of-plain-Redis will *not* propagate
|
109
|
+
# SCRIPT this way.
|
110
|
+
#
|
111
|
+
# Here is an twemproxy issue which tracks this question
|
112
|
+
# https://github.com/twitter/twemproxy/issues/68.
|
113
|
+
#
|
114
|
+
# This implementation is meant to transmit each script to each
|
115
|
+
# Redis no more than once per process, and thereafter be
|
116
|
+
# pure-EVALSHA.
|
117
|
+
#
|
118
|
+
sha = Digest::SHA1.hexdigest(lua)
|
119
|
+
sha_connection_key = [redis.object_id,sha]
|
120
|
+
if !@@preloaded_shas.include?(sha_connection_key)
|
121
|
+
_statsd_increment("preloaded_shas.cache_miss")
|
122
|
+
new_sha = redis.script(:load,lua)
|
123
|
+
if !new_sha.is_a?(Redis::Future)
|
124
|
+
if sha != new_sha
|
125
|
+
raise RuntimeError, "mismatch #{sha} vs #{new_sha} for lua #{lua}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
@@preloaded_shas << sha_connection_key
|
129
|
+
else
|
130
|
+
_statsd_increment("preloaded_shas.cache_hit")
|
131
|
+
end
|
132
|
+
result = redis.evalsha(sha,keys,args)
|
133
|
+
if configuration.preload_cache_size < @@preloaded_shas.size
|
134
|
+
#
|
135
|
+
# To defend against unbound cache size, at a predetermined
|
136
|
+
# limit throw away half of them.
|
137
|
+
#
|
138
|
+
# It is benign to re-load a script, just a performance blip.
|
139
|
+
#
|
140
|
+
# If these caches are running away in size, we have a worse
|
141
|
+
# time: redis.info.used_memory_lua could be growing without
|
142
|
+
# bound.
|
143
|
+
#
|
144
|
+
_statsd_increment("preloaded_shas.cache_purge")
|
145
|
+
num_to_keep = @@preloaded_shas.size / 2
|
146
|
+
@@preloaded_shas = @@preloaded_shas.to_a.sample(num_to_keep)
|
147
|
+
end
|
148
|
+
cache_size = @@preloaded_shas.size
|
149
|
+
_statsd_timing("preloaded_shas.cache_size",cache_size)
|
150
|
+
return result
|
151
|
+
end
|
152
|
+
in_pipeline = redis.client.is_a?(Redis::Pipeline) # thanks @marshall
|
153
|
+
if in_pipeline
|
154
|
+
_statsd_increment("pipeline_eval")
|
155
|
+
return redis.eval(lua,keys,args)
|
156
|
+
end
|
157
|
+
sha = Digest::SHA1.hexdigest(lua)
|
158
|
+
begin
|
159
|
+
_statsd_increment("evalsha1")
|
160
|
+
result = redis.evalsha(sha,keys,args)
|
161
|
+
if result.is_a?(Redis::Future)
|
162
|
+
#
|
163
|
+
# We should have detected this above where we checked whether
|
164
|
+
# redis.client was a pipeline.
|
165
|
+
#
|
166
|
+
raise "internal error: unexpected Redis::Future from evalsha"
|
167
|
+
end
|
168
|
+
result
|
169
|
+
rescue Redis::CommandError => ex
|
170
|
+
if nil != ex.to_s.index('NOSCRIPT') # some vulerability to change in msg
|
171
|
+
_statsd_increment("script_load")
|
172
|
+
new_sha = redis.script(:load,lua)
|
173
|
+
if sha != new_sha
|
174
|
+
raise RuntimeError, "mismatch #{sha} vs #{new_sha} for lua #{lua}"
|
175
|
+
end
|
176
|
+
_statsd_increment("evalsha2")
|
177
|
+
return redis.evalsha(sha,keys,args)
|
178
|
+
end
|
179
|
+
raise ex
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
@@preloaded_shas = Set[] # [redis.object_id,sha(lua)] which have been loaded
|
184
|
+
|
185
|
+
def self.purge_preloaded_shas # for test, clean state
|
186
|
+
@@preloaded_shas = Set[]
|
187
|
+
end
|
188
|
+
|
189
|
+
# To save bandwidth, minify the Lua code.
|
190
|
+
#
|
191
|
+
def self.minify_lua(lua)
|
192
|
+
lua
|
193
|
+
.split("\n")
|
194
|
+
.map { |l| l.gsub(/\s*--[^'"]*$/,'').strip } # rest-of-line comments
|
195
|
+
.reject { |l| /^\s*$/ =~ l } # blank lines
|
196
|
+
.join("\n")
|
197
|
+
end
|
198
|
+
|
199
|
+
# Reports a single count on the requested metric to statsd (if
|
200
|
+
# any).
|
201
|
+
#
|
202
|
+
# @param metric String
|
203
|
+
#
|
204
|
+
def self._statsd_increment(metric)
|
205
|
+
if configuration.statsd
|
206
|
+
configuration.statsd.increment(configuration.stats_prefix+metric)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Reports a timing on the requested metric to statsd (if any).
|
211
|
+
#
|
212
|
+
# @param metric String
|
213
|
+
#
|
214
|
+
def self._statsd_timing(metric,timing)
|
215
|
+
if configuration.statsd
|
216
|
+
configuration.statsd.timing(configuration.stats_prefix+metric,timing)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# @returns the current Redis::ScriptManager::Configuration.
|
221
|
+
#
|
222
|
+
def self.configuration
|
223
|
+
@configuration ||= Configuration.new
|
224
|
+
end
|
225
|
+
|
226
|
+
# Sets the current Redis::ScriptManager::Configuration.
|
227
|
+
#
|
228
|
+
def self.configuration=(configuration)
|
229
|
+
@configuration = configuration
|
230
|
+
end
|
231
|
+
|
232
|
+
# Yields the current Redis::ScriptManager::Configuration, supports
|
233
|
+
# the typical gem configuration pattern:
|
234
|
+
#
|
235
|
+
# Redis::ScriptManager.configure do |config|
|
236
|
+
# config.statsd = $statsd
|
237
|
+
# config.stats_prefix = 'foo'
|
238
|
+
# config.minify_lua = true
|
239
|
+
# config.max_tiny_lua = 1235
|
240
|
+
# config.preload_shas = true
|
241
|
+
# config.preload_cache_size = 10
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
def self.configure
|
245
|
+
yield configuration
|
246
|
+
end
|
247
|
+
|
248
|
+
class Configuration
|
249
|
+
|
250
|
+
# Defaults to nil. If non-nil, lots of stats will be tracked via
|
251
|
+
# statsd.increment and statsd.timing.
|
252
|
+
#
|
253
|
+
def statsd
|
254
|
+
@statsd || nil
|
255
|
+
end
|
256
|
+
def statsd=(statsd)
|
257
|
+
if statsd && !statsd.respond_to?(:increment)
|
258
|
+
raise ArgumentError, "bogus statsd A #{statsd}"
|
259
|
+
end
|
260
|
+
if statsd && !statsd.respond_to?(:timing)
|
261
|
+
raise ArgumentError, "bogus statsd B #{statsd}"
|
262
|
+
end
|
263
|
+
@statsd = statsd
|
264
|
+
end
|
265
|
+
|
266
|
+
# Defaults to ''.
|
267
|
+
#
|
268
|
+
# Prefixed onto all metrics.
|
269
|
+
#
|
270
|
+
def stats_prefix
|
271
|
+
@stats_prefix || ''
|
272
|
+
end
|
273
|
+
def stats_prefix=(stats_prefix)
|
274
|
+
if stats_prefix && !stats_prefix.is_a?(String)
|
275
|
+
raise ArgumentError, "bogus stats_prefix"
|
276
|
+
end
|
277
|
+
@stats_prefix = stats_prefix || ''
|
278
|
+
end
|
279
|
+
|
280
|
+
# Defaults to false.
|
281
|
+
#
|
282
|
+
# If true, all Lua is minified conservatively to save bandwidth
|
283
|
+
# before any other logic or evaluation.
|
284
|
+
#
|
285
|
+
def do_minify_lua
|
286
|
+
@do_minify_lua || false
|
287
|
+
end
|
288
|
+
def do_minify_lua=(do_minify_lua)
|
289
|
+
@do_minify_lua = [true,'true'].include?(do_minify_lua)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Scripts shorter than max_tiny_lua are always EVALed.
|
293
|
+
#
|
294
|
+
# We skip all logic regarding EVALSHA, extra round-trips for
|
295
|
+
# SCRIPT LOAD, etc.
|
296
|
+
#
|
297
|
+
# Defaults to 512. Integers and Strings which convert to Integers OK.
|
298
|
+
#
|
299
|
+
def max_tiny_lua
|
300
|
+
@max_tiny_lua || 512
|
301
|
+
end
|
302
|
+
def max_tiny_lua=(max_tiny_lua)
|
303
|
+
@max_tiny_lua = max_tiny_lua.to_i
|
304
|
+
end
|
305
|
+
|
306
|
+
# Defaults to false.
|
307
|
+
#
|
308
|
+
# If true, shas are preloaded for each Redis connection (which
|
309
|
+
# make safe to use EVALSHA even in pipelines).
|
310
|
+
#
|
311
|
+
def do_preload
|
312
|
+
@do_preload || false
|
313
|
+
end
|
314
|
+
def do_preload=(do_preload)
|
315
|
+
@do_preload = [true,'true'].include?(do_preload)
|
316
|
+
end
|
317
|
+
|
318
|
+
# The cache of shas which have been preloaded is not allowed to
|
319
|
+
# grow larger than this value.
|
320
|
+
#
|
321
|
+
# Defaults to 1000.
|
322
|
+
#
|
323
|
+
def preload_cache_size
|
324
|
+
@preload_cache_size || 1000
|
325
|
+
end
|
326
|
+
def preload_cache_size=(preload_cache_size)
|
327
|
+
@preload_cache_size = preload_cache_size.to_i
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Redis
|
2
|
+
class ScriptManager
|
3
|
+
#
|
4
|
+
# Version plan:
|
5
|
+
#
|
6
|
+
# 0.0.1 - still in Prosperworks/ALI/vendor/gems/redis-script_manager
|
7
|
+
#
|
8
|
+
# 0.0.2 - broke out into Prosperworks/redis-script_manager
|
9
|
+
#
|
10
|
+
# 0.1.0 - big README.md and Rdoc update, open repo
|
11
|
+
#
|
12
|
+
# 0.2.0 - solicit and incorporate initial feedback from select
|
13
|
+
# beta external users
|
14
|
+
#
|
15
|
+
VERSION = "0.0.2" # broken into standalone repo, not polished
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'redis/script_manager/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
|
7
|
+
spec.name = "redis-script_manager"
|
8
|
+
spec.version = Redis::ScriptManager::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
|
11
|
+
spec.authors = ["jhwillett"]
|
12
|
+
spec.email = ["jhw@prosperworks.com"]
|
13
|
+
|
14
|
+
spec.summary = 'Manage Lua script execution in Redis'
|
15
|
+
spec.homepage = 'https://github.com/ProsperWorks/redis-script_manager'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
27
|
+
spec.add_development_dependency 'redis', '~> 3.2'
|
28
|
+
spec.add_development_dependency 'redis-namespace', '~> 1.5'
|
29
|
+
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-script_manager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jhwillett
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redis-namespace
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.5'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.5'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- jhw@prosperworks.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
|
+
- Gemfile
|
93
|
+
- Gemfile.lock
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- lib/redis/script_manager.rb
|
100
|
+
- lib/redis/script_manager/version.rb
|
101
|
+
- redis-script_manager.gemspec
|
102
|
+
homepage: https://github.com/ProsperWorks/redis-script_manager
|
103
|
+
licenses: []
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.4.8
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Manage Lua script execution in Redis
|
125
|
+
test_files: []
|