<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ahmad Hoseiny</title>
    <description>The latest articles on DEV Community by Ahmad Hoseiny (@ahmad_hoseiny_1dce819a3d5).</description>
    <link>https://dev.to/ahmad_hoseiny_1dce819a3d5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3416658%2F7fa803ef-633e-4351-b5df-b02d4bf0f34d.png</url>
      <title>DEV Community: Ahmad Hoseiny</title>
      <link>https://dev.to/ahmad_hoseiny_1dce819a3d5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmad_hoseiny_1dce819a3d5"/>
    <language>en</language>
    <item>
      <title>Redis and Lua scripts</title>
      <dc:creator>Ahmad Hoseiny</dc:creator>
      <pubDate>Sat, 10 Oct 2026 01:54:50 +0000</pubDate>
      <link>https://dev.to/ahmad_hoseiny_1dce819a3d5/redis-and-lua-scripts-296f</link>
      <guid>https://dev.to/ahmad_hoseiny_1dce819a3d5/redis-and-lua-scripts-296f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9x8nl9bdp526ug9czgu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9x8nl9bdp526ug9czgu.jpg" alt="Redis Lua script" width="617" height="741"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redis is the de facto go-to solution for use cases such as counters, rate limits, and distributed locks in systems operating under high load with strict performance requirements. Since Redis is super popular for being single-threaded, hence atomic operations and transactions, it's fairly easy and not uncommon to make the deceptive assumption that the application logic is free of race conditions.&lt;/p&gt;

&lt;p&gt;What happens when we run a &lt;code&gt;GET&lt;/code&gt; command on some key, inspect its value in the application code, and then run a &lt;code&gt;SET&lt;/code&gt; command on the same key? This is a textbook sample case for a &lt;strong&gt;read-modify-write&lt;/strong&gt; race condition. To illustrate, in the meantime between these two commands, another connection could have edited the value of that key resulting in a dirty final value that may be distinct from the expected serializable one. Let's dig into practical use cases and check out how and whether they can be handled utilizing Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Building Blocks: SET, GET, and INCR
&lt;/h2&gt;

&lt;p&gt;Redis commands are simple and individually safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET counter 10
GET counter
INCR counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;INCR&lt;/code&gt; is really nice since it's atomic. Redis reads the existing value, increments it, and writes it back to the same key, all in a single indivisible process (i.e. The scenario in which the key read and write commands can be interleaved by some other connection is impossible here)&lt;/p&gt;

&lt;h2&gt;
  
  
  MULTI / EXEC transactions
&lt;/h2&gt;

&lt;p&gt;To bundle a set of operations into a single atomic transaction, Redis provides &lt;code&gt;MULTI&lt;/code&gt; / &lt;code&gt;EXEC&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MULTI
SET a 1
INCR b
EXEC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a &lt;code&gt;MULTI&lt;/code&gt; command is issued, Redis marks the connection as one entering a transaction. Following commands are not executed but rather queued. When &lt;code&gt;EXEC&lt;/code&gt; is issued, and only then, Redis executes the entire queue of commands back-to-back and sequentially. Again, no interleaving scenario is possible here.&lt;/p&gt;

&lt;p&gt;Seems problem is solved! But, is it really? Could we have lost some privilege in the process?&lt;/p&gt;

&lt;h2&gt;
  
  
  Digression: MySQL transactions
&lt;/h2&gt;

&lt;p&gt;Comparing this with a conventional relational database such as MySQL helps clarify the picture.&lt;/p&gt;

&lt;p&gt;In MySQL, you can open a transaction, run a query, actually have it executed, inspect its results, and then, proceed to the subsequent queries, all within the same atomic transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- application logic decides what to do next&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This capability is possible because of the isolation levels (&lt;code&gt;READ COMMITTED&lt;/code&gt;, &lt;code&gt;REPEATABLE READ&lt;/code&gt;, &lt;code&gt;SERIALIZABLE&lt;/code&gt;, etc.) implemented along with rollback mechanisms. A transaction can be atomic, compute intermediary results, and execute concurrently along with other transactions.&lt;/p&gt;

&lt;p&gt;Redis, on the other hand, being single threaded, runs transactions in a single go from beginning to completion independently. It doesn't communicate any intermediate results with the calling server for the obvious performance purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: Rate Limiter
&lt;/h2&gt;

&lt;p&gt;A naive rate limiter handling distributed web servers could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET client_x:counter        -- app reads: 99
-- app checks: 99 &amp;lt; RATE_LIMIT[client_x]
INCR client_x:counter       -- counter becomes 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple concurrent calls can read a value below the rate limit, and all actually pass and increment it by one, rendering the cap practically useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lua scripting
&lt;/h2&gt;

&lt;p&gt;Redis allows you to send a Lua script to be executed directly on the server as a single atomic command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- KEYS[1] = the counter key&lt;/span&gt;
&lt;span class="c1"&gt;-- ARGV[1] = the cap&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INCR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;EVAL &lt;span class="s2"&gt;"&amp;lt;script_path&amp;gt;"&lt;/span&gt; 1 counter 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this gives us more powerful capabilities. A Turing-complete high-level script can be run on the Redis server atomically. This gives rise to a larger class of applications. Imagine implementing a standard full leaky bucket rate limiter on Redis's side.&lt;/p&gt;

&lt;p&gt;One nice side effect to consider as well is the saved network round trips. Two read and write commands were replaced with a single network call carrying a script expecting the final required response back.&lt;/p&gt;

&lt;h4&gt;
  
  
  Production tip
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Parametrize everything through &lt;code&gt;KEYS&lt;/code&gt; and &lt;code&gt;ARGV&lt;/code&gt;:&lt;/strong&gt; Avoid injecting strings directly into the script (e.g. using template engines or similar tools). Redis caches scripts on its side by the SHA1 hash of their exact source text. Variables hardcoding forces new cache entries along with script recompiling causing performance hits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Lua scripts running on Redis's server side is a powerful feature that gives rise to a large class of applications. However, it doesn't magically solve race conditions related issues. One has to be mindful of the different paths through which the data flows to be able to apply it in the appropriate scenarios.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>backenddevelopment</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
