7

I'm using the Redis client from ServiceStack. I have a Lua script that fills up a Lua table with results from several Redis calls. I want to return this table in some way. My thought was to use the method ExecLuaShaAsList from the client lib and in the lua script just do a "return myTable". It doesnt work, I always get an empty list back.

How to return the lua table to the redis client?

This the C# script I use with the Redis client:

using (var redisClient = GetPooledRedisClient())
{
    var sha1 = redisClient.LoadLuaScript(luaBody);
    List<string> theList = redisClient.ExecLuaShaAsList(sha1);
    int listLength = theList.Count(); //listLength is always 0 for some reason
}

UPDATE AFTER TIPS FROM BELOW ANSWER

This is how LuaBody is created:

    private string GetLuaScript(List<CatalogItem> categories, List<CatalogItem> products)
    {
        string categoriesToAggregate = string.Join("\",\"", categories.Select(c=>c.Name));
        categoriesToAggregate = "\"" + categoriesToAggregate + "\"";

        string csSearchResult = string.Join("\",\"", products.Select(c => c.Name));
        csSearchResult = "\"" + csSearchResult + "\"";


        StringBuilder sb = new StringBuilder();
        sb.AppendLine("local categoriesToAggregate = {").Append(categoriesToAggregate).Append("}                        ");
        sb.AppendLine("local csSearchResult = {").Append(csSearchResult).Append("}                                      ");
        sb.AppendLine("local result = {}                                                                                ");
        sb.AppendLine();
        sb.AppendLine("for i=1,#categoriesToAggregate do                                                                ");
        sb.AppendLine(" result[categoriesToAggregate[i]] = 0                                                            ");
        sb.AppendLine();
        sb.AppendLine(" for j=1,#csSearchResult do                                                                      ");
        sb.AppendLine("     local fieldValue = redis.call('hget', 'asr:'..csSearchResult[j], categoriesToAggregate[i])  ");
        sb.AppendLine("     if fieldValue then                                                                          ");
        sb.AppendLine("         result[categoriesToAggregate[i]] = result[categoriesToAggregate[i]] + fieldValue        ");
        sb.AppendLine("     end                                                                                         ");
        sb.AppendLine(" end                                                                                             ");
        sb.AppendLine("end                                                                                              ");
        sb.AppendLine();
        sb.AppendLine("return cjson.encode(result)                                                                      ");

        return sb.ToString();
    }
Thomas
  • 469
  • 6
  • 16

1 Answers1

11

From Lua, you need to return a Lua Array, or a JSON object. 'myTable' sounds like a handle that is only valid inside the Lua interpreter. That handle is cleaned up directly after the call, so won't get propagated to the client.

Edit: a simple Lua Table/Array should be supported. Not sure what's going on then, without looking at the script.

See also this SO link for some extra info about atomicity of Lua scripts.

Hope this helps, TW

After edit OP:

This was the OP's original Lua script:

local a={}
for i = 1, 1, 1 do
  a["47700415"] = redis.call('hget', 'asr:47700415', 'MDEngines')
  a["47700415_000"] = redis.call('hget', 'asr:47700415_000', 'MGEngines')
end
return a

Answer: You can't return nested values in the Lua return value. As you can see from your ServiceStack function, a Lua script returns a list, and a list is not nested.

Here are two solutions, the one with JSON gives slight overhead (but might be easier when programming, and is nill-safe).

a: Using cjson

local a={}
for i = 1, 1, 1 do
  a["47700415"] = redis.call('hget', 'asr:47700415', 'MDEngines')
  a["47700415_000"] = redis.call('hget', 'asr:47700415_000', 'MGEngines')
end
return cjson.encode(a)

MsgPack is also a very nice and compact serialization format (we use it a lot), and can be returned like this:

a-alt: Using cmsgpack

return cmsgpack.pack(a)

b: Using a simple array

local a={}
for i = 1, 1, 1 do
  a[1] = "47700415"
  a[2] = redis.call('hget', 'asr:47700415', 'MDEngines')
  a[3] = "47700415_000"
  a[4] = redis.call('hget', 'asr:47700415_000', 'MGEngines')
end
return a

This returns:

a:

tw@srv-flux-02:~$ redis-cli -p 14312 EVAL "$(cat ~/tw_luatest.lua)" 0 0
"{\"47700415\":\"Hello\",\"47700415_000\":\"World\"}"

b:

tw@srv-flux-02:~$ redis-cli -p 14312 EVAL "$(cat ~/tw_luatest2.lua)" 0 0
1) "47700415"
2) "Hello"
3) "47700415_000"
4) "World"

As you can see, I put some dummy data in the HSET.

I can also recommend this link, some nice info in there: intro-to-lua-for-redis-programmers

A nice way of adding values to a Lua dict can be seen here :

