<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Articles</title>
	<subtitle>Article collection</subtitle>
	<link href="https://nakedible.org/articles/feed.xml" rel="self" type="application/atom+xml"/>
    <link href="https://nakedible.org/articles/"/>
	<updated>2024-10-04T00:00:00+00:00</updated>
	<id>https://nakedible.org/articles/feed.xml</id>
	<entry xml:lang="en">
		<title>Are &quot;FROM scratch&quot; images incompatible with vulnerability scans?</title>
		<published>2024-10-04T00:00:00+00:00</published>
		<updated>2024-10-04T00:00:00+00:00</updated>
		<link href="https://nakedible.org/articles/from-scratch-scanning/" type="text/html"/>
		<id>https://nakedible.org/articles/from-scratch-scanning/</id>
		<content type="html">&lt;p&gt;I decided to invoke &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Betteridge%27s_law_of_headlines&quot;&gt;Betteridge&#x27;s law of headlines&lt;&#x2F;a&gt; to start the article off on a good note.&lt;&#x2F;p&gt;
&lt;p&gt;No, &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; Docker images are not incompatible with vulnerability scans, but it requires a bit of effort to make them work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;huh-vulnerability-scanning&quot;&gt;Huh? Vulnerability scanning?&lt;a class=&quot;zola-anchor&quot; href=&quot;#huh-vulnerability-scanning&quot; aria-label=&quot;Anchor link for: huh-vulnerability-scanning&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A bit of context on the whole ordeal. Modern software development is riddled with tons of dependencies. In the good old days, all software dependended only on other packages published for the same distribution, and all packages were separately packaged for each distribution. It was a rule that no bundled packages were allowed, so if you upgraded the installed version of some library on your system, it would affect all the applications that depended on it. Then came the the different package managers separately for programming languages – probably Perl&#x27;s &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;www.cpan.org&#x2F;&quot;&gt;CPAN&lt;&#x2F;a&gt; was the one of the first ones, but then similarily for Python, Ruby, and so on. And finally Node.js broke the camel&#x27;s back with the proliferation of &lt;code&gt;node_modules&lt;&#x2F;code&gt; directories in every project, each containing multiple versions of the same library.&lt;&#x2F;p&gt;
&lt;p&gt;All these dependencies obviously can have security vulnerabilities, so it becomes important to track which dependencies are included with each piece of software. This can no longer be done simply by looking at date or version of the distribution, but instead a full scan of the entire software must be made to locate all bundled dependencies. This is the job of vulnerability scanners, of which &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;aquasecurity&#x2F;trivy&quot;&gt;Trivy&lt;&#x2F;a&gt; is a fine example. These tools take a piece of software, or a Docker image, and build a comprehensive list of all included dependencies, and then compare them against published vulnerabilities.&lt;&#x2F;p&gt;
&lt;p&gt;Since dependencies these days come from both the distribution and the programming language package managers, the scanners generally detect the OS of the image and then check the installed packages based on that, and in addition scan for any language-specific package manager state files, such as &lt;code&gt;package-lock.json&lt;&#x2F;code&gt; or &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt;, and detect software dependencies based on those. So far so good.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-itch-with-from-scratch&quot;&gt;The itch with &lt;code&gt;FROM scratch&lt;&#x2F;code&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-itch-with-from-scratch&quot; aria-label=&quot;Anchor link for: the-itch-with-from-scratch&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Docker images traditionally used to have the entire build history of the software included in its layers. All the build tools, all the source code, all the packages. This was a treasure trove for vulnerability scanners as they had all the data to work with. But then people realized that it&#x27;s a bit of a security vulnerability in itself to have all the tools included in the image that are not needed at runtime, and the images are unnecessarily bloated. So people started turning to multi-stage Docker builds, where the build stages have the tools but the final image has only the files required at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;The evolution of this, bit by bit, is &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; images, where the final stage of the build starts from an empty image, and only the necessary binaries and libraries are copied onto it. This is especially useful for compiled languages, though sometimes done for interpreted languages as well. The resulting images are as small as possible (even smaller than &quot;distroless&quot; images) and are difficult to exploit in case of a vulnerability since there are no extra tools to exploit.&lt;&#x2F;p&gt;
&lt;p&gt;But this is where the problem arises for vulnerability scanners. There&#x27;s no installed operating system, so they can&#x27;t know what vulnerable software might&#x27;ve been there when compiling the software. And there&#x27;s no package manager state files, so they can&#x27;t know what dependencies were included in the software. Hence, most vulnerability scanners will just refuse to scan &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; images entirely, which can be a showstopper in heavily regulated environments.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;scratching-the-itch&quot;&gt;Scratching the itch&lt;a class=&quot;zola-anchor&quot; href=&quot;#scratching-the-itch&quot; aria-label=&quot;Anchor link for: scratching-the-itch&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;So, the first step to make &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; images scannable is to include the language-specific package manager state files in the image. This means simply putting your &lt;code&gt;package-lock.json&lt;&#x2F;code&gt; or &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt; in the image next to your software, even if nothing actually requires it. The security scanners scan all files in the image, so they&#x27;ll usually just pick it up where ever it is placed. This part is easy, but also not necessarily sufficient to track everything that might actually cause an exploitable vulnerability. Even if the language-specific package manager state file is included, it doesn&#x27;t mean that everything that is included in the software only comes from there. Any system libraries, such as &lt;code&gt;openssl&lt;&#x2F;code&gt; or &lt;code&gt;libz&lt;&#x2F;code&gt;, are not necessarily versioned in the lock file, so vulnerabilities for them might not be tracked, yet they might be statically linked or otherwise included with the software.&lt;&#x2F;p&gt;
&lt;p&gt;But even more disturbingly, it seems that just including the programming language package manager state file is often not enough to get the image scannable. Many vulnerability scanners want to first detect the OS of the image and will determine based on that if they support the image or not. If they don&#x27;t detect the OS, or do not support that OS, then they refuse to also scan for the language-specific package manager state files. This is certainly a misfeature of the scanners, so it&#x27;s likely to get fixed in the future, but we can&#x27;t wait for that.&lt;&#x2F;p&gt;
&lt;p&gt;The solution is to &lt;strong&gt;fake the image to look like a supported OS image&lt;&#x2F;strong&gt; by including the release files that the vulnerability scanner is looking for, as well as the package state database for the image. Ideally, you should use these files directly from the image you are compiling the software on, as then it will track all the dependencies of any package that might&#x27;ve contributed to your binary. However, in practice, the compiler image might contain a ton of irrelevant packages, which means there&#x27;s a ton of vulnerabilities reported that are not relevant for your software. The good solution to this is to minimize your compiler image to only contain necessary packages, but simply truncating the package manager state file to a single non-vulnerable package also works, if you just want the image to be scannable and don&#x27;t care about any system libraries.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yap-yap-yap-just-show-me-how-it-s-done&quot;&gt;Yap yap yap, just show me how it&#x27;s done!&lt;a class=&quot;zola-anchor&quot; href=&quot;#yap-yap-yap-just-show-me-how-it-s-done&quot; aria-label=&quot;Anchor link for: yap-yap-yap-just-show-me-how-it-s-done&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;For Alpine Linux:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;Dockerfile&quot; class=&quot;language-Dockerfile z-code&quot;&gt;&lt;code class=&quot;language-Dockerfile&quot; data-lang=&quot;Dockerfile&quot;&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; alpine:&lt;span class=&quot;z-entity z-name z-enum z-tag-digest&quot;&gt;latest&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;AS&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; Build your software here
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; scratch
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;build&#x2F;Cargo.lock &#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;build&#x2F;target&#x2F;release&#x2F;mybinary &#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;etc&#x2F;alpine-release &#x2F;etc&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;etc&#x2F;os-release &#x2F;etc&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;lib&#x2F;apk&#x2F;db&#x2F;installed &#x2F;lib&#x2F;apk&#x2F;db&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For Debian:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;Dockerfile&quot; class=&quot;language-Dockerfile z-code&quot;&gt;&lt;code class=&quot;language-Dockerfile&quot; data-lang=&quot;Dockerfile&quot;&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; debian:&lt;span class=&quot;z-entity z-name z-enum z-tag-digest&quot;&gt;latest&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;AS&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-comment z-dockerfile&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-dockerfile&quot;&gt;#&lt;&#x2F;span&gt; Build your software here
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;FROM&lt;&#x2F;span&gt; scratch
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;build&#x2F;Cargo.lock &#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;build&#x2F;target&#x2F;release&#x2F;mybinary &#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;etc&#x2F;debian_version &#x2F;etc&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;etc&#x2F;os-release &#x2F;etc&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dockerfile&quot;&gt;&lt;span class=&quot;z-keyword z-control z-dockerfile&quot;&gt;COPY&lt;&#x2F;span&gt; --from=&lt;span class=&quot;z-variable z-stage-name&quot;&gt;builder&lt;&#x2F;span&gt; &#x2F;var&#x2F;lib&#x2F;dpkg&#x2F;status &#x2F;var&#x2F;lib&#x2F;dpkg&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Figuring out the right commands for a distribution of your choice should be quite straightforward. And that&#x27;d be it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#in-conclusion&quot; aria-label=&quot;Anchor link for: in-conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This approach seems to work with at least AWS Inspector and Trivy, but probably works with others as well. They are not so complex beasts, after all.&lt;&#x2F;p&gt;
&lt;p&gt;As &lt;code&gt;FROM scratch&lt;&#x2F;code&gt; images become more popular, it&#x27;s likely that the need for such workarounds will diminish, but for now, this is the way to go. Also, hopefully tools like &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-secure-code&#x2F;cargo-auditable&quot;&gt;&lt;code&gt;cargo-auditable&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; will remove the need to include &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt; separately as the same (and better) information is included directly in the produced binaries.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Redis + Strong consistency = AWS MemoryDB</title>
		<published>2024-08-16T00:00:00+00:00</published>
		<updated>2024-08-16T00:00:00+00:00</updated>
		<link href="https://nakedible.org/articles/memorydb/" type="text/html"/>
		<id>https://nakedible.org/articles/memorydb/</id>
		<content type="html">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;nakedible.org&#x2F;articles&#x2F;memorydb&#x2F;title.png&quot; alt=&quot;Redis + Strong consistency = AWS MemoryDB&quot; title=&quot;Redis + Strong consistency = AWS MemoryDB&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;&quot;&gt;Redis&lt;&#x2F;a&gt; (or the fully open source &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;valkey.io&#x2F;&quot;&gt;Valkey&lt;&#x2F;a&gt;) is a very versatile and fast in-memory data store. Read operations take microseconds, and write operations generally take 1 millisecond. It started as a very simple key-value database, but it has picked a huge number of commands and features over the years. There&#x27;s plenty of different datatypes, including &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;docs&#x2F;latest&#x2F;develop&#x2F;data-types&#x2F;streams&#x2F;&quot;&gt;streams&lt;&#x2F;a&gt; and &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;docs&#x2F;latest&#x2F;develop&#x2F;data-types&#x2F;json&#x2F;&quot;&gt;JSON&lt;&#x2F;a&gt;. There&#x27;s also &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;docs&#x2F;latest&#x2F;develop&#x2F;interact&#x2F;programmability&#x2F;functions-intro&#x2F;&quot;&gt;embedded functions written in Lua&lt;&#x2F;a&gt;, which are fully transactionally isolated, so it&#x27;s possible to do complex transactional operations with the minimum of data transfer. It&#x27;s no longer just a cache, it&#x27;s a full blown database that is suitable for a wide variety of uses. In short, Redis is an extremely useful tool for any fast state you might require that cannot be locally stored in memory.&lt;&#x2F;p&gt;
