<?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>memdb-oracle: an agent that answers Redis-vs-Dragonfly questions only from measured data.</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Tue, 22 Sep 2026 17:31:52 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/memdb-oracle-an-agent-that-answers-redis-vs-dragonfly-questions-only-from-measured-data-26dl</link>
      <guid>https://dev.to/sunnysahijwani/memdb-oracle-an-agent-that-answers-redis-vs-dragonfly-questions-only-from-measured-data-26dl</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Choosing between Redis, Valkey, Dragonfly, KeyDB and Garnet is a decision you cannot afford to get wrong, and the loudest sources are vendor pages. Earlier this month I published an independent benchmark of these engines on 48 bare-metal cores, with the harness, pinned versions and raw data public. &lt;strong&gt;memdb-oracle&lt;/strong&gt; is an agent that answers questions from that data. When a vendor's page says one thing and the measurement says another, it shows both, with sources, and tells you which one was measured.&lt;/p&gt;

&lt;p&gt;Why structured content is the whole point: "at 16 cores, read-heavy, pipeline 16, which engine has the lower p99?" is not a search. It is a query over 478 run documents, each with every knob that moved the number as a field: engine, mode, cores, read/write ratio, value size, pipeline depth, repetition, throughput, p50/p99/p99.9, hit rate, session. A keyword search over my blog posts returns a paragraph. GROQ returns two medians and the run ids behind them.&lt;/p&gt;

&lt;p&gt;Three things it does that I could not get from a search box:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Numbers come only from GROQ, prose only from the Knowledge Base.&lt;/strong&gt; Every answer ends with the exact GROQ it ran and the &lt;code&gt;_id&lt;/code&gt;s of the runs it used, so anyone can verify it against the public dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor claim and measurement side by side.&lt;/strong&gt; Ask "Is Dragonfly 25x faster than Redis?" and it reads Dragonfly's own claim from the Knowledge Base, runs the queries, and answers: not on this hardware, 3.4× over a realistic Redis cluster and 38% of the fully driven ceiling, and here is why the vendor's single-process comparison is a different test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It refuses to invent.&lt;/strong&gt; Ask for a combination that was never measured and it says so and lists the nearest real rows. Ask about Postgres and it declines in one paragraph.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also explains terms for people new to benchmarking, from the benchmark glossary in the Knowledge Base, and marks anything it explains from general knowledge as exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live: &lt;a href="https://memdb.two-techies.com" rel="noopener noreferrer"&gt;https://memdb.two-techies.com&lt;/a&gt;&lt;/strong&gt; — no login. Example questions are pre-loaded as chips. Each answer shows the Sanity tool calls it made and ends with the GROQ it ran. A turn takes 30 to 60 seconds because it is five to ten sequential Context MCP calls plus model reasoning; the page streams tool activity while it runs. Limit: 30 questions per IP per day.&lt;/p&gt;

