<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sunny Sahijwani</title>
    <description>The latest articles on DEV Community by Sunny Sahijwani (@sunnysahijwani).</description>
    <link>https://dev.to/sunnysahijwani</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4113627%2Fe9cf5601-7192-4c02-b2da-677dcb9c73c2.png</url>
      <title>DEV Community: Sunny Sahijwani</title>
      <link>https://dev.to/sunnysahijwani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sunnysahijwani"/>
    <language>en</language>
    <item>
      <title>I benchmarked Dragonfly vs Redis vs Valkey. First, let me show you how I kept it honest.</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Mon, 07 Sep 2026 10:22:27 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/i-benchmarked-dragonfly-vs-redis-vs-valkey-first-let-me-show-you-how-i-kept-it-honest-15oo</link>
      <guid>https://dev.to/sunnysahijwani/i-benchmarked-dragonfly-vs-redis-vs-valkey-first-let-me-show-you-how-i-kept-it-honest-15oo</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 1 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-valkey-benchmark" rel="noopener noreferrer"&gt;Series index&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-throughput-scaling" rel="noopener noreferrer"&gt;Next: Throughput &amp;amp; scaling →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 1 of a series. This post has no throughput charts — on purpose. Before I show you a single number, I want to show you the traps that make most in-memory benchmarks lie, and exactly how I disarmed each one. If the method isn't fair, the numbers aren't worth reading.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The uncomfortable truth about database benchmarks
&lt;/h2&gt;

&lt;p&gt;Almost every "X is 25× faster than Y" benchmark you've seen is rigged — usually not maliciously, just carelessly. The author picks a setup that happens to flatter one engine, runs it once, and publishes the number. The most common way it goes wrong is subtle: &lt;strong&gt;the benchmark measures the client, or the virtual machine, or a cache-miss path — anything except the database it claims to measure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So when the DragonflyDB team reached out with notes on how to benchmark Dragonfly fairly, I took it as a challenge to do this properly — and to publish my methodology &lt;em&gt;before&lt;/em&gt; my results, so you can attack the method first.&lt;/p&gt;

&lt;p&gt;Here's the question I'm actually answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On one server, using all its CPU cores, how much load can each engine sustain — and what does each one cost you in operational complexity to get there?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That "one server, all cores" framing matters, and it's where the first trap hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 1: "single process vs single process" is not the fair fight
&lt;/h2&gt;

&lt;p&gt;Redis executes commands on &lt;strong&gt;one thread&lt;/strong&gt;. Since Redis 6 it can use extra threads for network I/O, but the actual data work — the part that matters — still runs on a single core. Valkey is the same shape. Dragonfly, by contrast, is &lt;strong&gt;multi-threaded by design&lt;/strong&gt;: one process that uses every core you give it.&lt;/p&gt;

&lt;p&gt;So if I put one Dragonfly process (48 cores) next to one Redis process (1 core of real work) and shout "Dragonfly wins!", I've told you nothing except that 48 &amp;gt; 1. That's the rigged benchmark I refuse to publish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To use all the cores of one box, Redis has to be &lt;em&gt;sharded&lt;/em&gt; into a cluster&lt;/strong&gt; — many &lt;code&gt;redis-server&lt;/code&gt; processes on the same machine, each owning a slice of the keyspace. So the fair fight is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;one Dragonfly process&lt;/strong&gt; (uses the whole box), versus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;a Redis/Valkey cluster of N shards&lt;/strong&gt; (also uses the whole box).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same machine. Same cores. Same goal: saturate the node. That's apples to apples. Single-process Redis stays in my tests only as a &lt;em&gt;labelled baseline&lt;/em&gt;, never as the comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: how many shards? (the small version of the big trap)
&lt;/h2&gt;

