I am trying to extract a value from a json encoded string that returned from a sorted set in Redis.
127.0.0.1:6379> eval 'local r= redis.call("ZRANGEBYSCORE", "iprange:locations", 34625535, "+inf", "LIMIT", 0, 1); return type(r);' 0
"table"
127.0.0.1:6379> eval 'local r= redis.call("ZRANGEBYSCORE", "iprange:locations", 34625535, "+inf", "LIMIT", 0, 1); return r;' 0
1) "{\"countryCode\": \"IT\", \"countryName\": \"Italy\"}"
I just want to extract countryValue from the result.
Tried return r.countryCode;, return r["countryCode"]; but all of them returned (nil)
By the way, I alread handle this json encoded string in my app by decoding this json into data.
Just trying to delegate this simple task to Redis Lua script engine.
Use the built-in JSON library:
eval 'local r = redis.call("ZRANGEBYSCORE", "iprange:locations", 34625535, "+inf", "LIMIT", 0, 1);
return cjson.decode(r[1])["countryCode"];'
Note that ZRANGEBYSCORE returns an array of results, represented in Lua by a table. Presumably you'll want to loop over the results and extract countryCode for each.
Related
I am using Redis to store JSON data with a model that has Id and Name fields. My goal is to implement cursor pagination over this data using the Name as the cursor. I realize this isn't unique and I'll need to do some additional work to resolve the issues presented by that using that particular field as the cursor. However I am struggling to find a way to even begin to implement a basic cursor pagination with Redis commands. Redis has options for various string operations using the "Search" module but these are limited to search within a given string, nothing with comparisons. Am I barking up the wrong tree here and cursor pagination is not possible? Or is there something fundamental I am missing in my design?
Redis has options for various string operations using the "Search" module but these are limited to search within a given string, nothing with comparisons.
Can you please give an example for a required comparison not within a given string?
The Search query can do some comparisons.
For Cursor, FT.CURSOR can be used, for example:
127.0.0.1:6379> JSON.SET key1 $ '{"Id": 100, "Name": "foo", "data": "val1"}'
OK
127.0.0.1:6379> JSON.SET key2 $ '{"Id": 200, "Name": "bar", "data": "val2"}'
OK
127.0.0.1:6379> JSON.SET key3 $ '{"Id": 300, "Name": "foo", "data": "val3"}'
OK
127.0.0.1:6379> FT.CREATE idx ON JSON SCHEMA '$.Id' as id NUMERIC '$.Name' as name TAG
OK
127.0.0.1:6379> FT.AGGREGATE idx '#name:{foo}' LOAD 2 #id #name WITHCURSOR COUNT 1
1) 1) (integer) 1
2) 1) "id"
2) "300"
3) "name"
4) "foo"
2) (integer) 17284697
127.0.0.1:6379> FT.CURSOR READ idx 17284697 COUNT 1
1) 1) (integer) 1
2) 1) "id"
2) "100"
3) "name"
4) "foo"
2) (integer) 17284697
127.0.0.1:6379> FT.CURSOR READ idx 17284697 COUNT 1
1) 1) (integer) 0
2) (integer) 0
127.0.0.1:6379>
I want to keep some user feedbacks in redis. Some users may give multiple feedbacks. The users are assigned numerical user id
Here is an example:
zadd feedbacks 1 feedback1 2 feedback2 3 feedback3 1 feedback4
In this case user #1 gave feedbacks feedback1 and feedback4, #2 feedback2 and #3 feedback3.
If I use ZRANGEBYSCORE feedbacks 1 1
I will be able to see the feedback from user #1:
1) "feedback1"
2) "feedback4"
However I want to store more than just the text. I want to be able to retrieve the timestamp for example. Is there any way I can insert a hash vale to the key feedbacks above?
Something likes zadd feedbacks 1 text:feedback1 timestamp:123456
No, you cannot. Lists, sets, hashes and sorted sets only support Redis' string data type for the values.
You can stringify your field-value pairs though, using JSON or your preferred format.
ZADD feedbacks 1 "{\"text\":\"feedback1\",\"timestamp\":\"123456\""
Unless you need to modify a given field atomically, this approach should do.
And even in that case, you can use Lua scripts to achieve server-side JSON manipulation and updates. See How to nest a list into a structure in Redis to reduce top level? for a similar solution.
Secondary indexes
But you may want to query multiple ways: by user id, by timestamp, etc.
In that case, consider using regular keys to store the feedback object, say as a hash.
HSET feedbacks:feedback1 text feedback1 timestamp 123456 user 1 ...
And your indexes:
ZADD feedbacks-by-user 1 feedback1
ZADD feedbacks-by-timestamp 123456 feedback1
...
Say you want all feedbacks of a given user:
ZRANGEBYSCORE feedbacks-by-user 1 1
Then you go get the values for the returned keys. Of course, you may want to avoid the two round trips. Again, Lua script.
The script:
local keys = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[1])
for i, v in ipairs(keys) do
local k = {}
k[1] = v
k[2] = redis.call('HGETALL', 'feedbacks:'..v)
keys[i] = k
end
return keys
Usage:
> EVAL "local keys = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1], ARGV[1]) \n for i, v in ipairs(keys) do \n local k = {} \n k[1] = v \n k[2] = redis.call('HGETALL', 'feedbacks:'..v) \n keys[i] = k \n end \n return keys" 1 feedbacks-by-user 1
1) 1) "feedback1"
2) 1) "text"
2) "feedback1"
3) "timestamp"
4) "123456"
5) "user"
6) "1"
2) 1) "feedback4"
2) 1) "text"
2) "feedback4"
3) "timestamp"
4) "465465"
5) "user"
6) "1"
You can similarly query for a range of timestamps.
You can mix and match your queries using ZINTERSTORE or ZUNIONSTORE.
You may be interested in How to store in Redis sorted set with server-side timestamp as score?. You can make a nice Lua script to take care of creating the hash, and the secondary index entries, all in one go with redis-server-side timestamping.
Finally, whenever using Lua on Redis, consider to load the script and use EVALSHA.
I got several sorted sets with a common prefix (itemmovements:) in Redis.
I know we can use ZCOUNT to get the number of items for a single (sorted set) key like this:
127.0.0.1:6379> zcount itemmovements:8 0 1000000000
(integer) 23
(I am able to do this, since I know the range of the item scores.)
How to run this in a loop for all keys prefixed itemmovements:?
Taking hint from How to atomically delete keys matching a pattern using Redis I tried this:
127.0.0.1:6379> EVAL "return redis.call('zcount', unpack(redis.call('keys', ARGV[1])), 0, 1000000000)" 0 itemmovements:*
(integer) 150
but as you can see it just returns a single number (which happens to be the size of itemmovements:0, the first value returned by keys).
I realized I did not understand what that lua code in EVAL was doing. The code below works fine:
eval "local a = {}; for _,k in ipairs(redis.call('keys', 'itemmovements:*')) do table.insert(a, k); table.insert(a, redis.call('zcount', k, 0, 1000000000)); end; return a" 0
How do I push integers with lpush in a redis list type?
I want to test my finagle-redis client if it works correctly and insert manual sample data into redis like this
127.0.0.1:6379> rpush key:214 1 1 1
(integer) 3
127.0.0.1:6379> LRANGE key:214 0 -1
1) "1"
2) "1"
3) "1"
Redis already displays the numbers as char. When I extract them, I also get chars:
val data: List[ChannelBuffer] = Await.result(redisClient.lRange(key, 0, -1))
val buffer: ChannelBuffer = data(0)
buffer.readChar() // 1
buffer.readInt() // 49
Is writing integers to a list possible with the cli client? And if not, will the following even work?
val key = ChannelBuffers.copiedBuffer("listkey", utf8)
val list: List[ChannelBuffer] = List(1,1,1).map { number =>
val buffer = ChannelBuffers.buffer(4) // int sized buffer
buffer.writeInt(number)
buffer
}
// will this store the int's correctly??
redisClient.lpush(key, list)
In redis most everything is a string. This is because of the protocol used. It's text-based, so it's impossible to tell number 1234 from string "1234".
It might be storing integers internally, but you'll get strings anyway. You should cast your numbers in the app.
How do I get the value of multiple keys from redis using a sorted set?
zadd Users 0 David
zadd Users 5 John
zadd Users 15 Linda
zrevrange Users 0 -1 withscores
This will have two users in it.
How can I retrieve the users with key 'David' and 'Linda' in one query?
You can use Redis MGET
redis> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)
More here
http://redis.io/commands/mget
There are multiple ways to do it without introducing a new command in Redis.
For instance, you can fill a temporary set with the names you are interested in, then calculate the intersection between the temporary set and the zset:
multi
sadd tmp David Linda ... and more ...
zinterstore res 2 tmp Users weights 0 1
zrange res 0 -1 withscores
del tmp res
exec
With pipelining, this will only generate one roundtrip and you can fill an arbitrary number of input parameters in tmp.
With Redis 2.6, you can also wrap these lines into a server-side Lua script to finally get a command accepting an input list and returning the result you want:
eval "redis.call( 'sadd', 'tmp', unpack(KEYS) );
redis.call( 'zinterstore', 'res', 2, 'tmp', 'Users', 'weights', 0, 1 );
local res = redis.call( 'zrange', 'res', 0, -1, 'withscores' );
redis.call( 'del', 'res', 'tmp' ) ;
return res
" 2 David Linda
You can safely assume no new command will be added to Redis if it can easily been implemented using scripting.
One uses a sorted set because you want to deal with items that are sorted. What you are asking for is to not use a sorted set as a sorted set. If you don't care about sort order, then perhaps a sorted set is not what you are looking for. You already can retrieve multiple keys, but not arbitrary ones.
If your primary goal is to retrieve multiple arbitrary keys, use a hash and hmget. If your primary need is to access a sorted set, use sorted set and either go the scripting route or pipeline a series of zscore calls.
You cannot get this with one command. The closest you can do to get it in one response:
MULTI
ZSCORE Users David
ZSCORE Users Linda
EXEC
EDIT: Alternatively, you can maintain a parallel hash with users' scores, and query it with
HMGET UserScores David Linda