&lt;p&gt;Three curated answers from the session log (full versions with every tool call in &lt;a href="https://github.com/sunnysahijwani/memdb-oracle/blob/main/docs/TRANSCRIPTS.md" rel="noopener noreferrer"&gt;docs/TRANSCRIPTS.md&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is Dragonfly 25x faster than Redis?"&lt;/strong&gt; — 7 GROQ queries and 1 Knowledge Base read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Short answer: not on this hardware, in this benchmark. Measured, the gap is about 3.4× — and it flips the other way against a fully-driven Redis cluster.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Vendor statement (context, not measurement): Dragonfly's own marketing claims "25× higher throughput than Redis", backed by their AWS c6gn.16xlarge figures of ~3.97M QPS versus ~130k QPS on GET (KB entry &lt;em&gt;Dragonfly Self-Reported Performance Claims&lt;/em&gt;). That ~130k/s figure is a single Redis process, one core doing the data work, versus a Dragonfly process using the whole machine.&lt;/p&gt;

&lt;p&gt;Measured, this dataset (ground truth here). Session 1, AWS c7i.metal-24xl, 48 cores, read-heavy SET:GET 1:10, 100 B values, pipeline 16, hit rate 1.0, median across reps:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Throughput (median)&lt;/th&gt;
&lt;th&gt;vs Dragonfly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly, 1 process (&lt;code&gt;single&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.5 M ops/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster, 48 shards, &lt;code&gt;cluster&lt;/code&gt; (realistic: one normal client)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.6 M ops/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dragonfly 3.4× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster, 48 shards, &lt;code&gt;cluster-sat&lt;/code&gt; (driven ceiling, one client per shard)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40.6 M ops/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Redis 2.6× faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why the claim and the measurement differ: the 25× comparison is one Dragonfly process against one Redis process. The KB entry &lt;em&gt;Common Benchmark Pitfalls&lt;/em&gt; calls this "not a fair fight": Redis executes commands on one thread, so the like-for-like peer is a Redis Cluster with one shard per physical core. This dataset contains no single-process Redis runs, so the vendor's exact comparison cannot be reproduced here.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plain words: on the test machine here, Dragonfly served about three and a half times as many requests per second as a normal Redis cluster, and about a third as many as a Redis cluster pushed as hard as the hardware allows — nowhere near twenty-five times either way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Give me the Redis single-process result at 8 cores with pipeline 64."&lt;/strong&gt; — never measured.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;No matching run exists.&lt;/em&gt; Two parts of the request are not in the dataset: pipeline 64 was never measured (the only depths are 1 and 16), and Redis has no &lt;code&gt;single&lt;/code&gt;-mode runs; it appears only as &lt;code&gt;cluster&lt;/code&gt; and &lt;code&gt;cluster-sat&lt;/code&gt;, because one Redis process cannot use more than one core for command execution. Nearest measured combinations, Redis at 8 cores, pipeline 16, 100 B: cluster (realistic) 5.0 M ops/s with p99 35.2 ms; cluster-sat (driven ceiling) 9.2 M ops/s with p99 0.78 ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Which is better for my Postgres workload, MySQL or Postgres?"&lt;/strong&gt; — out of scope, zero tool calls.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That question is outside what I can answer.&lt;/em&gt; This dataset only covers in-memory data stores (Redis, Valkey, Dragonfly, KeyDB, Garnet) and their benchmark runs, not relational databases. What it can help with instead: throughput, p50/p99/p99.9 latency, hit rate, memory per key, and scaling behaviour across cores, value sizes, pipeline depths and read/write ratios for those five engines, plus the vendor claims and glossary definitions behind them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sunnysahijwani/memdb-oracle" rel="noopener noreferrer"&gt;https://github.com/sunnysahijwani/memdb-oracle&lt;/a&gt;&lt;/strong&gt; (MIT)&lt;/p&gt;

&lt;p&gt;The whole agent is one file plus a system prompt. It uses the Claude API's MCP connector, so Sanity's Context MCP is called server-side and there is no MCP client code at all: one API call per turn, streamed to the page over server-sent events. The repo also holds the schema, the CSV importer, the golden-number script and 13 tests (6 offline, 7 live against the real endpoints).&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Schema.&lt;/strong&gt; Four document types in &lt;code&gt;sanity/schemaTypes/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;benchmarkRun&lt;/code&gt; — one document per measured run, 30 fields in four fieldsets: identity and provenance (session, runId, the source CSV and line number, so every number is traceable), server configuration (mode, shards, coresUsed, cpuset), workload (ratio, dataBytes, pipeline, client threads and connections) and results (opsPerSec, p50/p99/p99.9, hitRate). Deterministic &lt;code&gt;_id&lt;/code&gt;s, so re-import is idempotent; I ran it twice and the count stayed at 497.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;engine&lt;/code&gt; — version pinned in the harness, threading model, configuration notes the agent must repeat, and the vendor page whose claims the Knowledge Base compares against.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finding&lt;/code&gt; — a headline number with its caveat, its metrics, and the verbatim vendor claim it is usually compared to.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;benchmarkSession&lt;/code&gt; — because two capture sessions on different dates must never be compared silently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Import.&lt;/strong&gt; CSV → NDJSON → &lt;code&gt;sanity dataset import --replace&lt;/code&gt;. The importer keeps only the bare-metal capture rows (350 from the Dragonfly/Redis/Valkey session, 128 from the KeyDB/Garnet session), validates every field, and drops 147 smoke, laptop and diagnostic rows that were never measurements. A grep-based leak test makes sure a private client experiment I have not published never enters the dataset or the Knowledge Base.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge Base.&lt;/strong&gt; Sources: the nine write-up pages (Website), the benchmark glossary, measurement playbook and findings (Files), the &lt;code&gt;finding&lt;/code&gt;/&lt;code&gt;engine&lt;/code&gt;/&lt;code&gt;benchmarkSession&lt;/code&gt; documents (Dataset — deliberately not the 478 runs, those are for GROQ), and the vendor pages: Dragonfly's homepage and comparison pages, the Redis benchmarks page, KeyDB's docs landing page and Garnet's published results.&lt;/p&gt;

&lt;p&gt;The first build raised eight Issues. Four were thin entries it offered to merge; I kept them standalone. The other four were &lt;strong&gt;Conflicts, marked Critical, that I had not told it to look for&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dragonfly's "25× more QPS than Redis" vs the measured 3.4× over a realistic cluster.&lt;/li&gt;
&lt;li&gt;Dragonfly's "30–60% better memory" vs the measured 12–13% (146 vs 165 bytes/key).&lt;/li&gt;
&lt;li&gt;Garnet "higher throughput than Dragonfly" vs my note that Garnet's 21.4M is a client-limited floor from a different session and a different load generator.&lt;/li&gt;
&lt;li&gt;Dragonfly's "sub-1 ms p99" vs the measured 2.42 ms at pipeline 16 and 0.55 ms without pipelining.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each resolution became an Instruction, a standing rule tied to the sources. When I then clicked &lt;strong&gt;Update entries&lt;/strong&gt;, the build re-read the vendor pages against those rules, flagged four of them as contradicting a rule, and rewrote them as "Quality fix" issues. Two of the four rules now show as Archived, because the entries they governed were rewritten and carry the decision themselves; I checked afterwards and the agent still answers the memory question with the measured figure as ground truth and the vendor figure as context. The re-read also raised four issues I dismissed: one about Dragonfly Cloud pricing, which is out of scope, and three where the build read two vendors' claims about different Dragonfly versions, or Dragonfly's 15.5M next to Garnet's 21.4M, as contradictions. They are different engines and different setups, and both numbers are true. Conflict detection is good at finding real disagreements; it still needs a human to say when two statements are simply about different things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficvn510rcdhys22gp95x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ficvn510rcdhys22gp95x.png" alt="Sanity Context Issues page, Resolved tab: four Critical conflicts between vendor claims and measurements, plus four Quality fix rewrites after the rules were applied" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh21k4l4hniaxzk3y2ot8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh21k4l4hniaxzk3y2ot8.png" alt="Sanity Context Instructions page: four standing rules, each created by resolving an issue" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the mechanism the agent leans on when it says "measured is ground truth here, the vendor figure describes their own setup".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context MCP.&lt;/strong&gt; Two endpoints, same organisation token: &lt;code&gt;memdb&lt;/code&gt; serves the dataset (tools &lt;code&gt;initial_context&lt;/code&gt;, &lt;code&gt;schema_explorer&lt;/code&gt;, &lt;code&gt;groq_query&lt;/code&gt;, &lt;code&gt;array_field_reader&lt;/code&gt;) and &lt;code&gt;memdb-kb&lt;/code&gt; serves the Knowledge Base (&lt;code&gt;initial_context&lt;/code&gt;, &lt;code&gt;knowledge_base_read&lt;/code&gt;). The system prompt forbids numbers from anywhere but &lt;code&gt;groq_query&lt;/code&gt; and prose from anywhere but &lt;code&gt;knowledge_base_read&lt;/code&gt;. Both endpoints carry the same custom instructions: cite the entry or run ids, measured data is ground truth, Garnet is client-limited, sessions are not comparable without saying so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What went wrong, honestly.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My first Dragonfly crawl pulled 492 pages and blew straight through the plan's 150-indexed-document cap (634 of 150). The fix was better than the original: I re-added only the homepage bucket and the five comparison pages, because the claims I contradict live on exactly those pages.&lt;/li&gt;
&lt;li&gt;I wanted one endpoint with both sources, which the docs suggest is the common setup. In the editor, only the last saved source survived a refresh: attach the Knowledge Base and the dataset dropped off, and GROQ mode then failed with a misleading "deploy a Studio v5.1.0+" error while the Studio was deployed and the schema listed. Two endpoints, one source each, fixed it in ten minutes.&lt;/li&gt;
&lt;li&gt;The model narrated "I'll start by loading both sources" before its first tool call and that leaked into the chat. Fix: the final answer is the text after the last tool call, plus a one-line rule in the prompt.&lt;/li&gt;
&lt;li&gt;After deploying, the Ask button reloaded the page with &lt;code&gt;/?&lt;/code&gt; instead of asking. A patch to the chat page had spliced the script at the wrong &lt;code&gt;&amp;lt;/script&amp;gt;&lt;/code&gt; and duplicated it, so the browser threw "Identifier 'examples' has already been declared" before the submit handler was attached. Headless Chrome and an on-page error banner found it in minutes; the lesson is that curl-testing the API is not testing the page.&lt;/li&gt;
&lt;li&gt;Turns are slow, 30 to 60 seconds, because each is a chain of sequential MCP round trips through the API plus reasoning. Streaming the tool activity to the page made that acceptable; it did not make it fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project ID: &lt;code&gt;ynsa3nyp&lt;/code&gt;, dataset &lt;code&gt;production&lt;/code&gt; (public). Try it without a token: &lt;a href="https://ynsa3nyp.api.sanity.io/v2025-08-15/data/query/production?query=count(*%5B_type%3D%3D%22benchmarkRun%22%5D)" rel="noopener noreferrer"&gt;https://ynsa3nyp.api.sanity.io/v2025-08-15/data/query/production?query=count(*%5B_type%3D%3D%22benchmarkRun%22%5D)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browse the dataset, no login: &lt;a href="https://memdb.two-techies.com/dataset" rel="noopener noreferrer"&gt;https://memdb.two-techies.com/dataset&lt;/a&gt; (reads the public API live; raw overview query: &lt;a href="https://ynsa3nyp.api.sanity.io/v2025-08-15/data/query/production?query=%7B%22runs%22%3Acount%28%2A%5B_type%3D%3D%22benchmarkRun%22%5D%29%2C%22runsPerEngine%22%3A%2A%5B_type%3D%3D%22engine%22%5D%7Bname%2CversionTested%2C%22runs%22%3Acount%28%2A%5B_type%3D%3D%22benchmarkRun%22%26%26references%28%5E._id%29%5D%29%7D%2C%22findings%22%3A%2A%5B_type%3D%3D%22finding%22%5D%7Corder%28slug%20asc%29%7Btitle%7D%7D" rel="noopener noreferrer"&gt;https://ynsa3nyp.api.sanity.io/v2025-08-15/data/query/production?query=%7B%22runs%22%3Acount%28%2A%5B_type%3D%3D%22benchmarkRun%22%5D%29%2C%22runsPerEngine%22%3A%2A%5B_type%3D%3D%22engine%22%5D%7Bname%2CversionTested%2C%22runs%22%3Acount%28%2A%5B_type%3D%3D%22benchmarkRun%22%26%26references%28%5E._id%29%5D%29%7D%2C%22findings%22%3A%2A%5B_type%3D%3D%22finding%22%5D%7Corder%28slug%20asc%29%7Btitle%7D%7D&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Studio (members only, as any Studio): &lt;a href="https://memdb-oracle.sanity.studio/" rel="noopener noreferrer"&gt;https://memdb-oracle.sanity.studio/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Knowledge Base: &lt;code&gt;memdb-kb&lt;/code&gt;, served by endpoint &lt;code&gt;memdb-kb&lt;/code&gt;; dataset served by endpoint &lt;code&gt;memdb&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this with Claude Code, but I am not embedding the raw session: the transcript carries private working context from my machine that no redaction layer would catch. Instead, the agent's own sessions are public in the repo: &lt;a href="https://github.com/sunnysahijwani/memdb-oracle/blob/main/docs/TRANSCRIPTS.md" rel="noopener noreferrer"&gt;docs/TRANSCRIPTS.md&lt;/a&gt; holds seven verbatim answers with every Sanity Context tool call and the GROQ each one ran, straight from the agent's session log. The build itself is reproducible from the repo: &lt;code&gt;npm run import:build&lt;/code&gt; rebuilds the 497 documents from the public CSVs, &lt;code&gt;npm run golden&lt;/code&gt; recomputes the numbers the tests check against, and &lt;code&gt;npm test&lt;/code&gt; runs the 13 tests, 7 of them live against the endpoints.&lt;/p&gt;

</description>
      <category>sanitychallenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>redis</category>
    </item>
    <item>
      <title>How long does a Redis Sentinel failover actually take? I measured it</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Wed, 16 Sep 2026 11:38:32 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/how-long-does-a-redis-sentinel-failover-actually-take-i-measured-it-31ho</link>
      <guid>https://dev.to/sunnysahijwani/how-long-does-a-redis-sentinel-failover-actually-take-i-measured-it-31ho</guid>
      <description>&lt;p&gt;&lt;em&gt;I killed an in-memory database primary on purpose — fifteen times, across three &lt;code&gt;down-after-milliseconds&lt;/code&gt; settings and three engines (Redis 8, Valkey 8, Dragonfly) — and timed how long writes actually fail before a replica takes over. Short answer: **your failover window is roughly &lt;code&gt;down-after&lt;/code&gt; plus about two seconds of election and promotion — and it's the same no matter which engine is dying.&lt;/em&gt;* Here's the runnable lab, the numbers, and the traps that bite in production.*&lt;/p&gt;




&lt;h2&gt;
  
  
  The answer up front
&lt;/h2&gt;

&lt;p&gt;On a 1-primary / 2-replica / 3-sentinel setup (Redis 8, Docker), measured as the gap between killing the primary and the first successful write on the promoted replica:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;down-after-milliseconds&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;write outage (mean of 3 runs)&lt;/th&gt;
&lt;th&gt;individual runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1000 (1s)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.5s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.52 / 2.54 / 2.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5000 (5s, a common default)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.3s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.30 / 7.37 / 7.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10000 (10s)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.1s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12.09 / 12.12 / 12.09&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The relationship is almost boringly linear: &lt;strong&gt;outage ≈ &lt;code&gt;down-after&lt;/code&gt; + ~2s of quorum vote, leader election, and replica promotion.&lt;/strong&gt; That overhead is the fixed cost; &lt;code&gt;down-after&lt;/code&gt; is the dial you control. Nobody tunes it, and it's the single biggest factor in how long your app stares at a dead Redis.&lt;/p&gt;

&lt;p&gt;And before you ask whether a different engine would fail over faster: I reran the same kill against &lt;strong&gt;Valkey 8&lt;/strong&gt; and &lt;strong&gt;Dragonfly&lt;/strong&gt; under identical sentinels — at &lt;code&gt;down-after=5s&lt;/code&gt; all three came back in 7.3–7.4s. The outage belongs to Sentinel, not to the engine — full table further down.&lt;/p&gt;

&lt;p&gt;Everything below is reproducible — the lab, the kill script, and the sweep that produced this table are on GitHub: &lt;strong&gt;&lt;a href="https://github.com/sunnysahijwani/redis-sentinel-failover-lab" rel="noopener noreferrer"&gt;github.com/sunnysahijwani/redis-sentinel-failover-lab&lt;/a&gt;&lt;/strong&gt;. &lt;code&gt;docker compose up&lt;/code&gt; and kill the primary yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody notices until 3am
&lt;/h2&gt;

&lt;p&gt;Here's a setup I see constantly. A Laravel app scaled across several web servers — three, five, ten app instances behind a load balancer. Feels highly available. But every one of those app servers talks to &lt;strong&gt;one&lt;/strong&gt; Redis: cache, sessions, and the Horizon queue all live there.&lt;/p&gt;

&lt;p&gt;That single Redis is a single point of failure hiding in plain sight. When it dies — OOM, a kernel panic, someone reboots the wrong box — it doesn't take down &lt;em&gt;one&lt;/em&gt; app server. It takes down the shared state behind &lt;em&gt;all&lt;/em&gt; of them at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache&lt;/strong&gt; goes cold. Every request that used to hit Redis now hammers the database — a cache stampede right when you can least afford it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sessions&lt;/strong&gt; vanish. Everyone's logged out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The queue&lt;/strong&gt; stops. Horizon can't read or write jobs; background work stalls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding more app servers does nothing for this. The fix isn't more app servers — it's making Redis itself survive the loss of a node. That's what &lt;strong&gt;Redis Sentinel&lt;/strong&gt; does, and this article is a working lab that proves it — with a stopwatch on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The options, briefly
&lt;/h2&gt;

&lt;p&gt;There are three honest ways to get Redis high availability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Redis Sentinel&lt;/strong&gt; — you keep one primary and one or more replicas. A small quorum of "sentinel" processes watches the primary, and when it dies, they &lt;em&gt;vote&lt;/em&gt;, pick the healthiest replica, and promote it to primary automatically. Your clients get pointed at the new primary. This is the standard answer for an app that fits on one node's worth of RAM (i.e. most apps).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis Cluster&lt;/strong&gt; — shards your keyspace across multiple primaries, each with its own replica, with failover built in. You reach for this when your data or write throughput is too big for a single node. More moving parts — I've written about &lt;a href="https://two-techies.com/blog/redis-cluster-operational-simplicity" rel="noopener noreferrer"&gt;what cluster mode actually costs you operationally&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed (AWS ElastiCache, managed Valkey, etc.)&lt;/strong&gt; — someone else runs the failover for you. Great, until you want to understand what you're paying for — which is exactly why building it once yourself is worth an afternoon.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We're centering on &lt;strong&gt;Sentinel&lt;/strong&gt;, because it's the right tool for the non-sharded, multi-instance Laravel case, and because it's the one you can genuinely understand end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Sentinel works, in plain language
&lt;/h2&gt;

&lt;p&gt;Four ideas and you've got it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring.&lt;/strong&gt; Each sentinel pings the primary continuously. If the primary doesn't answer for &lt;code&gt;down-after-milliseconds&lt;/code&gt;, that sentinel &lt;em&gt;subjectively&lt;/em&gt; marks it down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quorum.&lt;/strong&gt; One sentinel's opinion isn't enough — that's how you'd get false alarms from a brief network blip. A configured number of sentinels (the &lt;em&gt;quorum&lt;/em&gt;) must agree the primary is down before anything happens. This is why you run an &lt;strong&gt;odd number&lt;/strong&gt; of them, typically 3 or 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Election &amp;amp; promotion.&lt;/strong&gt; Once quorum agrees, the sentinels elect a leader among themselves, the leader picks the most up-to-date replica, and promotes it with &lt;code&gt;REPLICAOF NO ONE&lt;/code&gt;. The other replicas are repointed at the new primary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client redirection.&lt;/strong&gt; Your app doesn't hardcode the primary's address. It &lt;em&gt;asks&lt;/em&gt; a sentinel "who is the master right now?" and connects to whatever it's told — so after a failover, it simply reconnects to the new one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the one people miss: &lt;strong&gt;if your client points at a fixed IP, none of this helps you.&lt;/strong&gt; The client has to be Sentinel-aware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lab: 1 primary + 2 replicas + 3 sentinels
&lt;/h2&gt;

&lt;p&gt;Everything below is self-contained Docker. It touches nothing else on your machine, exposes no host ports, and cleans up completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-primary&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --save "" --appendonly no --maxmemory-policy noeviction&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.10&lt;/span&gt;

  &lt;span class="na"&gt;redis-replica-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-replica-1&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --save "" --appendonly no --maxmemory-policy noeviction&lt;/span&gt;
             &lt;span class="s"&gt;--replicaof 172.28.0.10 6379 --replica-announce-ip 172.28.0.11&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;redis-primary&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.11&lt;/span&gt;

  &lt;span class="na"&gt;redis-replica-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-replica-2&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --save "" --appendonly no --maxmemory-policy noeviction&lt;/span&gt;
             &lt;span class="s"&gt;--replicaof 172.28.0.10 6379 --replica-announce-ip 172.28.0.12&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;redis-primary&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.12&lt;/span&gt;

  &lt;span class="na"&gt;redis-sentinel-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;sentinel&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:8&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-sentinel-1&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;printf 'port 26379\nsentinel monitor mymaster 172.28.0.10 6379 2\nsentinel down-after-milliseconds mymaster ${DOWN_AFTER:-5000}\nsentinel failover-timeout mymaster 10000\nsentinel parallel-syncs mymaster 1\n' &amp;gt; /etc/sentinel.conf&lt;/span&gt;
        &lt;span class="s"&gt;exec redis-sentinel /etc/sentinel.conf&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;redis-primary&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.21&lt;/span&gt;

  &lt;span class="na"&gt;redis-sentinel-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*sentinel&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-sentinel-2&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;172.28.0.22&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

  &lt;span class="na"&gt;redis-sentinel-3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*sentinel&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-sentinel-3&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;172.28.0.23&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redisnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;172.28.0.0/16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few deliberate choices worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static IPs.&lt;/strong&gt; Sentinel identifies nodes by address. Fixed IPs make the demo deterministic and sidestep container-DNS quirks during a failover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sentinel monitor mymaster 172.28.0.10 6379 2&lt;/code&gt;&lt;/strong&gt; — watch the primary; the trailing &lt;code&gt;2&lt;/code&gt; is the quorum (2 of 3 sentinels must agree).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;down-after-milliseconds&lt;/code&gt; is parameterised&lt;/strong&gt; (&lt;code&gt;DOWN_AFTER&lt;/code&gt;, default 5000) — that's the dial we're about to sweep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each sentinel writes its own config at boot.&lt;/strong&gt; Sentinel &lt;em&gt;rewrites&lt;/em&gt; its config file at runtime (to record replicas, epochs, etc.), so a shared read-only file breaks it — generating one per container in &lt;code&gt;/etc&lt;/code&gt; is the robust pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kill the primary and watch
&lt;/h3&gt;

&lt;p&gt;The repo's &lt;code&gt;failover-demo.sh&lt;/code&gt; starts the cluster, writes one key per second through &lt;em&gt;whatever Sentinel currently calls the master&lt;/em&gt;, then kills the primary. This is real output, not a mock-up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;💥 KILLING THE PRIMARY:  docker &lt;span class="nb"&gt;kill &lt;/span&gt;redis-primary
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;The write log across the whole event &lt;span class="o"&gt;(&lt;/span&gt;watch the master IP change&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="go"&gt;   [001] wrote to master 172.28.0.10  OK
   [002] wrote to master 172.28.0.10  OK
   [003] wrote to master 172.28.0.10  OK
   [004] wrote to master 172.28.0.10  OK
   [005] write FAILED  (no reachable master yet)
   [006] write FAILED  (no reachable master yet)
   [007] write FAILED  (no reachable master yet)
   [008] wrote to master 172.28.0.11  OK
   [009] wrote to master 172.28.0.11  OK
&lt;/span&gt;&lt;span class="c"&gt;   ...
&lt;/span&gt;&lt;span class="gp"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;New master &lt;span class="o"&gt;=&lt;/span&gt; 172.28.0.11 &lt;span class="p"&gt;;&lt;/span&gt; confirm it now reports role:master
&lt;span class="go"&gt;role:master
connected_slaves:1
demo:counter survived = 20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it top to bottom and you can see the entire incident: writes flowing to the original primary, a window of failures while the primary is dead and the sentinels are voting, then writes resuming against the promoted replica &lt;strong&gt;with no human involved&lt;/strong&gt; — and the data survived.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the dial nobody tunes
&lt;/h2&gt;

&lt;p&gt;The demo shows failover &lt;em&gt;works&lt;/em&gt;. The next question is the one that matters at 3am: &lt;strong&gt;how long is that window, and what controls it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The controlling config is &lt;code&gt;down-after-milliseconds&lt;/code&gt; — how long a sentinel waits for silence before declaring the primary dead. Almost every tutorial copies a value (usually 5000 or 30000) without saying what it costs. So I measured it: for each of 1s / 5s / 10s, I ran the kill three times and timed the outage a client actually observes — writes attempted roughly every 350ms through Sentinel discovery, outage = kill → first successful write on the promoted node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result: the outage tracks the dial almost perfectly — ~2.5s at &lt;code&gt;down-after=1s&lt;/code&gt;, ~7.3s at 5s, ~12.1s at 10s. Your window is &lt;code&gt;down-after&lt;/code&gt; plus a 1.5–2.3 second promotion tax.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit307ssk18cet8lrrezp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fit307ssk18cet8lrrezp.png" alt="Bar chart of measured Redis Sentinel failover time: ~2.5s write outage at down-after 1s, ~7.3s at 5s, ~12.1s at 10s" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three things worth reading off that chart:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The floor isn't zero.&lt;/strong&gt; Even at &lt;code&gt;down-after=1s&lt;/code&gt;, you eat ~2.5s of outage. Detection is only part of the window — quorum agreement, leader election among the sentinels, and the replica's promotion are a tax of roughly 1.5–2.3s in my runs that you pay regardless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The relationship is linear, so the trade-off is legible.&lt;/strong&gt; Every second you add to &lt;code&gt;down-after&lt;/code&gt; is a second added to every real outage — and every second you remove increases the odds a GC pause, a &lt;code&gt;fork()&lt;/code&gt; for persistence, or a brief network blip triggers a &lt;em&gt;false&lt;/em&gt; failover. That false-positive risk is why you shouldn't just set it to 500ms and declare victory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The variance is small.&lt;/strong&gt; Three runs per setting landed within a tenth of a second of each other (max spread 0.07s). This is a deterministic machine, not a dice roll — which means you can actually &lt;em&gt;plan&lt;/em&gt; around your configured window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Method honesty, so you can attack it:&lt;/strong&gt; this is Docker on a Mac, timed from the client side with ~0.1s of &lt;code&gt;docker exec&lt;/code&gt; overhead per write, and failed attempts time out at 2s — so treat sub-second digits as approximate. The &lt;em&gt;shape&lt;/em&gt; — fixed promotion overhead plus your &lt;code&gt;down-after&lt;/code&gt; — is the durable finding, and the sweep script is in the repo so you can produce this table on your own hardware in about ten minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the engine matter? Redis vs Valkey vs Dragonfly
&lt;/h2&gt;

&lt;p&gt;There's a tempting thought hiding in that fixed ~2s tax: &lt;em&gt;maybe a faster engine fails over faster.&lt;/em&gt; Valkey ships multithreaded I/O; Dragonfly is a from-scratch multithreaded rewrite that can be dramatically faster under load — I've &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-valkey-benchmark" rel="noopener noreferrer"&gt;benchmarked exactly that&lt;/a&gt;. Would either shave the outage window?&lt;/p&gt;

&lt;p&gt;So I ran the identical experiment against all three. Same topology, same kill, and — deliberately — the &lt;strong&gt;same three &lt;code&gt;redis:8&lt;/code&gt; sentinels every time&lt;/strong&gt;, so the only variable is the engine under them. (Valkey is a drop-in; Dragonfly officially supports Redis Sentinel as its HA mechanism.) Three runs each at &lt;code&gt;down-after=5s&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;engine&lt;/th&gt;
&lt;th&gt;write outage (3 runs)&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 8&lt;/td&gt;
&lt;td&gt;7.30 / 7.37 / 7.31&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.33s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valkey 8&lt;/td&gt;
&lt;td&gt;7.37 / 7.38 / 7.36&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.37s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly&lt;/td&gt;
&lt;td&gt;7.33 / 7.36 / 7.43&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.37s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncat47i2w1bfdhasbi1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncat47i2w1bfdhasbi1v.png" alt="Same sentinels, same kill, three engines — same outage: bar chart showing 7.33s for Redis 8, 7.37s for Valkey 8 and 7.37s for Dragonfly" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fifteen kills across three engines, all within a tenth of a second of each other. &lt;strong&gt;Failover time is a property of Sentinel, not of the engine.&lt;/strong&gt; Which makes sense once you've watched it happen: the window is detection silence (&lt;code&gt;down-after&lt;/code&gt;) plus sentinel politics (quorum, leader election) plus one &lt;code&gt;REPLICAOF NO ONE&lt;/code&gt; — the data engine is just the thing being pointed at. Switching to a faster engine won't buy back those seconds; tuning &lt;code&gt;down-after&lt;/code&gt; will.&lt;/p&gt;

&lt;p&gt;Two field notes from the non-Redis runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly's Sentinel compatibility is real.&lt;/strong&gt; Unmodified Redis sentinels discovered the Dragonfly primary and replicas and ran the promotion with zero special configuration. The only friction: Dragonfly refuses to start with less than 256MiB of &lt;code&gt;maxmemory&lt;/code&gt; &lt;em&gt;per thread&lt;/em&gt;, so tiny lab configs need &lt;code&gt;--maxmemory&lt;/code&gt; raised accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Garnet isn't in this table:&lt;/strong&gt; Microsoft's Garnet doesn't speak Sentinel — its HA story is its own cluster-mode replication. Timing that would be a different (interesting) experiment, not this one, and I'd rather leave a gap than publish an apples-to-oranges number. (Garnet's raw throughput, though, &lt;a href="https://two-techies.com/blog/keydb-vs-garnet-benchmark" rel="noopener noreferrer"&gt;surprised me here&lt;/a&gt;.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wiring it to Laravel
&lt;/h2&gt;

&lt;p&gt;The lab proves the infrastructure. The other half is making Laravel &lt;em&gt;Sentinel-aware&lt;/em&gt; so it re-discovers the master after a failover instead of clinging to a dead IP. The Predis client supports this directly. In &lt;code&gt;config/database.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'redis'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'client'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'predis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// Point at the SENTINELS, not at a Redis master.&lt;/span&gt;
        &lt;span class="s1"&gt;'sentinel'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'tcp://172.28.0.21:26379'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'tcp://172.28.0.22:26379'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'tcp://172.28.0.23:26379'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'replication'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'sentinel'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'service'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mymaster'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// must match `sentinel monitor &amp;lt;name&amp;gt;`&lt;/span&gt;
            &lt;span class="s1"&gt;'parameters'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Predis asks the sentinels who the master is and reconnects automatically when it changes. Point it at a fixed master IP instead and you've built all this machinery for nothing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On phpredis:&lt;/strong&gt; the C extension is faster and great for a plain single-node Redis, but it has no built-in Sentinel discovery — you'd implement the "ask a sentinel, then connect" dance yourself. If you want painless Sentinel support, Predis is the path of least resistance; if you're committed to phpredis, packages like &lt;a href="https://github.com/Goopil/laravel-redis-sentinel" rel="noopener noreferrer"&gt;Goopil/laravel-redis-sentinel&lt;/a&gt; wrap that dance for Laravel (including Horizon probes). Benchmark before assuming the client speed difference matters for your workload — for most apps it doesn't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The traps nobody puts in the README
&lt;/h2&gt;

&lt;p&gt;The demo is the easy 80%. These are the things that bite in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failover is not zero data loss.&lt;/strong&gt; Replication is asynchronous. A write acknowledged by the old primary but not yet copied to the replica is &lt;em&gt;gone&lt;/em&gt; when that replica is promoted. Sentinel picks the most up-to-date replica to minimise it, but the window is real. For a cache, fine. For anything you truly cannot lose, understand this before you rely on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune &lt;code&gt;down-after&lt;/code&gt; for your reality — now with numbers.&lt;/strong&gt; Your total outage is &lt;code&gt;down-after&lt;/code&gt; + ~2s. Pick the largest value your SLO tolerates, because the price of going lower is false failovers: a stop-the-world GC pause or a 2-second network blip at &lt;code&gt;down-after=1s&lt;/code&gt; triggers a needless promotion, and needless promotions are how you discover the data-loss window above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your queue and your cache should not share a failover group.&lt;/strong&gt; Your cache wants an eviction policy (&lt;code&gt;allkeys-lru&lt;/code&gt;); your Horizon queue must use &lt;code&gt;noeviction&lt;/code&gt; or it will silently drop &lt;em&gt;jobs&lt;/em&gt; when memory fills. Different criticality, different data-loss tolerance — run them as separate Redis instances, each with its own sentinels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-flight jobs during the window.&lt;/strong&gt; Whatever a worker was mid-processing when Redis vanished may be retried after recovery. Make your jobs &lt;strong&gt;idempotent&lt;/strong&gt; and set sensible retry/backoff. This is a feature (nothing is lost) only if your jobs can safely run twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quorum and placement.&lt;/strong&gt; An odd number of sentinels (3 or 5), spread across separate failure domains — not all on the same host as the Redis nodes they watch. Two sentinels on the same box that dies is a quorum you can lose in one stroke.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable persistence in production.&lt;/strong&gt; I disabled it in the lab for clarity. In production, run AOF on at least the replicas so a promoted node comes up with data, not empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Sentinel isn't enough
&lt;/h2&gt;

&lt;p&gt;Sentinel gives you &lt;em&gt;availability&lt;/em&gt; for a dataset that fits on one node. You've outgrown it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data or write throughput is bigger than a single node can hold → &lt;strong&gt;Redis Cluster&lt;/strong&gt; (sharding) — see &lt;a href="https://two-techies.com/blog/redis-cluster-operational-simplicity" rel="noopener noreferrer"&gt;what that costs operationally&lt;/a&gt; and &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;what it does to your realistic throughput&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You don't want to run, patch, and monitor sentinels yourself → &lt;strong&gt;managed&lt;/strong&gt; (ElastiCache / managed Valkey) does exactly this failover for you. Now that you've built it by hand, you know precisely what that bill is buying: the on-call rotation for the bullet points above.&lt;/li&gt;
&lt;li&gt;You need multi-region survivability → that's a bigger conversation than Sentinel alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Adding app servers makes you feel highly available. Surviving the loss of your Redis node is what &lt;em&gt;makes&lt;/em&gt; you highly available — and now you know exactly what it costs: &lt;strong&gt;&lt;code&gt;down-after-milliseconds&lt;/code&gt; + ~2 seconds of write outage, measured, not guessed — and identical whether the node runs Redis, Valkey, or Dragonfly.&lt;/strong&gt; Kill the primary yourself once — the lab is &lt;a href="https://github.com/sunnysahijwani/redis-sentinel-failover-lab" rel="noopener noreferrer"&gt;right here&lt;/a&gt; and takes five minutes. Seeing writes resume on a promoted replica, untouched by human hands, is worth more than any diagram.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Lab: Redis 8 / Valkey 8 / Dragonfly data nodes under redis:8 sentinels, Docker on macOS, 1 primary + 2 replicas + 3 sentinels, quorum 2, &lt;code&gt;failover-timeout&lt;/code&gt; 10000. Outage = kill → first successful client write on the promoted node, via Sentinel discovery, 3 runs per setting and engine. Failover window and data-loss characteristics vary with replication lag and hardware — &lt;a href="https://github.com/sunnysahijwani/redis-sentinel-failover-lab" rel="noopener noreferrer"&gt;run it yourself&lt;/a&gt; and tune to your workload.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you found this useful, the same measure-don't-guess approach applied to engine choice lives in my &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-valkey-benchmark" rel="noopener noreferrer"&gt;Dragonfly vs Redis vs Valkey benchmark series&lt;/a&gt;, with an &lt;a href="https://two-techies.com/benchmarks" rel="noopener noreferrer"&gt;interactive explorer&lt;/a&gt; for the raw data.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the author&lt;/strong&gt; — I'm &lt;strong&gt;Sunny Sahijwani&lt;/strong&gt;, a senior backend &amp;amp; DevOps engineer. 👉 &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/sunnysahijwani/" rel="noopener noreferrer"&gt;Connect with me on LinkedIn&lt;/a&gt;&lt;/strong&gt; — I'm always happy to talk in-memory databases, performance, and systems architecture.&lt;/p&gt;

&lt;p&gt;The full lab — docker-compose, kill scripts, the sweep, and raw CSVs — is public on &lt;a href="https://github.com/sunnysahijwani/redis-sentinel-failover-lab" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — rerun it and tell me where I'm wrong.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://two-techies.com/blog/redis-sentinel-failover-time" rel="noopener noreferrer"&gt;two-techies.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>devops</category>
      <category>docker</category>
      <category>database</category>
    </item>
    <item>
      <title>When to use which: Dragonfly vs Redis vs Valkey</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Mon, 14 Sep 2026 05:50:28 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/when-to-use-which-dragonfly-vs-redis-vs-valkey-3p3b</link>
      <guid>https://dev.to/sunnysahijwani/when-to-use-which-dragonfly-vs-redis-vs-valkey-3p3b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 7 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/redis-cluster-operational-simplicity" rel="noopener noreferrer"&gt;← Operational simplicity&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-valkey-benchmark" rel="noopener noreferrer"&gt;Series index&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz0l7jve3wv0xk9fzofg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz0l7jve3wv0xk9fzofg.png" alt="Trade-off radar: Dragonfly leads on throughput-with-a-normal-client, operational simplicity, multi-key and memory; Redis/Valkey cluster leads on small failure domains and raw throughput ceiling" width="656" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 7, the finale. Six posts of measurements come down to this: there is no universal winner, and the right choice depends on which corner of a three-way trade-off you care about. Here's the decision guide I'd actually give a team.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-screen summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Dragonfly&lt;/th&gt;
&lt;th&gt;Redis / Valkey Cluster&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput, normal client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~3.4× the cluster&lt;/strong&gt; (15.5M vs 4.6M @48c)&lt;/td&gt;
&lt;td&gt;plateaus early; more shards don't help a naive client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput, perfectly driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.5M (that &lt;em&gt;is&lt;/em&gt; driven — one endpoint)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;raw ceiling wins: ~40M&lt;/strong&gt; — but needs per-shard routing + a client as big as the server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Effort to reach its throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;trivial (one-line client)&lt;/td&gt;
&lt;td&gt;serious client-side engineering + client hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency under load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;sub-ms, predictable&lt;/td&gt;
&lt;td&gt;sub-ms &lt;em&gt;if&lt;/em&gt; driven well; hundreds of ms with a naive client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-key / transactions / Lua&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;works across the whole keyspace on one node&lt;/td&gt;
&lt;td&gt;same-slot only (hash-tag tax)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory / key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~13% leaner&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure domain / HA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;bigger blast radius per node; has replicas + cluster&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;smaller shards = smaller blast radius&lt;/strong&gt;, mature, elastic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Governance / licensing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;source-available (BSL)&lt;/td&gt;
&lt;td&gt;Redis 8 tri-licensed (incl. AGPL); &lt;strong&gt;Valkey = BSD, Linux Foundation&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three-way trade-off
&lt;/h2&gt;

&lt;p&gt;Every "which is best" argument is really about which of these three you're optimizing — you rarely get all three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Throughput-per-node&lt;/strong&gt; — most work out of one box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational simplicity&lt;/strong&gt; — fewest moving parts, no cross-slot data modeling, a dumb client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure domain / elasticity&lt;/strong&gt; — small blast radius, easy incremental scale-out, mature HA.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dragonfly wins &lt;strong&gt;1 and 2&lt;/strong&gt; decisively for the single-big-node case. Redis/Valkey Cluster wins &lt;strong&gt;3&lt;/strong&gt;, and wins &lt;strong&gt;1&lt;/strong&gt; &lt;em&gt;only&lt;/em&gt; if you'll invest to drive it. That's the whole map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick by scenario
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reach for Dragonfly when…&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want a lot of throughput from one node with a &lt;strong&gt;normal client and minimal ops&lt;/strong&gt; — the common case, where it's ~3.4× a realistic cluster;&lt;/li&gt;
&lt;li&gt;you need &lt;strong&gt;multi-key ops / transactions / Lua across arbitrary keys&lt;/strong&gt; while using all your cores;&lt;/li&gt;
&lt;li&gt;you'd rather &lt;strong&gt;scale up than operate a cluster&lt;/strong&gt;, and pair one big node with a replica for HA;&lt;/li&gt;
&lt;li&gt;you're memory-sensitive on &lt;strong&gt;many-small-keys&lt;/strong&gt; workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reach for Redis / Valkey Cluster when…&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need &lt;strong&gt;maximum raw throughput&lt;/strong&gt; and can invest in &lt;strong&gt;flawless client-side routing + a large client fleet&lt;/strong&gt; (the 40M ceiling is real if you'll pay for it);&lt;/li&gt;
&lt;li&gt;you want &lt;strong&gt;small failure domains and elastic horizontal scale&lt;/strong&gt; — many modest shards + replicas, so losing one shard blips 1/N of the keyspace;&lt;/li&gt;
&lt;li&gt;you've &lt;strong&gt;outgrown a single big machine&lt;/strong&gt; (at which point Dragonfly would shard too, and hit the same cross-slot wall);&lt;/li&gt;
&lt;li&gt;you want the &lt;strong&gt;most mature, battle-tested&lt;/strong&gt; clustering, or a specific &lt;strong&gt;license&lt;/strong&gt; (Valkey's BSD / Linux Foundation governance is a real reason on its own).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Valkey vs Redis specifically:&lt;/strong&gt; performance is a wash (Valkey is the Redis 7.2 fork; both are multi-threaded-I/O now). Choose on &lt;strong&gt;governance and licensing&lt;/strong&gt;, not benchmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest one-liner
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Redis Cluster has the higher raw ceiling; Dragonfly gives you most of the throughput for almost none of the operational cost. If you can engineer for the ceiling, take it. If you'd rather not, Dragonfly is the better default — and for a single big node with a normal client, it isn't close.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I'd do
&lt;/h2&gt;

&lt;p&gt;For a new service that fits comfortably on one large node — which is &lt;em&gt;most&lt;/em&gt; services — I'd start with &lt;strong&gt;Dragonfly&lt;/strong&gt;: near-top throughput, sub-ms latency, full multi-key semantics, less memory, and a client I don't have to think about. I'd move to a &lt;strong&gt;cluster&lt;/strong&gt; (Dragonfly's or Redis/Valkey's) when I genuinely outgrow one machine or need small failure domains for HA — and I'd go in knowing the cross-slot tax and the client-side engineering that "using the cluster's throughput" actually requires.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;That's the series. Everything — the harness, pinned versions, raw &lt;code&gt;runs.csv&lt;/code&gt;, and the analysis scripts — 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 and challenge all of it. Verified against the DragonflyDB team's benchmarking notes before publishing; the fairness methodology (and the bias I caught in my own setup) is in &lt;a href="https://two-techies.com/blog/dragonfly-redis-valkey-benchmark-methodology" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;. You can also explore every number interactively in the &lt;a href="https://two-techies.com/benchmarks/dragonfly-redis-valkey" rel="noopener noreferrer"&gt;Benchmark Explorer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Benchmarked on AWS &lt;code&gt;c7i.metal-24xl&lt;/code&gt; (48 physical Sapphire Rapids cores) + &lt;code&gt;c7i.24xlarge&lt;/code&gt; client, us-east-1, same placement group. Redis 8.2.8 · Valkey 8.1.9 · Dragonfly v1.40.1 · memtier 2.5.1 · io_uring · client-side latency.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>performance</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>Operational simplicity: the cross-key tax, quantified</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Sun, 13 Sep 2026 08:45:00 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/operational-simplicity-the-cross-key-tax-quantified-1k1b</link>
      <guid>https://dev.to/sunnysahijwani/operational-simplicity-the-cross-key-tax-quantified-1k1b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 6 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-memory" rel="noopener noreferrer"&gt;← Memory efficiency&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-when-to-use-which" rel="noopener noreferrer"&gt;Next: When to use which →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fawk5tsf6u8d2g09rg3j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fawk5tsf6u8d2g09rg3j2.png" alt="Multi-key MGET(10) throughput scales to 2.4M commands/sec on Dragonfly; Redis/Valkey clusters reject cross-slot multi-key (CROSSSLOT)" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 6. Throughput and latency are what benchmarks measure. This is the thing they usually miss — and it's where Dragonfly's real structural advantage lives. It's also where I have to be most careful to stay honest, because it's easy to overclaim here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The demo (run it yourself)
&lt;/h2&gt;

&lt;p&gt;Take three plain keys and ask each engine to fetch them in one command. On a single Dragonfly process and on a single Redis, this just works. On a &lt;strong&gt;Redis Cluster&lt;/strong&gt; it doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Redis Cluster (slots: alpha=865  bravo=8623  charlie=1769)
  MGET alpha bravo charlie      → CROSSSLOT Keys in request don't hash to the same slot
  EVAL over alpha bravo charlie → CROSSSLOT Keys in request don't hash to the same slot
  MGET {u1}:name {u1}:email     → OK   (same hash-tag → same slot)

Dragonfly (single process)
  MGET alpha bravo charlie      → OK
  EVAL over alpha bravo charlie → OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a bug — it's how Redis Cluster is designed. Each shard is an independent process that only holds its slice of the 16,384 hash slots, and it &lt;strong&gt;refuses&lt;/strong&gt; any multi-key command whose keys don't all live in the same slot, rather than doing a slow cross-shard scatter-gather.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters: multi-key isn't a convenience, it's atomicity
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MGET&lt;/code&gt; being rejected is mildly annoying — you can do three separate GETs. But the same rule hits &lt;strong&gt;&lt;code&gt;MULTI&lt;/code&gt;/&lt;code&gt;EXEC&lt;/code&gt; transactions and Lua scripts&lt;/strong&gt;, and those are how you do &lt;em&gt;atomic&lt;/em&gt; multi-key logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deduct from &lt;code&gt;wallet:A&lt;/code&gt;, credit &lt;code&gt;wallet:B&lt;/code&gt;, all-or-nothing;&lt;/li&gt;
&lt;li&gt;check &lt;code&gt;stock:item&lt;/code&gt;, decrement it, write &lt;code&gt;reserved:user&lt;/code&gt; — atomically, so two buyers can't grab the last unit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a single Dragonfly (or single Redis) you write one Lua script over whatever keys you need and it runs atomically. On a Redis Cluster, that script can &lt;strong&gt;only touch same-slot keys&lt;/strong&gt; — so you must force the related keys onto one slot with a &lt;strong&gt;hash-tag&lt;/strong&gt; (&lt;code&gt;{group}:...&lt;/code&gt;). That works, but it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;concentrates those keys on one shard&lt;/strong&gt; — you lose the load-spreading you sharded for, and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;forces you to decide, at data-model time, which entities will ever need to be touched together&lt;/strong&gt; — often impossible to predict.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the tax. It's not "Redis is worse" — it's the unavoidable cost of splitting one logical keyspace across independent processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quantified: multi-key throughput
&lt;/h2&gt;

&lt;p&gt;Because Dragonfly handles cross-key ops natively, I could benchmark them — &lt;code&gt;MGET&lt;/code&gt;/&lt;code&gt;MSET&lt;/code&gt; of 10 keys, scaling with cores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MGET(10)&lt;/code&gt;, pipeline 16:&lt;/strong&gt; 4c → 0.3M, 8c → 0.5M, 16c → 0.9M, 24c → 1.3M, &lt;strong&gt;48c → 2.4M commands/sec&lt;/strong&gt; (= ~24M keys/sec).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MSET(10)&lt;/code&gt;, pipeline 16:&lt;/strong&gt; scales similarly to ~1.3M commands/sec at 48 cores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Redis/Valkey clusters get &lt;strong&gt;no bar on this chart&lt;/strong&gt; — cross-slot multi-key is rejected outright. To make it work you'd hash-tag everything onto shared slots, which changes the workload (and the point).&lt;/p&gt;

&lt;h2&gt;
  
  
  The honesty guard — this is important
&lt;/h2&gt;

&lt;p&gt;It would be easy, and wrong, to say "Dragonfly has no sharding limits." It does. &lt;strong&gt;Dragonfly Cluster, once you scale it across machines, uses the same 16,384-slot model and returns the same &lt;code&gt;CROSSSLOT&lt;/code&gt; error.&lt;/strong&gt; Dragonfly's advantage is narrower and more honest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;strong&gt;single Dragonfly node uses all your cores without sharding&lt;/strong&gt; — so it keeps single-instance semantics (any multi-key op, atomic Lua, transactions) &lt;em&gt;while&lt;/em&gt; scaling up. You postpone clustering — and its cross-key tax — until you genuinely outgrow one big machine. With Redis you pay that tax the moment you need more than one core's worth of throughput.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not "never." &lt;strong&gt;Later.&lt;/strong&gt; For a lot of workloads, "later" means "never in practice," because one big Dragonfly node goes a very long way. But if you truly outgrow a single machine, both engines shard and both hit the same wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The cross-key tax is the clearest structural difference between the two designs. Redis makes you choose, quite early, between &lt;em&gt;using all your cores&lt;/em&gt; and &lt;em&gt;keeping multi-key atomicity across arbitrary keys&lt;/em&gt;. Dragonfly lets you have both on one node. That — not raw ops/sec — is the strongest case for it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-when-to-use-which" rel="noopener noreferrer"&gt;Part 7 — when to use which&lt;/a&gt;, where all of this becomes a decision table. You can also explore every number in this series interactively — filter by workload, cores, and pipelining — in the &lt;a href="https://two-techies.com/benchmarks/dragonfly-redis-valkey" rel="noopener noreferrer"&gt;Benchmark Explorer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>performance</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>Memory efficiency: bytes per key</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Sat, 12 Sep 2026 07:51:33 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/memory-efficiency-bytes-per-key-2aba</link>
      <guid>https://dev.to/sunnysahijwani/memory-efficiency-bytes-per-key-2aba</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 5 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-latency" rel="noopener noreferrer"&gt;← Latency under load&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/redis-cluster-operational-simplicity" rel="noopener noreferrer"&gt;Next: Operational simplicity →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs84m0ki23dxfl3pktmlx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs84m0ki23dxfl3pktmlx.png" alt="Bytes per key: Dragonfly 146, Redis 165, Valkey 169 — Dragonfly ~13% leaner" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 5. The shortest post in the series, and the least arguable number in it — because memory footprint is about data-structure design, not how hard you drive the client. It's the same on a laptop and on 48 bare-metal cores.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement
&lt;/h2&gt;

&lt;p&gt;I loaded &lt;strong&gt;1,000,000 keys × 100-byte values&lt;/strong&gt; into each engine, subtracted the empty-server baseline, and divided by key count:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;bytes / key&lt;/th&gt;
&lt;th&gt;vs Dragonfly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;146.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 8.2&lt;/td&gt;
&lt;td&gt;165.3&lt;/td&gt;
&lt;td&gt;+13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valkey 8.1&lt;/td&gt;
&lt;td&gt;168.7&lt;/td&gt;
&lt;td&gt;+15%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dragonfly stores the same data in &lt;strong&gt;~12–13% less memory&lt;/strong&gt; than Redis, and a bit less than Valkey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one is trustworthy
&lt;/h2&gt;

&lt;p&gt;Two reasons I trust this number more than any throughput number:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It's hardware-independent.&lt;/strong&gt; I got 146.1 / 165.3 / 168.7 on my laptop &lt;em&gt;and&lt;/em&gt; 146.1 / 165.3 / 168.7 on the c7i.metal box — to the decimal. Memory layout doesn't care about cores, io_uring, or how big your client is. There's nothing to "drive" and nothing to get wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's methodology-proof.&lt;/strong&gt; No pipelining, no cluster routing, no client bottleneck — just "store a million keys, read the memory counter." The only care needed is subtracting the empty baseline and loading exactly N &lt;em&gt;distinct&lt;/em&gt; keys (an easy thing to get wrong — load them in parallel with overlapping ranges and you'll silently store far fewer).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it means (and doesn't)
&lt;/h2&gt;

&lt;p&gt;Dragonfly's edge comes from more compact internal data structures. For a 100-byte value, ~46–69 bytes of per-key overhead sits on top of the payload, and Dragonfly carries less of it.&lt;/p&gt;

&lt;p&gt;Two honest qualifiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The percentage shrinks as values grow.&lt;/strong&gt; At 100 B, overhead is a big fraction of each key; at 10 KB values it's noise. Memory efficiency matters most for &lt;strong&gt;many-small-keys&lt;/strong&gt; workloads (sessions, counters, feature flags, rate-limit buckets) — exactly where you also have the most keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;13% is real money at scale but not a headline-grabber.&lt;/strong&gt; On a 256 GB dataset it's ~33 GB — a meaningful instance-size difference. It won't decide an architecture on its own, but it's a steady, dependable point in Dragonfly's favor that no amount of benchmarking dispute can erode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://two-techies.com/blog/redis-cluster-operational-simplicity" rel="noopener noreferrer"&gt;Part 6 — operational simplicity&lt;/a&gt;, where the cluster's cross-key limitations get quantified — and where Dragonfly's real structural advantage lives. You can also explore every number in this series interactively — filter by workload, cores, and pipelining — in the &lt;a href="https://two-techies.com/benchmarks/dragonfly-redis-valkey" rel="noopener noreferrer"&gt;Benchmark Explorer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>performance</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>Latency under load: the same story as throughput, from the other side</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:27:46 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/latency-under-load-the-same-story-as-throughput-from-the-other-side-544j</link>
      <guid>https://dev.to/sunnysahijwani/latency-under-load-the-same-story-as-throughput-from-the-other-side-544j</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 4 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;← The cluster-saturation problem&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-memory" rel="noopener noreferrer"&gt;Next: Memory efficiency →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2For3f62wpl1zqxbpaf9e0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2For3f62wpl1zqxbpaf9e0.png" alt="Latency at 48 cores (log scale): Dragonfly sub-ms, a well-driven cluster 0.56ms, a naive cluster client 84ms p50" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 4. Throughput (&lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-throughput-scaling" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;) tells you how much work gets done; latency tells you how it feels. On this benchmark they turn out to be the same story — and the "realistic cluster" numbers need careful, honest reading.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All numbers below are &lt;strong&gt;client-side&lt;/strong&gt; latency from memtier (never the engine's self-reported &lt;code&gt;info&lt;/code&gt; — Dragonfly counts network+queue time there and Redis doesn't, so those aren't comparable). Read-heavy, 100 B, 48 cores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Without pipelining (depth 1) — concurrency only
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;series&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;th&gt;p99.9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.34 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.55 ms&lt;/td&gt;
&lt;td&gt;0.71 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis cluster (realistic)&lt;/td&gt;
&lt;td&gt;10.4 ms&lt;/td&gt;
&lt;td&gt;19.2 ms&lt;/td&gt;
&lt;td&gt;30.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valkey cluster (realistic)&lt;/td&gt;
&lt;td&gt;10.4 ms&lt;/td&gt;
&lt;td&gt;18.5 ms&lt;/td&gt;
&lt;td&gt;29.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  With pipelining (depth 16) — throughput regime
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;series&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p99&lt;/th&gt;
&lt;th&gt;p99.9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.88 ms&lt;/td&gt;
&lt;td&gt;2.42 ms&lt;/td&gt;
&lt;td&gt;3.33 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis cluster — fully driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.56 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.51 ms&lt;/td&gt;
&lt;td&gt;4.64 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Valkey cluster — fully driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.57 ms&lt;/td&gt;
&lt;td&gt;1.32 ms&lt;/td&gt;
&lt;td&gt;3.50 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis cluster — realistic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;138 ms&lt;/td&gt;
&lt;td&gt;151 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valkey cluster — realistic&lt;/td&gt;
&lt;td&gt;86 ms&lt;/td&gt;
&lt;td&gt;141 ms&lt;/td&gt;
&lt;td&gt;158 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reading this honestly
&lt;/h2&gt;

&lt;p&gt;Three things, and the third is the one people will (fairly) push on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dragonfly is consistently sub-millisecond&lt;/strong&gt; — 0.34 ms unpipelined, 0.88 ms under a heavy pipelined load. One endpoint, no client-side routing, predictable tail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A well-driven cluster has excellent latency too&lt;/strong&gt; — the fully-driven shards actually beat Dragonfly at the median (0.56 ms), because each shard gets a clean, dedicated, fully-pipelined stream. So &lt;em&gt;the shards themselves are fast.&lt;/em&gt; When Redis Cluster is driven right, its latency is great.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The "realistic cluster" latency explodes — 84 ms p50 — and that's a client-side effect, not the shards.&lt;/strong&gt; This is the same bottleneck from &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;: a single cluster-mode client can't route to 48 shards fast enough, so requests &lt;strong&gt;queue up on the client&lt;/strong&gt; waiting to be sent. That queuing shows up as latency. It is &lt;em&gt;not&lt;/em&gt; the cluster's servers being slow — driven well, the same shards do 0.56 ms.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I won't claim "Redis Cluster has 84 ms latency" — that would be dishonest. What I &lt;em&gt;will&lt;/em&gt; claim, because it's what the data shows: &lt;strong&gt;a normal single-process cluster client, under load heavy enough to matter, degrades in both throughput and tail latency at the same time&lt;/strong&gt; — and you feel it as multi-hundred-millisecond p99s. Fixing it means the client-side engineering from Part 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Latency here isn't a separate axis — it's throughput viewed from the other side. Where the client can keep up (Dragonfly always; a cluster only with per-shard routing + enough client capacity), latency is sub-millisecond and tight. Where the client is the bottleneck (a naive cluster client), &lt;strong&gt;latency and throughput fail together&lt;/strong&gt;. Dragonfly's value is that "the client keeps up" is the &lt;em&gt;default&lt;/em&gt;, not something you engineer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next: &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-memory" rel="noopener noreferrer"&gt;Part 5 — memory efficiency&lt;/a&gt;, the least-disputable number in the whole series. And you can now explore every chart in this series interactively — filter by workload, cores, and pipelining — in the &lt;a href="https://two-techies.com/benchmarks/dragonfly-redis-valkey" rel="noopener noreferrer"&gt;Benchmark Explorer&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>performance</category>
      <category>benchmarking</category>
      <category>database</category>
    </item>
    <item>
      <title>Why a normal client gets 4.6M ops/s out of a Redis cluster that can do 40M</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:24:42 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/why-a-normal-client-gets-46m-opss-out-of-a-redis-cluster-that-can-do-40m-2bco</link>
      <guid>https://dev.to/sunnysahijwani/why-a-normal-client-gets-46m-opss-out-of-a-redis-cluster-that-can-do-40m-2bco</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 3 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-throughput-scaling" rel="noopener noreferrer"&gt;← Throughput &amp;amp; scaling&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-latency" rel="noopener noreferrer"&gt;Next: Latency under load →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncb2o0ohntixxy2hfzsm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncb2o0ohntixxy2hfzsm.png" alt="Same 48-shard cluster, same hardware: a normal client gets 4.6M, one-client-per-shard gets 40.6M — only the client changed" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 3. This is the post I didn't expect to write. It started as a bug in my benchmark and turned into the most interesting finding of the whole project — about how hard it actually is to use a Redis Cluster's throughput, and why most cluster benchmarks (mine included, at first) quietly under-report it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-throughput-scaling" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; I showed two very different numbers for the same 48-shard Redis cluster on the same hardware: &lt;strong&gt;4.6M ops/s&lt;/strong&gt; with a normal client, &lt;strong&gt;40.6M&lt;/strong&gt; driven perfectly. This post is how I found that gap, and what it means. It's a debugging story, because that's what it was.&lt;/p&gt;




&lt;h2&gt;
  
  
  The smell: a 4-shard cluster tied a 48-shard cluster
&lt;/h2&gt;

&lt;p&gt;My first cluster numbers looked wrong. I ran a quick check — does throughput improve as I add shards? — and got this (read-heavy, pipeline 16):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 shards: 2.65M ops/s&lt;/li&gt;
&lt;li&gt;48 shards: 2.66M ops/s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Identical. Doubling the shards did &lt;em&gt;nothing&lt;/em&gt;. That's not how a cluster is supposed to behave — 48 single-threaded shards should crush 24. Something was capping throughput before the shards even got busy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tell: the shards were idle
&lt;/h2&gt;

&lt;p&gt;So I measured CPU on both boxes during the run. The server, running all 48 shards, was at &lt;strong&gt;779% of 4800% — about 16% busy.&lt;/strong&gt; The shards were &lt;strong&gt;mostly asleep.&lt;/strong&gt; Meanwhile the client's load-generator threads were pinned at ~100%.&lt;/p&gt;

&lt;p&gt;That's the signature of a &lt;strong&gt;client-side bottleneck&lt;/strong&gt;: the cluster had tons of spare capacity, but the client couldn't feed it. And counter-intuitively, adding &lt;em&gt;more&lt;/em&gt; client threads made it &lt;strong&gt;worse&lt;/strong&gt; (4.3M → 3.0M) — the classic sign of lock contention inside a single process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proof: one shard can do 474K/sec on its own
&lt;/h2&gt;

&lt;p&gt;To find out how much the cluster &lt;em&gt;should&lt;/em&gt; be able to do, I measured one standalone Redis shard, pinned to one core, driven directly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;474,000 ops/s at 100% CPU&lt;/strong&gt; — one core, fully saturated.&lt;/p&gt;

&lt;p&gt;So the math on a 48-core box is brutal: &lt;strong&gt;48 × 474K ≈ 22.8M ops/s&lt;/strong&gt; of raw capacity (and in practice, driven well, each shard did even more — the cluster topped out near 40M). I was extracting &lt;strong&gt;~4.3M&lt;/strong&gt;. I was using &lt;strong&gt;less than a fifth&lt;/strong&gt; of what the cluster could do, and no amount of tuning the &lt;em&gt;single&lt;/em&gt; client fixed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cause: cluster-mode routing, in one process, doesn't pipeline
&lt;/h2&gt;

&lt;p&gt;Here's the mechanism, and it's worth understanding because it affects real applications, not just benchmarks.&lt;/p&gt;

&lt;p&gt;A Redis Cluster client has to &lt;strong&gt;route every key to the shard that owns its hash slot&lt;/strong&gt; (&lt;code&gt;slot = CRC16(key) mod 16384&lt;/code&gt;). A single-process cluster client (like memtier's &lt;code&gt;--cluster-mode&lt;/code&gt;) does this routing on the client side, and two things go wrong under load:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pipelining fragments.&lt;/strong&gt; Pipelining is fast because you batch many requests down one connection before waiting for replies. But in a cluster, consecutive &lt;em&gt;random&lt;/em&gt; keys go to &lt;em&gt;different&lt;/em&gt; shards — so a batch of 16 gets split across 16 connections, one request each. The single most important throughput lever (deep pipelines) collapses to depth ~1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One process, one routing bottleneck.&lt;/strong&gt; All that per-key CRC16 + connection-selection happens in one process's threads, which contend. Adding threads adds contention, not throughput.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: the client saturates itself doing routing bookkeeping while the shards sit idle. &lt;strong&gt;4.3M, and the cluster barely breaks a sweat.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: one client per shard, pinned by hash-tag
&lt;/h2&gt;

&lt;p&gt;If the problem is "one client can't route to 48 shards efficiently," the fix is to &lt;strong&gt;stop routing.&lt;/strong&gt; I gave each shard its own dedicated memtier process, connected &lt;strong&gt;directly to that shard's port&lt;/strong&gt; (no cluster-mode), using keys with a &lt;strong&gt;hash-tag&lt;/strong&gt; (&lt;code&gt;{tag}:...&lt;/code&gt;) chosen so every key hashes to that shard's slots. Now each shard gets a single, dedicated, fully-pipelined stream — exactly like the standalone-shard test that hit 474K.&lt;/p&gt;

&lt;p&gt;The result, 48 shards, summed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;39.8M ops/s, server at 91% CPU — near-saturated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From 4.3M to 39.8M by changing &lt;em&gt;only the client&lt;/em&gt;, not the cluster. The cluster was never the bottleneck. The &lt;strong&gt;client's ability to route&lt;/strong&gt; was.&lt;/p&gt;

&lt;p&gt;(For the curious: mapping tags to shards is deterministic — I query &lt;code&gt;CLUSTER NODES&lt;/code&gt; for each shard's slot range, then pick a hash-tag whose &lt;code&gt;CRC16 mod 16384&lt;/code&gt; lands in it. The tool that does this is in the repo.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters beyond benchmarking
&lt;/h2&gt;

&lt;p&gt;This isn't a benchmark artifact — it's a real property of Redis Cluster, and it cuts two ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For benchmarks:&lt;/strong&gt; any Redis Cluster number produced by a single cluster-mode client is probably &lt;strong&gt;under-reported&lt;/strong&gt; — often massively. If you see a cluster benchmark and the shards weren't near 100% CPU, treat the number as a lower bound. (This is exactly why I report &lt;em&gt;two&lt;/em&gt; cluster numbers.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For production — this is the important part:&lt;/strong&gt; reaching a cluster's throughput ceiling requires &lt;strong&gt;client-side routing that actually pipelines per-shard, plus enough client capacity to drive every shard.&lt;/strong&gt; Good cluster client libraries do route client-side — but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your &lt;strong&gt;client fleet has to be large enough&lt;/strong&gt; to keep 48 shards busy (in my test, saturating the cluster took a client as powerful as the server), and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pipelining/batching only helps if your keys co-locate per shard&lt;/strong&gt; — random-key workloads fragment it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the cluster's 40M ceiling is real, but it comes with a bill: &lt;strong&gt;serious client-side engineering and client hardware.&lt;/strong&gt; Dragonfly's 15.5M comes with a one-line client and no routing to think about. That's the trade — and now it's not hand-waving, it's a factor of ~9 between "cluster, naively driven" and "cluster, perfectly driven," with the truth for most teams sitting closer to the naive end.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest caveat
&lt;/h2&gt;

&lt;p&gt;The "realistic" number (4.6M) reflects &lt;strong&gt;one specific normal client&lt;/strong&gt; (memtier cluster-mode) under a heavy pipelined load. A production app with a good client library and a modest request rate won't hit memtier's exact contention — but it also won't reach the 40M ceiling without real effort. The &lt;em&gt;shape&lt;/em&gt; is what's robust: &lt;strong&gt;a cluster's usable throughput depends enormously on client-side routing quality and client capacity, in a way a single process like Dragonfly simply sidesteps.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://two-techies.com/blog/dragonfly-vs-redis-latency" rel="noopener noreferrer"&gt;Part 4: latency under load&lt;/a&gt; — where this same client-bottleneck effect shows up dramatically in the tail (a naive cluster client sat at p99 ≈ 138ms while Dragonfly held 2.4ms), and how to read that honestly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full harness, the per-shard saturation tool, and raw data are 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 it. Verified against the DragonflyDB team's benchmarking notes before publishing; the fairness choices are in &lt;a href="https://two-techies.com/blog/dragonfly-redis-valkey-benchmark-methodology" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>performance</category>
      <category>benchmarking</category>
      <category>database</category>
    </item>
    <item>
      <title>Dragonfly vs Redis vs Valkey: throughput and scaling on 48 real cores</title>
      <dc:creator>Sunny Sahijwani</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:09:11 +0000</pubDate>
      <link>https://dev.to/sunnysahijwani/dragonfly-vs-redis-vs-valkey-throughput-and-scaling-on-48-real-cores-o7b</link>
      <guid>https://dev.to/sunnysahijwani/dragonfly-vs-redis-vs-valkey-throughput-and-scaling-on-48-real-cores-o7b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 2 of 7&lt;/strong&gt; · &lt;a href="https://two-techies.com/blog/dragonfly-redis-valkey-benchmark-methodology" rel="noopener noreferrer"&gt;← Methodology&lt;/a&gt; · &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;Next: The cluster-saturation problem →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part 2 of the series. &lt;a href="https://two-techies.com/blog/dragonfly-redis-valkey-benchmark-methodology" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; covered how I kept this fair. This post is the numbers — with the complete setup, every configuration, and every tweak I had to make, so you can reproduce or challenge all of it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you only remember one thing: &lt;strong&gt;there is no single "winner," and the shape of the answer is more useful than any single number.&lt;/strong&gt; Same hardware, read-heavy pipelined load, 48 cores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐉 &lt;strong&gt;Dragonfly, one process: 15.5M ops/s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔴 &lt;strong&gt;Redis Cluster, driven by a normal client: 4.6M ops/s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔴 &lt;strong&gt;Redis Cluster, driven perfectly: 40.6M ops/s&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are real, measured numbers. The rest of this post explains the setup that produced them and what they mean.&lt;/p&gt;




&lt;h2&gt;
  
  
  The exact setup (so you can reproduce it)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hardware (AWS, us-east-1, single Availability Zone, cluster placement group, private network):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server (engine under test):&lt;/strong&gt; &lt;code&gt;c7i.metal-24xl&lt;/code&gt; — &lt;strong&gt;bare metal&lt;/strong&gt;, Intel Xeon Platinum 8488C (Sapphire Rapids), &lt;strong&gt;48 physical cores / 96 threads, single NUMA node&lt;/strong&gt;, 192 GB RAM. Bare metal specifically so CPU pinning is real and there's no hypervisor jitter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client (load generator):&lt;/strong&gt; &lt;code&gt;c7i.24xlarge&lt;/code&gt; — 96 vCPU, on its own instance so it can never steal the server's CPU. I verified the client was never the bottleneck (more on that below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Software (pinned by image digest for reproducibility):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis &lt;strong&gt;8.2.8&lt;/strong&gt;, Valkey &lt;strong&gt;8.1.9&lt;/strong&gt;, Dragonfly &lt;strong&gt;v1.40.1&lt;/strong&gt;, memtier_benchmark &lt;strong&gt;2.5.1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;OS: Amazon Linux 2023, kernel 6.18. Transparent huge pages disabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I measured — three configurations, not two.&lt;/strong&gt; This is the key methodological choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly&lt;/strong&gt; — a single multi-threaded process (&lt;code&gt;--proactor_threads = N&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis/Valkey Cluster, "realistic"&lt;/strong&gt; — an N-shard cluster driven by a normal client (memtier in cluster-mode): what a typical application actually experiences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis/Valkey Cluster, "fully-driven ceiling"&lt;/strong&gt; — the same cluster, but with one dedicated memtier process per shard, each pinned to its shard via a hash-tag, so every shard is saturated: the cluster's raw hardware capacity if you route perfectly and throw unlimited client resources at it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why single-DF vs cluster (and not vs single Redis)? Because Redis executes commands on &lt;strong&gt;one thread&lt;/strong&gt;. To use all 48 cores you &lt;em&gt;must&lt;/em&gt; shard it into a cluster — so a single Dragonfly process (uses the whole box) vs an N-shard Redis cluster (uses the whole box) is the apples-to-apples comparison. Single-node Redis appears only as a labelled baseline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engine configuration (identical treatment, parity made explicit):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistence &lt;strong&gt;off&lt;/strong&gt; (&lt;code&gt;save ""&lt;/code&gt;, &lt;code&gt;appendonly no&lt;/code&gt;), &lt;code&gt;maxmemory&lt;/code&gt; well above the dataset, &lt;code&gt;noeviction&lt;/code&gt; — set explicitly on all three (Dragonfly's &lt;code&gt;--cache_mode=false --snapshot_cron=''&lt;/code&gt;, verified from its docs, matches Redis &lt;code&gt;noeviction&lt;/code&gt; + persistence-off).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;memlock&lt;/code&gt; unbounded on &lt;strong&gt;all&lt;/strong&gt; engines (not just Dragonfly).&lt;/li&gt;
&lt;li&gt;Cluster = &lt;strong&gt;one shard per physical core&lt;/strong&gt; (I verified with a sweep that more shards didn't help — see Part 3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Workload (memtier, client-side latency — never the engine's self-reported &lt;code&gt;info&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GET/SET ratios &lt;strong&gt;10:1&lt;/strong&gt; (read-heavy) and &lt;strong&gt;1:1&lt;/strong&gt; (mixed); value sizes &lt;strong&gt;100 B&lt;/strong&gt; and &lt;strong&gt;1 KB&lt;/strong&gt;; pipeline depth &lt;strong&gt;1&lt;/strong&gt; and &lt;strong&gt;16&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;1M-key keyspace, &lt;strong&gt;pre-populated so every read hits real data&lt;/strong&gt; (I checked — hit rate was 1.0 on every run; an un-populated benchmark measures the cheap "key not found" path).&lt;/li&gt;
&lt;li&gt;2 repetitions per cell, median reported.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The tweaks I had to make (and why they matter)
&lt;/h2&gt;

&lt;p&gt;Being honest about these is the whole point — each one changes the numbers, and skipping any of them would have produced a misleading result:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly needs io_uring — and Docker blocks it by default.&lt;/strong&gt; On a fresh AL2023 box, Dragonfly silently fell back to &lt;code&gt;epoll&lt;/code&gt; (its slower path) because Docker's default seccomp profile blocks the io_uring syscalls. Running Dragonfly on epoll would have &lt;strong&gt;unfairly handicapped it&lt;/strong&gt;. Fix: &lt;code&gt;--security-opt seccomp=unconfined&lt;/code&gt; so it uses io_uring — its native I/O. Redis/Valkey use epoll natively, so each engine runs on its own best path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly needs &lt;code&gt;maxmemory ≥ ~256 MB × threads&lt;/code&gt;&lt;/strong&gt; (12 GB at 48 threads). I gave it 32 GB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The client has to be &lt;em&gt;big&lt;/em&gt;.&lt;/strong&gt; My first cluster runs were quietly limited by too few client threads. It took &lt;strong&gt;48 client threads&lt;/strong&gt; to saturate the server — and I confirmed at peak load the server sat at ~100% CPU while the client had ~78% headroom. If the client is the bottleneck, every engine flatlines at the &lt;em&gt;client's&lt;/em&gt; limit and the benchmark is worthless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Driving a cluster to its real throughput is genuinely hard&lt;/strong&gt; — hard enough that it's &lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;its own post&lt;/a&gt;. It's why there are two cluster numbers, not one.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Scaling: throughput vs cores (read-heavy, 100 B, pipeline 16)
&lt;/h2&gt;

&lt;p&gt;This is the chart that tells the story. Throughput in &lt;strong&gt;millions of ops/sec&lt;/strong&gt;, as the server's core count grows 4 → 48:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw32ma97gdjf33gu0viwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw32ma97gdjf33gu0viwv.png" alt="Throughput vs cores 4→48: Dragonfly climbs smoothly to 15.5M, the realistic cluster plateaus at ~5M, the fully-driven cluster ceiling scales to 40M" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cores&lt;/th&gt;
&lt;th&gt;Dragonfly&lt;/th&gt;
&lt;th&gt;Redis &lt;em&gt;realistic&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;Redis &lt;em&gt;ceiling&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;Valkey &lt;em&gt;realistic&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;Valkey &lt;em&gt;ceiling&lt;/em&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;td&gt;3.1&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;9.2&lt;/td&gt;
&lt;td&gt;4.9&lt;/td&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td&gt;5.0&lt;/td&gt;
&lt;td&gt;15.8&lt;/td&gt;
&lt;td&gt;4.9&lt;/td&gt;
&lt;td&gt;15.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;9.0&lt;/td&gt;
&lt;td&gt;4.8&lt;/td&gt;
&lt;td&gt;22.5&lt;/td&gt;
&lt;td&gt;4.8&lt;/td&gt;
&lt;td&gt;21.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three completely different shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly scales smoothly&lt;/strong&gt;, 2M → 15.5M. One process, one connection endpoint, a dumb client — and it just keeps climbing with cores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The realistic cluster plateaus at ~5M around 8 shards and then goes &lt;em&gt;flat&lt;/em&gt; — even slightly down.&lt;/strong&gt; Adding shards past 8 doesn't help a normal client at all, because the client spends more and more effort routing keys across more and more shards. This surprised me, and it's the single most important practical finding: &lt;strong&gt;throwing more shards at a normal cluster client does not buy you throughput.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ceiling scales hard&lt;/strong&gt;, 5M → 40.6M — because the underlying hardware (48 single-threaded shards, each doing ~850K ops/s) genuinely can do that much. You just have to route perfectly to reach it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dragonfly overtakes the realistic cluster at ~16 cores&lt;/strong&gt; and finishes &lt;strong&gt;3.4× ahead&lt;/strong&gt; at 48 (15.5M vs 4.6M). It reaches &lt;strong&gt;38% of the raw ceiling — with a one-line client.&lt;/strong&gt; Redis/Valkey are near-identical throughout (Valkey is the Redis fork; no surprise).&lt;/p&gt;




&lt;h2&gt;
  
  
  What each number is &lt;em&gt;for&lt;/em&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you deploy a cluster and use a normal client&lt;/strong&gt; (the overwhelmingly common case): you get ~4.6M, and &lt;strong&gt;Dragonfly gives you 3.4× that&lt;/strong&gt; for a fraction of the operational effort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you can invest in flawless client-side routing and a client as powerful as your server&lt;/strong&gt;: the cluster's ceiling (40.6M) beats Dragonfly (15.5M) by 2.6×. That's real — but it's a lot of "if."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dragonfly's pitch, quantified:&lt;/strong&gt; most of the cluster's throughput, for almost none of the cluster's operational cost.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Two honest wrinkles
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Big values are network-bound.&lt;/strong&gt; At 1 KB values (read-heavy, pipeline 16, 48 cores), everything compresses: Dragonfly 4.8M, realistic cluster 3.7M, ceiling 7.7M. Once each op moves ~1 KB, you're bandwidth-limited, not engine-limited, and the gaps shrink. Pick the value size that matches &lt;em&gt;your&lt;/em&gt; workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without pipelining, nobody is fast&lt;/strong&gt; (it's a round-trip-bound world): read-heavy, pipeline 1, 48 cores — Dragonfly 2.8M, realistic cluster 1.4M. Dragonfly still leads, but this is the regime where your network RTT dominates, not the engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://two-techies.com/blog/redis-cluster-saturation-problem" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt; is the one I find most interesting: &lt;strong&gt;why a normal client only gets 4.6M out of a cluster that can do 40M&lt;/strong&gt; — and what that says about benchmarking (and operating) Redis Cluster at scale. Then latency, memory, and the operational-simplicity tax.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Everything here — scripts, pinned versions, raw &lt;code&gt;runs.csv&lt;/code&gt;, the analysis — 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 it. Verified against the DragonflyDB team's benchmarking notes before publishing; the fairness choices (and the bias I caught in my own setup) are in &lt;a href="https://two-techies.com/blog/dragonfly-redis-valkey-benchmark-methodology" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>infrastructure</category>
      <category>performance</category>
    </item>
    <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>