&lt;p&gt;Once you agree the fair peer is a Redis cluster, a new question appears: &lt;strong&gt;how many shards?&lt;/strong&gt; Too few and Redis can't use the cores; too many and they thrash. Rather than guess, I swept shard counts — and one shard per physical core turned out to be about right on this hardware.&lt;/p&gt;

&lt;p&gt;That was the &lt;em&gt;easy&lt;/em&gt; version. The nasty one was realising I couldn't even &lt;strong&gt;drive the cluster hard enough to measure it.&lt;/strong&gt; To read any database's true speed, you have to push enough load that the &lt;em&gt;database&lt;/em&gt; is the bottleneck — not your test tool. But a cluster is many independent shards, and to see its real ceiling you have to keep every shard busy at once. A normal load tool routes every request from a single process, and that routing work pins the tool's &lt;em&gt;own&lt;/em&gt; CPU long before the shards are busy — so my first cluster runs measured my load generator, not the cluster, with the shards sitting ~16% idle. That's the trap that nearly wrecked the whole benchmark, and it gets &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;its own post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 3: the client was the real bottleneck
&lt;/h2&gt;

&lt;p&gt;This is the big one, and it's why so many benchmarks are worthless.&lt;/p&gt;

&lt;p&gt;A benchmark has &lt;em&gt;two&lt;/em&gt; machines doing work: the database (server) and the load generator (client). Throughput is capped by whichever runs out of CPU &lt;strong&gt;first&lt;/strong&gt;. If the client maxes out before the server does, then every engine flatlines at the &lt;em&gt;client's&lt;/em&gt; ceiling — and they all look identical, even if one is twice as fast. You've benchmarked your load generator.&lt;/p&gt;

&lt;p&gt;The fix: make the client &lt;strong&gt;more powerful than the server&lt;/strong&gt;, put it on a &lt;strong&gt;separate machine&lt;/strong&gt;, and then &lt;em&gt;prove&lt;/em&gt; it wasn't the limit. My harness ramps the load up step by step and records &lt;strong&gt;both&lt;/strong&gt; server and client CPU. At the throughput plateau I check who's saturated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;server maxed, client has headroom&lt;/strong&gt; → server-bound → the number is real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client maxed, server has headroom&lt;/strong&gt; → client-bound → throw it out, get a bigger client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I can't show you which side was the ceiling, I won't show you the number.&lt;/p&gt;

&lt;p&gt;And here's where it got genuinely surprising. Even with a big, separate client, a &lt;em&gt;single&lt;/em&gt; cluster-mode client couldn't saturate a 48-shard cluster — the shards sat ~16% busy while the client thrashed on routing. I was about to report &lt;strong&gt;~4.6M ops/s&lt;/strong&gt; as "the cluster's throughput" when the same cluster, driven correctly (one dedicated client per shard), does &lt;strong&gt;~40M&lt;/strong&gt;. Reporting either number alone is a lie: 4.6M sandbags Redis; 40M pretends everyone routes perfectly with unlimited client hardware.&lt;/p&gt;

&lt;p&gt;So I report the cluster &lt;strong&gt;two ways&lt;/strong&gt; — what a normal client actually gets, &lt;em&gt;and&lt;/em&gt; the fully-driven ceiling — and let you see the gap. That gap turned out to be the most interesting finding in the whole study, and it gets &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;its own post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 4: I was benchmarking cache &lt;em&gt;misses&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;When I first ran it, ~72% of my reads were &lt;strong&gt;misses&lt;/strong&gt; — I was reading random keys from a database that was mostly empty, so I was measuring the "key not found" path, which is cheaper and meaningless. Fixed by &lt;strong&gt;pre-loading the full keyspace&lt;/strong&gt; before every read-heavy run. Now every run reports its hit rate, and I only trust runs where reads actually hit real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the engines truly identical
&lt;/h2&gt;