local fkeys = redis.call('sinter', unpack(KEYS))
local r = {}
for i, key in ipairs(fkeys) do
  r[#r+1] = redis.call('hgetall',key)
end
return r
Community
  • 1
  • 1
Tw Bert
  • 3,659
  • 20
  • 28
  • Hi TW, I have updated with my code. Do you know what can be wrong? – Thomas Mar 09 '14 at 20:51
  • Thanks again. I ended up using json :-) – Thomas Mar 10 '14 at 13:26
  • Hi again TW. I have updated above how I create the Lua script. When I try this with large amount of data (categoriesToAggregate contains 6 items and csSearchResult contains 84000 entries) it takes very long time (7,5 minutes). It's the line "redisClient.ExecLuaShaAsList(sha1)" that takes time. If I run it several times it can be that it is much faster sometimes (seconds rather than minutes). Mostly it is slow though. For small amount of data is is always very fast. I have cheked CPU and memory on the server and they are never at maximum. Do you know what can be the reason? – Thomas Mar 10 '14 at 20:17
  • Correction, shall be ExecLuaShaAsString, NOT ExecLuaShaAsList. – Thomas Mar 10 '14 at 20:52
  • Hi Thomas, glad to help out, but this is not within the scope of your topic title. Please post a different question, I guess this one is answered. In the new question, please state exactly what you want to achieve inside the Lua script, and what the redis data is like. An attached rdb file could help, if possible. To help with troubleshooting/finding the performance bottleneck: write some log info to the [redis logfile:] (http://redis.io/commands/eval) -> emitting redis logs from scripts – Tw Bert Mar 11 '14 at 02:39
  • 1
    Hi TW, no problem. I solved it. I changed my Azure server from small to medium (1.7 Gb ram to 3.5) and now it takes only 3 sec. Turned out it was a RAM issue. Before I used 94% of the ram, now 50%. – Thomas Mar 11 '14 at 19:25
  • Hi Thomas, good to hear that. We also use Azure (CentOS VM). If you want to minimize memory usage, it's easy to zip the data before storing it in Redis. Normally this also speeds things up, depending on your internet connection ofcourse. 3 seconds is still rather long for a Redis operation by the way, a Lua script is transactional and blocks other client ops. If the scripts returns a lot of data, you might want to add a parameter to your Lua script and split it in a few calls. Also with your StringBuilder approach: it helps to keep the Lua script static (parameritized). +Plz mark topic answrd. – Tw Bert Mar 12 '14 at 03:51
  • Do you mean that 84000*6 HGET in 3 seconds are slow? What would you say is a reasonable time for that? – Thomas Mar 12 '14 at 11:03
  • No, that's not what I meant to say. What I mean is, that if you split that huge client call in 84 client calls of a 1000 records, others users of redis don't experience hickups. You'll probably remember my post to your [previous](http://stackoverflow.com/questions/21865045/how-to-store-aggregated-catalog-tree-search-result-in-redis/22137476#22137476) question. At our company we have a requirement for .3 s max. Sidenote: Addendum to my Lua script remark: if you keep the script exactly the same (parameterize it), the SHA1 checksum does not change so it's already loaded/precompiled at the server. – Tw Bert Mar 12 '14 at 12:09
  • Note: If you pipeline your '84' requests, other clients can peep in inbetween, and you won't notice the overhead. – Tw Bert Mar 12 '14 at 12:13
  • I might be wrong here, but I assumed you were working on "Now I just need to create a small lua script that aggregates" that you mentioned in your other post. An aggregate is normally a very small result, which you should be able to get in 10 milliseconds, if you prepare it well. What I said before "you should try to prevent retrieving any data that you don't need" is always valid. Which person is going to read (literally) 84000*6 records? Unless it's a validity check, this usually does not make sense. Just a heads up! – Tw Bert Mar 12 '14 at 12:41
  • I have a huge catalog tree consisting of categories and product, mostly products. It is about 1 milion (Penta parts). Everything is stored in SQL Server. When u search we use sql freetext search. If you search "washer" you get around 84000 products. The catalog tree has 6 root categories. So to aggregate number of hits per category I need to make 84000*6 HGET. Next level has more than 6 categories which will take even longer. So the Lua result contains only 6 "rows", i.e the root categories with its aggregated result. You can try search here (current sql version) http://www.volvopentashop.com. – Thomas Mar 12 '14 at 18:51
  • I use the approach with hash per product you suggested in my previous post. I can also not split the calls since I need all categories aggregated and showed to the user in one go. – Thomas Mar 12 '14 at 18:53
  • I agree that mixing two datastore solutions is not trivial. Challenging tho ;) I can't see a reason why you can't split the redis calls. In one ASP request you can make as many redis calls as you want. The website user does not know this, and has to wait the same three seconds. Which is (ux-wise) perfectly acceptable with a freetext search. I still advice to use static Lua, which you pass a variable number of product ID's, and split it up in 1000 ID's passed to the script per Lua call. If tiny afterward deduplication/aggregation is needed, do that in ASP after all calls, which you pipeline. – Tw Bert Mar 12 '14 at 23:52
  • StackOverflow specific: When you wander from your original question, it's better to post a different question. This conversation in comments is imho far too long, and isn't helpful to future SO readers of this post. – Tw Bert Mar 13 '14 at 00:32
  • Last comment from me :) I don't see the benefit of using several calls when the total time is still 3s. If I have ex 2 simultaneously users then 1:st one has to wait 3s and the other one 6s. With several calls they both has to wait 6s because I guess the total script time will be the same. I will change to static script. Maybe I can optimize by storing all products in each category instead (i.e opposite way) and then use HMGET insted. Will try this. Anyway, thanks again TW. It has been very helpful to me. – Thomas Mar 13 '14 at 18:12
  • You're welcome, and if that single query is the only thing you do with Redis, you are making the right choice. Good luck with finishing up ;) – Tw Bert Mar 13 '14 at 19:07