&lt;p&gt;The biggest drawback for using Redis for anything else than a simple in-memory cache is that it doesn&#x27;t provide strong consistency. Under normal operation, when a master is stable, Redis is very consistent as everything happens just in-memory and it is single threaded. But everything being in-memory means that Redis will acknowledge successful writes before they have been persisted anywhere, which means that if the master goes down, you might lose just written data. Redis can be configured with a number of different options for persistence, but most commonly it writes to an AOF (append-only-file) once per second.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;nakedible.org&#x2F;articles&#x2F;memorydb&#x2F;redis-flow.png&quot; alt=&quot;Redis flow&quot; title=&quot;Redis flow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To combat this, there is the &lt;code&gt;WAIT&lt;&#x2F;code&gt; command which can be used to wait until a certain number of replicas have acknowledged the write, which means that at least it&#x27;s not up to just a single machine anymore. This doesn&#x27;t mean that it would be synchronized to disk necessarily (for example if all replicas lose power &#x2F; crash at the same time), but if they are independent enough it&#x27;s quite likely that at least one replica will persist the data to disk. But even then there are no actual guarantees that a replica being promoted would have all the data. It tries to promote the replica with the most up-to-date state, but given that failure scenarios are often quite complex, it&#x27;s still just best-effort consistency. In the worst case, a replica could be promoted that doesn&#x27;t have &lt;em&gt;any&lt;&#x2F;em&gt; data, so the whole database would start from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;nakedible.org&#x2F;articles&#x2F;memorydb&#x2F;redis-wait-flow.png&quot; alt=&quot;Redis + wait flow&quot; title=&quot;Redis + wait flow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But what if I told you there&#x27;s an alternative (if you are on AWS)...&lt;&#x2F;p&gt;
&lt;h2 id=&quot;aws-memorydb&quot;&gt;AWS MemoryDB&lt;a class=&quot;zola-anchor&quot; href=&quot;#aws-memorydb&quot; aria-label=&quot;Anchor link for: aws-memorydb&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;MemoryDB is a Redis OSS compatible database that claims to offer both durability and strong consistency, while keeping the fast in-memory performance of Redis. That sounds almost too good to be true. &lt;em&gt;So how do they do it? Is it really compatible with Redis? What are the guarantees they provide?&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;consistency&quot;&gt;Consistency&lt;a class=&quot;zola-anchor&quot; href=&quot;#consistency&quot; aria-label=&quot;Anchor link for: consistency&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;What MemoryDB promises is that the moment a write operation completes, that write operation is durably persisted (eleven nines, just like S3) and strongly consistent, meaning that no operation will be run on the database without that change present. This means it acts like any ACID database with SERIALIZABLE isolation level - that&#x27;s everything one could hope for. This is exactly the same as Redis offers by default if the master is stable. It&#x27;s still single threaded.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;speed&quot;&gt;Speed&lt;a class=&quot;zola-anchor&quot; href=&quot;#speed&quot; aria-label=&quot;Anchor link for: speed&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;em&gt;But it can&#x27;t do that while keeping the exact same speed that Redis has, can it?&lt;&#x2F;em&gt; Well, no, obviously it can&#x27;t, since Redis answers immediately after something has been written to memory only on a single machine. But it can get surprisingly close.&lt;&#x2F;p&gt;
&lt;p&gt;First of all, read operations are not affected at all and exhibit the same performance as with Redis, as is to be expected. Well, to be exact, it&#x27;s even a bit better than Redis, but that&#x27;s due to Amazon optimizing the database for their hardware with some enhanced IO multiplexing. So the response times for both databases are in microseconds - this is an in-memory database after all.&lt;&#x2F;p&gt;
&lt;p&gt;Write operations in Redis will complete usually in 1-2 milliseconds. For MemoryDB that same figure is 3-5 milliseconds. That&#x27;s obviously slower, and a meaningful difference, but still quite a bit faster than what most databases can manage. It&#x27;s pretty much on par with a normal write + &lt;code&gt;WAIT&lt;&#x2F;code&gt;  for Redis. Especially if there&#x27;s any network latency in general, a couple more milliseconds will end up fully hidden in that latency.&lt;&#x2F;p&gt;
&lt;p&gt;This figure is also pretty much exactly the optimal result one can theoretically achieve, while keeping the same availability approach. In order for the data to be durably persisted, it needs to be stored in multiple data centers. In AWS this means multiple availability zones inside a region (an availability zone might actually have multiple data centers, close by, as they are huge). These availability zones are physically located at least a few kilometers away from each other and a maximum of 100 kilometers. The networking round-trip latency is less than 2 milliseconds, but can&#x27;t be instant, because the speed of light isn&#x27;t instant. Hence if Redis takes 1 millisecond to process the operation, and getting the same operation delivered to two other datacenters takes a 2 millisecond round-trip, we arrive exactly to the 3 millisecond minimum time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;nakedible.org&#x2F;articles&#x2F;memorydb&#x2F;region.png&quot; alt=&quot;AWS Region &#x2F; AZs&quot; title=&quot;AWS Region &#x2F; AZs&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;implementation&quot;&gt;Implementation&lt;a class=&quot;zola-anchor&quot; href=&quot;#implementation&quot; aria-label=&quot;Anchor link for: implementation&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;em&gt;So how do they do it?&lt;&#x2F;em&gt; They add a strongly consistent transactional log into the normal Redis codebase, where the implementation of the log is AWS secret sauce. The transactional log is placed after the normal in-memory operation for Redis, so once the operation is done, the results of the operation are committed into the log and will end up getting relayed to the replicas. And each replica will always ensure it has the entire log replayed before it can become a master.&lt;&#x2F;p&gt;
&lt;p&gt;But if this were done naively, then every write operation would end up taking three times as long because of the transactional log, which would reduce the throughput of the entire database similarily. But that doesn&#x27;t seem to be the case as total throughput, while less than a standard Redis installation, is quite close in the end. &lt;em&gt;That can&#x27;t be the way they do it, then.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;What they actually do is that they only delay the sending of the acknowledgement message after a command until they have received an acknowledgement from the transactional log. This means that the database itself can actually perform operations at full speed in the in-memory database, and only the clients are delayed until there is confirmation that the data has been durably persisted and will be strongly consistent. This is quite a cool idea, and it&#x27;s easy to reason about the performance knowing this.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;nakedible.org&#x2F;articles&#x2F;memorydb&#x2F;memorydb-flow.png&quot; alt=&quot;MemoryDB flow&quot; title=&quot;MemoryDB flow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;How do I know all this?&lt;&#x2F;em&gt; AWS published an actual &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;www.amazon.science&#x2F;publications&#x2F;amazon-memorydb-a-fast-and-durable-memory-first-cloud-database&quot;&gt;research paper on MemoryDB&lt;&#x2F;a&gt; where they explain all this. I&#x27;m just making it worse by explaining it in my own words.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;compatibility&quot;&gt;Compatibility&lt;a class=&quot;zola-anchor&quot; href=&quot;#compatibility&quot; aria-label=&quot;Anchor link for: compatibility&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Everything else about Redis stays the same, as everything is first executed in-memory and then written to the transaction log. So we know it supports all the same stuff as Redis does without having to reimplement everything and keeping all the quirks that we&#x27;ve already gotten used to (unliked some... krhm... &lt;a rel=&quot;nofollow noreferrer&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;documentdb&#x2F;latest&#x2F;developerguide&#x2F;compatibility.html&quot;&gt;DocumentDB&lt;&#x2F;a&gt;). And they&#x27;ve added the same JSON support that&#x27;s currently part of the not-so-free Redis offering.&lt;&#x2F;p&gt;
&lt;p&gt;Replication in Redis is also a very standard feature and thoroughly battle tested. This solution just improves the behaviour around the replication stream, but the actual data elements are the very same. So we don&#x27;t have to worry about new quirks around randomness or functions or other similar things appearing, but those will still be just like they are in Redis.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cost&quot;&gt;Cost&lt;a class=&quot;zola-anchor&quot; href=&quot;#cost&quot; aria-label=&quot;Anchor link for: cost&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;AWS offers the standard Redis as well, under the name Amazon ElastiCache with Redis OSS compatibility. This makes it easy to compare the prices of that against MemoryDB. Comparing the costs for on-demand instances, they seem to be charging around 40-50% more for the same instance types. Given that Redis costs are usually quite low compared to the value they bring, that isn&#x27;t too bad.&lt;&#x2F;p&gt;
&lt;p&gt;In addition to that, there&#x27;s a cost of $0.20 &#x2F; GB for data written. This is obviously to cover the cost of the transactional log. Usually Redis is read heavy, and the written data elements are quite small. Evaluate your use case thoroughly, but I would wager a guess that this cost is also not prohibitively expensive.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;a class=&quot;zola-anchor&quot; href=&quot;#summary&quot; aria-label=&quot;Anchor link for: summary&quot; style=&quot;visibility: hidden;&quot;&gt;#&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Overall, this gives a very interesting new choice for development. Usually anything that requires strong consistency has had to have been implemented with DynamoDB, or some other real database. And that has a cost per transaction that may get significant. Anything read heavy, or more ephemeral, would then be handled by Redis where the cost would simply be based on the dataset size (the memory size of the nodes).&lt;&#x2F;p&gt;
&lt;p&gt;MemoryDB allows us to get pricing that&#x27;s based on the dataset size, and not the amount of operations, for a strongly consistent database. A database that is in-memory speed for reads and nearly in-memory speed for writes. I&#x27;m betting that&#x27;s quite an interesting proposition for a number of developers.&lt;&#x2F;p&gt;
&lt;p&gt;Now if they would just add cross-region replication...&lt;&#x2F;p&gt;
&lt;!--- Eraser file: https:&#x2F;&#x2F;app.eraser.io&#x2F;workspace&#x2F;KoSBBMGFSpKInsUpJSNv ---&gt;
</content>
	</entry>
</feed>