<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Vadim Samokhin on Medium]]></title>
        <description><![CDATA[Stories by Vadim Samokhin on Medium]]></description>
        <link>https://medium.com/@wrong.about?source=rss-44ddf0b0cb48------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*1XRdRTjXTHPkgoIFNREFuQ.jpeg</url>
            <title>Stories by Vadim Samokhin on Medium</title>
            <link>https://medium.com/@wrong.about?source=rss-44ddf0b0cb48------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 21:27:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@wrong.about/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How lucky are you?]]></title>
            <link>https://medium.com/@wrong.about/how-lucky-are-you-97f2fccfd793?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/97f2fccfd793</guid>
            <category><![CDATA[life]]></category>
            <category><![CDATA[death]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Wed, 19 Aug 2026 17:06:56 GMT</pubDate>
            <atom:updated>2026-08-19T18:03:12.102Z</atom:updated>
            <content:encoded><![CDATA[<p>Luck has layers.</p><p>Turning 38 is bad luck. Good luck is being healthy.</p><p>Getting cancer at 38 is bad luck. Good luck is having no metastases.</p><p>Developing metastases a year later is bad luck. Good luck is having them only in the lungs.</p><p>Having the first two lines of treatment fail is bad luck. Good luck is responding to the third.</p><p>Having the tumor quickly become resistant to the third line is bad luck. Good luck is that molecular testing points to a couple of other promising drugs.</p><p>Running out of treatment options and slowly dying from suffocation is bad luck. Good luck is knowing that it will soon be over.</p><p>Luck has layers.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97f2fccfd793" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Internals: Redis Processes a Command]]></title>
            <link>https://medium.com/better-programming/redis-internals-redis-processes-a-command-c465d7bb1f96?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/c465d7bb1f96</guid>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[hash-table]]></category>
            <category><![CDATA[c]]></category>
            <category><![CDATA[data-structures]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Tue, 08 Aug 2023 15:50:39 GMT</pubDate>
            <atom:updated>2023-08-16T09:54:14.943Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Internals: Redis Processes a Command</h3><h4>Dictionary structure, key eviction, hash table resize and rehash, and command execution</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*i7KBjSXdhtkDH0im" /><figcaption>Photo by <a href="https://unsplash.com/@ryland_dean?utm_source=medium&amp;utm_medium=referral">Ryland Dean</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>By now, I’ve already considered how <a href="https://medium.com/@wrong.about/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d">Redis server starts up</a>, how <a href="https://medium.com/p/faf6ad1a7df5">clients connect to it</a>, and <a href="https://medium.com/p/b06178d98825">how Redis reads their commands and writes responses</a> — both in a single-threaded and <a href="https://medium.com/p/9e3e8c463f7">I/O threaded mode</a>. Finally, I’ve made my way to the heart of Redis in this <a href="https://medium.com/p/7fff917b6a60">Redis 7 Internals series</a>. In this post, I’ll describe how its db engine, if I may call it so, is implemented. This is where Redis finally executes a command. Unlike I/O operations, like reading and parsing clients’ commands and writing responses, this is always done in a single main thread.</p><p>I won’t repeat the basic concepts that Redis relies upon, like <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#b068">epoll multiplexing mechanism</a>, <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#8a2b">event loop</a>, and <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#099a">non-blocking</a> <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#0a71">I/O</a>. Instead, let’s get straight to where I left off when I described command parsing: <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3806">command processing</a>.</p><p>From a higher-level view, this logic looks like the following. A command is looked up, then Redis checks whether it can execute it for this client, and if it can, it does some chores and finally executes it.</p><h3>Redis Looks Up a Command and Checks Whether It Can Execute It</h3><p>First, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3845">Redis looks up a command</a>. All commands are stored in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1540">redisServer.command</a>s dict, which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2094">populated during start-up</a>. Redis simply <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3134">fetches a command</a> from this dictionary. If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3847">it doesn’t exist</a>, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3848">it’s not executed</a>, apparently.</p><p>Before executing a command, Redis runs some checks. There are lots of them, indeed. Here are some of those:</p><ul><li>Redis makes sure that <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3851">a client has passed a correct number of arguments</a>.</li><li>Redis checks <a href="https://redis.io/docs/management/security/acl/">ACL</a> whether <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3908">a client is allowed to run this command</a>.</li><li>Given <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4003">there are problems with saving data on disk</a>, if a command is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4005">write one</a> and it’s sent by an ordinary client, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4007">not a master</a>, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4023">Redis rejects this command</a>.</li><li>Given healthy replicas, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/replication.c#L3452-L3453">quantity and max allowed lag</a> are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3158-L3159">configured</a>. If there’re <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4030">not enough </a>of them, the write command can’t be run.</li><li>Apparently, Redis can’t execute a<a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4037-L4039"> write command from ordinary clients (non-master) on a read-only slave</a>.</li><li>Given <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3057">stale reads are not allowed</a>, if a command which doesn’t allow stale reads is run on a replica with a broken connection to a master, it’s rejected. You can tell that a command allows stale reads if it has a CMD_STALE flag — <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/commands.c#L7232">echo</a> or <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/commands.c#L7233">hello</a>, for instance. Most of them don’t, though.</li><li>Given <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4077">an RDB file or AOF file is loading right now</a> if the current command isn’t loading-compatible, then it’s rejected. As always, this property is defined as a flag — CMD_LOADING, in this case. The same goes for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4083">asynchronous loading</a>, which is available <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/replication.c#L2113">only on a replica during a catch-up with the master</a>.</li><li>If a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4111">replica wants to execute a read-, write-, or a replicable command</a> (that is, almost <em>any</em> command), Redis master rejects it.</li></ul><p>Besides those checks, Redis runs some mandatory procedures.</p><h3>Overview of Structures Redis Uses for Data Storage</h3><p>Each Redis database has two dictionaries. The first one is used for keys with expiry date. It’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L961">redisDb.expires</a>, and values stored there are expiration timestamps. The other is for client values; it’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L960">redisDb.dict</a>.</p><p>Each Redis dict has <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L87">two hash tables</a>. Both are implemented as a plain array; each slot, or bucket, contains a list of elements — in case of several elements’ hashes point at the same array index (this is known as a hash collision). Thus, it’s a classic hash table implementation, nothing special:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*A2sSPigsxWJlsE2Y.jpeg" /></figure><p>But why two of them? Just wait a sec, I’ll describe it in the next section.</p><p>Which data structures do hash tables contain? Each one stores linked lists of <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L63">dictEntry</a>s. The client key is in its key field. In the case of a dict struct, val field stores a redisObject which <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L898">points</a> to a client-set value. Most of the time, data that clients create and manipulate is stored exclusively there — in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L898">redisObject.ptr</a> pointer. expires dictionary stores client-defined expiration timestamps in the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L68">dictEntry.v.s64</a> field in milliseconds.</p><p>Here is an overall view of what a dictionary looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gsGJWchCH4V3BukF9xkHpA.jpeg" /><figcaption>Redis db has several dict structs. Each one contains two hash tables. Each slot contains a list of dictEntry’s. Each dictEntry has a key and a value. *val pointer refers to a client-set value wrapped into a redisObject struct.</figcaption></figure><h3>Short Recap of Maintenance Procedures: Resize and Rehash of Dictionaries</h3><p>When the number of elements in a hash table is high, the hash collision number increases. For instance, if a hash table has four buckets and four hundred elements, each bucket contains a list of about a hundred elements in length. Search time complexity effectively degrades to linear — O(n) instead of remaining constant — O(1).</p><p>That’s why Redis monitors a hash table size: if the number of elements becomes too high, a hash table is expanded. Having a sparse hash table is no good either: Redis has some procedures which iterate over a hash table. If it has a million buckets and just one element, most CPU cycles are wasted on iteration itself instead of doing some useful stuff. That’s why Redis shrinks sparse hash tables.</p><p>But it’s not <a href="https://en.wikipedia.org/wiki/In-place_algorithm">an in-place algorithm</a>. Expanding and rehashing all of the elements in one go would take a lot of time. That’s why Redis does that incrementally. First, it creates a new hash table with a suitable size. Then, it starts moving elements there from the first hash table gradually. In each cron iteration, Redis rehashes elements residing in one hundred consecutive buckets. Besides, while accessing or mutating a hash table, elements from a single bucket are rehashed. Redis remembers a bucket index where it left off; it’s stored in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L90">dict.rehashidx</a>. All the previous buckets are guaranteed to be empty; all the subsequent ones probably contain some data:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2alok5x1yuMJ3Z0V2RK--w.jpeg" /></figure><p>You can read implementation details of <a href="https://medium.com/p/921af129c27d#e3a4">those</a> <a href="https://medium.com/p/921af129c27d#9ce8">processes</a> in <a href="https://medium.com/p/921af129c27d">the first post</a> of this series.</p><h3>Redis Runs Some Regular Procedures Before Command Execution</h3><h4>Clients are evicted</h4><p>Redis tracks <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L926">how much memory each client consumes</a>. Those clients are stored in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1600">redisServer.client_mem_usage_buckets</a> array. Clients are grouped in buckets by the amount of memory they occupy, and clients are stored in each bucket as a list. For instance, the first bucket stores clients, which take up to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L142">32 kB</a>. The second one stored clients taking up to 64 kB, and so on: memory threshold doubles with each subsequent bucket. The last one stores clients occupying <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L143">more than 4 GB</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hU_u8crjDOpN_POy0woUeQ.jpeg" /></figure><p>If a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4465">memory limit</a> for how much memory clients can occupy is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3204">configured</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3944">Redis starts the eviction procedure</a>. What is meant by client eviction is all memory associated with a client is freed. Redis starts with the last redisServer.client_mem_usage_buckets bucket, where the clients <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4462">consuming the highest amount of memory</a> are located. Before each new client’s eviction, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4468-L4469">Redis checks whether the total memory occupied by all clients fits into the specified limit</a>. If it does, eviction stops. If not, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4470">next client in the current bucket is evicted</a>. If no clients are left in a current bucket, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4479">Redis moves to the next one</a>. During a client eviction process, everything about a client is freed: <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1573">read but not yet parsed commands</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1596-L1597">parsed commands with arguments</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1593-L1594">non-sent replies</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1582">watched keys</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1585-L1590">pubsub-related stuff</a>. Besides, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1607">a connection socket is closed</a>.</p><h4>Keys are evicted</h4><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3957">Key eviction</a> is a process that runs when <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L551">Redis occupies more memory</a> than a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L410">configured limit</a>, and it has to delete some keys. If a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L636-L637">policy has a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L636-L637">random in its name</a>, it’s really nothing special: <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L648">Redis just picks a random key</a> in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L642">each database</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L672">deletes it</a>. It’s quick and dirty.</p><p>But for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L582-L583">all other policies</a>, key eviction logic is more involved. From the higher level, each iteration looks like the following. Redis takes a <a href="https://en.wikipedia.org/wiki/Sampling_(statistics)">sample</a> of keys from the first database, then for each element, it calculates a property called “idle time” and finally inserts an element in a 16-sized pool in ascending order. Over time, the meaning of “idle time” broadened, but the crucial property remained: the greater the value, the more suitable it is for eviction. This process is repeated for each database. Finally, when Redis has a pool of keys from each database, the key with the highest “idle time” is deleted.</p><p>Don’t forget that this logic is executed in the context of running a command. Redis doesn’t aim to arrive within memory limits in one go — this can take too long. Instead, keys are evicted iteratively and within strict temporal boundaries. The maximum duration is configured by <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3143">maxmemory-eviction-tenacity</a> setting. When it’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L501">lower or equal to ten</a>, the specified value is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L503">factor of 50 μs</a>. When it’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L506">higher than ten but lower than 100</a>, each subsequent duration is 15% higher than the previous. maxmemory-eviction-tenacity equaling 100 <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L511">effectively disables a time limit</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fmwEdg7PcUIbCnTTiSRiwg.jpeg" /></figure><p>The default value is 10, so before executing a command, Redis performs key eviction for not much longer than 0.5 ms.</p><p>Now let’s look at one iteration of key eviction in more detail. I start with an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L598">eviction pool filling</a>. The following logic is executed for each database, but keys are inserted in a single fixed-sized pool.</p><p><strong>Redis gets a sample of keys from a current database<br></strong>This is what <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L150">dictGetSomeKeys()</a> does. Strictly speaking, it doesn’t get a sample. In statistics, the sample is supposed to be random and filled independently. Instead, this procedure simply takes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3142">maxmemory-samples</a> (by default, it’s five) subsequent elements starting with the one chosen randomly from a given dictionary.</p><p>Which dictionary is sampled? If a <a href="https://redis.io/docs/reference/eviction/#eviction-policies">policy</a> has a volatile in its name, it means that only keys with explicitly set expiry timestamps are eligible for eviction. In this case, an expires dictionary is sampled. Otherwise, dict is chosen.</p><p>When <a href="https://medium.com/p/921af129c27d#e3a4">rehashing</a> is not in progress, implementation is quite straightforward. Redis just <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1067">picks a random bucket</a> in a hash table and iterates it, collecting elements along the way. First, it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1097-L1105">iterates each bucket’s list</a>. If it’s empty or there are no more elements in it, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1108">Redis moves to the next bucket</a>. When <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1104">it’s collected </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1104">maxmemory-samples of them</a>, it stops.</p><p>When rehashing is in progress, the logic is a bit more involved. Generally, it looks like the following. First, Redis generates a random index. Then, it goes to the first hash table’s bucket, which resides under this generated initial index and iterates all elements from that bucket’s list. Then, it goes to the second table’s bucket of the same index and gets all elements from there. Then, Redis repeats that with the subsequent index, and so on — until the necessary number of elements is collected. To illustrate this process, I’d like to consider one normal case and two more edge ones, in more detail.</p><p><em>Rehashing is in progress: the dictionary is expanding, and a random index is greater than a </em><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L90"><em>rehash index</em></a><em>.</em></p><p>Let’s say a random index is 5:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PFlPQANyAzfrG5TZPWMZ4A.jpeg" /></figure><p>The iteration looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e86wwa0AOlir5nRRP8ii6g.jpeg" /></figure><p>Here is what the resulting pool looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iwLgsnbes93NfI-fPxCyjQ.jpeg" /></figure><p>Now, let’s move on to some more rare cases.</p><p><em>Rehashing is in progress: the dictionary is shrinking, and a random index is less than a rehash index but greater than the second hash table size.</em></p><p>Let’s say a random index is 4. In this case, an initial index is set to rehash index because all the buckets before the rehash index are empty:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bHmCcPMvBf5vJffEXQsFEA.jpeg" /></figure><p>During pool population, only the larger table is iterated — the one that is being shrunk because an initial index is greater than the size of the second hash table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QqYCHLP0Qx2Ozhqm9UkMGg.jpeg" /></figure><p>As a result, there are only four items in a pool — this is less than what was asked. But there are no more items left.</p><p><em>Rehashing is in progress: the dictionary is expanding, and a random index is still less than a rehash index.</em></p><p>Let’s say a random index is 1:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c14j0uMmmUqMNNWQhXxjKg.jpeg" /></figure><p>In this case, Redis iterates through both hash tables but skips already rehashed buckets from the first hash table — they are empty anyway:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zdO28FfU7vmO4o2cFErC2g.jpeg" /></figure><p>Here is the resulting pool:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vdBC9S3U6sJqNVYhxs1XxA.jpeg" /></figure><p>After a pool is ready, Redis goes to the next part.</p><p><strong>Redis sorts a pool<br></strong>Then, Redis sorts a pool in an ascending idle time order. For different key eviction policies, “idle time” means different things, but it has a crucial property: the greater it is, the better candidate for eviction a corresponding item is.</p><p><em>Idle time in case of LRU policy<br></em>The “idle time” name was coined at times when the only policy implemented in Redis was the <a href="https://redis.io/docs/reference/eviction/">Last Recently Used</a>. When it’s employed, the best candidate for eviction is the last recently used one, indeed. In this case, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L894">redisObject.lru</a> stores <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L884">LRU_BITS</a> (which is 24) least significant bits of a timestamp when a corresponding key was last looked up:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gbuzufp_697R0gYxoes_dg.jpeg" /></figure><p>It means that this value wraps around every 194 days. In other words, Redis can’t tell the difference between the last lookup time of one day ago and 195 days ago. For most workloads, I think it’s OK though: if you have keys that are looked up that rarely, it’s not a problem to hit a database one or two times a year in case they got evicted.</p><p>In the case of LRU policy, the best candidate for eviction is the one used the least recently. In this case, “idle time” means what it is: a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L172">time interval when a current key has not been looked up</a>:</p><pre>idle = now() - dictEntry.val.lru</pre><p>Let’s now move to how idle time is calculated when LFU is used.</p><p><em>Idle time in case of LFU policy<br></em>In this case, redisObject.lru stores a combination of when the key was last looked up, concatenated with a relative usage frequency:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w_W_jlw3NnrnWphDixre1A.jpeg" /></figure><p>The last lookup time wraps around every 45-something days, which isn’t a problem still, I believe. Redis needs it to calculate key usage frequency; I’ll describe it in a few moments. The frequency itself is not a precisely calculated value reflecting a physical concept of <a href="https://en.wikipedia.org/wiki/Frequency">frequency</a>. We don’t need that. It’s enough for us if it just correlates with usage frequency so that it can be used for comparing with each other. Since it is just 8 bits, 0 is for the least frequency, and 255 is for the greatest one.</p><p>Now, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L54">how is it updated</a>? It’s a three-step process. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L55">First</a>, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L319">counter is decreased</a> proportionally to the time a corresponding key has not been looked up. That sounds natural: if a key was last looked up a month ago, its usage frequency is low. That’s what Redis stores the last lookup time for. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L56">Second</a>, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L299">counter is increased probabilistically</a>: the greater a counter, the less likely it is to increase. Why so? Because we need a counter to fit in 8 bits, so we slower its increase rate. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L57">Third</a>, a new timestamp in minutes is taken, and the 16 least significant bits are extracted and concatenated with a new counter.</p><p>How’s “idle time” calculated when this policy is in place? In the case of LFU, we need an inversed frequency so that the higher the frequency, the lower the “idle time,” so the less suitable this item is for eviction. The opposite is true: the lower the frequency, the higher the “idle time”, so the more suitable this item is for eviction. Thus, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L181">Redis extracts this frequency and subtracts it from its maximum possible value, 255</a>:</p><pre>idle = 255 - LFUDecrAndReturn(o);</pre><p><em>Idle time in case of </em><em>volatile-ttl policy<br></em>When this eviction policy is used, it is an <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223912201&amp;cot=14">expires</a> dictionary, which is sampled. Keys there are ordinary client-set keys, but values are expiration timestamps. We invert them: the lower an expiration timestamp, the better it suits for eviction:</p><pre>idle = ULLONG_MAX - (long)dictGetVal(de);</pre><p><strong>Redis removes a key with the highest “idle time”<br></strong>By now, Redis has an ordered pool of elements. Then, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L626">Redis just picks the last element</a> — the one with the highest “idle time”, and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/evict.c#L672">deletes it</a>.</p><p>That’s pretty much it. Now Redis is ready to run a client command.</p><h3>Redis Executes a Command</h3><p>Finally, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L4140">Redis runs a command</a>. This boils down to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L3518">invoking</a> a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L2340">redisCommandProc *proc field</a> of a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1161">client’s parsed command</a>. So, if you want to see a source code of some specific command, head to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/commands.c#L7215">commands table</a>, find there a command you’re interested in, and click on the eleventh argument. This procedure is an implementation of a command you’re looking for.</p><h4>Generic flow of a command</h4><p>It’s quite simple, actually. First, Redis finds an element in a dictionary with one of the procedures whose name starts with lookupKey…. Typically, it’s either lookupKeyWrite(), or lookupKeyRead(), or lookupKeyWriteOrReply(), or lookupKeyReadOrReply(). The difference between read and write procedures are that the latter deletes an expired element even on a read-only replica. ...OrReply procedures add a reply in case the key is missing.</p><p>After an element is found, Redis runs whichever logic is implied by a command. That’s the core of a command. If it’s complicated enough, it’s often extracted into its own procedure. For instance, in the case of a simple <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_string.c#L335">get command</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_string.c#L324">the found value</a> is just <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_string.c#L331">wrapped in a reply</a> and stored in a client struct for future sending. In the case of a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_zset.c#L1799">zadd</a> command, where Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_zset.c#L1768">inserts</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_zset.c#L1762">each element a client sent</a> in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_zset.c#L1748">sorted set</a>, this logic is encapsulated in zsetAdd procedure, and so on. Just pick a command you’re interested in and dive into its implementation!</p><h4>Key lookup</h4><p>The majority of Redis commands first look up a passed key. Let’s consider this in more detail.</p><p>lookupKey…-family of procedures operate on a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L959">redisDb</a> level: they all have it as a first argument. This is quite a high level of abstraction: there is no search logic itself. Instead, it’s outsourced to the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L665">dictFind</a> procedure, which operates on dict-level. This procedure is a typical implementation of a hash table search logic, with some Redis specifics in case of rehash is in progress. First, Redis<a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L671"> runs one step of it</a>, and second, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L673">Redis checks both hash tables</a> for a searched key. Besides, if rehash is in progress, Redis runs a rehash step on <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1433">each</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L561">data</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L671">manipulation</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L989">operation</a>.</p><p>When <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L88">dictFind</a> returns a dictEntry and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L90">it exists</a>, Redis checks whether <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L106">this element has already expired</a>. If it has, but a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L1714">command is executed on a replica by a master</a>, elements are never considered expired. Otherwise, unless a key was looked up with either lookupKeyWrite() or lookupKeyWriteOrReply(), it’s considered expired but not physically removed. Besides, if <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L1720">EXPIRE_AVOID_DELETE_EXPIRED</a> is present, the current expired key is not evicted either. In all other cases, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L1729">an expired key is deleted</a>. As a special case, this includes a situation when a key has expired on a read-only replica, but it’s looked up with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L171">…Write-family</a> of <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L181">procedures</a>.</p><p>Finally, if a key has survived an expiration logic, its access time is updated depending on an eviction policy used. There are two exceptions for this. The first one is when <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L116">a client explicitly specified</a> that <a href="https://redis.io/commands/client-no-touch/">it doesn’t want to modify an access time</a>. The second one is when <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L119">there is currently a child process running</a>. Why? When a child process is created through forking, it inherits access to the same data as the parent process.</p><p>This is made possible by <a href="https://en.wikipedia.org/wiki/Copy-on-write">the copy-on-write (CoW) technique</a>, which is a mechanism implemented by the operating system. If either the parent or child process modifies shared data, a new memory page is copied, and the changes are visible only to the process that made the modification. This results in increased memory usage and potential performance overhead. That’s why Redis avoids performing maintenance tasks while a child process is running.</p><h3>In closing</h3><p>That’s it for this article and likely for this Redis 7 Internals series. I’ve described some parts which were interesting to me, but of course, there are lots of cool features left that I haven’t touched.</p><p>Redis is an incredible piece of software: it’s extremely fast, well-designed, and its code is remarkably readable. I’ve had a really good time diving into it, and I encourage you to do it too.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c465d7bb1f96" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/redis-internals-redis-processes-a-command-c465d7bb1f96">Redis Internals: Redis Processes a Command</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Internals: Client Sends a Command and Receives a Response]]></title>
            <link>https://medium.com/better-programming/redis-internals-client-sends-a-command-and-receives-a-response-9e3e8c463f7?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/9e3e8c463f7</guid>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[threads]]></category>
            <category><![CDATA[tcp]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[c]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Tue, 01 Aug 2023 20:28:48 GMT</pubDate>
            <atom:updated>2023-08-16T09:53:05.284Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Internals: Client Sends a Command and Receives a Response with Threaded I/O</h3><h4>With one I/O thread</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*g4ylZft62r3Q0b6b" /><figcaption>Photo by <a href="https://unsplash.com/@charlesdeluvio?utm_source=medium&amp;utm_medium=referral">charlesdeluvio</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In <a href="https://betterprogramming.pub/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825">the previous </a>article of <a href="https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60">the Redis 7 Internals series</a>, I discussed how Redis handles command parsing and response sending to clients without using I/O threads. I encourage you to read it as this current article is a logical continuation, building upon the concepts explained there. With this in mind, I won’t recap here the major Redis concepts it relies heavily on, such as an epoll instance, connection sockets, and an event loop.</p><p>I still won’t delve into a command execution itself — that’s what my next article is about. Instead, I’m focusing on I/O threads implementation. I think this is where <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880362&amp;cot=14">my Miro diagram</a> is helpful the most.</p><h3>What are I/O Threads, and When Might They Be Useful</h3><p>In short, I/O threads are ordinary threads that execute I/O operations — namely, response writing and, optionally, command parsing — in parallel. To realize when this can be useful, let’s first consider two scenarios that seem to be a reason to introduce this functionality.</p><h4>Problem: thousands of consequent socket writes of under-64-kB-sized data in the single main thread can take too long</h4><p>Suppose we have a Redis instance with a single main thread and lots of simultaneously connected clients (think of <a href="https://en.wikipedia.org/wiki/C10k_problem">thousands</a>). Then, at about the same time, each sends a command. Redis executes those commands one by one and then writes a response. As you’ve hopefully read in <a href="https://betterprogramming.pub/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825#700a">the previous a</a>rticle, this writing is done in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1929">NET_MAX_WRITES_PER_EVENT</a>-sized batches — that is, 64 kB each. As a reminder, this is done so that each client gets an equal share of time.</p><p>How long would it take to send responses to each of them? Let’s suppose that each response size is exactly NET_MAX_WRITES_PER_EVENT. Single write call with data of this size takes about 20 μs on my laptop. Multiplying it by ten thousand, you get a whopping 200 ms. That is, the client whose command was received last will experience 200 ms of added latency induced exclusively by a sequence of ten thousand write system calls.</p><p>That’s what a really heavy and under-optimized search query takes in my PostgreSQL instance. Even if I neglect data processing and network transmission time, both mean and median latencies are roughly 100 ms. In reality, this number would be several times more. Sounds like a lot for a highly performant in-memory data store.</p><p>Even if a response size is more reasonable, like 1 kB, write call duration doesn’t decrease proportionally. I suppose this is due to a fixed cost of a system call itself, which is comparable with the cost of moving data of this size. It becomes about 7–10 μs on my laptop. Thus, despite some latency gain, it seems that median latency of 35–50 ms is something we can and should do about.</p><p>And what about reading and parsing commands? Given the most frequent Redis commands is likely <a href="https://redis.io/commands/get/">GET</a>, an entire command is <a href="https://redis.io/docs/reference/protocol-spec/">RESP</a> format is about 30 bytes. Reading this data takes several microseconds — not that different from reading or writing 1 kB, actually. Thus, the latency induced solely by read system calls is similar to that induced by write system calls.</p><h4>Solution: Redis runs all I/O in parallel threads</h4><p>The solution represents a common way these problems are solved: if a system has a bottleneck, identify a limited resource and scale out. The bottleneck here is evident: it’s a single thread that does all the readings and writings. A limited resource in Redis is the single main thread running on a single CPU core at any moment. Scaling out runs more threads on more CPU cores.</p><p>Hence, this brings us to I/O threads, which hopefully run all I/O operations in parallel. Actually, this is an OS that decides which thread to run on which CPU core. But, generally, if the overall system load is normal, and your application utilizes several parallel threads, chances are high that they run on separate cores. If they don’t, you can always tweak the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/redis.conf#L2272-L2273">CPU affinity</a> setting.</p><p>Once again, I’m talking about this specific scenario when write size is under 64 kB. Now, let’s consider the next scenario where the size of the data being written is higher.</p><h4>Problem: thousands of consequent socket writes of over-64-kB-sized data in the single main thread can take way longer</h4><p>In this case, after the first NET_MAX_WRITES_PER_EVENT-sized chunk is written for each client, Redis subscribes to write events for each connection socket. That means that if the network is not congested, Redis will send the remaining part of a response in a subsequent event loop iteration. In other words, in the best-case scenario, when the network doesn’t experience congestion and send buffers are emptied quickly, Redis sends NET_MAX_WRITES_PER_EVENT-sized chunks to each client in each event loop iteration — until an entire response is written:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LiA00bO6cHT8eWCfie8OEQ.jpeg" /></figure><p>Even if each response size is slightly higher than 64 kB, mean latency triples. As I’ve written in the previous section, both mean and median latencies are about 100 ms. If Redis needs two event loop iterations, both mean and median latencies become 300 ms. In other words, this value increases by 200 ms on each event loop iteration. In percentage terms, some clients experience latency way worse than others: a client whose command was received first has to wait until Redis writes responses to all other 9999 clients on the first iteration. Thus, it has to wait an additional 200 ms. You can feel the difference with the previous scenario when additional latency induced by writes was just its own write system call, that is, 20 μs.</p><h4>Solution: Redis runs writes in I/O threads</h4><p>Given an average command size is still 30 bytes, the contribution of a read call to the overall latency is still a few microseconds. If an average response size is hundreds of kilobytes, the contribution of write calls is higher. Moving the reading part into I/O threads won’t be as noticeable as parallelizing the writing.</p><h4>OK, so should I try this feature?</h4><p>As I’ve described above, I/O threads shine when there are lots of clients and even better when responses are large. How much is “lots of clients,” and how much is “responses are large,” exactly? You can do some math and calculate it for your specific case and setting, but generally, enabling this feature and seeing the result is a lot easier. But first, ensure you have a good monitoring tool to compare the performance adequately.</p><p>Last but not least, note that command execution is still single-threaded! I’ll show it in a source code later anyway, but if you don’t read that far, I think it’s important to mention this fact.</p><p>Let’s consider what happens in Redis internals when several clients send commands. I/O threads mechanics unfold gradually, so I’ll describe a specific example starting with Redis start-up followed by several event loop iterations.</p><h3>Redis Configuration and Start-Up</h3><p>I assume that Redis is configured with one I/O thread (<a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3128">io-threads</a> equals 2), and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3032">reading and parsing</a> is also done in I/O threads. Thus, our single I/O thread is created and paused before running the first event loop iteration.</p><p>Here is a brief description of how <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#0a71">I/O threads are created and run</a>.</p><h4>I/O Threads Start-Up Recap</h4><p>As a reminder, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2799">IO threads start</a> when <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d">Redis starts up</a>. If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3128">the number of threads specified in a config</a> is 1, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4137">I/O threads mechanism is efficiently disabled</a>. If their <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4139">quantity is no more</a> than <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4049">IO_THREADS_MAX_NUM</a>, Redis repeats the following logic IO_THREADS_MAX_NUM - 1 times (excluding the main thread).</p><p>First, it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4153">initializes</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4155">acquires</a> a current thread’s mutex. Then, sets a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4154">number of clients to be processed by it</a> to zero. And after that, Redis actually <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4156">creates a thread</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4082">IOThreadMain</a> is the procedure that runs in each of those threads. It’s an infinite loop that basically does two things.</p><p>First, after an I/O thread is created, it tries to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4101">acquire a lock</a> if there are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4100">no clients assigned to it</a>. If the main thread already acquires this lock, then IO thread hangs. And that’s exactly <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4155">what happens when Redis starts</a>. Note that it’s not a busy wait, though. That is, the CPU is not utilized. There is also a tricky 1,000,000 for-loop that breaks when getIOPendingCount(id) != 0. I’ll get back to it soon.</p><p>The second one is iteration through <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4112-L4113">clients designated to a current I/O thread</a>. For each client, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4115-L4118">either reads a command or writes a response</a>. The specific strategy is defined by an io_threads_op global variable. When it’s completed, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4123-L4124">clients list is emptied</a>.</p><h3>First Iteration: One Client Sends a Single Command</h3><p>I’ve described this case in the previous article in detail. Here is a short recap:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WQTH3PPIYGSLmfh5Lt9Q1g.jpeg" /><figcaption>Single event loop iteration. Redis parses a client command and constructs a response</figcaption></figure><p>To begin, Redis receives new events with epoll_wait() procedure. For each read event, data is read. Then, Redis parses a command, executes it, and constructs a reply. Besides, a client is saved into a server.clients_pending_write list. It’s important to note that the reply is not immediately sent to a client. Instead, this occurs during the subsequent event loop iteration in the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1735">beforeSleep()</a> procedure.</p><h3>Second Iteration: Several Clients Send Commands; Redis Reads, Parses, and Executes Them</h3><p>The second iteration starts <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1617">beforeSleep()</a> runs. Let’s suppose more clients start sending commands:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1001/1*m5ixrZ9h29QkksCo8-CFQQ.jpeg" /></figure><p>Meanwhile, Redis reaches <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1645">handleClientsWithPendingReadsUsingThreads()</a>. Our instance is configured to do client reads in I/O threads (server.io_threads_do_reads = 1), but they are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4130">inactive at the moment</a>. So for now, we don’t do reads in parallel, even if currently there were thousands of clients sending commands.</p><p>Shortly after this, Redis writes a response for commands executed on a previous event loop iteration. This is done in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4227">handleClientsWithPendingWritesUsingThreads</a> procedure. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4233">Redis first checks</a> whether I/O threads are disabled (server.io_threads_num == 1) and even if they are not, assesses whether it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4207">makes sense to utilize parallelization</a>. In our case, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">it doesn’t</a>: there is a single client with pending writes, and it’s less then server.io_threads_num * 2, which is 4.</p><p>Thus, the four clients that have sent a command during this event loop iteration are processed as usual right after an epoll_wait call, in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L415-L467">a loop over </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L415-L467">aeEventLoop-&gt;fired</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tghD_e9mt5Q3j3lG5oYEzw.jpeg" /></figure><p>By the end of this iteration, there are four clients with pending responses. That is, the length of server.clients_pending_write is four.</p><h3>Third Iteration: Redis Write Responses in I/O Threads</h3><p>Clients keep sending new commands. Similar to the previous iteration, each one of the four clients sends a command. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1645">handleClientsWithPendingReadsUsingThreads</a> doesn’t have a chance to do reading and parsing in parallel, even if there were thousands of clients: I/O threads are still inactive (server.io_threads_active = 0). And at this stage, Redis isn’t even supposed to activate them.</p><p>The situation is different with writes. Unlike a previous iteration, now there are enough clients to justify threaded response writing: there are four clients with pending responses, which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">no less than </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">server.io_threads_num * 2</a>. Hence, Redis finally <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4238">activates I/O threading facility</a>. This boils down to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4184">unlocking each thread’s mutex</a> and setting server.io_threads_active to 1:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LdvZPZ7igF5aqc-fqaFzQg.jpeg" /></figure><p>As soon as mutexes are unlocked, all I/O threads wake up: they are finally <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4101">able to acquire a lock</a> they had been waiting on. But they <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4102">release it right away</a> and start a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4093">new iteration of a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4093">while-loop</a>. And here goes that <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4095">one million </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4095">for-loop</a> I’ve already mentioned earlier. While it runs, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4265-L4266">the main thread assigns clients from a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4265-L4266">server.clients_pending_write to threads</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/924/1*LHS92Q0o2s9IP-VCsHVsOQ.jpeg" /><figcaption>The higher part is the main thread, lower part is an I/O thread. Time passes from left to right. You can also check out this iteration <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880369&amp;cot=14">here</a></figcaption></figure><p>After that, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4272">it sets a context for those threads</a>. In our case, it’s a write context which is indicated by a IO_THREADS_OP_WRITE value. Finally, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4273-L4276">the main thread sets a number of clients to be processed by each thread</a>, and this is a signal for each I/O thread that they can break that million-for-loop and start doing its work.</p><blockquote>If a million iterations take less time than it’s needed for the main thread to assign IO threads with clients, nothing bad happens: mutex is locked, then unlocked right away, and the iteration continues — until getIOPendingCount(id) != 0. So this million-loop is just a way not to mess with locking and unlocking too often.</blockquote><blockquote>I’m calling it a poor-man’s <a href="https://www.geeksforgeeks.org/condition-wait-signal-multi-threading/">condition variable signaling</a>, but it’s not an arrogant sentiment from my side. I realize that Redis developers probably had very good reasons to do it the way it’s currently implemented — I’m simply not aware of them.</blockquote><p>Once <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4266">clients are assigned to threads</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4272">IO context is defined</a>, and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4275">“condition variables” are set</a>, all threads start writing responses. This is a <a href="https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup?tabs=csharp">fan-out</a> part of a concurrency pattern Redis uses here. After <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4280">the main thread</a> is done with it, it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4287-L4292">waits</a> until <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4113-L4122">all other I/O threads finish</a>.</p><p>Meanwhile, each I/O is doing its share of response sending. When they complete, their “condition variable” <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4124">is zeroed</a>, and an I/O thread <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4095-L4104">enters a busy-wait</a> state: it waits for another portion of clients.</p><p>Finally, a fan-in part follows in the main thread: <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4307">Redis checks which clients still have pending writes</a>. For those that do, it subscribes to write events in a corresponding socket and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L237">sets a handler function</a> to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1982">sendReplyToClient</a>. From a lower-level implementation point of view, response sending is no different from a <a href="https://betterprogramming.pub/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825#700a">single-threaded scenario</a>.</p><p>This iteration ends the same way as all the others: Redis iterates through fired read events and invokes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4118">readQueryFromClient</a>. But it behaves differently now: those <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2586">reads are postponed</a> for further processing. Besides, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4332">each client is put on a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4332">server.clients_pending_read</a> list. Here is a higher-level view of the third iteration:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c5CTRWZALsHTQQ-stoFN6Q.jpeg" /><figcaption>Click to zoom in</figcaption></figure><p>Thus, at the end of this iteration, four clients have pending reads. Let’s see how they are processed.</p><h3>Fourth Iteration: Redis Reads and Parses Commands in I/O Threads</h3><p>Finally, Redis can read and parse client commands in I/O threads. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4352">handleClientsWithPendingReadsUsingThreads</a> checks server.io_threads_active , which is 1, and server.io_threads_do_reads, which is 1 either. Conceptually, the following logic differs from threaded writing; the same fan-out/fan-in approach is utilized. Let’s consider it briefly.</p><p>The fan-out part goes like this. First, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4360">main thread takes a list of clients with pending reads</a> created at the end of the previous iteration and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4365">allocates them per threads</a>. Then it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4371">sets an IO context to reading</a>. Finally, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4372-L4375">it sets a “condition variable” to the number of clients to be processed</a>, which is a non-zero value.</p><p>Note that IO threads have already turned active on a previous event loop iteration. That is, they are not hanging: their mutex is already unlocked, and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4095-L4104">they are busy-waiting for new clients</a>. As soon as <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4096">getIOPendingCount(id) != 0</a>, they start doing their work, depending on <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4117">io_threads_op</a> value. In this case, they <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4118">readQueryFromClient</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4379-L4382">The main thread does its reads</a> too.</p><p>It’s important to remember that <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4118">readQueryFromClient</a> procedure <a href="https://betterprogramming.pub/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825#f3ee">is used in a single-threaded context</a> for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2629">reading</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2673">parsing</a> (specifically, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2518">processMultibulkBuffer</a> is used in case of multi-bulk requests), <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2537">executing a command, and constructing a response</a>. But <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2530">in the case of the I/O thread context, the command execution doesn’t occur</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2518">After command is parsed</a>, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2532">client is marked as having a pending command</a>, and that’s it.</p><p>It’s worth mentioning that during parsing, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2232">Redis</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2247">can</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2251">create</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2276">responses</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2299">indicating</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2303">a protocol error</a>. Like any reply, it boils down to addReplyProto procedure, which first calls <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L290">prepareClientToWrite</a>. This procedure <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L318">puts a client in a pending write queue</a> so that Redis will iterate it a few moments later and sends those responses. But there are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L317">two conditions</a> for a client to end up there: it shouldn’t already be in that queue, which is natural, and this procedure should be called outside of a thread context.</p><p>The second condition&#39;s rationale is that Redis developers probably wanted to avoid lock contention. When appending a queue in a multi-threaded context, it should be done with locking (since Redis doesn’t use lock-free queues). However, this approach could potentially become a bottleneck. Thus, Redis simply avoids this concurrent mutation by adding a client in the write queue or in the fan-in stage.</p><p>The fan-in part is when it’s time to collect results. After <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4386-L4391">all threads are done</a>, the main thread <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4393">drops an I/O context</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4396">iterates through clients whose commands Redis has just parsed</a>, and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4414">executes those commands</a>. If they imply a response, it’s created the same way as <a href="https://betterprogramming.pub/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825#0e83">in a single-threaded scenario</a>; specifically, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L455">client is put in a pending write queue</a>. Now remember the end of the previous paragraph — if a current client had an error in its command, it’s not put in that queue. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4425">That’s what Redis does now</a>.</p><p>This pending write queue is processed in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1735">handleClientsWithPendingWritesUsingThreads</a> procedure. I’ve described it in detail in the previous section.</p><p>Here is its higher-level view of this iteration:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kCJ7uEcquDyAzwPjnhQYYw.jpeg" /><figcaption>Click to zoom in</figcaption></figure><p>Suppose that one client sends a command during this iteration. Since I/O threads are still active, this client is added in a server.clients_pending_read list — the same way as at the end of the third iteration.</p><h3>Fifth Iteration: I/O Threads Shut Down</h3><p>There is a client with pending reads; it’s processed in handleClientsWithPendingReadsUsingThreads. Since there is only one client, it will be processed by the main thread. Its command is parsed and executed, as always. Then this client is put in a pending write queue that’s processed by a handleClientsWithPendingWritesUsingThreads procedure. And that is where <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4233">Redis checks whether it’s still worthwhile to keep I/O threads running</a>.</p><p>If there <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">are fewer clients with pending writes then </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">server.io_threads_num * 2</a>, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4214">Redis stops threaded I/O</a>. That’s what happens in our case: one (listLength(server.clients_pending_write)) is less than four (server.io_threads_num * 2 = 2 * 2 = 4). The <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4194">main thread just locks each thread’s mutex</a>, and that’s it. I/O threads hang while <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4101">trying to obtain an already-taken lock</a>. Busy waiting ceases.</p><p>Note that if <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4229">there were no clients sent on a previous iteration</a>, Redis won’t have a chance to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4233">shut I/O threads down</a>. They will be busy-waiting until some client sends a command that implies a response.</p><h3>In closing</h3><p>I hope you’ve enjoyed reading this article as much as I’ve enjoyed reading Redis source code and writing this piece. See you in the final part of this series, where I’m diving into command execution logic.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9e3e8c463f7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/redis-internals-client-sends-a-command-and-receives-a-response-9e3e8c463f7">Redis Internals: Client Sends a Command and Receives a Response</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Internals: Client Sends a Command and Receives a Response]]></title>
            <link>https://medium.com/better-programming/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/b06178d98825</guid>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[c]]></category>
            <category><![CDATA[tcp]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Tue, 18 Jul 2023 15:54:48 GMT</pubDate>
            <atom:updated>2023-08-16T09:51:11.845Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Internals: Client Sends a Command and Receives a Response</h3><h4>The case when there are no I/O threads</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WO_G9n0sc0gwFF9b" /><figcaption>Photo by <a href="https://unsplash.com/@rangel?utm_source=medium&amp;utm_medium=referral">David Rangel</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In <a href="https://betterprogramming.pub/redis-internals-client-connects-to-redis-by-tcp-faf6ad1a7df5">the previous article</a> of the <a href="https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60">Redis 7 Internals series</a>, I described what happens on the Redis side when a client connects to it via TCP. Now, I’ll consider what happens when a client sends a command and receives a response. Please note that I won’t delve here into an implementation of how Redis retrieves or writes data — its db engine, if I may call it so.</p><p>This part is command specific, and I will consider a general way of figuring this out, with an example, in a future article. Instead, this article focuses on how Redis receives a request and writes a response when there are no I/O threads. It’s probably the shortest and easiest article in this series, but it is a foundation for a more sophisticated I/O threads mechanism. I will describe it in the next article.</p><p>This article relies heavily on a concept of event loop — Redis’ driving force behind executing commands. So, at the risk of repeating myself time and again, I’ll first skim through its main parts. If you’ve already read <a href="https://betterprogramming.pub/redis-internals-client-connects-to-redis-by-tcp-faf6ad1a7df5#98af">any</a> <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#8a2b">of my previous</a> <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#b665">articles</a>, feel free to skip them and head right to <a href="#f3ee">the main part</a>.</p><h3>Recap of Redis Start-Up Process</h3><h4>Epoll instance</h4><p>An <a href="https://man7.org/linux/man-pages/man7/epoll.7.html">epoll instance</a> is a fundamental data structure within the Linux kernel, encompassing two crucial components: an interest list and a ready list. By <a href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html">adding a file descriptor to the interest list</a>, also known as an epoll set, the epoll instance continuously monitors it asynchronously for events. Events signify changes in the state of a file descriptor, primarily involving two distinct alterations.</p><p>The first change occurs when a file descriptor becomes readable, indicating data availability for reading. Essentially, the file descriptor transitions from a not-ready state to a state ready for reading. The second alteration occurs when a file descriptor’s buffer has sufficient space to write data. In such scenarios, these file descriptors are commonly referred to as writable. Whenever any of these events occur, the epoll instance promptly adds the corresponding file descriptor to the ready list:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*5EI9XX1pMOQjVnwE.jpeg" /><figcaption>State of a file descriptor where one has just changed. For instance, now it’s readable or writable</figcaption></figure><p>To retrieve the file descriptors that are currently ready for input/output operations, the <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait()</a> procedure can be invoked.</p><p>Epoll represents just one of several multiplexing mechanisms utilized to track the state of file descriptors asynchronously. In the context of Redis, when it is built on the Linux operating system, epoll serves as the chosen implementation.</p><h4>Connection socket</h4><p>During a startup, Redis creates listening sockets and registers them in an epoll instance for read events. Those listening sockets store all incoming client connection requests in an associated TCP buffer called an accept queue:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*Z8ZcfhvT5fOdMfnM.jpeg" /></figure><p>Redis accepts up to one thousand connection requests per single event loop iteration. As a result, each client has its own connection socket — the one with which a TCP connection is established. This connection socket receives its client’s commands:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nN2suQ4kstiQVFD2tzzv2g.jpeg" /></figure><p>Each connected socket is also registered in an epoll instance for read events. When a client sends a command and it’s put in a connection socket’s <a href="https://levelup.gitconnected.com/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1#cdeb">receive queue</a>, it means that a socket has just become readable and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2579">readQueryFromClient()</a> is executed.</p><h4>Event loop</h4><p>The event loop represents a programming construct that builds upon a system’s multiplexing mechanism. Its essence lies in an infinite loop that continuously retrieves file descriptors whose state has undergone changes, indicating their readiness for read or write operations.</p><p>The event loop operates through a series of fundamental steps:</p><ol><li>Initially, you register interest in I/O events for each relevant file descriptor. In the case of using an epoll as the multiplexing mechanism, it’s epoll_ctl() system call. This action, often referred to as subscribing to an event, signifies detecting a change in a file descriptor’s state. For example, when a client sends a request and it’s received by a connection socket, the socket’s state alters, signaling the presence of newly arrived data awaiting an application&#39;s reading.</li><li>Subsequently, you await I/O events on the registered file descriptors by invoking a blocking system call specific to the chosen multiplexing mechanism. In the case of an epoll mechanism, this involves utilizing the epoll_wait() procedure. The blocking system call can either wait indefinitely until at least one event occurs or specify a timeout duration.</li><li>Once the blocking system call from the previous step returns, you obtain the events that have transpired.</li><li>Following that, you identify the type of event, such as a read or write event, and handle it accordingly by executing the associated callback function, typically referred to as a handler in Redis.</li><li>Lastly, you continuously repeat these steps, perpetually monitoring and tracking the state of file descriptors asynchronously.</li></ol><p>By adhering to this event loop pattern, developers can efficiently manage I/O operations and respond promptly to changes in file descriptor states.</p><h3>Redis Receives a Command</h3><p>That’s what <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2579">readQueryFromClient()</a> does. First, it checks whether a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2586">command should be parsed right away</a> or should be postponed until IO threads do it. I’ll consider an IO threads scenario in the next article, so here, we have this mechanism disabled. Redis then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2629">reads a raw command data</a> from a socket and stores it in a client’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1152">query buffer.</a> This is a piece of functionality involving a connection, and in such cases it’s implemented as <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#L98">a function pointer</a> in ConnectionType struct. Here is an example of what a raw command data could look like if a client sent set a 12345:</p><pre>*3 // this is a number of arguments<br>$3 // this is a length of the following lexeme -- a command, in this case<br>set // this is a command itself<br>$1 // this is a length of the first argument, in bytes<br>a<br>$5 // this is a length of the second argument, in bytes<br>12345;</pre><p>This is a multi-bulk request format — a part of <a href="https://redis.io/docs/reference/protocol-spec/">RESP</a>, the default Redis protocol.</p><p>Then, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2673">reads a client’s buffer and parses a command</a>. All <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2518">the heavy lifting is implemented</a> in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2219">processMultibulkBuffer()</a> procedure. It fills client’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1156">argv</a> field with parsed arguments. For instance, for the previous command, there are three of those: set, a, 12345.</p><p>Note that <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2245">the first line of a multi-bulk command</a> is always <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2260">the number of arguments</a>. Thus, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2372">Redis always knows whether an entire command was parsed</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2375">It might not be the case</a> when, for example, a current command is really large, and it can’t fit within a single TCP segment. Or when a client sends multiple commands, and the last one, again, doesn’t make it in a TCP segment. And if epoll_wait() returns just before the second TCP segment arrives, and Redis gets a partial command.</p><p>It’s OK, though: the TCP stack of an underlying system will wait until it arrives, and Redis will find this out by calling epoll_wait() in the subsequent event loop iteration.</p><p>If an entire command is parsed, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2537">Redis is ready to execute it</a>. That’s what <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2425">processCommandAndResetClient</a>() does. Besides actually executing a command, it does some auxiliary bookkeeping stuff:<a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2430"> resets </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2430">client fields</a> used for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2394">command parsing tracking</a>, and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2433">updates some stats</a>.</p><p>I will describe a command execution in one of my following articles.</p><h3>Redis Writes a Response</h3><p>Sending a reply to a client contains three logical parts:</p><ol><li>Redis adds a client to a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1588">server.clients_pending_write</a> list</li><li>Then, it constructs a reply in a specific format</li><li>This reply is stored in a client structure</li><li>In the subsequent event loop iteration, each pending reply is written asynchronously in a connection socket.</li></ol><p>Now, let’s consider each part in more detail.</p><h4>Redis reply is constructed</h4><p>Reply formatting is implemented in an addReply* set of procedures located in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c">networking.c</a> file. It could be an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1024">addReplyBulk</a> procedure, for instance. First, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1025">reply length</a> is added. It’s followed by a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1026">response body</a>, and finally, by a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1027">new line</a>.</p><p>For example, if a key a equals 12345, when a client gets it, the reply looks like that:</p><pre>$5<br>123456<br>// this is a new line</pre><h4>Redis reply is stored in a client</h4><p>Internally, this reply can be stored <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L389">in a fixed-size buffer and, if it doesn’t fit there entirely, in a reply list.</a> If a reply length is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L137">under 16 kB</a> so that it fits in a reply buffer, it’s stored only in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1262">buf</a> field of a client struct. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L408">Otherwise</a>, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L353">remaining part is stored in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L353">reply field</a>. It is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1171">a linked list</a>. If its current <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L365">tail has some spare space</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L367">reply is appended to its buffer</a>. After that, the remaining part is stored in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L377">a new list item</a> which becomes the new tail. Its <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L376">minimal size is 16 kB, and the maximum size is unbounded</a>.</p><p>Why does Redis need this mechanism? Why can’t everything be stored in a dynamic-sized buffer? Its size can be increased and decreased with each new reply indeed. Well, I’m not a systems developer, but I can think of the following reasons:</p><ol><li>Memory reallocation is not cheap, especially when applied on the Redis scale. For instance, you can have thousands of clients receiving responses at about the same time. It’s thousands of <a href="https://linux.die.net/man/3/realloc">realloc</a>-like procedures in a single event loop iteration. Given a very rough estimate of a single event loop iteration is about one millisecond (in reality, it can be two or even three orders of magnitude less), this can add up to millions of memory reallocations per second. This can affect Redis’ performance.</li><li>Frequent memory reallocations lead to memory fragmentation. Although Redis has <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#b330">a built-in way to mitigate it</a>, it’s better to avoid it altogether.</li><li>Memory allocation and its subsequent <a href="https://en.cppreference.com/w/c/memory/free">free</a>ing is cheaper than two reallocation calls. Each reallocation involves copying the existing data to a new memory block of a different size, which can have a time complexity of O(n), where n is the size of the data being reallocated. Meanwhile, free() procedure has a constant time complexity, typically O(1). It only releases the memory without any additional CPU or memory overhead.</li></ol><p>So that’s why Redis uses a fixed-size 16 kB buffer which, on the one hand, is enough for most replies and, on the other, is not too much of an overhead.</p><h4>Redis adds a client to a clients_pending_write list</h4><p>Before setting a reply, Redis makes sure that a client is in a server.clients_pending_write list. This behavior is implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L290">prepareClientToWrite()</a> procedure. Besides a few cases, Redis checks whether a client doesn’t have a stored reply (if it does, it means that this client is already in a pending write list), and it is currently not in an IO thread. If those two conditions are satisfied, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L249">putClientInPendingWriteQueue()</a> is executed, which finally adds a client wrapped into a listNode in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L264">server.clients_pending_write</a> list.</p><h4>Short recap</h4><p>I think now it’s a good time to remind you of what a higher-level command execution looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WQTH3PPIYGSLmfh5Lt9Q1g.jpeg" /><figcaption>Single event loop iteration. Redis parses a client command and constructs a response</figcaption></figure><p>First, new events are received through epoll_wait() procedure. Then, they are looped through, and for each read event, a command is parsed, executed, and a reply is built. Note that it’s not yet sent to a client — it occurs on the subsequent event loop iteration in beforeSleep() procedure. Let’s get right into it.</p><h4>Redis reply is sent to a client</h4><p>This logic consists of two logical parts. The first one is where Redis sends a response. The second one is where Redis registers a connection socket for write events. This is needed when a response was sent only partially. It can occur when, for instance, a socket is blocked for writing because its send buffer is full.</p><p>This logic is implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4227">handleClientsWithPendingWritesUsingThreads()</a> procedure. The name might trick you into thinking that IO threads are involved somehow; actually, this is not necessarily a case. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4233">IO threads machinery runs only</a> if this behavior is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3128">enabled in settings</a> and if Redis considers <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">this whole enterprise worthwhile</a>. If either of those conditions fail, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4234">responses are delivered in a the main thread</a>. This logic is implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1991">handleClientsWithPendingWrites()</a> procedure. Its implementation is quite straightforward. Redis takes all the clients with pending writes (as I’ve written above, they are stored in a server.clients_pending_write list) and calls <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2010">writeToClient()</a> for each one. As you can guess, this is a procedure that sends a response to a client.</p><p><strong>Redis sends a response<br></strong> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2010">writeToClient</a> has a limit of how much data Redis can write to a socket. Why? There are specific scenarios where the likelihood of connection socket blocking is lower than the average. In such cases, Redis can spend too much time serving a response to a single client. Think of a user who connects to Redis through a loopback interface locally. I can hardly imagine network congestion there. Besides, the bandwidth is extremely high.</p><p>Another example might be when a client skips the TCP stack altogether and connects to Redis with Unix Domain Sockets. Despite their buffer capacity is generally lower, the data written in them is copied between processes. Thus, in both scenarios, it might take a while for connection sockets to block. Hence, Redis serves no more than <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1929">NET_MAX_WRITES_PER_EVENT</a> bytes for a client in each event loop iteration.</p><p>There are two procedures used for actual data writing. Ordinary <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L154">write()</a> is used for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1884">data stored in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1884">c-&gt;buf buffer</a>. It’s a contiguous block of memory, and the write procedure works with exactly this kind of data.</p><p>But <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1876">for a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1876">reply list</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L169">writev()</a> is used. Why? Because linked list occupies a non-contiguous memory region. And that’s exactly what <a href="https://linux.die.net/man/2/writev">writev</a> is used for. Thus, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1792">Redis iterates through each item</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1801-L1802">constructs an array</a> of scattered <a href="https://man7.org/linux/man-pages//man3/iovec.3type.html">iovec</a> structs. Then, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1807">it’s written</a> atomically in a connection socket. So, if Redis had used a write procedure, it would have had two options. The first one is issuing several write calls, which is costly: generally, the fewer system calls, the better. The second option is to copy all reply list items’ contents into a single contiguous memory block and issue a single write. I don’t think the second option is subpar to a writev approach, though.</p><p>Each of those write procedures returns several bytes written, and if it’s zero, an errno is filled with <a href="https://github.com/torvalds/linux/blob/v6.4/include/uapi/asm-generic/errno-base.h">an error code</a> explaining what happened. Some of them indicate a permanent error; others are retryable. EAGAIN is among the latter. It means a connection socket’s send buffer is full, and writing gets blocked. In other words, as the comment goes, it’s Try again.</p><p><strong>Redis subscribes to write events in a connection socket<br></strong>Finally, Redis checks whether <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2014">there is some data left for a current client to be sent</a>. As I’ve already mentioned, there are at least two scenarios when this can occur. The first one is hitting a NET_MAX_WRITES_PER_EVENT limit, and the second one is an error code returned from a write procedure — most notably, an EAGAIN. In either case, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2015">Redis registers a connection socket for write events</a>. It means that when it becomes writable, Redis will figure that out with an epoll_wait procedure and send the remaining part of a response with a previously set write handler.</p><p>It’s represented with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L237">sendReplyToClient</a> procedure. It boils down to simply calling a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1984">writeToClient()</a> — an exact same procedure that was issued during the first attempt, in a handleClientsWithPendingWrites. The only difference is that the write handler is installed, as implied by the second argument. In this case, when <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1956">all the data is finally sent</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1964">Redis unsubscribes from write events</a> for a current connection socket.</p><p>Here is how response sending looks like when it can’t get sent in a single write call:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ppDLhriBhWxQY4nRxzCVew.jpeg" /></figure><p>This is a very high-level view. Check out <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880307&amp;cot=14">this Miro board</a> for details.</p><h3>In Closing</h3><p>In short, an entire command execution flow looks like that. In the first event loop iteration, a command is parsed, executed, and a response is saved. In the subsequent iteration’s beforeSleep procedure, Redis sends this response to a client. If it’s sent only partially for some reason, Redis registers a current client’s connection socket for write events. When it fires, a socket becomes writable, and Redis writes the remaining part of a response. This process is repeated until an entire response is sent. When it’s done, Redis unsubscribes from write events.</p><p>Stay tuned. The next article is about how IO threads work.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b06178d98825" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/redis-internals-client-sends-a-command-and-receives-a-response-b06178d98825">Redis Internals: Client Sends a Command and Receives a Response</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Internals: Client Connects to Redis by TCP]]></title>
            <link>https://medium.com/better-programming/redis-internals-client-connects-to-redis-by-tcp-faf6ad1a7df5?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/faf6ad1a7df5</guid>
            <category><![CDATA[c]]></category>
            <category><![CDATA[tcp]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[redis]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Fri, 07 Jul 2023 15:11:07 GMT</pubDate>
            <atom:updated>2023-08-16T09:50:43.336Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Internals: Client Connects to Redis by TCP</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5ep4YZCPHLlC8PLk" /><figcaption>Photo by <a href="https://unsplash.com/@jstrippa?utm_source=medium&amp;utm_medium=referral">James Harrison</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>I’m continuing the <a href="https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60">Redis 7 internals series</a>. First off, this article is much shorter than <a href="https://medium.com/better-programming/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d">the previous one</a>, where I delved into an implementation of Redis server startup. If you’re not one of the six percent of people who managed to read that article to the end (assuming the Medium stats page is correct, and I’m confident it is), don’t worry. I’ll provide enough context so you won’t have to finish that big boy.</p><h3>Short Recap of Redis Server Start-Up</h3><p>Here, I’ll walk through the concepts that the client connection process relies on.</p><h4>Epoll instance</h4><p>An <a href="https://man7.org/linux/man-pages/man7/epoll.7.html">epoll instance</a> is a data structure in the Linux kernel that contains an interest list and a ready list. When a <a href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html">file descriptor is added to the interest list</a>, also known as an epoll set, the epoll instance monitors it asynchronously for events. Events are changes in a file descriptor state. There are mainly two such changes. The first one is when a file descriptor has data available for reading. In other words, it becomes readable: its state has changed from not-ready to ready for reading. The second occurs when a file descriptor’s buffer has available space for writing. In this case, such file descriptors are usually referred to as writable. As soon as any of those events occurs, the epoll instance adds the file descriptor to the ready list:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JFpfEX_6mB4U9LYvA7JS5w.jpeg" /><figcaption>File descriptor 1 has some data to be read</figcaption></figure><p>To retrieve the file descriptors that are ready for I/O operations, you can call the <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait()</a> procedure.</p><p>Epoll is one of several so-called multiplexing mechanisms that allow tracking file descriptors’ state asynchronously. It’s used in Redis when it’s built on Linux. Other systems use other implementations of the same concept.</p><p><a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#b068">Here</a> is a bit more detailed version.</p><h4>Event loop</h4><p>Event loop is a programming construct built on top of a system’s multiplexing mechanism. Essentially, it boils down to obtaining file descriptors whose state has changed in an infinite loop. That is, those file descriptors are ready either for writes or reads.</p><p>It works by following a set of basic steps:</p><ol><li>First, you register interest in I/O events for each file descriptor using the appropriate system call. This is often referred to as subscribing to an event, which, as I’ve already mentioned, represents a change in the state of a file descriptor. For example, when a client connects to a listening socket, the state of the socket changes: it has a connection request waiting to be read and accepted by an application.</li><li>Next, you wait for I/O events on the registered file descriptors by blocking the system call that depends on your multiplexing mechanism. For an epoll mechanism, this is an <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait()</a> procedure. This can be either an indefinite wait until at least one event happens or a timeout can be specified.</li><li>Once a blocking system call from the previous step returns, you receive the events that have happened.</li><li>Then, you determine the type of event that occurred (e.g., read, write) and handle it appropriately by executing the associated callback function.</li><li>Finally, you repeat these steps to keep tracking the state of file descriptors asynchronously.</li></ol><p>For event loop implementation details in Redis, you can refer to <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#8fb3">this section</a>.</p><h4>Listening sockets: user’s perspective</h4><p>When Redis starts up, it creates listening sockets. They handle incoming connection requests. Redis can create <a href="https://en.wikipedia.org/wiki/TCP_socket">TCP sockets</a> when it’s connected to with <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a>, and <a href="https://en.wikipedia.org/wiki/Unix_domain_socket">Unix Domain Sockets</a> when it’s connected to <a href="https://en.wikipedia.org/wiki/Inter-process_communication">locally</a>. Each socket has an associated memory allocated by a Linux kernel. This memory is often referred to generally as a buffer. This is an umbrella term for all kinds of data structures used to store a TCP request until it’s processed. Typically, they all exhibit a queue-like behaviour. In case of connection requests, they are accepted from <a href="https://levelup.gitconnected.com/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1#cdeb">an accept queue</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QKOqCOej9VauUjRwDJ4mkA.jpeg" /></figure><p>When Redis accepts a connection request, another socket is created. It’s usually referred to as a connection socket:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nN2suQ4kstiQVFD2tzzv2g.jpeg" /></figure><p>From now on, it serves all client requests. I’m describing this in more detail in the <a href="https://betterprogramming.pub/redis-internals-client-connects-to-redis-by-tcp-faf6ad1a7df5#426c">main part</a> of this post.</p><p>When listening sockets are created, Redis puts them in an epoll set for read events:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/952/1*1XYGLCjPTG5UkF4KOsQzgw.jpeg" /><figcaption>There are N tcp sockets for non-TLS communication, N tcp sockets for TLS communication (same address but different port), and a single Unix domain socket in an epoll instance’s interest list</figcaption></figure><p>Thus, Redis checks whether there are new connection requests on each event loop iteration. If there are, Redis runs a specific procedure which I’ll consider soon.</p><p><a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#b665">Here</a> you can find more details about <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#f76d">overall connection lifecycle</a>, <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#3499">reusable addresses</a>, <a href="https://betterprogramming.pub/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d#099a">non-blocking sockets</a>, and more. And <a href="https://medium.com/@wrong.about/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1">here</a> is a detailed description of what happens in the Linux TCP stack when a connection is established.</p><h3>Redis Accepts a Connection Request</h3><p>After a connection request is in a listening socket’s accept queue, it’s ready to be accepted by Redis.</p><p>On the next event loop iteration, Redis checks whether any of listening sockets are readable. If there are, then Redis executes a corresponding procedure. For reading listening sockets, that is, accepting a connection request, it’s a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L310">connSocketAcceptHandler()</a>. First, it creates a connected socket, then it creates a connection and a client data structures associated with this socket, and puts a socket in an epoll set for read events. This procedure <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L311">accepts up to 1000 connection requests</a> from an accept queue in each event loop iteration. <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880831&amp;cot=14">Here</a> is what’s going on there on a higher level.</p><p>Now, let’s consider it in more detail.</p><h4><strong>Connection request accepting on a network level</strong></h4><p>This logic is implemented by the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L551">anetTcpAccept()</a> procedure from an anet module. On Linux systems, a specific system call used for connection accepting is <a href="https://linux.die.net/man/2/accept4">accept4()</a>. It is similar to <a href="https://man7.org/linux/man-pages/man2/accept.2.html">accept()</a>, but with an additional flag argument that allows for various options to be set. Redis uses it to set an accepted socket as non-blocking automatically.</p><p>Briefly, here is what happens in the kernel. An entry point for connection request accepting is <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/af_inet.c#L737">inet_accept</a> procedure. All the heavy lifting is done in a protocol-specific accept() procedure, which in the case of TCP IPv4 proxies to <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L3104">inet_csk_accept()</a>. It <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L687">dequeues</a> a head of an accept queue and returns it.</p><h4><strong>Connection data structure creation</strong></h4><p>That’s what <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L326">connCreateAcceptedSocket()</a> does. It sets a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L79">connection type to </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L79">CT_Socket</a>. It’s one of the implementations of the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#68">ConnectionType</a> type which contains many connection-related procedures. Besides, the newly created connection structure has <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L98">a connected socket descriptor</a> and a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L99">CONN_STATE_ACCEPTING state</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KraCZKc35jA2vn4RmHvKuQ.jpeg" /></figure><p>Why is it still accepting? Despite a connected being accepted on a network level, it’s not yet ready to serve client requests because its connected socket is not yet registered in an epoll instance. I’m describing it in the next section.</p><h4><strong>Client structure creation</strong></h4><p>If the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1300">total number of connections doesn’t exceed</a> a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3165">maxclients</a> setting, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1322">client structure</a> is created. Besides initializing all of its fields, there are some operations carried out with a connected socket.</p><p>Let’s take a look at TCP protocol again.</p><p><strong>The Nagle’s algorithm</strong><br>When sending data over TCP, each packet has a certain amount of overhead, including metadata such as the TCP header, IP header, and likely Ethernet header. This metadata is necessary for the packet to be properly routed and processed by the receiving end.</p><p>For example, if the payload is just one byte, the total size of the packet will be much larger than one byte due to the overhead. Assuming an Ethernet header size of 14 bytes, an IP header size of 20 bytes, and a TCP header size of 20 bytes (that’s the minimum possible size; it can reach up to 60 bytes though), the total packet size would be 55 bytes. This means that sending many small packets can result in a significant amount of overhead, leading to decreased network performance. To mitigate this, the Nagle algorithm can be used.</p><p>It works like the following. Before sending a new TCP segment, the TCP stack checks if there is unacknowledged data. If there is any, the TCP stack waits until one of the following conditions is met:</p><p>1. It has accumulated enough data to fill a TCP segment. The maximum amount of data that can be sent in a single TCP segment is called Maximum Segment Size, or MSS for short:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p5TN2TOTw-ACD7yNw4zdbw.jpeg" /><figcaption>Nagle’s algorithm: scenario when a sending host waits until tcp buffer can fill an entire tcp packet</figcaption></figure><p>2. Or, there is no unacknowledged data remaining. This means that all previously sent data has been acknowledged by the receiver.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L2m9e6-OGPsV81PntC9Zwg.jpeg" /><figcaption>Nagle’s algorithm: scenario when a sending host waits until all the sent data is ack’ed</figcaption></figure><p>In other words, Nagle’s algorithm causes delays in the transmission of small packets when there is unacknowledged in-flight data. This way, it can reduce the overall number of packets being transmitted, which, in turn, can reduce network congestion and improve performance. When a connection is first established, the algorithm is enabled by default.</p><p>Just to clarify, when an application sends a tcp request and there is no unacknowledged data, tcp packets are sent right away, even if their payload is just one byte:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v_v6usxirSseAA_9zPKm8A.jpeg" /></figure><p>The same way, if each tcp segment is filled to its maximum size, sending side doesn’t wait for an ack of previously sent data:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QySNbIS2vclkN7ix099xiA.jpeg" /></figure><p>In some cases, however, the Nagle algorithm can be detrimental to performance. For example, in situations with a need for real-time or low-latency communication, the buffering and delay caused by the Nagle algorithm can be unacceptable. In such cases, disabling the Nagle algorithm and, hence, “synchronous” awaiting of packet acknowledgments in some scenarios, may be necessary to achieve optimal performance.</p><p><strong>Delayed acknowledgment mechanism</strong><br>Delayed acknowledgment is a feature in TCP that allows a receiver to delay sending acknowledgment packets for received data. Instead of sending an immediate ACK for each individual packet, the receiver can wait for one of the following outcomes:</p><p>1. Combining ACKs: The receiving host may wait to accumulate multiple ACKs before sending them together in a single packet:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q8ap6aeJjn-T64TarfZUzw.jpeg" /></figure><p>This helps reduce the overhead of sending frequent small ACK packets and improves network efficiency.</p><p>2. Response-piggybacking ACK: the host may delay sending the ACK until it generates a response packet. The ACK can be included in the response packet, reducing the number of separate ACK packets:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DgjZ-UF9CaiuFnIKBSrupg.jpeg" /></figure><p>3. Timeout: If no other packet or application response triggers the ACK, the host may eventually send an ACK after a certain timeout period. This timeout is typically around 200–500 milliseconds. It ensures that the sender eventually receives an ACK even if there is no immediate data or response to trigger it.</p><p>The purpose of delayed acknowledgment is to minimize network overhead and improve efficiency. By delaying the transmission of ACKs, the receiver can reduce the number of small control packets being sent, leading to better network utilization and reduced congestion. This can be beneficial in scenarios where a high volume of small data packets is exchanged, such as interactive applications. Although, <a href="https://news.ycombinator.com/item?id=9050645">John Nagle denies the benefits</a> that this feature provides.</p><p>As I wrote earlier, Nagle’s algorithm may wait for an acknowledgment of a previously sent packet before sending the next one — for instance, in cases when data flow is not very intensive and packets can stay underfilled for quite some time. From the other hand, delayed acknowledgment mechanism delays acknowledgment sending which a sending host awaits. Hence, if a sending host has Nagle’s algorithm enabled (which is so by default in TCP), and a receiving host has the delayed acks feature enabled (which is also a default), two hosts may wait for each other. This situation is effectively a deadlock, which can be resolved only with a timeout. For example, consider this scenario:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I0BAzfgL4bucSLsUbj8kGQ.jpeg" /></figure><p>That’s the reason why they don’t always play well together.</p><p><strong>How Redis deals with it</strong><br>Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L131">disables the Nagle algorithm</a> by setting the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L191">TCP_NODELAY option</a> on a connected socket using the setsockopt() system call. In other words, Redis instructs the operating system to send a response to a client as soon as it is available, no matter its size, and without waiting for an acknowledgment of previously sent response packets.</p><p>But Redis keeps the default delayed TCP acknowledgment feature. When a request sent to Redis doesn’t quite fit in a single TCP segment, the response can be delayed dramatically:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wQaq6KqNnA1bnTk0dwnxFg.jpeg" /><figcaption>Deadlock: sending host wait for an ack of the first packet, and the receiving host waits for the second packet</figcaption></figure><p>This can be eliminated by a TCP_QUICKACK option, but Redis doesn’t use it. But Camellia, Redis proxy by NetEase, <a href="https://github.com/netease-im/camellia/blob/master/update-en.md#1113202301301113-is-broken-in-maven-central-repository-so-re-deploy-in-1114">supports this option</a>.</p><p>Note that disabling Nagle’s algorithm on the Redis side does not affect a client&#39;s behaviour. Generally, when TCP_NODELAY is set only on a server side, clients will continue to buffer requests or wait for acknowledgment of previous packets. That’s why you should consider setting this option on the client side as well. For instance, that’s how the previous case looks like when Nagle’s algorithm is disabled on a sending side:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7LY6Yobt0gclm0Y8bEGmYw.jpeg" /></figure><p><strong>TCP keepalive</strong><br>If a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3132">tcp-keepalive</a> is specified, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L133">keepalive mechanism is enabled</a> by Redis. TCP uses it to detect whether the other end of a connection is still alive and reachable. It does this by periodically sending small packets, or keepalive messages, to the other end and waiting for a response. The idea is to detect whether a connection has become idle or lost due to a network failure or an endpoint crash. In this case, OS closes a corresponding socket.</p><p>To enable keepalives, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L140">SO_KEEPALIVE socket option</a> must be set on a socket. This option tells the TCP stack to send keepalive messages on the socket periodically.</p><p>The frequency and number of keepalive messages sent are controlled by three TCP socket options: TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L153">TCP_KEEPIDLE</a> specifies the time in seconds the connection must be idle before keepalive messages are sent. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L163">TCP_KEEPINTVL</a> specifies the interval in seconds between subsequent keepalive messages. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L171">TCP_KEEPCNT</a> specifies the number of keepalive messages that can be sent before the connection is considered broken.</p><p>For example, if TCP_KEEPIDLE is set to 60, TCP_KEEPINTVL is set to 10, and TCP_KEEPCNT is set to 5, then after 60 seconds of inactivity, the first keepalive message is sent. If no response is received within 10 seconds, a second message is sent, and so on, until 5 messages have been sent. If no response is received to these messages, the connection is considered broken, and the operational system closes a socket.</p><p><strong>Connected socket registration in an epoll instance<br></strong>Finally, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L134">connected socket is registered for read events in an epoll instance</a>. As any functionality related to connection, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#L100">procedure implementing this logic</a> is defined in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#L68">ConnectionType</a>. For a TCP connection, it’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L240">connSocketSetReadHandler()</a>. It adds a connection socket in an epoll set to monitor for read events and sets a procedure to be executed when new data arrives to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2579">readQueryFromClient()</a>. It will be invoked with a single argument which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L248">a current connection structure</a>.</p><p>Finally, that’s what a client structure looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*whi09SYdCaz6OqbbsqsA5g.jpeg" /></figure><h4>Connection is accepted on the Redis level</h4><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1345">Finally</a>, connection is accepted on <a href="https://en.wikipedia.org/wiki/User_space_and_kernel_space">Redis land</a>. This boils down to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#L159">invoking</a> a ConnectionType’s accept procedure, which in the case of TCP <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L416">is resolved</a> to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L200">connSocketAccept</a>. There, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L204">connection state is set to </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L204">CONN_STATE_CONNECTED</a>. Now, a client can start sending commands and receive responses. As I’ve written at the end of the previous section, an entry point for all commands sent to Redis is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2579">readQueryFromClient()</a> procedure, which I’ll write about in the next article.</p><p>As usual, you can get a higher-level view <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880771&amp;cot=14">here</a>.</p><h3>In closing</h3><p>As promised, this is a much lighter article than <a href="https://medium.com/p/921af129c27d">the first one</a>. It wasn’t such for me personally, though: while writing it, I published an article about <a href="https://medium.com/@wrong.about/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1">TCP connection establishment internals</a> and a retrospection about <a href="https://medium.com/p/725e42e7f42f">implementing a Radix tree for HTTP routing</a>. I did my best to ensure this article didn’t lose its integrity, and I’m quite happy with where I landed. All in all, stay tuned. I’ll describe how Redis executes user commands in the next article.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=faf6ad1a7df5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/redis-internals-client-connects-to-redis-by-tcp-faf6ad1a7df5">Redis Internals: Client Connects to Redis by TCP</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Sped Up Route Lookup 200x with Radix Trees]]></title>
            <link>https://medium.com/@wrong.about/how-i-sped-up-route-lookup-200x-with-radix-trees-725e42e7f42f?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/725e42e7f42f</guid>
            <category><![CDATA[routing]]></category>
            <category><![CDATA[prefix-tree]]></category>
            <category><![CDATA[radix-tree]]></category>
            <category><![CDATA[regular-expressions]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Thu, 29 Jun 2023 11:32:23 GMT</pubDate>
            <atom:updated>2023-06-29T11:32:23.173Z</atom:updated>
            <content:encoded><![CDATA[<p>Once upon a time, while doing dishes, I thought why not use a radix tree for an http routing mechanism in a project I’m working with. First of all, this project doesn’t use any frameworks, and route lookup logic was as simple as iterating through each of them and checking whether current request matches it. Of course, there is <a href="https://github.com/topics/radix-tree">no shortage of routers</a> based on a <a href="https://en.wikipedia.org/wiki/Radix_tree">radix tree</a> (aka patricia tree, or prefix tree), but I liked the idea of implementing it myself, from scratch. I wrote it on php, but the concept applies to other languages. So, here is how I did it.</p><h3>What is a Radix tree?</h3><p><a href="https://en.wikipedia.org/wiki/Radix_tree">Wikipedia</a> does a great job describing it already, but for the sake of this post to be autonomous, I’ll give a short informal description.</p><p>Suppose you have lots of string keys which point to some values. You need a data structure that allows to store and look them up. First option is a hash table: it’s the fastest way to look up the keys, but each key is a byte sequence of a fixed length. With a Radix tree, you can utilize the fact that some strings have a common prefix.</p><p>For instance, suppose you have three strings: vasya, vasily, and vasilevich. They correspond to the values of 1, 2, and 3. Those strings have a common prefix of vas. After it’s extracted, the last two strings have an il common prefix. Now imagine how much space you save when utilize this approach at scale, when you have billions of strings.</p><p>Internal nodes contain those common prefixes, and each prefix points either no the next internal node, or a leaf node with a value:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kydRUWqR-9Y_Pe-137uwUw.jpeg" /></figure><p>Radix tree is a great way to avoid O(n) complexity — where n is a number of routes. Instead, it becomes O(m), where m is a length of a searched key.</p><h3>Why use Radix tree for route lookup?</h3><p>First of all, there is nothing better than a hash table lookup: nothing beats O(1). But routes can have placeholders: /transactions/:id/refund, for instance, and this is the case I’m optimizing for in this post. If there are a couple of dozen million transactions, I need some persistent storage for storing all possible route values. Besides, I should write a logic for dynamically adding new routes as new transactions are created. But I don’t want to add an extra network hop for route lookup. So, I’m not taking this path.</p><p>I need a data structure which can be used locally and, hence, doesn’t take too much space. Besides, this structure should deal nicely with placeholders. And, finally, I want to avoid O(n) complexity. So, I think Radix tree is worth the try.</p><h3>Visual overview of a Radix tree</h3><p>First, I consider where I wanted to land. I make it visual, and I give no implementation details — hopefully, it can serve as a mental model and helps to build up heuristics. Then, I’ll proceed with ways to get there.</p><h4>Keys with no placeholders</h4><p>I’ve already given an example for this case in a “What is a Radix tree?” section. So let’s skip this part and move on to the next scenario.</p><h4>Keys with placeholders</h4><p>What if I want to add a key with a placeholder, for example, vasily/:id/hey with a value 1? I can’t think of a nice semantic way to form a static prefix out of a dynamic one. So I introduce a new entity — “internal node with a placeholder prefix”:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mMZ6S5HDViW74FQXeeqRmQ.jpeg" /></figure><p>I think of it this way: if I can’t represent a dynamic prefix statically in a nice way, I’m left with the single option — not to represent it at all. This placeholder is hidden inside an Internal-node-with-a-placeholder-prefix-thing.</p><p>Now, let’s add another key to a radix tree above: vasily/:id/heyday, and its value is 2. In an innermost internal node, their common prefix is /hey. But what should I do with the first key? It ends with /hey, so the evident option is to add an empty string into the new innermost Internal node. To be honest, I don’t think it’s such a bad idea, but at the time of writing code I thought it would be more semantic to introduce a new entity called “Internal node with leaf node”. Here is how the result could look like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8nxxzGfn-lqG40NLHXfbng.jpeg" /></figure><p>And finally, let’s consider the last and most interesting case.</p><h4>Keys with and without placeholders which conflict with each other</h4><p>Now, what if I want to build a radix tree with two keys: vasily/:id/hey = 1 and vasily/hey/:id = 2? They contain a dynamic and a static prefix on the same positions, thus conflicting with each other. Why do I use a word “conflict”? Because placeholders and static keys can’t be represented at the same time in a single internal node. Hence, I need to introduce a new node — I called it a “Conflict-resolving node”. Basically, it contains two internal nodes: the first one with fixed, static prefixes, and the second one with placeholder prefix. Mind the leaf node corresponding to value 2. Its key ends with a placeholder, thus I represent this fact with a “Leaf node with a terminating placeholder” entity:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*re55rvTOmXp_5wkbFNZ35Q.jpeg" /></figure><p>For me, the only sensible way of key lookup in this case is to first check the static key, and if there is no match, move on to placeholder node. Why? I think of a fixed key as a special case of a placeholder. It can come in handy when I decide to override a generic key with some special key. For instance, if I lookup a key vasily/hey/hey in an aforementioned tree, I will always end up with the vasily/hey/:id key.</p><h3>Visual overview of Radix tree building</h3><p>Let’s now consider the process of key adding. Here are the examples; they represent the same cases I outlined in the previous section, and contain some extra ones.</p><h4>Keys with no placeholders</h4><p><strong>First example</strong></p><p>Let’s add the first key — vasily. It’s quite straightforward. The resulting tree looks like that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MbaZJ0j1O7hs--Jx7FwsAw.jpeg" /></figure><p>Now, let’s add a key vasya. Is there some prefix of this key which is already present in a tree? Yes, it’s vas:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aBz24lRm7_XHgHEx-kBn4g.jpeg" /></figure><p>Now, I need to turn a leaf node with key vasily into an internal node with a key vas:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QUbHO4VtIwXit5SaeeGH0A.jpeg" /></figure><p>Finally, I can add a key ya in an innermost internal node — the one corresponding to vas prefix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y5Y9DQkj-iELZ8yWHlTHhg.jpeg" /></figure><p>We’re done. This is probably the simplest example, but it demonstrates important concepts which allow me to come up with a draft of a general higher-level solution for key adding. It goes like that, referencing the previous example:</p><ol><li>Find the deepest internal node where to add a key. It’s the single internal node with the single key, vasily.</li><li>Then, find the left-hand common part. It’s vas. This leaves me with the key ya that I should add. But I don’t have a internal node where I can add it.</li><li>That means that I should split an existing key, using the common prefix, vas, as a key, so that I end up with an internal node.</li><li>Finally, I can add a key ya in a newly created internal node.</li></ol><p><strong>Second example</strong></p><p>Now, let’s add the third key, vassal, in this tree. The common prefix is vas:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3saq_ckjKuKkDfEBPbscsQ.jpeg" /></figure><p>This key, vas, is already present in a tree. Let’s dive in further then, and cut the prefix off a key:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/974/1*YsbUwp-ip1Nj8ku1SJafHA.jpeg" /></figure><p>Great, empty common prefix is a condition indicating that it’s time to add a key in an internal node. An entire tree now looks like that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8D-nHI64kbkHSlsoUn3Dyg.jpeg" /></figure><p>At this point I can refine a general algorithm a bit:</p><ol><li>Find the deepest internal node where I should a key, the suitable key in this node, and a common prefix. Do so recursively, diving deeper into a tree on each run.</li><li>If a common part is empty — great, add a key into an internal node.</li><li>If not, I should modify a tree’s structure prior to adding a key. Those modifications can take several forms. You’ve already seen the first one, where an existing key is split. I’ll describe some others in the examples below.</li><li>When it’s done, add a key.</li></ol><p>Let’s consider some more examples.</p><h4>Keys with placeholders</h4><p><strong>First example</strong></p><p>Suppose we have a tree with a single key: vasily/:id/hey. It can be represented visually like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BTbC4plMZ_Yg55sHr4o_BQ.jpeg" /></figure><p>Now, let’s add a key vasily/:id/heyday. Following an algorithm I’ve outlined above, I end up with the following deepest internal node, along with the single suitable key:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xos8sZD0KzBzxOZyYnv8bQ.jpeg" /></figure><p>Why is it suitable? Because it has a left-hand common part with a key to add:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QaNs20wtBi6sfRPOU14lww.jpeg" /></figure><p>Thus, I need to modify an existing structure so that I can add a key day later:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lbBV5zQnMI3fW6O465nDeg.jpeg" /></figure><p>I’ve replaced a leaf node with an “internal node with a leaf node” entity. Internal-node-part is empty, while the previous leaf node migrated here. Now, everything is ready for adding the last suffix of an added key:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9hXe5_TSJF6i_w1VhcNjDw.jpeg" /></figure><p><strong>Second example</strong></p><p>Now what if it’s the other way round: there is a vasily/:id/heyday key in a tree, and I want to add vasily/:id/hey? First, I’m going to split heyday into hey and day:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G1FO8dhQm4oYjktqIOpO8g.jpeg" /></figure><p>Then I’m ready to add a new key. But I’m adding it as a leaf of an internal-node-with-a-leaf entity which replaces an existing internal node:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9hXe5_TSJF6i_w1VhcNjDw.jpeg" /></figure><p>This results into the same tree I arrived at in the previous example.</p><h4>Keys with and without placeholders which conflict with each other</h4><p>That’s the last case, and probably the most interesting one. Let’s consider we have a tree with a single key vasily/:id/hey. It looks like that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5csY3x28krhYalf-TmTyvA.jpeg" /></figure><p>Now I want to add a key vasily/hey/:id. Apparently, there is a conflict: prefix vasily/ is followed by an :id placeholder in the first key, and a static string hey in the second key. Hence, I run a structure-modifying step: wrap an existing node-with-placeholder-prefix with a conflict-resolving node, and pass an empty internal node where I will add a new key a few moments later:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9PZ4MByQgjCh7H_ODrO_wA.jpeg" /></figure><p>And finally I add a new key in a previously created internal node. Now, the whole tree looks like the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VzjDbNSfVJ0aFO8bFv04tA.jpeg" /></figure><p>You can find code implementing these concepts and building a radix tree <a href="https://github.com/wrong-about-everything/RadixTree/blob/master/src/Generation/RadixTreeGenerator.php">here</a>.</p><h3>How to output a Radix tree as code</h3><p>Now, what I need is effectively a memory dump of a tree into plain-text code. Besides, I want nice formatting reflecting my aesthetic preferences. This implementation boils down to traversing a tree: checking each node’s class and outputting a current node based on it. Its argument might be another node, so this whole procedure is recursive in nature. For example, our radix tree contains the following eight keys: /vasily/:lebov = 0, /vasilev/:anton/vasily/:belov = 1, /vasilev/:anton/anton/:petrovich/anton/:vasilyev = 2, /vasilev/baton/:vakhlakov/vakhlakov = 3, /vasilevs/:antons/tabakovs/:matskyavichus = 4, /vasile/:anton = 5, /vasily/belov = 6, /vasilev/baton/vakhlakov/:vakhlakov = 7. Its output looks like that:</p><pre>new DefaultInternalNode(<br>    [6],<br>    [<br>        &#39;/vasil&#39; =&gt;<br>            new DefaultInternalNode(<br>                [1, 2],<br>                [<br>                    &#39;y/&#39; =&gt;<br>                        new DefaultConflictResolvingNode(<br>                            new DefaultInternalNode(<br>                                [5],<br>                                [<br>                                    &#39;belov&#39; =&gt; new DefaultLeafNode(6),<br>                                ]<br>                            ),<br>                            new LeafNodeWithTerminatingPlaceholder(0)<br>                        ),<br>                    &#39;e&#39; =&gt;<br>                        new DefaultInternalNode(<br>                            [1],<br>                            [<br>                                &#39;v&#39; =&gt;<br>                                    new DefaultInternalNode(<br>                                        [1, 2],<br>                                        [<br>                                            &#39;/&#39; =&gt;<br>                                                new DefaultConflictResolvingNode(<br>                                                    new DefaultInternalNode(<br>                                                        [6],<br>                                                        [<br>                                                            &#39;baton/&#39; =&gt;<br>                                                                new DefaultConflictResolvingNode(<br>                                                                    new DefaultInternalNode(<br>                                                                        [10],<br>                                                                        [<br>                                                                            &#39;vakhlakov/&#39; =&gt; new LeafNodeWithTerminatingPlaceholder(7),<br>                                                                        ]<br>                                                                    ),<br>                                                                    new InternalNodeWithPlaceholderPrefix(<br>                                                                        [10],<br>                                                                        [<br>                                                                            &#39;/vakhlakov&#39; =&gt; new DefaultLeafNode(3),<br>                                                                        ]<br>                                                                    )<br>                                                                ),<br>                                                        ]<br>                                                    ),<br>                                                    new InternalNodeWithPlaceholderPrefix(<br>                                                        [1],<br>                                                        [<br>                                                            &#39;/&#39; =&gt;<br>                                                                new DefaultInternalNode(<br>                                                                    [6, 7],<br>                                                                    [<br>                                                                        &#39;vasily/&#39; =&gt; new LeafNodeWithTerminatingPlaceholder(1),<br>                                                                        &#39;anton/&#39; =&gt;<br>                                                                            new InternalNodeWithPlaceholderPrefix(<br>                                                                                [7],<br>                                                                                [<br>                                                                                    &#39;/anton/&#39; =&gt; new LeafNodeWithTerminatingPlaceholder(2),<br>                                                                                ]<br>                                                                            ),<br>                                                                    ]<br>                                                                ),<br>                                                        ]<br>                                                    )<br>                                                ),<br>                                            &#39;s/&#39; =&gt;<br>                                                new InternalNodeWithPlaceholderPrefix(<br>                                                    [10],<br>                                                    [<br>                                                        &#39;/tabakovs/&#39; =&gt; new LeafNodeWithTerminatingPlaceholder(4),<br>                                                    ]<br>                                                ),<br>                                        ]<br>                                    ),<br>                                &#39;/&#39; =&gt; new LeafNodeWithTerminatingPlaceholder(5),<br>                            ]<br>                        ),<br>                ]<br>            ),<br>    ]<br>);</pre><p>The first argument of each internal node is an array with key lengths, so that a <a href="https://github.com/wrong-about-everything/RadixTree/blob/9334187453ae5aba35bdeafc2c5240df6203eaa2/src/ReadModel/Node/DefaultInternalNode.php#L22">key lookup logic</a> always knows exactly how many symbols to cut off from an input string.</p><p>This radix tree has sped up route lookup tenfold, comparing to linear search. To be honest, I was expecting even more significant boost. So, since I’m elbow-deep in this enterprise already, why not combine this approach with the one Nikita Popov suggested <a href="https://www.npopov.com/2014/02/18/Fast-request-routing-using-regular-expressions.html">back in 2014</a> — to collect all routes in a single regular expression?</p><h3>How to output a Radix tree as a regular expression</h3><p>What I need is just another class for representing a tree, this time in a form of a regular expression. Conceptually, it’s not that different from a memory-dump version. The most interesting part here is a way to map a found pattern to an original key. For example, I have the following regular expression: ^(a|b)$. There are two strings that match it: “a” and “b”. But, how do know which one matches in each case? Here is what the <a href="http://www.rexegg.com/backtracking-control-verbs.html#mark">MARK verb</a> does: it tags a path that a regexp engine has taken. Now, you can write the previous regular expression this way: ^(a(*:1)|b(*:2))$. Given you have a way to identify each match by a tag, you’re done. For instance, if you know in advance that tag 1 corresponds to a string a, and tag 2 corresponds to a string b, all you’re left to figure out is a way to get that tag. To do that, you can access a MARK key of an array you pass in the third argument of a <a href="https://www.php.net/manual/en/function.preg-match.php">preg_match</a> procedure.</p><p>The route set from the previous section corresponds to this regular expression — in a readable form with tabs:</p><pre>(?|/vasil<br>    (?|<br>    y/<br>        (?|<br>            (?|<br>            belov(*:6))|([^/]+)(*:0))|<br>    e<br>        (?|<br>        v<br>            (?|<br>            /<br>                (?|<br>                    (?|<br>                    baton/<br>                        (?|<br>                            (?|<br>                            vakhlakov/([^/]+)(*:7))|<br>                            ([^/]+)(?|/vakhlakov(*:3))))|<br>                    ([^/]+)(?|/<br>                        (?|<br>                        vasily/([^/]+)(*:1)|<br>                        anton/<br>                            ([^/]+)(?|/anton/([^/]+)(*:2)))))|<br>            s/<br>                ([^/]+)(?|/tabakovs/([^/]+)(*:4)))|<br>        /([^/]+)(*:5))))</pre><p>Or, in a more compact form:</p><pre>#(?|/vasil(?|y/(?|(?|belov(*:6))|([^/]+)(*:0))|e(?|v(?|/(?|(?|baton/(?|(?|vakhlakov/([^/]+)(*:7))|([^/]+)(?|/vakhlakov(*:3))))|([^/]+)(?|/(?|vasily/([^/]+)(*:1)|anton/([^/]+)(?|/anton/([^/]+)(*:2)))))|s/([^/]+)(?|/tabakovs/([^/]+)(*:4)))|/([^/]+)(*:5))))#</pre><p>That’s more concise than a radix tree memory dump. Here is <a href="https://github.com/wrong-about-everything/RadixTree/blob/a7624a18066d49f5237af083f3473f29b3f65ec1/test/Representation/RadixTreeAsRegExTest.php#L20">a regular expression</a> for <a href="https://github.com/wrong-about-everything/RadixTree/blob/a7624a18066d49f5237af083f3473f29b3f65ec1/test/Representation/RadixTreeAsRegExTest.php#L35-L103">69 highly-conflicting routes</a> — pretty neat.</p><p>This optimization gave another whopping 20x boost to route lookup speed. Who knew regular expressions are <em>that</em> fast?! Crazy.</p><h3>In Closing</h3><p>It was fun for real implementing this logic. Being mostly a business applications developer, I don’t do such things very often. Route lookup speed increased two orders of magnitude, which saves us some CPUs. For a typical I/O-bound application with decent load, it’s not life-changing but noticeable still.</p><p>And, as it turned out, <a href="https://nicolas-grekas.medium.com/making-symfony-router-lightning-fast-2-2-19281dcd245b">Symfony already has it this way</a>! Kudos to <a href="https://medium.com/u/4db6278e0cea">Nicolas Grekas</a>. I’ve never been a big fan of frameworks in general, but I always considered that people involved in Symfony ecosystem are brilliant.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=725e42e7f42f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deep-Dive into TCP Connection Establishment Process]]></title>
            <link>https://levelup.gitconnected.com/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/f6cfb7b4e8e1</guid>
            <category><![CDATA[tcp]]></category>
            <category><![CDATA[syncookies]]></category>
            <category><![CDATA[three-way-handshake]]></category>
            <category><![CDATA[linux-kernel]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Sun, 28 May 2023 08:47:10 GMT</pubDate>
            <atom:updated>2023-07-04T03:06:20.212Z</atom:updated>
            <content:encoded><![CDATA[<h4>With lots of links to the Linux kernel TCP stack</h4><p>While working on <a href="https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60">the Redis Internals series</a>, I decided to take a quick look at <a href="https://www.ietf.org/rfc/rfc793.txt">TCP</a> implementation in <a href="https://github.com/torvalds/linux/tree/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff">the latest commit</a> of the Linux kernel master branch. Who knew it would be a week-long detour, but boy was it worth it!</p><p>Let’s get right into it.</p><h3>Overview: the Three-Way Handshake and All of His Friends</h3><p>Here, I start with the basics, and then narrow down my focus and proceed to a connection establishment specifically.</p><h4>What is a TCP socket?</h4><p>It’s just a programming construct used in the Linux kernel source code. It’s an abstraction over how Linux communicates with other hosts distributed over a network.</p><h4>Client-server communication: a client’s view</h4><p>When an application wants to exchange data with a server, first it should establish a connection. As a result, Linux kernel creates a socket containing all the information needed to send requests in a reliable way, and returns its id to an application. Then, an application can send a request using this socket. When it does so, an underlying Linux TCP stack temporarily <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp.c#L1336">saves this request in a socket</a>; it’s actually sent over a network a few moments later. This process is referred to as “write in a socket”.</p><h4>Client-server communication: a server’s view</h4><p>A server must create at least one listening socket to be able to accept client connections. When a client sends a tcp connection request, server’s listening socket reads it and the Linux kernel creates a new socket, specifically for further data exchange with this client. This newly created socket is called a connection socket. A server application source code must have access to its identifier, or a descriptor — to be able to read a specific client’s requests and write it responses. In order to do that, a server <em>accepts</em> a connection request, issuing a specific command. When it’s done, a data exchange becomes possible.</p><p>When a server receives a request, server-side Linux TCP stack <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L5968">stores it in a socket</a> until an application reads it. That’s why a process of receiving data is often called “reading from a socket”.</p><h4>Connection establishment</h4><p>The process of connection creation in TCP is called the three-way handshake. It has three stages. First, a client sends a SYN packet. Server receives it and creates a data structure typically referred to as a connection request, or mini socket. It has an information that the future connection socket will need, so it has to store it until a connection is fully established. After this, a server sends a confirmation that a syn packet was processed. It’s sent in a syn-ack packet. When a client receives it, it finally sends the last packet, an ack. Server Linux kernel then creates a connected socket waiting for a server application to obtain, or accept, it.</p><h4>TCP segments, sequence numbers, and acknowledgements</h4><p>TCP stack receives a stream of bytes and converts them into chunks known as TCP segments. It’s size is agreed upon during a connection establishment. It’s known as MSS: maximum segment size. This name is misleading: it denotes the maximum size of tcp segments’ <em>payload</em> that a current connection will use. That is, it excludes tcp and ip headers size. What’s important here, a sending part can suggest its own value in a syn packet, but ultimately it’s up to a receiving part what value to select. Generally, it doesn’t choose a value greater than what a client proposed. This value is then communicated back to a sending part in a syn-ack packet.</p><p>Each tcp segment contains a sequence number of the first byte of data in current segment, within a current connection. It’s called just a sequence number, for short. But it doesn’t start with zero. In this case, an attacker <a href="https://en.wikipedia.org/wiki/TCP_sequence_prediction_attack">could guess it</a> and send counterfeit packets. That’s why it’s chosen randomly by both sides during the three-way handshake. Each side generates its own sequence number, sends it to a counterpart, and waits for an acknowledgment:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bk3RVVMd83AOk0b5-pNbhw.jpeg" /><figcaption>Three-way handshake sequence generation for both hosts. From now on, they can exchange data.</figcaption></figure><p>When a host receives a sequence number generated by the other host, it simply increments it and sends back. Thus, sequence number validation is trivial for an original sender.</p><p>After a three-way handshake process completed successfully, data exchange looks like that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WwHbZVGe0v0i66CdlnVf0w.jpeg" /><figcaption>Here, only an initiating host sends packets with data. Receiving host just acknowledges them.</figcaption></figure><h4>Queues</h4><p>When a syn packet arrives, server-side Linux kernel creates a connection request and saves it in a way historically referred to as a queue; you can stumble upon it elsewhere by the names of “half-open connection queue”, an “incomplete connection queue”, a “SYN queue”, “connection request queue”, or “request socket queue”. The last one owns its name to a structure which represents a connection request, a request_sock:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1015/1*adA79u06r75BZJe-fGE3nw.jpeg" /></figure><p>After an ack packet has arrived, connection request is removed from a connection request queue and a connection socket is created. It’s put in another queue which is known by a variety of names: accept queue, backlog queue, pending connection queue, or connection backlog. When a server application “accepts” a connection (using some command that boils down to an <a href="https://man7.org/linux/man-pages/man2/accept.2.html">accept</a> system call), it simply dequeues an existing socket from an accept queue.</p><p>You’ll see a more detailed explanation of this process below.</p><h4>SYN-flood DDoS attack</h4><p>What happens if lots of clients send a syn packet, but don’t send an ack packet? In previous Linux versions, half-open connections were added in an accept queue. This caused problems to legitimate connections, since an accept queue is home for not yet accepted connection sockets.</p><p>In newer Linux versions, there is a separate storage for connection requests. But server resources can get exhausted anyway — Linux kernel has to store all those connection requests until an ack packet arrives, which, in case this attack is carried out, never happens.</p><p>But there is a workaround.</p><h4>Syncookies</h4><p>Actually, almost all of the data a connection request contains can be obtained on an ack-processing step. An exception for this is an mss value sent by an initiating host: it sends it only in a syn packet. Frankly, I don’t think that there is some fundamental principle violated if an initiating host sends an mss in an ack packet. For me, it looks more of a TCP design flaw.</p><p>However, TCP does the following. When a receiving host processes a syn packet, it extracts an mss value, clamps it to a user-defined value if set, and encodes it in a sequence number which is sent back to a sending host in a syn-ack packet. Sending host acknowledges a syn-ack packet just the same way: it increments receiving host’s sequence number and sends it in an ack packet. Receiving host extracts an mss value from it and calculates a final result.</p><p>This mechanism can be either totally disabled, or enabled only when a connection request queue is full, or always used, regardless of connection request queue state.</p><p>Now, let’s dive into the source code.</p><h3>The Three-Way Handshake with no Syncookies</h3><p>First, here is an image again of what a three-way handshake looks like when syncookies are not used:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1005/1*rgXykunw7cXVE2OnnFH4YA.jpeg" /><figcaption>The three-way handshake with no syncookies</figcaption></figure><p>Let’s consider it step by step.</p><h4>Receiving host receives a syn packet</h4><p>When a client initiates a 3wh, it sends a syn packet to the server. There,<br>the TCP layer’s entrypoint is a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1975">tcp_v4_rcv</a> procedure. When a packet is received, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2017">listening socket is looked up</a>. If it’s found, its state is <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2132">TCP_LISTEN</a>. Through a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2133">tcp_v4_do_rcv()</a> helper procedure, we reach <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1744">tcp_rcv_state_process()</a>. This procedure is effectively a state machine applied to a socket that processes a current packet: it first checks a socket state, then runs some logic based on it, and advances the state accordingly. There, I fall into a clause indicating that <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6478">it’s a </a><a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6478">syn packet processing stage</a>. Then, through a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1529">tcp_v4_conn_request()</a> procedure, I find myself in <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6924">tcp_conn_request()</a>. I’m interested in a case when syncookies are not used, but normal operation is possible. This boils down to a single case when <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6946">syncookies are either disabled or have a default value of 1, and a request socket queue is not full</a> (and given a listening socket is not in a TIME_WAIT state).</p><blockquote>As a side note, when tcp_syncookies is 2, then syncookies are always used, even when request socket queue is not full.</blockquote><p>A request socket <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6957">is created</a>. As I mentioned above, request socket is not a full-blown connection socket used for data exchanging, instead it’s a <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/include/net/request_sock.h#L51">connection request, or a mini socket</a> — as it’s typically referred to in Linux source code. Its ireq_state is <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6815">TCP_NEW_SYN_RECV</a>, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/request_sock.h#L101">listener socket is attached</a> to it and a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7031">syn packet saved</a>. Finally, a <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/net/ipv4/inet_connection_sock.c#L1101">request_sock is cast to a </a><a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/net/ipv4/inet_connection_sock.c#L1101">sock struct</a> and is added in a “queue”. Well, actually it’s not a queue. It’s a mechanism consisting of two parts:</p><ol><li>a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/inet_hashtables.h#L149">hash-table</a>, usually referred to as an ehash, which is used for socket lookups;</li><li>and, however crazy, an <em>accept queue</em> tracking its length through the <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/include/net/request_sock.h#L180">qlen</a> field. To make things more confusing, an accept queue is represented as a struct with a request_sock_queue name.</li></ol><p>This “queue” is often referred to as a half-open connection queue, or a SYN queue — as I mentioned above. Here we can see its length <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/include/net/request_sock.h#L228">incrementing</a> when a <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/net/ipv4/inet_connection_sock.c#L1113">new connection request is added</a>.</p><p>And finally, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7053">syn-ack is sent</a>.</p><p>So, for now, no socket is created. Only a mini socket is created, which is a <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/include/net/request_sock.h#L53">request_sock</a> struct disguised as a normal socket represented as a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/sock.h#L356">sock</a> structure.</p><h4>Receiving host receives an ack packet</h4><p>As during a syn packet processing step, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2017">socket is looked up</a>. In an <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/inet_hashtables.h#L392">__inet_lookup</a> procedure, <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_hashtables.c#L471">__inet_lookup_established</a> is issued first. It <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_hashtables.c#L484-L500">looks up a socket</a> in an <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_hashtables.c#L486">ehash</a>. And there is one indeed — a temporary request_sock cast to a sock struct which was added during a syn packet processing stage. Its state is still TCP_NEW_SYN_RECV, so we fall through a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2027">sk-&gt;sk_state == TCP_NEW_SYN_RECV</a> clause. Then, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2070">tcp_check_req</a> invoked there <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_minisocks.c#L807">creates a child socket</a> by <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1570">cloning a listening socket</a>. This newly created socket is in a <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/net/ipv4/inet_connection_sock.c#L1145">TCP_SYN_RECV</a> state. <a href="https://github.com/torvalds/linux/blob/f1fcbaa18b28dec10281551dfe6ed3a3ed80e3d6/net/ipv4/tcp_ipv4.c#L1630">It’s added in an ehash</a>, so __inet_lookup_skb will find it when a client starts exchanging data. Right after this normal socket was added, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_hashtables.c#L662">mini socket is removed</a> from an ehash — it’s not needed there anymore.</p><p>Then, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L1351">socket is added</a> in a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L1309">tail of an accept queue</a> and <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L1310">its length incremented</a>. From an implementation point of view, it’s not a socket actually. Instead, it’s the same request_sock created on a syn stage, but <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L1303">it now stores a child socket</a> created earlier. It’s a funny asymmetry: request queue conceptually has connection requests, although technically it contains sockets (not to mention it’s not a queue whatsoever), while an accept queue conceptually contains sockets, although technically it consists of connection requests. The length of an accept queue is tracked through a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/sock.h#L491">sk_ack_backlog field</a> of a listening socket.</p><p>Finally, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2092">tcp_child_process</a> is called. It calls an already familiar <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_minisocks.c#L879">tcp_rcv_state_process</a>, but this time for a newly created socket. Its state is <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6550">TCP_SYN_RECV</a>, and it’s set to <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6565">TCP_ESTABLISHED</a>. It’s worth noting that each socket arriving in an accept queue is in an TCP_SYN_RECV state; it becomes established a few moments later.</p><h3>The Three-Way Handshake with Syncookies</h3><p>I won’t delve into the same level of detail as in a previous section — much of an implementation remains the same. I only highlight the differences between a scenario when syncookies are used and when they’re not.</p><p>First, here is a flow:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ll9puOweRfjXMPujZOZ1Zg.jpeg" /><figcaption>The three-way handshake with syncookies</figcaption></figure><h4>Receiving host receives a syn packet</h4><p>As on a previous step, <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1744">tcp_rcv_state_process</a> is invoked through <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1975">tcp_v4_rcv</a> entrypoint and <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2133">tcp_v4_do_rcv</a> helper. There, in a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6488">tcp_conn_request</a> procedure, we read <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/netns/ipv4.h#L131">sysctl_tcp_syncookies</a> setting. It’s <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L3203">1 by default</a>. Syncookies mechanism is used if <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6946">it’s </a><a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6946">2 or if a connection queue is full</a>, given a listening socket is not in a TIME_WAIT state, namely a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/include/net/tcp.h#L861-L867">tcp_tw_isn field</a> is empty.</p><p>Then, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7020">syncookie is generated</a>. In case of tcp, <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L176">cookie_v4_init_sequence</a> procedure does that. Syncookie is a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L103-L106">sum of the following values</a>:</p><ol><li>two hashes of addresses and ports of a source and a destination;</li><li>Linux internal representation of current time;</li><li>a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L4074">clamped</a> <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L6783">mss</a> <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7020">value</a> and a sequence number sent by an initiating host.</li></ol><p>Then it’s all <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7053">sent to</a> a client in a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_output.c#L3600">seq field</a> of a syn-ack packet.</p><p>Although a request socket was created as part of an implementation, it’s not put in a connection request queue. Instead, <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_input.c#L7058">it’s disposed of</a>.</p><h4>Receiving host receives an ack packet</h4><p>As always, everything starts with a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1975">tcp_v4_rcv</a> procedure. Since there is no connection request in an ehash, only a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2017">listening socket</a> can be found. It’s in a TCP_LISTEN state, so we find ourselves in <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L2143">tcp_v4_do_rcv</a>. There, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1730">syncookies mechanics it started</a>. First, a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L349">syncookie value is validated</a>:</p><ol><li>It’s checked that a syncookie must be <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L127-L128">less than 120 seconds old</a>;</li><li>Then, an <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L131-L133">mss value is extracted and returned</a>. If this value is <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L195">in a range from 0 to 3</a>, then syncookie is considered valid.</li></ol><p>If everything is ok, then a <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L448">socket is created</a>. More precisely, it’s achieved with <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L207">tcp_v4_syn_recv_sock</a> procedure — the same one used when syncookie is disabled. This <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/syncookies.c#L219">socket ends up in an accept queue</a> the same way either. For now, it’s still in an <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/inet_connection_sock.c#L1145">TCP_SYN_RECV</a> state.</p><p>The three-way handshake completes similar to the previous section — with <a href="https://github.com/torvalds/linux/blob/4d6d4c7f541d7027beed4fb86eb2c451bd8d6fff/net/ipv4/tcp_ipv4.c#L1735">tcp_child_process</a> procedure invoked for a child socket.</p><h3>In closing</h3><p>So that’s about it. This topic is extensive indeed, I have highlighted only those details that were of particular interest to me. Hopefully, there will be some more posts about intricacies of TCP implementation.</p><p><strong>UPD</strong>: an original version of this post contained a mistake in a syncookies explanation. Thanks to the noble and benevolent <a href="https://news.ycombinator.com/item?id=36112874">Hacker News</a> souls who pointed it out — in a <a href="https://news.ycombinator.com/item?id=36123537">kind</a> and <a href="https://news.ycombinator.com/item?id=36126326">gentle</a> manner that we all lack in the darker places of the Internet.</p><h2>Level Up Coding</h2><p>Thanks for being a part of our community! Before you go:</p><ul><li>👏 Clap for the story and follow the author 👉</li><li>📰 View more content in the <a href="https://levelup.gitconnected.com/?utm_source=pub&amp;utm_medium=post">Level Up Coding publication</a></li><li>💰 Free coding interview course ⇒ <a href="https://skilled.dev/?utm_source=luc&amp;utm_medium=article">View Course</a></li><li>🧠 AI Tools ⇒ <a href="https://www.aimind.so/">View Now</a></li></ul><p>🔔 Follow us: <a href="https://twitter.com/gitconnected">Twitter</a> | <a href="https://www.linkedin.com/company/gitconnected">LinkedIn</a> | <a href="https://newsletter.levelup.dev/">Newsletter</a></p><p>🚀👉 <a href="https://jobs.levelup.dev/talent/welcome?referral=true"><strong>Join the Level Up talent collective and find an amazing job</strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f6cfb7b4e8e1" width="1" height="1" alt=""><hr><p><a href="https://levelup.gitconnected.com/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1">Deep-Dive into TCP Connection Establishment Process</a> was originally published in <a href="https://levelup.gitconnected.com/">Level Up Coding</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Internals Series]]></title>
            <link>https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/7fff917b6a60</guid>
            <category><![CDATA[redis]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Fri, 21 Apr 2023 10:53:10 GMT</pubDate>
            <atom:updated>2023-08-16T09:37:43.704Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Internals Series</h3><h3>What this is all about</h3><p>Recently I decided to have a look at Redis source code. At that time, I couldn’t think that it will get me hooked for a good couple of months, so I wasn’t really too much picky about choosing any specific version. I just cloned current dev branch. It’s stable — I mean, the code works, tests pass, but this is a work in progress: probably that’s the reason why the branch name is, uhm, “unstable”. However, everything covered here applies to Redis 7, except some lower-level details. More specifically, the commit I’m referring to throughout this series is ahead of Redis 7.0.X versions, and is part of recently released Redis 7.2.0 version. Oddly enough, <a href="https://github.com/redis/redis/releases">there is no such thing as Redis 7.1</a>.</p><h3>What is covered, and what is not</h3><p>My primary purpose is to give a higher-level overview of how Redis works — that’s I think the most important part when diving into a new codebase. Being a business applications developer by day, I had absolutely no idea how such kind of systems work internally. And that’s the overview that I personally wish I had when I started this endeavor.</p><p>Besides a higher-level overview, there are some parts that I’ve found particularly interesting. I’m describing those too. But there are way more interesting things that I’m not diving into here. However, having an overall picture, it won’t be much of a trouble to do oneself.</p><p>Originally, this overview was in a half-visual-half-textual form in this <a href="https://miro.com/app/board/uXjVMUwQrWs=/">Miro board</a>. Current series is, at least to some extent, a complementary afterthought. Comparing those two, I think that the Miro version allows to see better a bigger picture, and by its very nature it’s more visual. While accompanying posts makes an effort of explaining some parts in more depth.</p><h3>Scenarios</h3><p>I think that the best way to describe how the system works is through user stories. As a side note, no wonder that I also think that the best way to decompose any new feature is through writing and discussing user stories as well. Moreover, I prefer to discuss them through <a href="https://cucumber.io/blog/bdd/example-mapping-introduction/">specific examples</a> which might serve as BDD-style tests. That’s what I’ve tried to apply here. There are several specific scenarios I consider, some are small, some are large. Within these scenarios, when applicable, I’ve tried to describe lower-level functionality by a concrete example.</p><p>The <a href="https://medium.com/p/921af129c27d">initial post</a> delves into the processes set in motion when Redis initiates. This piece is quite substantial, and understandably so, given the multitude of activities transpiring during this phase. Redis establishes listening sockets, initializes background threads and threaded I/O machinery, and finally throws a party with an event loop — essentially the heartbeat of Redis. Alongside the execution of client commands, it manages various cyclic tasks: client eviction, key expiration, defragmentation, maintenance procedures for hash tables like resizing and rehashing, as well as data persistence.</p><p>The <a href="https://medium.com/p/faf6ad1a7df5">following post</a> outlines the way Redis accepts client connections. There I examine the intricacies of listening sockets, their configuration, and how they generally function. If you get fascinated by this whole network-thing, you might also like delving directly into <a href="https://levelup.gitconnected.com/deep-dive-into-tcp-connection-establishment-process-f6cfb7b4e8e1">the implementation of TCP connection establishment within the Linux TCP stack</a>.</p><p>The <a href="https://medium.com/p/b06178d98825">next post</a> explores the process in which Redis reads and parses a command, assembles a response, and transfers it into a socket. Furthermore, I delve into how Redis manages responses based on their size, detailing its approach to handling different reply sizes.</p><p>The <a href="https://medium.com/p/9e3e8c463f7">fourth post</a> takes a lot of cues from the previous one and zooms in on threaded I/O. It’s a deep dive into I/O threads — what they are, when they come in handy, and how they’re put into action. I’m also digging into five back-to-back event loop iterations to describe in detail how I/O threads jump in and out of the game.</p><p><a href="https://medium.com/p/c465d7bb1f96">Lastly</a>, I’m going to detail how Redis goes about carrying out a command. In that section, I’ll guide you through the primary data structures that Redis leans on for storing data. Then, I’ll dive into the necessary maintenance procedures that kick in with every command. This will include revisiting hash table resizing and rehashing, plus breaking down the nitty-gritty of key eviction. To wrap things up, I’ll sketch out the general steps that a command execution follows.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7fff917b6a60" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Redis Server Start-Up Internals]]></title>
            <link>https://medium.com/better-programming/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/921af129c27d</guid>
            <category><![CDATA[c]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[redis]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Fri, 21 Apr 2023 10:52:05 GMT</pubDate>
            <atom:updated>2023-08-16T09:55:11.812Z</atom:updated>
            <content:encoded><![CDATA[<h3>Redis 7 Server Start-Up Internals</h3><h4>Redis 7 Internals Series: Redis server starts in standalone mode</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6ne2mOcOAYLMPKpa" /><figcaption>Photo by <a href="https://unsplash.com/@safarslife?utm_source=medium&amp;utm_medium=referral">Safar Safarov</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>This is the first post in the <a href="https://medium.com/@wrong.about/redis-internals-series-7fff917b6a60">Redis 7 Internals Series</a>. You can find this scenario <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880882&amp;cot=14">here</a>. Read it left to right; that’s the order in which each logic block is executed while the Redis server starts.</p><p>Everything starts with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L7009">main()</a> procedure in the server.c file. There are several major parts there.</p><h3>Config Initialization</h3><p>It can be roughly divided into two stages. The first is initializing default config values hard-coded in the Redis source code. This logic is implemented in an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1977">initServerConfig()</a> procedure. The second one is reading a config file and overwriting default settings; you can find it in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L648">loadServerConfig()</a> procedure.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1977">initServerConfig()</a></h4><p>There is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3028">standardConfig static_configs[]</a> variable defined in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c">config.c</a> file. Each <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L274">standardConfig</a> struct has, among others, a default value for a setting it defines and an address of a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1525">redisServer</a> field that stores this value. For example, one of the static_config[] elements goes like this:</p><pre>createBoolConfig(&quot;appendonly&quot;, NULL, MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly)</pre><p>It means that the default value for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1730">server.aof_enabled</a> is set to 0. Default config values are set in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3262">initConfigValues()</a> procedure. There, for each standardConfig struct defined in static_configs array, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3266">config-&gt;interface.init(config)</a> is called. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L278">interface field</a> is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L257">typeInterface</a> struct, which mainly deals with getting and setting data of a specific type. For example, for boolean config, init procedure is set to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L1815">boolConfigInit </a>— you can see that in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L1851">createBoolConfig</a> <a href="https://www.programiz.com/c-programming/c-preprocessor-macros">macros</a> which initializes a standardConfig struct:</p><pre>#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, apply) { \<br>    embedCommonConfig(name, alias, flags) \<br>    embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite, apply) \<br>    .type = BOOL_CONFIG, \<br>    .data.yesno = { \<br>        .config = &amp;(config_addr), \<br>        .default_value = (default), \<br>        .is_valid_fn = (is_valid), \<br>    } \<br>}</pre><p>Besides, each standardConfig is added in a dictionary, that is, a hash table — so that a config name can find any item in constant time. It is represented as a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L84">dict</a> struct in Redis.</p><p>You can check out a more concise Miro version <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223912805&amp;cot=14">here</a>.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L648">loadServerConfig()</a></h4><p>Here, each setting provided in redis.conf, is read and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L501-L507">set</a> with the typeInterface’s set procedure. Thus, default values are overwritten.</p><p>All data manipulation procedures for each typeInterface implementation are defined in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c">config.c</a>.</p><h3>Listening Sockets Creation</h3><p>Pair this description with <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880770&amp;cot=14">visuals</a>.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.c#L60">connTypeInitialize()</a></h4><p>It starts with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/connection.h#L68">ConnectionType</a> initialization. This struct contains procedures that are related to a connection and specific to an underlying socket type. It has procedures both for a client and a server. Among other things, they connect to an endpoint, create a listening socket bound to a specific address, accept connections, write data to a client socket, read data from a listening or connection socket, and shut down a connection.</p><p>So, for now, initialization simply means that a static ConnectionType *connTypes[CONN_TYPE_MAX] is filled with connection types for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L391">TCP sockets</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/unix.c#L160">Unix domain sockets</a>, and, if Redis was compiled with BUILD_TLS=yes, for <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/tls.c#L1108">tls</a> (which in Redis can be used only with TCP sockets).</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2720">initListeners()</a></h4><p>Two main things happen here. First, listening sockets are created, and second, their file descriptors are added in an epoll set. Let’s now go through everything in order.</p><p><strong>Overall lifecycle of a network application</strong><br>When clients send connection requests, they first create a socket on their side. After that, they send a connection request which boils down to <a href="https://man7.org/linux/man-pages/man2/connect.2.html">connect()</a> system call. To accept connection requests, the server side must have listening sockets. Each socket has some allocated memory for a queue which temporarily stores incoming client connection requests:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QKOqCOej9VauUjRwDJ4mkA.jpeg" /><figcaption>Remaining images by author</figcaption></figure><p>Each <a href="https://man7.org/linux/man-pages/man2/accept.2.html">accept()</a> system call issued against a listening socket extracts the first connection request and creates a new connected socket. This socket will be used to communicate with a specific client that made this connection request. This recently created connection socket will handle the next command the client sends. Here’s what the flow looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nN2suQ4kstiQVFD2tzzv2g.jpeg" /></figure><p>When either server or client wants to terminate their connection, one of them first issues the <a href="https://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown()</a> system call. It disables sending or receiving data on the specified socket so any pending requests can be handled. After that, the socket is <a href="https://man7.org/linux/man-pages/man2/close.2.html">closed</a>, which removes the underlying file descriptor and releases its resources.</p><p>So, for now, listening sockets are created. In the case of TCP, this logic is implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2416">listenToPort() procedure</a>. Binding and listening specifically are made in an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L420">anetListen() procedure</a>.</p><p>Two more things worth noting. First, addresses that sockets are bound to are set as reusable. Second, created sockets are made non-blocking, but what does this mean?</p><p><strong>Reusable addresses<br></strong>If the address is not reusable, which is the default setting, only one socket can be bound with it. Otherwise, more than one address can be bound to it, and OS automatically distributes incoming requests to different sockets. Here’s what the flow looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7Pil4_IK44K-V0HEB9WfVA.jpeg" /></figure><p>It’s useful when you want to restart a server quickly, but your operating system thinks that the address is still used by some socket, which, in the case of Redis, is not the case: it binds an address to a single socket. So that’s the way to avoid this situation. The address is made reusable in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/anet.c#L270">anetSetReuseAddr() procedure</a>.</p><p><strong>Non-blocking sockets<br></strong>When you read data from an ordinary blocking socket, and there is none available, the <a href="https://man7.org/linux/man-pages/man2/read.2.html">read()</a> system call will hang — or block — until data becomes available. It’s not a busy-wait, that is, your CPU is not utilized, but further code execution just doesn’t happen. In the case of non-blocking sockets, a special code is returned instead: it means that there is no data to be read right now. Thus, your thread doesn’t hang, but your code should be ready for this scenario.</p><p>The same thing applies for the <a href="https://man7.org/linux/man-pages/man2/write.2.html">write()</a> system call. But how can writing be blocked? First, this is what writing in a socket looks like.</p><p>When you issue a write() system call, you don’t actually write into the underlying file descriptor. It’s not like writing in a file. Moreover, there is no file at all. Instead, there is a file descriptor, which is just an entry in a file descriptor table maintained by a kernel for each process, and some kernel memory associated with it is called a buffer. I wrote about it earlier.</p><p>That’s where the kernel actually writes your data. What happens if you write a message that doesn’t fit into a buffer? If it’s a blocking socket, your write() system call hangs. When a kernel finally releases some memory, and your message fits in the buffer, the write() call still hangs. It waits until this message is delivered to a recipient. And since TCP has no upper bound for when exactly a message is delivered, your app can hang indefinitely.</p><p>Now, if I write to a non-blocking socket and a message doesn’t fit in a buffer entirely, only a part of a message is written, and a special code is returned. Your app should be ready to resume writing when the buffer is ready. And this brings us to an epoll instance.</p><p><strong>epoll instance<br></strong>Epoll instance is a Linux kernel data structure that consists of two lists: an interest list and a ready list. When you want to know that some file descriptor has some data to be read or it is ready for writing, you can <a href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html">add</a> a file descriptor to an interest list, which is also called an epoll set. The epoll instance asynchronously tracks the state of the registered file descriptors and adds them to a ready list as soon as they become ready for I/O operations. You can get a ready list by calling an <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait()</a> procedure.</p><p>Each epoll set item is called an epoll event, which might sound misleading. Usually, when there is an “event,” it’s already happened. You can subscribe to events; after some time, they happen, and you can somehow get that event. That’s what happens in the case of an epoll set as well, but an epoll_event is probably an unfortunate name for an interest list item.</p><p>You do the following when you subscribe to read or write events for your file descriptors. First, you create an epoll_event struct. Then, you specify what exactly you’re interested in. If you subscribe to read events, it goes like epoll_event.events |= EPOLLIN. And finally, you specify a file descriptor you’re interested in: epoll_even.data.fd = my_fd;.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JFpfEX_6mB4U9LYvA7JS5w.jpeg" /><figcaption>Socket with file descriptor 1 has some data to be read</figcaption></figure><p>In Redis, epoll logic is implemented in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae_epoll.c">ae_epoll.c file</a>. To get events that actually happened since the last time you checked, you can call <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait</a>.</p><p><strong>Event handlers<br></strong>How does Redis know which procedure to run when a specific event occurred? It keeps event handlers in an aeEventLoop struct’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.h#L103">aeFileEvent *events</a> array. Its name seems misleading a bit; it should’ve been handlers I think. Array indices are file descriptors, so Redis can quickly find a correct handler. If you’re subscribing to read events, you want to set a read handler which is stored <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.h#L74">aeFileProc *rfileProc</a> field, and if to write events, then you set a handler in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.h#L75">aeFileProc *wfileProc</a>.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L158">aeCreateFileEvent</a> is a procedure which subscribes to specific events and sets corresponding handlers.</p><p><strong>Quick Recap</strong><br>So, in short, you subscribe to read or write events for your file descriptors, and then you check their state. If you do so periodically in an infinite loop, this whole mechanism is called an event loop. And that’s exactly what happens with listening sockets. Redis puts them in an epoll set, subscribes to read events, and sets aeFileProc *rfileProc field to ConnectionType.accept_handler, which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L310">connSocketAcceptHandler</a> in case of TCP connection. After that, Redis periodically checks whether those socket descriptors have pending client connections. When there are, Redis accepts them. There will be a separate post about it. For now, you can check <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880771&amp;cot=14">this diagram</a>.</p><h3>Background I/O Initialization</h3><p>There are some system calls that can block for quite some time. Most notably, it’s <a href="https://man7.org/linux/man-pages/man2/fsync.2.html">fsync</a>, which actually moves data from OS memory pages to a storage device. Besides, if a large amount of memory has been allocated using <a href="https://www.geeksforgeeks.org/dynamic-memory-allocation-in-c-using-malloc-calloc-free-and-realloc/">malloc()</a> and <a href="https://www.tutorialspoint.com/c_standard_library/c_function_free.htm">free()</a> is called on it, the operating system may need to perform some behind-the-scenes operations, such as defragmentation of memory, which can take a significant amount of time and cause the free() call to block.</p><p>That’s why Redis doesn’t run operations that employ those calls in the main thread. Instead, it utilizes special background threads — one thread per job type, three threads in total. In a source code, they are often referred to as bio threads, where “bio” stands for <strong>b</strong>ackground <strong>I</strong>/<strong>O</strong>, although free has nothing to do with I/O. There are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/bio.h#L46">four job types</a>:</p><pre>enum {<br>    BIO_CLOSE_FILE = 0, /* Deferred close(2) syscall. */<br>    BIO_AOF_FSYNC,      /* Deferred AOF fsync. */<br>    BIO_LAZY_FREE,      /* Deferred objects freeing. */<br>    BIO_CLOSE_AOF,      /* Deferred close for AOF files. */<br>    BIO_NUM_OPS<br>};</pre><p>File closing is not a blocking call usually, and it returns most of the time immediately. But there are some obscure cases when it can block, and what’s worse, this behavior varies across systems. So generally, closing file descriptors in the background is a good practice. Moreover, in Redis, this is done with a prior fsync call, which is relatively costly.</p><p>Each thread runs <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/bio.c#L205">bioProcessBackgroundJobs</a> procedure. If the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3091">bio_cpulist</a> setting is specified, it’s applied here with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L6878">redisSetCpuAffinity</a> procedure. It boils down to <a href="https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html">sched_setaffinity</a> system call. Effectively, you specify a set of CPUs for those threads to run on. This can reduce the overhead of context switching, cache invalidation, and other CPU-related tasks, leading to faster execution times and improved performance.</p><p>Then, an infinite loop processes a job list specific to this thread. If it’s empty, the thread waits for a signal notifying about the new job and hangs:</p><pre>if (listLength(bio_jobs[worker]) == 0) {<br>    pthread_cond_wait(&amp;bio_newjob_cond[worker], &amp;bio_mutex[worker]);<br>    continue;<br>}</pre><p>If it’s not, Redis gets the first element from bio_jobs[worker] list and processes it.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/bio.c#L152">bioSubmitJob</a> is a procedure used to add a job for a background execution.</p><h3>Threaded I/O</h3><p>Redis is usually claimed to be single-threaded. That doesn’t mean that Redis has a single main thread for everything. As you’ve already seen in a previous part, there are background threads, for instance. Moreover, Redis forks child processes sometimes. So, what it actually means, instead, is that all client commands are handled by a single main thread on a data storage level.</p><p>For instance, if you have ten thousand simultaneously connected clients, and all of them issue a SET command against the same key at just the same moment of time; it means that Redis will set this key with a specified value ten thousand times, one by one, one at a time, in total isolation from each other, in the single main thread.</p><p>But this fact nevertheless leaves some space for optimization in specific scenarios. The primary scenario that is being targeted is as follows. If you have lots of clients connected at the same time, and they issue commands that have large responses, it might take some time to iterate all those clients and, for each of them, issue a write() system call.</p><p>This command copies data from Redis, which resides in <a href="https://en.wikipedia.org/wiki/User_space_and_kernel_space">user space</a>, to a TCP buffer, which is in kernel space. So, first, it’s inherently slow because of this boundary crossing, and second, the larger response, the more time it takes to copy it. And Redis came up with a solution targeting this issue: it can write responses in threads running in parallel. Those threads are usually referred to as io threads. The number of threads can be set with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3128">io-threads</a> setting.</p><p>The second scenario is conceptually the same, but it targets command parsing. Since it’s less typical for clients to have a large request body, this case is even rarer. This behavior is controlled by a separate setting, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3032">io-threads-do-reads</a>.</p><p>I/O threads implementation is considered in more detail in <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880362&amp;cot=14">this diagram</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2799">Threads are started</a> in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4129">initThreadedIO()</a> procedure. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4082">IOThreadMain</a> is a procedure that runs in each thread.</p><p>A couple of notes about implementation: First, how does Redis understand that there are some responses to write or some incoming requests to parse? It checks <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4072">getIOPendingCount</a> procedure that returns several items to be processed by a thread whose id is passed as an argument. Second, how does Redis know exactly what it should do — send a response or parse a command? A global static variable defines it, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L2452">io_threads_op</a>. Here’s how to use this variable:</p><pre>if (io_threads_op == IO_THREADS_OP_WRITE) {<br>    writeToClient(c,0);<br>} else if (io_threads_op == IO_THREADS_OP_READ) {<br>    readQueryFromClient(c-&gt;conn);<br>}</pre><p>In the case of writing responses to clients, both thread operation and pending responses count are set synchronously in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4227">handleClientsWithPendingWritesUsingThreads</a>. Reads are done in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4352">handleClientsWithPendingReadsUsingThreads</a>.</p><p>Last but not least, it might seem that I/O threads are busy-waiting when there is nothing to do for them. Here’s the code:</p><pre>for (int j = 0; j &lt; 1000000; j++) {<br>    if (getIOPendingCount(id) != 0) break;<br>}<br>/* Give the main thread a chance to stop this thread. */<br>if (getIOPendingCount(id) == 0) {<br>    pthread_mutex_lock(&amp;io_threads_mutex[id]);<br>    pthread_mutex_unlock(&amp;io_threads_mutex[id]);<br>    continue;<br>}</pre><p>They’re not, actually. Their state is controlled by locking corresponding mutexes, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4063">io_threads_mutex[id]</a> — you can see that in a comment that goes like Give the main thread a chance to stop this thread. Initially, they are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4155">locked by the main thread</a>. When Redis decides that <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4213">there are enough clients</a> to justify their processing with I/O threads, it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4184">unlocks their mutexes</a> in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4238">startThreadedIO() procedure</a>. And when active clients quantity decreases, Redis simply <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4194">acquires a lock again</a> in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4214">stopThreadedIO()</a> procedure. That leads to I/O threads hanging when they try to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L4101">lock already locked mutex</a>.</p><p>I’ll write a separate post about how the whole client request handling is done with I/O threads. For now, you can head over to <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880362&amp;cot=14">this Miro diagram</a>.</p><h3>Load Client Data From Disk Into Memory</h3><p>First, here’s a quick overview of what <a href="https://redis.io/docs/management/persistence/">persistence</a> looks like in Redis.</p><h4>Two persistence options in Redis</h4><p>The first one is generating a data snapshot, all Redis data in a single binary file. This file is called an RDB file, and this whole mechanism is usually referred to as RDB, which stands for <strong>R</strong>edis <strong>d</strong>ata<strong>b</strong>ase. The filename can be configured with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3087">dbfilename</a> setting.</p><p>The second one is logging all mutating commands in a human-readable format — the same one used in the Redis protocol. All files created by Redis when this option is used are collectively called AOF files, which stands for <strong>a</strong>ppend-<strong>o</strong>nly <strong>f</strong>ile. You can enable this option with the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3064">appendonly</a> setting. The specific implementation of an underlying mechanism changes across different Redis versions.</p><h4><strong>AOF in Redis 7</strong></h4><p>In Redis 7, the AOF mechanism was reworked significantly. It employs two types of files for storing client data.</p><p>The first one is a base file. It’s a snapshot of an entire Redis instance dataset. Its format depends on <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3053">aof-use-rdb-preamble</a> setting. If it’s on, the base file has raw binary data. In this case, the base file is no different from an RDB file. If it’s off, the base file contains data as a compacted list of commands.</p><p>For instance, if some client has set a specific key with different values a hundred times, only the last actual command will end up in a base file. The same applies to other data structures. If a client appended a hundred values to a list of one item per command, the base file will contain a single <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/commands.c#L7323">RPUSH</a> command with a hundred arguments.</p><p>The second type is an increment file, or, for short, an incr file. That’s where all the currently run mutating commands are written. They are stored as is.</p><p>When the Redis server starts, it checks a config for a currently-used persistence mechanism and loads a corresponding file. In the case of AOF, it first loads a base file and then an incr file.</p><p>As new data is being written in storage, the current incr file requires rotation, so it wouldn’t grow indefinitely. This rotation process, usually referred to as AOF rewrite, runs periodically. Instead of overwriting existing files, new ones are created. In short, it goes like this. First, a new incr file is created. All the current commands are written in this new file now. After that, a new snapshot is taken. When it’s completed, previous versions of a base file and an incr file are deleted. I describe this process in more detail <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223912664&amp;cot=14">here</a>, and I’ll write about it a bit later.</p><p>Now, let’s consider the loading part. As usual, you can find it in a diagram <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880907&amp;cot=14">here</a>.</p><h4>Implementation of AOF files loading into memory</h4><p>As I’ve written earlier, two types of files store client data. They can have several versions at once, so they have to be tracked somehow. That’s what a manifest file serves for. It stores currently active versions of a base file and incr files, along with their previous, historical, versions that will be deleted soon. If appendfilename setting equals my_appendonly_file, a manifest file can look like that:</p><pre># this is a base file, type &quot;b&quot; for &quot;base&quot; <br>file my_appendonly_file.aof.2.base.aof seq 2 type b<br><br># This is a previous version of a base file, mind &quot;.base&quot; prefix in a filename.<br># Type h means &quot;historical&quot;<br>file my_appendonly_file.aof.1.base.aof seq 1 type h<br><br># this is a previous version of an incr file. Mind &quot;.incr&quot; prefix.<br>file my_appendonly_file.aof.1.incr.aof seq 1 type h<br><br># this is a currently active incr file. All current commands are written here<br>file my_appendonly_file.aof.2.incr.aof seq 2 type i</pre><p>The AOF rewrite process starts with loading this manifest file into memory, namely in an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1490">aofManifest</a> struct. Here’s what that looks like:</p><pre>typedef struct {<br>  aofInfo     *base_aof_info;       /* BASE file information. NULL if there is no BASE file. */<br>  list        *incr_aof_list;       /* INCR AOFs list. We may have multiple INCR AOF when rewrite fails. */<br>  list        *history_aof_list;    /* HISTORY AOF list. When the AOFRW success, The aofInfo contained<br>              in `base_aof_info` and `incr_aof_list` will be moved to this list.<br>              We will delete these AOF files when AOFRW finish. */<br>  long long   curr_base_file_seq;   /* The sequence number used by the current BASE file. */<br>  long long   curr_incr_file_seq;   /* The sequence number used by the current INCR file. */<br>  int         dirty;                /* 1 Indicates that the aofManifest in the memory is inconsistent with<br>                                         disk, we need to persist it immediately. */<br>} aofManifest;</pre><p>This is done in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L232">aofLoadManifestFromDisk()</a> procedure.</p><p>Then, client data is loaded. First, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L6738">Redis checks</a> which persistence mechanism is currently used. If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L6746">AOF is disabled</a>, then it loads an RDB snapshot. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L6740">Otherwise</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1650">AOF files are loaded</a>: first, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1706">base file</a>, and second, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1736">all the </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1736">incr files</a>. Usually, there is a single incr file. But if a previous AOF rewrite process is completed with an error, Redis can have more than one incr file. Incoming commands are written in a single incr file at any given time.</p><blockquote>Important consequence: if you’ve been using only RDB mechanism, when you stop Redis, then enable AOF, and then start your instance, your data from an RDB file will not be loaded. Like almost any setting in Redis, you can switch AOF on the fly: <em>CONFIG SET appendonly yes</em>.</blockquote><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1396">loadSingleAppendOnlyFile()</a> is a procedure that loads an AOF file. It has a back-compatibility check, so this procedure can work with files created in previous Redis versions. Earlier, if <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3053">aof-use-rdb-preamble</a>was on, an AOF file contained a so-called preamble. It was just an initial Redis dataset in a binary format, no different from an RDB snapshot. So, if Redis starts for the first time after an upgrade, this procedure first reads this preamble if it’s present, and then it goes on with a so-called AOF tail. In Redis 7, a base file can contain either raw binary data or a compacted list of commands, not both.</p><p>When data is loaded, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L713">opens an incr file</a> from a manifest so it is ready for writing client commands. If it’s the first Redis 7 start, neither a base nor an incr file exists. In this case, they’re created, and a manifest file is updated.</p><p>Finally, if history files are left after the last AOF rewrite, Redis deletes them now, in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L668">aofDelHistoryFiles()</a> procedure. Like any file deletion in Redis, this is done asynchronously in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/bio.c#L228">a bio thread</a>.</p><h3>Event Loop</h3><h4>Overview</h4><p>I’ve already mentioned an event loop concept when I talked about an epoll set. But epoll mechanism is an implementation detail of an event loop, which is, in and of itself, just an abstract concept. It is a programming construct that enables a program to efficiently handle multiple input/output operations without blocking or wasting CPU cycles. Typical cases are network I/O and file I/O. What powers any event loop is a specific multiplexing mechanism. A concrete kernel implementation tracks registered file descriptors and reports their state when you ask. Epoll is one of those implementations, the latest and the most performant one. It’s now used in Redis when it’s built on Linux systems.</p><p>Regardless of an implementation mechanism, an event loop has these basic steps:</p><ol><li>Register interest in I/O events for each file descriptor using the appropriate system call. In other words, you subscribe to an event. I/O event is basically a change in the state of a file descriptor. For instance, if you have a listening socket and some client connects to an address bound with it, the state of this socket changes: now it has a connection request in its TCP buffer, waiting to be read and accepted.</li><li>Wait for I/O events on the registered file descriptors by blocking the system call depending on your multiplexing mechanism. You can wait indefinitely until at least one event happens or with some timeout.</li><li>When a blocking system call from a previous step returns, you get events that have happened so far.</li><li>Determine the type of event that occurred (read, write, etc.) and handle it appropriately by executing the associated callback function.</li><li>Repeat.</li></ol><h4>Event loop with an epoll mechanism</h4><p>First, Redis <a href="https://man7.org/linux/man-pages/man2/epoll_create.2.html">creates a so-called epoll instance</a>: epoll_create(1024);. This system call effectively creates a container for an epoll set and a ready list and returns a file descriptor associated with it. In early Linux versions, its single argument hinted to the kernel about what size it should expect an epoll set to be. But now it is ignored. To start tracking the state of a file descriptor, you issue an <a href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html">epoll_ctl</a> system call. Besides adding an item in an epoll set, this procedure can also modify and remove it. And when you want to get all events that happened to registered file descriptors, you call <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait</a>.</p><h4>Event loop in Redis</h4><p>I’ve built, installed, and run Redis on Linux, so I’ll refer to an epoll as a multiplexing mechanism. As usual, you can combine reading this text with <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880286&amp;cot=14">this diagram</a>.</p><p>First, an epoll instance is created in<a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae_epoll.c#L39">aeApiCreate</a> procedure, which is called during event loop initialization in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L67">aeCreateEventLoop</a>. To track some file descriptor’s state, you can call <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae_epoll.c#L74">aeApiAddEvent()</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae_epoll.c#L91">aeApiDelEvent()</a> removes a file descriptor from an interest list.</p><p>As the last step in server start-up, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L498">runs an infinite loop</a> of <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L357">aeProcessEvents</a> procedure, effectively implementing an entire event loop iteration. It has several logical parts.</p><ol><li>The first one is all the logic that should be run before waiting on events. Since this wait is blocking with a specific timeout, the logic executed before <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait</a> is collected in a procedure called <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L400">beforesleep</a>. It’s implemented <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1617">here</a>. Among others, it calls procedures dealing with reading clients’ commands and writing a response to them. I’ll write a separate post about several scenarios when a client sends a request, and Redis handles it. Nevertheless, you can already find the corresponding diagrams <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880883&amp;cot=14">here</a>.</li><li>After that, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L404">asks for all the events that happened since the latest event loop iteration</a>. This logic is implemented in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae_epoll.c#L109">aeApiPoll</a> procedure. In it, Redis issues a <a href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html">epoll_wait</a> system call. It’s always made with a specific timeout, so it never blocks indefinitely. Then, Redis collects fired events in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.h#L104">aeEventLoop-&gt;fired</a>.</li><li>Then, Redis loops over aeEventLoop-&gt;fired and runs corresponding handlers. They are stored in an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.h#L103">aeEventLoop-&gt;events</a> array. Handlers are added by calling a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L158">aeCreateFileEvent</a> procedure.</li><li>Finally, so called <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L471">time events are handled</a>. Those are events indicating that specific time interval has passed. In C, they are known as timer events. You can subscribe to them natively with creating a timer with <a href="https://man7.org/linux/man-pages/man2/timerfd_create.2.html">timerfd_create</a> and putting a resulting file descriptor in an epoll set, and then setting a timeout with <a href="https://man7.org/linux/man-pages/man2/timerfd_settime.2.html">timerfd_settime</a> procedure. It can come in handy for executing a logic that needs to be run periodically. However, there is actually no such thing as a classical timer event in Redis. Instead, it emulates them. First, you register a handler with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L215">aeCreateTimeEvent</a> procedure, where you effectively specify a timestamp when your handler should run. Then, at the end of each event loop iteration, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/ae.c#L276">processTimeEvents</a> is called. There, Redis iterates each handler and checks whether a specified timestamp has come. If it has, the corresponding handler is executed.<br>That’s how <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2678">cron</a> is implemented in Redis, for instance. We’ll see shortly what happens there, but first a quick tour of a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1617">beforeSleep()</a> procedure.</li></ol><h3><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1617">beforeSleep()</a></h3><p>There are some procedures that should be executed before receiving new events. As I’ve already written, among them are procedures dealing with clients’ input and output; there will be a separate post covering those. Here, I’ll briefly walk through procedures related to blocking clients. Hopefully, someday I’ll update this part with key invalidation tracking feature that uses a pub/sub mechanism.</p><h4>Clients’ blocking</h4><p>There is a set of procedures executed in beforeSleep() that all relate to a scenario when client connection is blocked. Blocking means that Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2487">doesn’t parse</a> and, hence, doesn’t execute any commands that a client sends, though <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2629">it reads and stores those raw requests in client’s buffer</a>.</p><p><strong>Overview<br></strong>First, let’s consider how this blocking generally works using <a href="https://redis.io/commands/brpop/">BRPOP</a> command as an example.</p><p>When a client sends a <a href="https://redis.io/commands/brpop/">BRPOP</a> command, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_list.c#L1274">Redis blocks a client</a> for specified keys with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_list.c#L1218">specified timeout</a> if <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_list.c#L1227">none of those keys exist</a>. This logic is implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L364">blockForKeys()</a> procedure. There, each client first <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L372">sets the keys it blocks on</a>. Then, each key is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L378">added in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L378">redisDb-&gt;blocking_keys</a> dict, and its value is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L388">list of clients blocked on it</a>. Thus, Redis has access to both the keys blocked by a given client and the clients blocked on a given key. Besides, timeout is handled in the same manner. It’s set to a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L369">currently blocked client</a>, as well as <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L98">it’s added</a> in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/timeout.c#L120">server.clients_timeout_table</a> radix tree. Finally, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L94">client is marked as blocked</a>.</p><p>When some other client <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_list.c#L486">adds a first item</a> into any of non-existing lists the first client blocks on, this key is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/t_list.c#L498">added in a database</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L191">dbAdd()</a> procedure <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L196">checks</a> whether there are any <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L473">clients blocked on it</a>, and if there are, this key is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L494">added in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L494">server.ready_keys</a> list.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/timeout.c#L137"><strong>handleBlockedClientsTimeout()</strong></a><strong><br></strong>This procedure walks through a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/timeout.c#L120">server.clients_timeout_table</a> radix tree and gets <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/timeout.c#L148">all clients with expired timeout</a>. Each of them <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L702">is unblocked</a>. It means that the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L218">client is not marked as blocked</a> anymore, hence from now on all of its futher commands will be processed. Finally, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L222">Redis puts it in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L222">server.unblocked_clients list</a> which will be <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1680">processed in the next part</a>.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L124"><strong>processUnblockedClients()</strong></a><strong><br></strong>Here, Redis <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L128">iterates </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L128">server.unblocked_clients list</a>. As I’ve written above, it has all the clients unblocked recently. If any of those <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2472">sent any commands while being blocked</a>, they were suspended; now it’s time <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L2473">to parse and execute them</a>. I’ll write a separate post about command execution scenario, but you can <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223880307&amp;cot=14">check out a Miro diagram</a> right away.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L314"><strong>handleClientsBlockedOnKeys()</strong></a><strong><br></strong>This procedure iterates through <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L327">recently added client keys</a> that at least one client blocks on. As I’ve mentioned earlier, they are stored in a server.ready_keys list. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L345">For each such key</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L567">each client that blocks on it</a> is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L581">unblocked</a>. As I’ve already written, from now on all of its futher commands will be processed: both parsed and executed. Besides, it ends up in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L222">server.unblocked_clients list</a> that Redis iterates in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/blocked.c#L124">processUnblockedClients()</a> procedure described above.</p><h3>Redis Cron</h3><p>Redis cron refers to a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1261">serverCron</a> procedure. It runs <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3157">hz</a> times per second. But if <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3040">dynamic-hz</a> is 1 (it’s the default), then the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1274-L1284">cron frequency is adapted</a> according to the number of active clients:</p><pre>if (server.dynamic_hz) {<br>    while (listLength(server.clients) / server.hz &gt;<br>           MAX_CLIENTS_PER_CLOCK_TICK)<br>    {<br>        server.hz *= 2;<br>        if (server.hz &gt; CONFIG_MAX_HZ) {<br>            server.hz = CONFIG_MAX_HZ;<br>            break;<br>        }<br>    }<br>}</pre><p>There is lot going there. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1289-L1306">Redis stats is updated</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1326-L1339">shutting down</a> is handled gracefully, some <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1342-L1366">info is written in logs</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1376-L1381">some</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1435-L1439">postponed</a> <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1496-L1505">logic</a> is executed — I won’t dive into those. I’ll describe what I consider the most significant parts.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L980">clientsCron()</a></h4><p>Actually, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L964-L978">the comment section</a> of this procedure already does a great job of describing what’s going on here. In short, it takes all the active clients and processes 1 / server.hz of them, so all the clients are supposed to be processed within about one second. For each client, Redis runs the following procedures:</p><ol><li>If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3144">client timeout</a> is specified, Redis calculated an amount of time when a current client has been idle. If it exceeds a config setting, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1519">client is disposed of</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1447">connection is closed</a> —connection socket’s file descriptor is first <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L137">removed from an event loop</a> and then closed.</li><li>client struct’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1152">querybuf</a> field size is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L728">decreased if needed</a>. This is where a client request body is stored until it parsed. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L769">The same thing is done</a> to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1262">client’s output buffer</a>, which serves for storing data waiting to be written to a connection socket.</li><li><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1031-L1040">Client memory usage statistics is updated</a> (clientsCronTrackExpansiveClients is about clients exp<em>e</em>nsive in terms of memory they use).</li><li>If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3235">client-output-buffer-limit</a> is reached, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L3820">client is closed immediately</a>.</li></ol><p>Historically, Redis has used the following trick here. In the beginning of each iteration, the tail of server.clients is moved to a head. Why? If this client is removed, O(n) complexity is avoided: Redis starts searching an item from list’s head, but since it is always the first one, the search always takes a constant time. However, as far as I understand, this is not needed anymore. When a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/socket.c#L326">client connection request is accepted</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L1322">client struct is created</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/networking.c#L220">server.clients list item is cached there</a>:</p><pre>void linkClient(client *c) {<br>    listAddNodeTail(server.clients,c);<br>    /* Note that we remember the linked list node where the client is stored,<br>     * this way removing the client in unlinkClient() will not require<br>     * a linear scan, but just a constant time operation. */<br>    c-&gt;client_list_node = listLast(server.clients);</pre><p>Moreover, since new clients are added in the tail of server.clients list, clientsCron() will process them first, leaving old clients neglected for some time.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1049">databasesCron()</a></h4><p>Here are the procedures needed for Redis storage level maintenance.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/expire.c#L140">activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW)</a><br>You can find a <a href="https://miro.com/app/board/uXjVMUwQrWs=/?moveToWidget=3458764551223912182&amp;cot=14">visual counterpart here</a>.</p><p>This procedure removes expired keys. They are also removed <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/db.c#L106">upon retrieval</a>, but what if they are never retrieved? They can fill all the available memory. That’s why keys are expired proactively.</p><p>Keys with set expiration time are stored in a redisDb’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L961">expires</a> field, which is a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L84">dict</a> struct. There are actually <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.h#L87">two hash tables</a> in each dict for gradual resizing. It’s needed when a number of items in a hash table grows too large, so hash table size must be adjusted accordingly. Or, the number of elements may get too low so a hash table is shrunk for more efficiency. I’ll write more on that later. For now, it’s enough that there are two of them and both of them are implemented as arrays.</p><p>That’s how a hash table in an redisDb’s <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L961">expires</a> field could look like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hGScmxu9xx8HIbRTMZwCDw.jpeg" /></figure><p>Those violet circles are bucket entries, or hash table elements. When they belong to the same bucket, they form a singly linked list, with each element pointing to the next. Each element is represented as a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L63">dictEntry</a> struct:</p><pre>struct dictEntry {<br>    void *key;<br>    union {<br>        void *val;<br>        uint64_t u64;<br>        int64_t s64;<br>        double d;<br>    } v;<br>    struct dictEntry *next;     /* Next entry in the same hash bucket. */<br>    void *metadata[];           /* An arbitrary number of bytes (starting at a<br>                                 * pointer-aligned address) of size as returned<br>                                 * by dictType&#39;s dictEntryMetadataBytes(). */<br>};</pre><p>Expiration time is stored in v.s64 field as a unix timestamp. The key pointer that is used in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L961">redisDb-&gt;expires</a> item is the same key pointer that is used in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L960">redisDb-&gt;dict</a>’s dictEntry storing a client value. Now, let’s suppose that the circles marked as red are expired:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H92ybmmi3FdCxndVhSZEbg.jpeg" /></figure><p>Redis walks through each hash bucket in a hash table, and if a current bucket has a linked list of bucket entries, it’s iterated either. This iteration logic is implemented in <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1286">dictScanDefrag()</a> procedure. It’s more complicated than that, but for now this approximation is enough. For each visited bucket entry, Redis invokes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/expire.c#L123">expireScanCallback()</a> procedure. It checks whether an expiration time has passed, and if it has, then a key is deleted from both dictionaries — the one storing an expiration time itself and the one storing actual client data:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YwmbwGAVEq_2dxnvim2X4g.jpeg" /></figure><p>Redis continues this scanning until it hits either a bucket entries limit or hash buckets limit, so this won’t take too long. In reality, the former limit is several dozens on average, but in this example it’s 3. The latter is twenty times more.</p><p>By the end of this iteration, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L961">redisDb-&gt;expires</a>’s hash table looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yv0AKE8dGM4bENdJbg0TOQ.jpeg" /></figure><p>And <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L960">redisDb-&gt;dict</a>’s hash table looks like that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8ZFEU_N2mroUUnDhknuxDg.jpeg" /></figure><p>This process will continue on the next cron iteration from where it left off.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L921"><strong>activeDefragCycle()</strong></a><strong><br></strong>Defragmentation is the process of reducing memory fragmentation, which occurs when free memory in a program is divided into small pieces that are too small to be useful for allocating new objects or data. This can lead to inefficient memory usage and slower program performance over time. Defragmentation helps to consolidate free memory into larger contiguous blocks, which can then be more effectively used for new allocations. This process is quite costly and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3061">is disabled in Redis by default</a>. Typically, you can identify that you need defragmentation by examining info memory output periodically.</p><p>Defragmentation itself boils down to reallocating memory for a current pointer in case it’s worthwhile. Redis performs this process incrementally, the same way it approaches key expiration. That is, it doesn’t perform all the required work in a single run, because it might take a while and Redis will be unresponsive during those periods. Instead, such kinds of operations are carried out in small steps, in a controllable manner with tight limits, but often. It iterates hash tables with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1286">dictScanDefrag()</a> procedure, walks through hash table buckets, and, for each bucket, defragments three types of pointers: <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L63">dictEntry</a>, its key, and v.val. It starts with dictEntrys:</p><ol><li>First, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1116">dictDefragBucket()</a> is invoked for a current bucket. There, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L53">activeDefragAlloc()</a> is executed against each bucket entry. It boils down to two things. First, Redis asks <a href="https://jemalloc.net/">jemalloc</a> memory allocator whether <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L56">it’s worthwhile to reallocate a current pointer</a>. And if it is, this <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L64">pointer’s memory is reallocated</a> and an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L65">old pointer’s memory is copied into a new one</a>. After that, an old pointer is freed.</li><li>After that, Redis walks through each dictEntry in a current bucket again, and for each one, executes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L751">defragScanCallback()</a>. This procedure boils down to calling <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L673">defragKey()</a> procedure. First, it <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L680">defragments a key</a>. Then, Redis performs defragmentation on a value by using a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/defrag.c#L700-L748">procedure that depends on</a> the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L892">type</a> of a current dictEntry.v.val.</li></ol><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L603"><strong>tryResizeHashTables()</strong></a><strong><br></strong>If the number of elements in a hash table becomes too low relative to its size, this can lead to several problems. First, a large number of buckets means that a lot of memory is allocated for the hash table, but most of it remains unused. Second, performance degrades when a sparse hash table is scanned. That’s why Redis periodically shrinks underutilized hash tables. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L603">tryResizeHashTables()</a> procedure does exactly that: it reduces the size of hash tables if they are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L598">fewer than ten percent full</a>.</p><p>Resizing process never touches a hash table with client data. Imagine a hash table with a million keys. If it resized in-place, each key must be rehashed and moved to a new slot. This might take a while. Instead, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L269">a hash table with new size is allocated in a </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L269">dict-&gt;ht_table[1]</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GSmEZv9PBYGoGu1pB9ebeA.jpeg" /></figure><p>Its size is set to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L236">the closest power of two greater or equal</a> to the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L215">number of elements in a hash table</a>.</p><p>Resizing here only shrinks a dictionary. Expanding takes place in several places, most notably — <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L448">while inserting new value</a>, namely in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1441">dictFindPositionForInsert()</a> procedure.</p><p>Note that creating a new hash table and moving data there from an old one, that is, dict-&gt;ht_table[0], are two different processes. The process of moving data into a new hash table is called rehashing. It’s done incrementally while interacting with a dictionary, both in read and write operations. For instance, check out <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L671">dictFind()</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L989">dictGetRandomKey()</a>, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L1438">dictFindPositionForInsert()</a>, etc. Besides, it’s performed in each cron iteration with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L617">incrementallyRehash()</a> procedure.</p><p><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L617"><strong>incrementallyRehash()</strong></a><strong><br></strong>So, we have a dict that has just been resized, that is, a second hash table with a new size:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Nz7VMpo_kDH1Z2yDQA_bBg.jpeg" /></figure><p>It’s time to move the first hash table’s data there. When it’s performed in a cron iteration, Redis moves bucket entries in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L396">hundred-size batches</a>, and with a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L398">specific time limit</a> which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L620">one millisecond</a>. So, let’s start with a first bucket:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CHJEbcH03OvDFi1zQpRkNw.jpeg" /></figure><p>When the second hash table is larger than the first one, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L323">new bucket index is calculated by taking an item key’s hash</a>. So their distribution in a new hash table has good chances to be even. This is not the case when the second hash table is smaller. In this case, new hash is not calculated. Instead, a new bucket index is taken as a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L328">current index in a first hash table masked with the size of smaller has table</a>. Since hash tables are always powers of two, all the elements residing in the first bucket of a first hash table end up in the first bucket of a second hash table, and so on:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/932/1*1Ufyl3d5K8Q_HuIoTU7EbQ.jpeg" /></figure><p>That’s barely a problem though. If a hash table is shrinking, it means it’s really sparse and a scenario when any single bucket has lots of elements is unlikely.</p><p>Then Redis continues with the next non-empty bucket:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P9StJWdVGlRPvGsJMnWD7Q.jpeg" /></figure><p>And so on. When done, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L368-L370">hash tables swap places</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L371">the second hash table is reset</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y_2ZNeWlTqXZEfvPB_N_kw.jpeg" /></figure><p>Until then, when rehashing is still in progress, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L673">reads are done in both hash tables</a>. New keys <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/dict.c#L469">are added in the second one</a>, so they are not lost when rehashing is over.</p><p><strong>A new process forking and a copy-on-write technique<br></strong>That’s about all that happens in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1049">databasesCron()</a> procedure. One thing to note is that all those manipulation with in-memory data are allowed only when there is no child process. Why? When a child process is forked, than it has access to all the data that the parent process has. This happens thanks to copy-on-write technique implemented by an OS. But if either process mutates some shared data, an underlying memory page is copied and becomes visible only to a process that did a modification. Another process still sees data that was actual at the moment of forking.</p><p>This CoW technique has associated costs, such as increased space usage and potential performance overhead. That’s why Redis avoids maintenance tasks when there is a child process running. That’s the reason why you occasionally see <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L658">this check</a> throughout a codebase. Besides, data mutation in a child process is worthless — the parent won’t see it anyway. That’s why resizing — and, hence, a following rehashing —are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L6588">disabled in a child process</a>.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1519">rdbSaveBackground()</a></h4><p>This procedure creates an rdb snapshot. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L2045-L2047">By default</a>, this procedure is triggered once an hour if there is a single change, every 5 minutes with at least 100 changes, and once a minute with at least 10000 changes. Those <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3234">settings can be changed</a> though.</p><p>All of the heavy lifting takes place in a child process. There, an <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1441">rdbSave()</a> procedure is executed and a process exits. Its implementation is quite straightforward but tedious. The whole instance data is first <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1471">written in a file</a>. For performance reasons, some of this data might be stored in a buffer within the C library’s I/O subsystem. So when the write is over, Redis first needs to make sure that all of this data is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1478">moved to the specified file</a>. That’s what a <a href="https://cplusplus.com/reference/cstdio/fflush/">fflush</a> procedure does. However, this does not guarantee that the data has been fully written to the disk. It can be cached by OS or disk controller. To <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1479">actually move this data on disk</a>, <a href="https://man7.org/linux/man-pages/man2/fsync.2.html">fsync</a> call is needed.</p><p>The <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1371">rdbSaveRio()</a> procedure in Redis is responsible for writing the contents of the Redis database to disk. It starts by writing <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1380-L1381">some metadata</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1385">function definitions</a>, and then proceeds to write <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1390">the contents of each database</a>. For each entry in the database, the procedure writes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1334">the key and value</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1152">The key</a> is preceded by a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1151">value type identifier</a>, and the value is written in its <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1153">raw binary form</a>. In addition to the key and value, the procedure writes <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1127">the expiration time</a> of the key, as well as either <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1135">the LRU</a> or <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1147">LFU</a> eviction information, depending on the configured eviction policy.</p><h4><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2427">rewriteAppendOnlyFileBackground()</a></h4><p>I’ve already wrote about the process of loading data from files in case of AOF mechanism. Here is a rewrite process described.</p><p>It’s triggered <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1414-L1422">when</a> all of the following conditions hold true:</p><ol><li>AOF is on (that is, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3064">appendonly setting</a> is 1)</li><li>rewrite process has not already started</li><li>current AOF file size exceeds <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3208">auto-aof-rewrite-min-size</a> setting</li><li>aof file size grew more than <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3129">auto-aof-rewrite-percentage</a> config value since the latest rewrite</li><li>and a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L875">rewrite process is not postponed</a> because of repeated failures</li></ol><p>This process is performed in several steps.</p><p><strong>Flush Redis buffer into an AOF file<br></strong><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2442">First of all</a>, if there are <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.h#L1744">commands in Redis memory</a> not yet written in an AOF file, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L1130">it’s done right away</a>.</p><p><strong>New incr file created where the current commands are written<br></strong><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2443">Then</a>, a new incr file with <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L805">the next sequence number</a> is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L808">created</a> and <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L818">added in a manifest file</a>. For instance, a manifest file had looked like this before rewrite process started:</p><pre>file my_appendonly_file.aof.2.base.aof seq 2 type b<br><br># If previous aof rewriting ended with an error, history files can be left here. They are deleted in the end of successful aof-rewriting.<br>file my_appendonly_file.aof.1.base.aof seq 1 type h<br>file my_appendonly_file.aof.1.incr.aof seq 1 type h<br><br># This is a currently active incr file. All the current commands are logged here.<br>file my_appendonly_file.aof.2.incr.aof seq 2 type i</pre><p>It can contains history files if the latest rewrite ended with failure. Then, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L834">previous incr file fsynced and closed</a>. And, finally, a new incr file is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L838">set to write current commands</a>. Now, a manifest file looks like that:</p><pre>file my_appendonly_file.aof.2.base.aof seq 2 type b<br><br>file my_appendonly_file.aof.1.base.aof seq 1 type h<br>file my_appendonly_file.aof.1.incr.aof seq 1 type h<br><br>file my_appendonly_file.aof.2.incr.aof seq 2 type i<br>file my_appendonly_file.aof.3.incr.aof seq 3 type i</pre><p><strong>Entire Redis instance snapshot is written into temporary base file<br></strong>After new incr file is ready to writing all mutating commands, a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2448">new child process is forked</a>. There, the actual rewrite happens. It’s implemented in a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2347">rewriteAppendOnlyFile()</a> procedure. The whole Redis dataset is written in a temporary file called <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2454">temp-rewriteaof-bg-&lt;current pid&gt;.aof</a>. If <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/config.c#L3053">aof-use-rdb-preamble</a> is enabled (it’s so by default), then the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/rdb.c#L1371">data is written in a raw binary format</a> — the same one that an rdb snapshot has. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2377">If it’s disabled</a>, data is written as a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2237">human-readable compacted list of commands</a>. It <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2265">iterates through all </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2265">dictEntrys in each </a><a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2265">dict</a> and, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2278-L2296">depending on a value type, writes a set command</a>. For instance, for a key key5 having a simple string value value5, it writes:</p><pre>*3<br>$3<br>SET<br>$4<br>key5<br>$6<br>value5</pre><p>It has a form of a command, but unlike an incr file, there is no history in this file. Each key is written only once, with its current value.</p><p>Worth noting that during the snapshot creation, the parent process continues to handle client commands, including mutating ones, which Redis writes to a new incr file. However, the child process that takes the snapshot does not see these changes. It only sees a snapshot of the data at the moment of forking. As a result, the data in the snapshot and the live dataset neither intersect nor have gaps. Hence, there is no data duplication or data loss when they are loaded into memory when Redis starts up.</p><p><strong>When snapshot is ready, it becomes a new base file<br></strong>In each cron iteration, <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1387">Redis checks</a> whether a <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/server.c#L1185">snapshot creation has completed</a>. If it has, then <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2580">the temporary file</a> is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2596">renamed</a> to <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2590">the new base file</a>, while <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L432">the previous base file becomes historical</a>. <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2648">Additionally</a>, all the incr files <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L505-L508">except for the currently active one</a> become historical as well. When a file becomes historical, it is not renamed; rather, it is marked as such in the manifest file, which is <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2651">persisted</a> at this point. Now it looks like that:</p><pre># this is a new base file<br>file my_appendonly_file.aof.3.base.aof seq 3 type b<br><br># this is a previous incr file<br>file my_appendonly_file.aof.2.incr.aof seq 2 type h<br># this is a previous base file<br>file my_appendonly_file.aof.2.base.aof seq 2 type h<br># this is a base file left from the latest non-successful aof rewrite<br>file my_appendonly_file.aof.1.base.aof seq 1 type h<br># this is an incr file left from the latest non-successful aof rewrite<br>file my_appendonly_file.aof.1.incr.aof seq 1 type h<br><br># this is a currently active incr file where incoming commands are written<br>file my_appendonly_file.aof.3.incr.aof seq 3 type i</pre><p>Finally, the <a href="https://github.com/redis/redis/blob/d69109834999c9f06fb31ea4472e0eb3b99d8dc8/src/aof.c#L2680">historical files are deleted both from disk and from the manifest file</a>, which is updated again:</p><pre>file my_appendonly_file.aof.3.base.aof seq 3 type b<br><br>file my_appendonly_file.aof.3.incr.aof seq 3 type i</pre><h3>In closing</h3><p>That’s what Redis server start-up looks like in a standalone mode. I haven’t considered cluster capability, replication, or sentinel mode. However, I hope that if you dive into those, you’ll have some background helping you with that.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=921af129c27d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/better-programming/redis-internals-series-redis-server-starts-in-standalone-mode-921af129c27d">Redis Server Start-Up Internals</a> was originally published in <a href="https://betterprogramming.pub/">Better Programming</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What to Test in CI/CD Pipeline]]></title>
            <link>https://medium.com/the-devops-corner/what-to-test-in-ci-cd-pipeline-e106036ba75?source=rss-44ddf0b0cb48------2</link>
            <guid isPermaLink="false">https://medium.com/p/e106036ba75</guid>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[oop]]></category>
            <dc:creator><![CDATA[Vadim Samokhin]]></dc:creator>
            <pubDate>Fri, 29 Jan 2021 18:17:20 GMT</pubDate>
            <atom:updated>2021-01-29T18:17:20.340Z</atom:updated>
            <content:encoded><![CDATA[<h4>A journey to more reliable software delivered earlier</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TWGF2vVSP7fh71DM" /><figcaption>Photo by <a href="https://unsplash.com/@max_duz?utm_source=medium&amp;utm_medium=referral">Max Duzij</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Overall hierarchy of tests</h3><p>In 2009, Mike Cohn, an agile evangelist and practitioner, came up with the term “<a href="https://www.mountaingoatsoftware.com/blog/the-forgotten-layer-of-the-test-automation-pyramid">Testing pyramid</a>”. From the bottom to the top, it consisted of three layers: Unit tests, Service Tests, and UI Tests. The naming might seem a bit vague, and the pyramid itself is outdated to a certain extent. But the overarching idea behind it is quite simple. There should be a good amount of small and fast lower-level tests, typically called unit-tests. As you go higher up the pyramid, that is, the coarser-grained your tests are, the amount of them should decrease. The reasoning behind this idea is quite simple either: it is cheap and simple to write fine-grained unit-tests. Besides, they typically run extremely fast.</p><p>Every new layer is a leap to a notable abstraction. At least talking about a backend code, the most practical levels are the ones that correspond to abstraction layers. Those are typically the following, from the highest to lowest:</p><ol><li>End-to-end tests, customer journey level. They typically include browser, imitating real user’s behaviour.</li><li>Endpoint tests that include transport. They typically correspond to the controller level of your application. Such tests are typically called Integration tests.</li><li>Unit tests for classes used within a user story. It is your domain level.</li></ol><p>This list aligns with ports-and-adapters architecture, popularized by Robert Martin in his <a href="https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164">book</a>.</p><h4>Unit tests</h4><p>There isn’t a single definition of a unit-test. For me, a unit test is the one that relies only on clear and specific abstractions. The scope they operate in is typically small. A litmus test is the following: when your tests are “unit” enough, you don’t need to debug. If you need, the problem is probably not with your tests, but with your design. Create finer-grained classes, cover them with tests. If you are able to spot a bug in a matter of seconds, congratulations: you’ve come up with a decent unit-tests suite. It’s crucial since there should be no tension in running tests like every five seconds or so.</p><p>Why not having only unit tests, if they are so great? Because when you have to refactor some user story, you’ll be covered by controller tests. If there are none, you’ll be in big trouble.</p><h4>Integration tests</h4><p>Unit test concept has a quite vague definition. Integration test even more so.</p><p>In practice, most of the time they mean testing either controllers (or Application Services, in <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">DDD</a> lingo) or endpoints. Typically, this implies that you mock third-party service calls, but don’t mock a database, using the real one instead. Being a higher-level test, you don’t test its implementation details, that is the behaviour of its internal classes. Instead, you test that the HTTP response is correct and desired side effects are in place.</p><p>Be very cautious to stay at the same abstraction level. For example, if you test order registration endpoint, don’t test that there is a record in a database. It’s an implementation detail. Today you use Postgres, and tomorrow you end up with Mongo. Instead, call order details API endpoint to make sure that it contains all the required data. What’s the whole point in doing so? Integration tests are your safety net. When implementation details change, sometimes drastically, you want to make sure that everything works as expected.</p><p>Writing integration tests often involves discussions on what to mock and what not to mock. For me, the guideline is simple. Your code should be the most fragile and error-prone part in the whole testing process. Say, what is more reliable, your database or your application code? I bet it’s the former. And what about the network? The network is notoriously fragile and slow, and this is the reason why network calls rarely take part in tests. Another example is when your application deals with external devices that are out of your control. For example, devices that print checks are pretty fragile. You don’t want your test fail because the printer is run out of paper or inks or anything else. Otherwise, you end up testing those devices instead of testing your code.</p><h4>E2e tests</h4><p>End-to-end tests are the last line of defense. They test a complete customer journey, from UI to the backend, to UI again, and so forth. I always keep in mind the Pareto principle when writing them: 20% of any effort gives 80% of the result. Identify your key business-processes and automate them. Usually, it’s the one that brings your business most of the money. For example, order registration and further processing, resulting in its delivery. Thus, with such tests in your test-suite, there is not a chance for you to introduce a devastating bug, so your back will be covered.</p><p>The flipside is the speed and reliability, both of which e2e tests lack.<br>Tests running in a console are about one order of magnitude faster comparing to tests with browser involved. So despite things might look shining in the beginning, a year ago you could end up with a test suite that takes an hour to run.</p><p>Besides, end-to-end tests are often flaky. And the more complicated they are, the flakier they get, because of an increasing amount of unforeseen factors. Some rare browser quirk, unexpected layout behavior — all this adds up to overall flakiness. There is a common workaround, though. Don’t spin up a browser; work only through DOM. Thus your tests will be way faster, not 100% end-to-end though.</p><p>Having end-to-end tests help you to reveal unexpected coupling, particularly the one that is hard to spot — for example, an accidental coupling between user stories. Remember how last time you modified some user story, and another one got broken unexpectedly? That’s precisely the case for end-to-end tests, covering a set of user stories that form a particular customer journey.</p><p>People often conflate e2e tests with UI tests, which is a broader concept.</p><h4>UI tests</h4><p>UI tests probably deserve their own pyramid. Principles are the same, after all. Doing UI development, most of the tests are typically small, fast, and the ones operating in a small scope, which generally falls into the category of “unit”. In other words, this term is often used as a frontend equivalent of a unit-test, which is typically applied to backend development.</p><p>Generally, there are two things to test in UI. First one is behaviour. There is a myriad of functionality examples that can be covered with UI unit tests. Calculating an order’s total, a link leading to a correct URL, an alert notification is displayed in red, etc. Besides, you don’t have to test most of the behaviour in an end-to-end fashion, even if you need a response from a backend. Write a stub, and you’re good to go. It can very well be tested in isolation.<br><a href="https://jasmine.github.io/">Jasmine</a> and <a href="https://mochajs.org/">Mocha</a> are a good place to start out with UI unit tests.</p><p>Testing UI layout is typically a bit more intricate endeavour. The basic idea is simple enough, though. With every build, your tests automatically create browser snapshots and compare them with the ones from the previous builds. If they differ, something is wrong. For example, <a href="https://www.cypress.io/">cypress</a> can do such kind of stuff.</p><h4>Tests within Continuous delivery</h4><p>If you want to deploy your software frequently and reliably, having a test portfolio is not enough. The key is automating it. You should be able to deploy your code at all times of day and night. Do you want to rely on human beings, who can forget to run tests before pushing code? Do you want to deploy your code only to find out that it doesn’t compile? I bet you don’t. So the best advice you can get is to use a CI/CD pipeline because it excludes a human-factor.</p><p>Besides, infrastructure provisioning and deployment process are quite tedious and repetitive tasks, which we humans are particularly bad at. CI/CD pipelines, on the contrary, are extremely good at automating those tasks. Manual tasks don’t scale. If you need a single manual tester now, you’ll need a growing number of them as your system grows bigger. So instead of spending an increasingly significant amount of time carrying them out, you can automate all your processes once and for all.</p><h3>Common pitfalls</h3><p><strong>Manually-run tests</strong><br>The absence of tests automation is a perfect recipe on how to abandon your team’s testing activity. A couple of times when some team member forgets to run tests before pushing code could be enough to get disappointed in a whole testing endeavor. How not to? Use a proper CI/CD pipeline that never forgets running your tests before deployment.</p><p><strong>Flaky tests<br></strong>The flaky test is the one which fails once in a while. There are several problems with this kind of tests.</p><p>First one, when a new build is tested and fails, you can’t say for sure why it happened: because of some flaky test or because a new bug was introduced.</p><p>Second, the credibility of test suite decreases and a team ceases to write tests, considering it a useless activity. Having a CI/CD pipeline which alerts in case of a failed test and declines to ship stuff literally makes you fix that flaky test, restoring overall test-suite credibility and team morale.</p><p><strong>Ice cream cone<br></strong>A small amount of cheap and fast tests, a vast amount of slow and expensive (in terms of time required to write them) UI tests. So a slim test pyramid degenerates into a clumsy ice cream cone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S6EBXcg0eg1TqHPvuxzX7g.jpeg" /><figcaption>Ice cream cone looks tasty indeed, but don’t allow your test suite to end up looking like that</figcaption></figure><p>This cone appears when you start with more high-level tests than you should. Your whole test suite becomes slow, and bugs become hard to get spotted. Instead of testing, you end up in gloomy debug-land. Besides, such kind of tests is not particularly reliable. If you run the same browser-involved e2e test a hundred times, the chances are that one of those tests fails — because of some browser quirks.</p><p><strong>It’s not real code — it’s just a test!<br></strong>Test code is by no means different from the rest of your code. Indeed, it’s not run in production. But you still have to maintain it. And maintainability costs are much higher than in writing ones. So the same best practices that are used in production code are applied to testing code as well.</p><p><strong>Desire to test a private method<br></strong>This is a clear indication that a class where that private method belongs to breaks the Single-Responsibility Principle. It’s a God Object that knows too much and does too much. The cure is simple: extract this private method in its own class and cover it with unit tests.</p><p><strong>Testing the wrong thing</strong><br>Do you test stuff that brings you most of the money? Very often, developers test for the sake of testing itself, without realizing the true value of testing — reducing risks and saving money. So before writing any tests, especially higher-level ones, talk with your product-owner, she certainly could give you some clues on the stuff of the utmost importance.</p><p><strong>Tests structure exactly mirrors classes structure</strong><br>This seems logical from the first sight but look at it this way. Some of your tests should serve you as a safety net which ensures that your system’s behaviour doesn’t change when you modify its internal implementation. More often than not, most of our lower-level classes represent that internal implementation. So any modification in that class would cause a corresponding test failure. Fixing it each time is a daunting task. What Robert Martin recommends is a “<a href="https://blog.cleancoder.com/uncle-bob/2017/10/03/TestContravariance.html">test contra-variance</a>,” which means testing only observable behaviour of a class. Which means that if your class A, providing some domain-specific behaviour, uses a bunch of implementation-specific and stable helpers, you don’t need to test them. XTest would be just fine. So no need in testing all the internal implementation, that’s very fragile.</p><h3>In closing</h3><p>This topic is huge indeed, and I’ve barely scratched the surface here. I suggest that you read some more on it; <a href="https://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627">Growing Object-Oriented Software, Guided by Test</a> and <a href="https://www.amazon.com/Art-Software-Testing-Glenford-Myers/dp/1118031962/">The Art of Software Testing</a> are great sources of further inspiration.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e106036ba75" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-devops-corner/what-to-test-in-ci-cd-pipeline-e106036ba75">What to Test in CI/CD Pipeline</a> was originally published in <a href="https://medium.com/the-devops-corner">The DevOps Corner</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>