<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://illuminatedcomputing.com/">
  <id>https://illuminatedcomputing.com/</id>
  <title>Illuminated Computing</title>
  <updated>2012-10-11T00:00:00Z</updated>
  <link rel="alternate" href="https://illuminatedcomputing.com/" type="text/html"/>
  <link rel="self" href="https://illuminatedcomputing.com/tags/redis/atom.xml" type="application/atom+xml"/>
  <author>
    <name>Paul A. Jungwirth</name>
    <uri>https://illuminatedcommputing.com/</uri>
  </author>
  <entry>
    <id>tag:illuminatedcomputing.com,2012-10-11:/posts/2012/10/redis-eval/</id>
    <title type="html">EVAL in Redis</title>
    <published>2012-10-11T00:00:00Z</published>
    <updated>2012-10-11T00:00:00Z</updated>
    <link rel="alternate" href="https://illuminatedcomputing.com/posts/2012/10/redis-eval/" type="text/html"/>
    <content type="html">
&lt;p&gt;I had a chance to play with Redis’s new &lt;code&gt;EVAL&lt;/code&gt; command recently. This command is only available in Redis 2.6 (currently at Release Candidate 8), and it lets you run Lua code on the Redis server. The nice part about &lt;code&gt;EVAL&lt;/code&gt; is that your Lua code is atomic, so you can get transactional semantics.&lt;/p&gt;

&lt;p&gt;Redis already had the &lt;code&gt;MULTI&lt;/code&gt; command, which let you issue several commands as an atomic unit, but the problem there is that none of the commands give you a return value until the entire block is complete, so you can’t make decisions inside the &lt;code&gt;MULTI&lt;/code&gt; command. Here is an example of when that’s important:&lt;/p&gt;

&lt;p&gt;I’ve been working on a web crawler lately, and it needs to crawl a site with millions of pages. I wanted to store the pages to be crawled in a queue called &lt;code&gt;seeds&lt;/code&gt;, and the crawler thread(s) would grab a page off the queue, crawl it, and then push newly-discovered pages back onto the queue. But I also needed to keep track of which pages I’d seen, because I didn’t want to crawl them twice. So I kept those pages in a set called &lt;code&gt;crawled&lt;/code&gt;. You only queue a page on &lt;code&gt;seeds&lt;/code&gt; if it isn’t in &lt;code&gt;crawled&lt;/code&gt;. Simple enough. I decided to store both &lt;code&gt;seeds&lt;/code&gt; and &lt;code&gt;crawled&lt;/code&gt; in Redis, so that if the crawler crashed it could pick up again where it left off.&lt;/p&gt;

&lt;p&gt;The trick is that if you’re running multiple crawlers in parallel, then the queue-a-seed-unless-already-crawled part needs to be atomic, but it requires two steps: check in &lt;code&gt;crawled&lt;/code&gt;, then push to &lt;code&gt;seeds&lt;/code&gt;. And you can’t use &lt;code&gt;MULTI&lt;/code&gt;, because whether to queue depends on &lt;code&gt;crawled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you could use external syncrhonization to handle this, which may be easier if you’re just running several threads within a single process, but if you’re running several processes, perhaps even distributed across multiple machines, then Redis is a convenient synchronization point since you’re going there already.&lt;/p&gt;

&lt;p&gt;In the Redis &lt;code&gt;EVAL&lt;/code&gt; command, you pass several arguments. The first is a string with the Lua code you want run. The next is an integer telling how many more arguments to treat as Redis keys, available in Lua from the &lt;code&gt;KEYS&lt;/code&gt; array as &lt;code&gt;KEYS[1]&lt;/code&gt;, &lt;code&gt;KEYS[2]&lt;/code&gt;, etc. Any further arguments are available from the &lt;code&gt;ARGV&lt;/code&gt; array. (Yes, Lua array indexes start at 1.)&lt;/p&gt;

&lt;p&gt;The Lua script can run Redis commands via &lt;code&gt;redis.call()&lt;/code&gt; and &lt;code&gt;redis.pcall()&lt;/code&gt;. The former passes errors back to you; the latter lets you trap errors in Lua.&lt;/p&gt;

&lt;p&gt;Here was my &lt;code&gt;EVAL&lt;/code&gt; command (called via a Ruby client):&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-ruby"&gt;redis.eval(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;if redis.call('SISMEMBER', KEYS[1], ARGV[1]) == 0 &lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; +
           &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;then redis.call('RPUSH', KEYS[2], ARGV[1]) &lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; +
           &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;end&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;,
           [&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;crawled&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;seeds&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;], [seed])&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A couple things to note: First, we are passing our key names in the &lt;code&gt;KEYS&lt;/code&gt; array rather than hard-coding them into the Lua script. This is per the Redis documentation, and allegedly it helps Redis optimize our code, although I don’t understand the explanation why.&lt;/p&gt;

&lt;p&gt;Actually I suspect it is only an optimization when your key names are variable, so you get better performance vs. passing them in ARGV. But since we use the same key names every time, I’d bet it would be just as fast to hard-code them in Lua rather than pass them to KEYS. It may even be faster that way!&lt;/p&gt;

&lt;p&gt;Second, we are saying &lt;code&gt;if redis.call(...) == 0&lt;/code&gt; rather than simply &lt;code&gt;if not redis.call(...)&lt;/code&gt;. This is because &lt;code&gt;ISMEMBER&lt;/code&gt; returns 0 or 1, and to Lua both those values are true.&lt;/p&gt;

&lt;p&gt;I think this is a very nice example of why you might use Redis’s new &lt;code&gt;EVAL&lt;/code&gt; command and how it works. I hope it was helpful to you!&lt;/p&gt;
</content>
  </entry>
</feed>