&lt;p&gt;Small asymmetries add up, so every engine gets the exact same treatment: persistence off, identical memory limit, no eviction, same value sizes, same request mix, same pipeline depths. Where one engine needed a special flag, I applied the equivalent to all three rather than to one. And I made each engine's "no persistence, no eviction" settings &lt;strong&gt;explicit&lt;/strong&gt; — verified from the docs, not assumed from defaults. Every knob a run used is recorded in the results row, so any number is fully reproducible.&lt;/p&gt;

&lt;p&gt;A concrete example of that care, because it changed the numbers: on a fresh cloud box, Docker's default security profile silently blocked Dragonfly's &lt;strong&gt;&lt;code&gt;io_uring&lt;/code&gt;&lt;/strong&gt; and dropped it to a slower I/O path — which would have quietly handicapped Dragonfly. I gave it back its native &lt;code&gt;io_uring&lt;/code&gt; (Redis and Valkey use their native &lt;code&gt;epoll&lt;/code&gt;), so &lt;strong&gt;each engine runs on its own best path&lt;/strong&gt; rather than one being crippled by an environment quirk. That's the difference between a fair test and an accidental hit piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honesty guard I'll hold all series long
&lt;/h2&gt;

&lt;p&gt;It would be easy — and wrong — to claim "Dragonfly has no sharding limitations." It does. &lt;strong&gt;Dragonfly Cluster, once you scale it across machines, uses the same 16,384-hash-slot model as Redis and hits the same limits.&lt;/strong&gt; Dragonfly's real advantage is narrower and more honest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;To make Redis use your whole server you must cluster it, and clustering costs you cross-key freedom. Dragonfly gives you the cores without that cost — so you postpone clustering until you truly outgrow one big machine.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not "never." &lt;em&gt;Later.&lt;/em&gt; That's the claim, and I'll keep it precise even when a punchier overstatement would get more clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one laptop can and can't tell you
&lt;/h2&gt;

&lt;p&gt;I built and validated the whole harness on a laptop — but I won't publish speed numbers from it, and you should distrust anyone who does. Under a virtual machine, CPU pinning isn't real, cores aren't uniform, and the hypervisor injects timing noise. My own ramp test proved it: at the throughput plateau, &lt;em&gt;neither&lt;/em&gt; the server nor the client was CPU-saturated — the ceiling was the virtualization layer itself.&lt;/p&gt;

&lt;p&gt;So the split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware-independent results&lt;/strong&gt; (how the engines &lt;em&gt;behave&lt;/em&gt; — cross-slot rules, memory-per-key) I can and will show from anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance results&lt;/strong&gt; (throughput, latency, scaling) come only from &lt;strong&gt;bare metal&lt;/strong&gt;: an AWS &lt;code&gt;c7i.metal-24xl&lt;/code&gt; server (48 physical Sapphire Rapids cores, no hypervisor), a separate over-provisioned &lt;code&gt;c7i.24xlarge&lt;/code&gt; client, same availability zone and placement group — real cores, real pinning, a network I control. That's the setup the next posts run on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's coming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Throughput per node&lt;/strong&gt; — the fair fight, across read/write mixes and pipeline depths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core scaling&lt;/strong&gt; — what happens from 4 to 48 cores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency under load&lt;/strong&gt; — the tail (p99, p99.9), not just averages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory efficiency&lt;/strong&gt; — bytes per key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational simplicity&lt;/strong&gt; — cross-slot, hash tags, Lua and transactions: the real cost of a cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use which&lt;/strong&gt; — a decision guide, because (spoiler) there is no universal winner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full harness — scripts, pinned versions, raw results — is public at &lt;a href="https://github.com/sunnysahijwani/dragonfly-redis-valkey-benchmark" rel="noopener noreferrer"&gt;github.com/sunnysahijwani/dragonfly-redis-valkey-benchmark&lt;/a&gt;, so you can rerun everything and check my work. That's the whole point.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next: the throughput numbers, on real hardware, with the receipts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>performance</category>
      <category>benchmarking</category>
    </item>
  </channel>
</rss>
