I want to store a count in redis. I want to increment the count only if the key exists. What am I doing wrong? exists is returning false and the incr is being executed.
key = "blah"
result = REDIS_DB.multi do
exists = REDIS_DB.exists(key)
REDIS_DB.incr(key) if exists
end
# result: [false, 1]
I am new to redis. I just read the redis transactions doc. From what I understand, the commands in multi should execute one after the other?
Rails 4.0.2, Redis 3.0.1, redis-rb (A Ruby client library for Redis)
As of redis 2.6 lua scripting is supported and it is transactional by definition so following code can be used as well.
redis_script = <<SCRIPT
local exists = redis.call('exists', KEYS[1])
if exists == 1 then
return redis.call('incr', KEYS[1])
end
SCRIPT
# returns incremented value if key exists otherwise nil
REDIS_DB.eval(redis_script, ['testkey'])
Read more about scripting and eval command
Coming to why incr in your actual code executed was because
Each REDIS_DB function call in multi block will return a Redis::Future object not an actual value, as redis-rb caches the commands in multi block. e.g.
REDIS_DB.multi do
return_value = REDIS_DB.exists('tesstt')
puts return_value.inspect
end
will print
<Redis::Future [:exists, "tesstt"]>
Now, If we got through the block in your example
exists = REDIS_DB.exists(key) # returns Redis::Future object
REDIS_DB.incr(key) if exists # evaluates to true as value of exists is an object
So where does result: [false, 1] comes from.
This is because REDIS_DB.multi after yielding your block (and caching the commands) finally converts it into redis query and sends it to redis which eventually runs it and returns the result. so your code is actually converted into following query.
MULTI
exists 'blah'
incr 'blah'
EXEC
and submitted together in a single call to redis which returns 0 for exists ( which redis-rb converts into boolean false) and 1 for incr.
Point to be noted is that this behavior is understandable as if you send each command individually to redis then redis itself will queue everything after MULTI and process it when call to exec is received
This might be what I was looking for:
result = REDIS_DB.watch(key) do
if REDIS_DB.exists(key)
REDIS_DB.incr(key)
else
REDIS_DB.unwatch
end
end
Related
I have a Rake task in my Rails project which executes openssl through a system call.
The code looks like this:
system('bash', '-c', 'openssl cms -verify...')
I need to run the command in bash rather than dash (which is default on Ubuntu) to use process substitution in the command.
I need to create a test with rspec which checks that, in this case, the argument verify was passed as expected.
I have tried the following:
expect(Kernel).to receive(:system) do |args|
expect(args[2]).to match(/verify/)
end
However, this only gives me the third letter in the first string sent to system - i.e. the letter s from bash - rather than the third argument sent in the system call.
What am I doing wrong here? Any suggestions would be much appreciated.
Args are being passed to the block as sequential arguments, so if you want to treat them as an array, you need a splat operator in do |*args|:
expect(Kernel).to receive(:system) do |*args|
expect(args[2]).to match(/verify/)
end
Just to take a step back, it's important to understand how block arguments work, since they are different from methods. For example:
def my_fn(*args)
yield(*args)
end
my_fn(1,2,3) { |args| print args }
# => 1
my_fn(1,2,3) { |a, b, c| print [a,b,c] }
# => [1,2,3]
my_fn(1,2,3) { |*args| print args }
# => [1,2,3]
So if you did do |args| (without the splat), you are assigning the args variable to the first argument passed to the block ("bash") and ignoring the other arguments.
I'm attempting to sandbox some functions using setfenv, and I recieve the following output:
123
nil
Why is testValue when calling sandboxTest() nil, but it's 123 when it's accessed in callSandboxedTest()?
Using LuaJIT 2.1.0-beta2 (Lua 5.1)
function sandboxTest()
print(testValue)
end
local aNumber = 123
function callSandboxedTest()
setfenv(1, {
print = print,
testValue = aNumber,
sandboxTest = sandboxTest
})
print(testValue)
sandboxTest()
end
callSandboxedTest()
Environments aren't part of the call stack. Every function has its own environment. So sandboxTest has an environment, as does callSandboxTest. Changing the environment of one function has no effect on the environment of another.
sandboxTest will continue to use the default environment, so it will access the regular global table to find testValue. And since testValue is never set in the global table, sandboxTest will get nil.
That's why, when maintaining a sandbox, it is very important to carefully choose what functionality to expose to the sandbox. If a function needs to be part of the sandbox, then that function needs to have its environment set.
That's why it's best to sandbox based on compiled Lua chunks rather than individual functions. When creating functions, the functions created will inherit the current environment.
You haven't modified the environment that sandboxTest is using; you only modified the environment of the current function. You can use setfenv to set the environment of a particular function by passing a function name (passing a number modifies the environment of a function in the call stack):
setfenv(sandboxTest, {
print = print,
testValue = aNumber,
sandboxTest = sandboxTest
})
This will print 123 123.
I have a global variable in one of my Rails initializer config/initializers/globals.rb. This variable is simply a list, set to the members of a Redis set.
$listOfBannedDomains = $redis.smembers("banned_domains")
The Redis set gets updated once in awhile. How can I update $listOfBannedDomains with what's in the Redis set once every X hours without restarting my Rails app?
Note that I do not want to read from the Redis set in real-time.
You can use EventMachine for this and utilize it's perioidic timer to do that asynchronously in an evented loop.
Create a new rails initializer - config/initializers/redis_periodic.rb
$listOfBannedDomains = $redis.smembers("banned_domains")
Thread.new { EM.run } unless EM.reactor_running? # Run the EventMachine reactor if it's not running already in a new thread.
Signal.trap("INT") { EM.stop }
Signal.trap("TERM") { EM.stop }
# Add a periodic timer:
time_interval = 5 #seconds
EM.add_perioidic_timer(time_interval) do
# Update the constant by re-reading from redis
$listOfBannedDomains = $redis.smembers("banned_domains")
end
Note: This won't block your main Rails thread from requests and will allow you do that operation asynchronously, per rails instance. You can add extra conditions in initializer to exclude this from happening in Rake(if required).
You can create a wrapper method for returning this variable. You can add some additional variable for storing time of last update. If time from last update is more than X hours update the value from Redis.
I have created a redis lua script to execute a command based on key data type :-
local keyType = redis.call("TYPE", KEYS[1])
if (keyType == "string")
then
return redis.call("GET",KEYS[1])
else
return nil
end
It is returning null every time I am executing it.Can any please help in correcting the script.
The response to redis.call returns as a table that looks like this: {"ok": "string"} (if the type is a string of course)
So in order to properly check, you should change your code to:
local keyType = redis.call("TYPE", KEYS[1]).ok
and the rest of the code will work fine.
The issue is this: the TYPE command is one of the few commands that return a "simple string" or "status" redis reply (see the redis protocol specs for the response types). In the redis lua documentation it's stated that:
Redis status reply -> Lua table with a single ok field containing the status
Which is what happened here.
Using [1] will also work.local keyType = redis.call("TYPE", KEYS[1]) return keyType[1]
I have the following setup:
2 different datastructures: Sets, Strings
They are in different namespaces *:collections:*, *:resources:*
The client doesn't know about this and I try to get both namespaces every time.
Based on exists I decide which datastructure to get finally.
all calls to redis are done asynchronous (vert.x redis-mod)
Now I have to decide if I execute this as lua script or as single commands.
The lua script I came up with:
local path = KEYS[1]
local resourcesPrefix = ARGV[1]
local collectionsPrefix = ARGV[2]
if redis.call('exists',resourcesPrefix..path) == 1 then
return redis.call('get',resourcesPrefix..path)
elseif redis.call('exists',collectionsPrefix..path) == 1 then
return redis.call('smembers',collectionsPrefix..path)
else
return "notFound"
end
Are there any pros and cons for single calls or lua script?
Yes, LUA script is a best solution in case of EVALSHA call:
You are working woth redis asynchronous. So LUA helps you to reduce number of code and code readability.
LUA case is faster becouse of reduce network communication.
I think you can write your code with just 2 commands. You do not need exists in your code.
local path = KEYS[1]
local resourcesPrefix = ARGV[1]
local collectionsPrefix = ARGV[2]
local ret
set ret = redis.call('get',resourcesPrefix..path)
if ret then
return ret
end
set ret = redis.call('smembers',collectionsPrefix..path)
if ret then
return ret
end
return "notFound"
It looks like a good use of Redis LUA scripting to me. The execution time will be short (long scripts are to be avoided as they are blocking). It avoids doing multiple calls, so reduces the total network communication time. So I think it's a better solution than single calls, if you make many of these calls. Especially if you use EVALSHA to have the script cached in Redis.