<?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: Joshua Chi</title>
    <description>The latest articles on DEV Community by Joshua Chi (@joshua_c).</description>
    <link>https://dev.to/joshua_c</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%2F3711060%2F31c40cd9-f128-4ad4-a8ae-5ca4c66f6222.png</url>
      <title>DEV Community: Joshua Chi</title>
      <link>https://dev.to/joshua_c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshua_c"/>
    <language>en</language>
    <item>
      <title>d-engine Dev Notes (Part 1): The Hard Parts of Software Development</title>
      <dc:creator>Joshua Chi</dc:creator>
      <pubDate>Wed, 28 Oct 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/joshua_c/d-engine-dev-notes-part-1-the-hard-parts-of-software-development-19ah</link>
      <guid>https://dev.to/joshua_c/d-engine-dev-notes-part-1-the-hard-parts-of-software-development-19ah</guid>
      <description>&lt;h2&gt;
  
  
  Want
&lt;/h2&gt;

&lt;p&gt;To explain where &lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;d-engine&lt;/a&gt; really came from, I have to go back four or five years — to before d-engine itself existed.&lt;/p&gt;

&lt;p&gt;The first time I came across Kafka — especially Kafka Streams and Kafka Connect — I got hooked on event streaming platforms: pulling data in different formats together and aggregating it solved a lot of real business problems. An event is an abstraction of the real world: something that has already happened, a plain fact. That idea of "event" goes back, in my memory, to reading how Martin Fowler talks about &lt;a href="https://martinfowler.com/articles/201701-event-driven.html" rel="noopener noreferrer"&gt;event-driven architecture&lt;/a&gt;. But actually standing up and running a real Kafka cluster eats up a lot of hardware and a lot of your ops team's time — and using Confluent, the commercial version, is expensive for SMEs (small and medium-sized businesses). My impression at the time was that it was billed by the hour.&lt;/p&gt;

&lt;p&gt;What I really wanted, even back then, was to make distributed streaming cheap and simple enough for any developer to run on their own — not something only a large team with a dedicated ops org could afford. That's the same vision that later became d-engine's: distributed coordination should be cheap to run and simple to use.&lt;/p&gt;

&lt;p&gt;My own background has always been tied to messaging — I built an XMPP-based “online presence” component in Erlang early on, and later worked on a push notification platform — the same kind of system behind things like Google's push notification services (GCM). So I spent a year and a half building my own Kafka-style distributed event streaming platform in Go, starting with my own stream core. I got stuck at the consumer group part — it was just too slow, and looking back, some of that was probably just my own inexperience at the time. For the underlying distributed storage layer, I used embedded etcd rather than a standalone etcd cluster — I really liked that design. There were a lot of detours and research along the way.&lt;/p&gt;

&lt;p&gt;Before deciding to implement Raft myself, I looked at alternatives — TiKV, for example — but they all assumed a standalone cluster, and standing one up was just as expensive. That wasn't what I wanted. So after a while, I decided to start by implementing the Raft protocol myself. The hardest part wasn’t writing the code — it was proving the implementation actually matched the Raft paper, that it was correct, not just that it ran. So I spent a few months learning TLA+. TLA+ isn’t a programming language at all — it’s a formal specification language: you use math to abstract Raft's components and how they behave, then use a model checker to explore the reachable states of that model, verifying that things like leader election still hold up under concurrent events — within the bounded state space you define, not an exhaustive proof over every possible case. It's a white-box method, and later on I paired it with Jepsen, which tests the same kind of thing from the outside, as a black box.&lt;/p&gt;

&lt;p&gt;I respected &lt;a href="https://en.wikipedia.org/wiki/Leslie_Lamport" rel="noopener noreferrer"&gt;Leslie Lamport&lt;/a&gt;, the author of &lt;a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)" rel="noopener noreferrer"&gt;Paxos&lt;/a&gt;. He hosts &lt;a href="https://lamport.azurewebsites.net/video/videos.html" rel="noopener noreferrer"&gt;The TLA+ Video Course&lt;/a&gt;. What drew me in was how he taught — every lecture filmed in a different natural setting, and he kept changing his outfit. Small things, but they gave a dry subject some warmth and wonder, and I found myself looking forward to the next lecture every time. A few months later I had a model-checked design for d-engine’s Raft core, and only then did I start actually coding.&lt;/p&gt;

&lt;p&gt;I guess what I’m trying to say is: &lt;strong&gt;wanting&lt;/strong&gt; to do something always has a cost. And paying that cost is exactly how you find out how real your interest actually was.&lt;/p&gt;




&lt;h2&gt;
  
  
  Follow Nature
&lt;/h2&gt;

&lt;p&gt;If I had to point at one idea that quietly shapes most of d-engine's architecture, it's this: in nature, the things that go with the &lt;strong&gt;flow&lt;/strong&gt; — that work with nature's own rules instead of fighting them — take the least energy. I've felt this pattern again and again, and it's the same principle d-engine's architecture tries to follow.&lt;/p&gt;

&lt;p&gt;A good example is how d-engine drains its inbound event channel. I was stuck on a performance problem, and the moment I saw this pattern in the open-source world, I knew it was the answer — because I already carried an old story with me: &lt;a href="https://en.wikipedia.org/wiki/Dujiangyan_Irrigation_System" rel="noopener noreferrer"&gt;Li Bing and the Dujiangyan irrigation project&lt;/a&gt;. He didn't try to tame the flood by damming it; he worked with the flow instead, channeling it. Draining the channel follows the same idea: instead of pulling one event at a time, or blocking until a batch is "full," just drain whatever is sitting in the channel right now, up to a cap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;/// Drain all pending inbound events (up to max_batch_size).&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;drain_inbound_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.ctx.node_config.raft.batching.max_batch_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.event_rx&lt;/span&gt;&lt;span class="nf"&gt;.try_recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inbound_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buffered_inbound_event&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.inbound_event_oldest_pending_since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buffered_inbound_event&lt;/span&gt;&lt;span class="nf"&gt;.push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inbound_event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;break&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="nf"&gt;Ok&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;&lt;code&gt;try_recv&lt;/code&gt; never blocks — it just grabs what's already there. So when traffic is quiet, this loop exits almost instantly with a small batch. When traffic spikes, it keeps pulling, up to &lt;code&gt;max_batch_size&lt;/code&gt;, and the batch naturally grows. Nothing is throttled; the batch is simply capped at &lt;code&gt;max_batch_size&lt;/code&gt;, growing and shrinking with the flood.&lt;/p&gt;




&lt;h2&gt;
  
  
  See
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Seeing&lt;/strong&gt; the problem is hard too, especially in a distributed, async system. Inside a single millisecond, a huge number of things are happening at once — across machines, code paths, and the network. Concurrency has to preserve ordering and consistency, on top of coordinating Raft's own network layer. There's just too much going on at once. Making all of that complexity something you can actually &lt;em&gt;see&lt;/em&gt;, simply — that's the core of how I deal with architecture debugging.&lt;/p&gt;

&lt;p&gt;AI is being used more and more these days, and I've tested this myself: hand a pile of performance metrics to a model, and it's quick to catch small bugs — but in the face of this many variables moving at once, it tends to miss the one thing that actually matters most, the real design flaw. Spotting that still takes an experienced human eye; AI isn't there yet. So rather than wait for a model that can "see" for me, I built a few side tools that make all that complexity something I can see for myself — repeatably, almost boring in a good way.&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%2F4wxxfpuk3joz7kq3g6z7.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%2F4wxxfpuk3joz7kq3g6z7.png" alt="d-engine write path dashboard: one write's latency split into propose→commit, commit→apply and client side, with " width="800" height="943"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A diagnostic screenshot: 3 nodes and the load generator on one 8-core machine, data on a RAM disk. It shows where one write spends its time, not a benchmark result.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This kind of work pays off many times over — just stare at this chart for a while, let your mind drift, and ideas start popping into your head on their own. This is the view that grounds me; it’s how I actually figure out what to fix next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The hardest things in this world are often the simplest ones — and that's exactly why they're so hard to write about. A post like this can't really capture something simple; you only get there by practicing it, by living inside it for a while.&lt;/p&gt;

&lt;p&gt;I’ve been writing d-engine’s code for three or four years now, as a solo developer. It's been my way of putting how I understand the world into practice, one commit at a time. I hope it can give something back to the open-source community, even in a small way. Thanks for reading, and for the support.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>raft</category>
      <category>tlaplus</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>d-lmdb is now available as a Docker image: a 3-node cluster and failover demo</title>
      <dc:creator>Joshua Chi</dc:creator>
      <pubDate>Fri, 31 Jul 2026 01:00:00 +0000</pubDate>
      <link>https://dev.to/joshua_c/d-lmdb-is-now-available-as-a-docker-image-a-3-node-cluster-and-failover-demo-58pk</link>
      <guid>https://dev.to/joshua_c/d-lmdb-is-now-available-as-a-docker-image-a-3-node-cluster-and-failover-demo-58pk</guid>
      <description>&lt;p&gt;I’m happy to share &lt;code&gt;d-lmdb-server&lt;/code&gt; and the corresponding Docker image: &lt;a href="https://hub.docker.com/r/deventlab/d-lmdb" rel="noopener noreferrer"&gt;deventlab/d-lmdb&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
The code is here: &lt;a href="https://github.com/deventlab/d-lmdb" rel="noopener noreferrer"&gt;github.com/deventlab/d-lmdb&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to try it without reading the code first, you can now test it directly with Docker. It should take about 5 minutes to get started.&lt;/p&gt;

&lt;p&gt;What I wanted to do here is simple: turn an experimental distributed LMDB setup into something easier to try, so people can validate it quickly and give feedback more easily.&lt;/p&gt;

&lt;p&gt;If you try it and find problems, or think the docs, API, or UX can be improved, PRs and issues are very welcome.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. You can now run d-lmdb-server directly with Docker
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;d-lmdb-server&lt;/code&gt; can now run as a standalone HTTP service.&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quick single-node startup&lt;/li&gt;
&lt;li&gt;a 3-node HA cluster demo&lt;/li&gt;
&lt;li&gt;continued reads and writes after leader failover&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, the main point of this step is to let developers try it with the lowest possible cost, without embedding Rust code into their own applications.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Why build a Docker version
&lt;/h2&gt;

&lt;p&gt;When I look at infrastructure projects, I usually ask one very practical question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I run it in 5 minutes and see if it is a good fit for me?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Docker version is built for this use case.&lt;/p&gt;

&lt;p&gt;It is not meant to replace the library version, and it is not meant to add an unnecessary extra layer.&lt;br&gt;&lt;br&gt;
It is just a way to help people try it faster and start validating it sooner.&lt;/p&gt;

&lt;p&gt;If you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not sure yet whether you want to embed the library&lt;/li&gt;
&lt;li&gt;interested in a standalone service&lt;/li&gt;
&lt;li&gt;trying to test multi-node behavior and failover&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the Docker version may be easier to start with.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. One-sentence description
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;d-lmdb-server&lt;/code&gt; is a standalone HTTP service for distributed, LMDB-backed key-value storage.&lt;/p&gt;

&lt;p&gt;It is not trying to replace LMDB itself.&lt;br&gt;&lt;br&gt;
It is more like a demo of how to add a replicated write path to an existing embedded storage engine.&lt;/p&gt;

&lt;p&gt;If you prefer a shorter version:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reads stay local. Writes go through Raft. Each node still stores the full dataset.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reads still use the local node by default&lt;/li&gt;
&lt;li&gt;writes go through Raft consensus&lt;/li&gt;
&lt;li&gt;every node keeps the full dataset, so there is no sharding&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  4. How to try it
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Single node
&lt;/h3&gt;

&lt;p&gt;If you just want to see whether the service starts correctly, you can try the single-node setup first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; dlmdb 2&amp;gt;/dev/null   &lt;span class="c"&gt;# clean up from a previous run&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; dlmdb &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;-v&lt;/span&gt; dlmdb-data:/data deventlab/d-lmdb
&lt;span class="nb"&gt;sleep &lt;/span&gt;3                            &lt;span class="c"&gt;# wait for startup&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'PUT: %{http_code}\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT localhost:8080/kv/hello &lt;span class="nt"&gt;-d&lt;/span&gt; world
curl localhost:8080/kv/hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works, you should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PUT&lt;/code&gt; returns &lt;code&gt;204&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; returns &lt;code&gt;world&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no extra config needed for the first start.&lt;br&gt;&lt;br&gt;
A single-node TOML file will be generated automatically.&lt;/p&gt;

&lt;p&gt;If you want to use your own config, you can mount a custom &lt;code&gt;config.toml&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  3-node cluster
&lt;/h3&gt;

&lt;p&gt;If you want to see how it behaves in a multi-node setup, you can try the 3-node cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/deventlab/d-lmdb.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;d-lmdb
docker pull deventlab/d-lmdb:latest
docker tag deventlab/d-lmdb:latest d-lmdb-server:compose   &lt;span class="c"&gt;# compose uses this local tag&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;10   &lt;span class="c"&gt;# wait for leader election and health checks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can check the cluster status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose ps
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'node1: %{http_code}\n'&lt;/span&gt; localhost:18081/primary
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'node2: %{http_code}\n'&lt;/span&gt; localhost:18082/primary
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'node3: %{http_code}\n'&lt;/span&gt; localhost:18083/primary
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'PUT: %{http_code}\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT localhost:8080/kv/hello &lt;span class="nt"&gt;-d&lt;/span&gt; world
curl localhost:8080/kv/hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to test failover, you can kill the leader and keep writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; KILL node1   &lt;span class="c"&gt;# replace this with the actual leader node&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;5
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'PUT: %{http_code}\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT localhost:8080/kv/post-failover &lt;span class="nt"&gt;-d&lt;/span&gt; alive
curl localhost:8080/kv/post-failover
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I like most about this part is not that it looks complete, but that you can directly feel one thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;after the leader goes down, the client does not need to know that anything changed internally.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;compose/smoke-test.sh&lt;/code&gt; contains the full automated walkthrough, including leader crash and recovery checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. What it roughly does
&lt;/h2&gt;

&lt;p&gt;If I describe the architecture in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reads stay local. Writes go through Raft. Each node still stores the full dataset.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal here is not sharding or scaling out.&lt;br&gt;&lt;br&gt;
It is fault tolerance.&lt;/p&gt;

&lt;p&gt;In a way, it turns LMDB, which is a local embedded storage engine, into a system that can tolerate failures across multiple nodes.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. This is still 0.1.0, so feedback is very welcome
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;d-lmdb-server&lt;/code&gt; is still an early version.&lt;/p&gt;

&lt;p&gt;What I hope to learn from real usage is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the documentation is clear enough&lt;/li&gt;
&lt;li&gt;whether the API feels natural&lt;/li&gt;
&lt;li&gt;whether the failure cases are covered well enough&lt;/li&gt;
&lt;li&gt;whether the read/write semantics are easy to understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and find anything that can be improved, please open an issue.&lt;br&gt;&lt;br&gt;
If you find a bug or a clear way to improve the code or docs, PRs are also very welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Closing
&lt;/h2&gt;

&lt;p&gt;If you want to try it, you can pull the Docker image &lt;a href="https://hub.docker.com/r/deventlab/d-lmdb" rel="noopener noreferrer"&gt;deventlab/d-lmdb&lt;/a&gt; and give it a try.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;d-lmdb-server&lt;/code&gt; is still early-stage, and I hope it can be used more in real situations first, then improved based on feedback.&lt;/p&gt;

&lt;p&gt;Project page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/deventlab/d-lmdb" rel="noopener noreferrer"&gt;d-lmdb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Issues, PRs, and direct feedback are all very welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>d-lmdb: A Distributed LMDB Built on d-engine (Experimental)</title>
      <dc:creator>Joshua Chi</dc:creator>
      <pubDate>Sun, 26 Jul 2026 15:59:00 +0000</pubDate>
      <link>https://dev.to/joshua_c/d-lmdb-a-distributed-lmdb-built-on-d-engine-experimental-4c5</link>
      <guid>https://dev.to/joshua_c/d-lmdb-a-distributed-lmdb-built-on-d-engine-experimental-4c5</guid>
      <description>&lt;p&gt;I’m happy to open source &lt;a href="https://github.com/deventlab/d-lmdb" rel="noopener noreferrer"&gt;d-lmdb&lt;/a&gt;. This is still an experimental project. The goal is to try using d-engine’s distributed capability to “wrap” an existing KV store.&lt;/p&gt;

&lt;p&gt;There are already many Raft-based distributed KV systems in the community (many written in Go). d-engine itself is not a KV database, but in my actual work I do need a simple and reliable KV store. So I chose LMDB, which is well-known and stable, to run this experiment. If this approach works, I can also use it directly.&lt;/p&gt;

&lt;p&gt;After putting the architecture together, one thing became quite clear: adding a “consensus-based write path” to a single-node KV is actually feasible, and more straightforward than I initially expected. Of course, most of the complexity still comes from the nature of distributed systems vs single-node systems. More details will likely show up during real usage.&lt;/p&gt;




&lt;h2&gt;
  
  
  What d-lmdb is (and is not)
&lt;/h2&gt;

&lt;p&gt;d-lmdb is an experimental project. The core path is working, and it can be used for evaluation and testing, but it is not production-ready.&lt;/p&gt;

&lt;p&gt;It is also trying to answer a question: can &lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;d-engine&lt;/a&gt; be reused on top of an existing storage engine to provide a “replicated write path”? LMDB is the first working example. In theory, the same pattern should also work with other embedded storage engines (not fully verified yet).&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&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%2F80h5a95d74pslpvz765r.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%2F80h5a95d74pslpvz765r.png" alt="d-lmdb embedded architecture: the application uses d-lmdb, where writes go through the d-engine Raft layer and are replicated to peers, while reads directly access the local LMDB instance; LMDB is used for both the state machine and the Raft WAL" width="800" height="1397"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Simplified model
&lt;/h2&gt;

&lt;p&gt;d-engine runs in embedded mode, without a separate server process (each node is a peer process). Reads bypass the consensus layer and go directly to the local LMDB instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your process
    │
    ├─ db.get(key)      ────────────────► Local LMDB (sync, local read)
    │
    └─ db.put(key, val) ──► d-engine Raft ──► Local LMDB (async, replicated)
                             leader election
                             log replication
                             quorum commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Core idea
&lt;/h2&gt;

&lt;p&gt;This design adds a distributed write path to an existing storage engine.&lt;/p&gt;

&lt;p&gt;By default, get() reads locally without linearizability guarantees. If you need linearizable reads, d-lmdb also provides get_linearizable(), which is backed by d-engine’s ReadIndex / lease-read and goes through the Raft layer.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LMDB (before)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;txn&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// d-lmdb (after)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// still sync, still local read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unavoidable change is that writes become async:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="nf"&gt;.put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"user:1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;b"alice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="nf"&gt;.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"user:1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern — “local reads, consensus writes” — is what d-engine is trying to demonstrate. It is not tied to a specific KV implementation, but can be reused on top of other storage engines.&lt;/p&gt;




&lt;h2&gt;
  
  
  What d-lmdb solves
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Single point of failure → quorum fault tolerance&lt;br&gt;
Writes go through Raft. As long as a majority of nodes are alive, the system can continue serving. Native LMDB has no replication, and recovery from node failure can be difficult.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strong consistency across nodes (for writes)&lt;br&gt;
All nodes share the same linearizable write history. The only additional dependency is d-engine itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read performance is not affected&lt;br&gt;
&lt;code&gt;get()&lt;/code&gt; reads directly from local LMDB, so read latency is not impacted by replication. However, reads are not guaranteed to be linearizable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What d-lmdb does NOT solve
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write throughput will not improve&lt;br&gt;
A local LMDB write is one fsync. A d-lmdb write is a network round-trip plus quorum fsync. This trades latency and throughput for availability and consistency (which is expected in distributed systems).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No sharding&lt;br&gt;
d-engine replicates data but does not partition it. Each node stores the full dataset.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Still just a KV store&lt;br&gt;
Adding Raft does not add relational modeling or complex queries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operational complexity&lt;br&gt;
Raft introduces real operational concerns: leader election, membership changes, snapshots, log compaction, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;p&gt;d-lmdb is still in an early stage. The core functionality works and can be used for evaluation and experiments. Feedback is very welcome.&lt;/p&gt;

&lt;p&gt;It has not been validated in production yet, and both API and internal behavior may change.&lt;/p&gt;

&lt;p&gt;If you are looking for a production-ready distributed KV, this is not there yet. But if you want to see what it looks like to “add a replicated write path” to an existing storage engine, d-lmdb can serve as a concrete example.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final note
&lt;/h2&gt;

&lt;p&gt;d-lmdb is not the end goal. If you already have your own storage engine or application and want a similar model (“local reads + consensus writes”), you can use d-lmdb as a reference or starting point.&lt;/p&gt;

&lt;p&gt;d-lmdb itself is just one concrete implementation of that pattern. The more general part lives in &lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;d-engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/deventlab/d-lmdb" rel="noopener noreferrer"&gt;https://github.com/deventlab/d-lmdb&lt;/a&gt;&lt;br&gt;
Issues and PRs are welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>database</category>
      <category>distributedsystems</category>
      <category>opensource</category>
    </item>
    <item>
      <title>d-engine: Embedded Distributed Coordination for Rust Apps</title>
      <dc:creator>Joshua Chi</dc:creator>
      <pubDate>Sat, 25 Jul 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/joshua_c/d-engine-embedded-distributed-coordination-for-rust-apps-26o0</link>
      <guid>https://dev.to/joshua_c/d-engine-embedded-distributed-coordination-for-rust-apps-26o0</guid>
      <description>&lt;p&gt;If you're writing a backend service in Rust and need a bit of distributed consistency ability — leader election, state synchronization, simple configuration coordination — but don't want to pull in a separate system to deploy and operate, this post is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is d-engine right for you
&lt;/h2&gt;

&lt;p&gt;If your service already runs in the Rust ecosystem and you want to embed coordination directly into your application, rather than deploying a separate piece of middleware, d-engine is worth a look. It fits leader election, state synchronization, lightweight coordination, and scenarios where you want to start with a single node and grow to a cluster only when you need to.&lt;/p&gt;

&lt;p&gt;If your team values &lt;strong&gt;simplicity and low operational overhead&lt;/strong&gt; over standing up a full distributed infrastructure stack from day one, d-engine's design is aimed at you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is d-engine
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;d-engine&lt;/a&gt; (&lt;a href="https://crates.io/crates/d-engine" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;) does one thing: it's a distributed coordination engine. It's not a message queue, not a database — it's a foundational component, and the goal is to make coordination clearer and more reliable.&lt;/p&gt;

&lt;p&gt;DEventLab's founding motivation is to make distributed systems development cheap. "Cheap" here isn't just about running on inexpensive hardware — it's about low cost to adopt, low cost to set up, and low cost to maintain over time.&lt;/p&gt;

&lt;p&gt;Making something "cheap" for developers to use isn't easy. The principles behind d-engine's development reflect that: keep the architecture simple, avoid complexity unless it's earned, keep long-term maintenance cost low, and keep the code and comments easy to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple architecture: state changes are processed serially
&lt;/h2&gt;

&lt;p&gt;d-engine's Raft core state machine runs on a single logical execution path. Every state change — term changes, log writes, commit index advancement — is serialized. Network I/O and disk I/O can happen concurrently, but the state-change step itself is always processed in order, one at a time. There's no scenario where two operations race to modify the same state.&lt;/p&gt;

&lt;p&gt;That means you don't need to reason about complex multi-threaded interleavings in your head — most of the time, you can just follow the log and see where the queue currently is. That makes debugging and reasoning about consistency more direct.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it embeds into your application
&lt;/h2&gt;

&lt;p&gt;In embedded mode, d-engine compiles directly into your existing Rust application as a local library — no separate deployment, no extra distributed system to operate. From your application's point of view, it behaves more like an embedded coordination layer than an external service you have to babysit.&lt;/p&gt;

&lt;p&gt;Your application &lt;em&gt;is&lt;/em&gt; a Raft node. Coordination stops being an external dependency and becomes part of the service itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer architecture diagram
&lt;/h2&gt;

&lt;p&gt;The diagram below shows the layers a developer actually touches: &lt;code&gt;EmbeddedClient&lt;/code&gt; on top, the d-engine core in the middle, storage and consensus underneath. Strongly consistent reads/writes and weakly consistent fast reads take different paths — that's one of the key points in the development experience.&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%2Fnngzpoijbu6oxotvzxql.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnngzpoijbu6oxotvzxql.jpg" alt="d-engine embedded architecture: EmbeddedClient sits above the d-engine core (Raft consensus engine, pluggable state machine, pluggable WAL storage), which replicates over gRPC to peer nodes" width="800" height="1145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting with a single node
&lt;/h2&gt;

&lt;p&gt;You can start with a single node to validate your logic, then scale out to a multi-node cluster once you actually need high availability — without rebuilding anything. For early-stage projects, this path tends to work better: validate the business logic first, then add system complexity incrementally.&lt;/p&gt;

&lt;p&gt;If you want to see it in practice, check out the &lt;a href="https://github.com/deventlab/d-engine/tree/main/examples/single-node-expansion" rel="noopener noreferrer"&gt;single-node-expansion example&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-node deployment example
&lt;/h2&gt;

&lt;p&gt;If you need a multi-node deployment, &lt;a href="https://docs.rs/d-engine/latest/d_engine/docs/examples/ha_deployment_load_balancing/index.html" rel="noopener noreferrer"&gt;the examples demonstrate one reference pattern&lt;/a&gt;: a 3-node cluster behind a load balancer, with writes routed to the leader, reads spread across all nodes, and the load balancer detecting each node's role through health-check endpoints.&lt;/p&gt;

&lt;p&gt;To be clear about the boundaries here: this is a pattern we demonstrate in the examples for reference — it hasn't been validated in production. Actual failover time and data-safety guarantees depend on your deployment environment, network conditions, and load, so you should test it in your own setting.&lt;/p&gt;

&lt;p&gt;The division of labor in one sentence: the load balancer handles routing, d-engine handles consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not limited to Rust
&lt;/h2&gt;

&lt;p&gt;If your team doesn't use Rust, standalone mode lets you connect to a d-engine cluster over gRPC. See the &lt;a href="https://github.com/deventlab/d-engine/tree/main/examples/quick-start-standalone" rel="noopener noreferrer"&gt;quick-start-standalone example&lt;/a&gt; (Go client + 3-node cluster). That way you don't have to pull Raft logic into your whole stack — you can use the coordination layer as an independent piece of infrastructure instead.&lt;/p&gt;

&lt;p&gt;This tends to work better for cross-language teams, and it's easier to adopt incrementally into an existing system.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is just the first step
&lt;/h2&gt;

&lt;p&gt;d-engine is still growing, and we don't want to claim it's the answer to every distributed coordination problem. The more honest framing: this post explains what it is, how to use it, and what kind of developer it's a good fit for.&lt;/p&gt;

&lt;p&gt;If you want to try it hands-on, start with the &lt;a href="https://github.com/deventlab/d-engine/tree/main/examples" rel="noopener noreferrer"&gt;examples directory&lt;/a&gt; — everything from a 5-minute quick start to a 3-node cluster. If you're evaluating a similar distributed coordination solution, feel free to open an &lt;a href="https://github.com/deventlab/d-engine/issues" rel="noopener noreferrer"&gt;issue on GitHub&lt;/a&gt; and tell us about your use case.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>distributedsystems</category>
      <category>opensource</category>
      <category>software</category>
    </item>
    <item>
      <title>d-engine: A Lightweight Distributed Coordination Engine for Rust</title>
      <dc:creator>Joshua Chi</dc:creator>
      <pubDate>Fri, 16 Jan 2026 03:57:20 +0000</pubDate>
      <link>https://dev.to/joshua_c/d-engine-a-lightweight-distributed-coordination-engine-for-rust-210j</link>
      <guid>https://dev.to/joshua_c/d-engine-a-lightweight-distributed-coordination-engine-for-rust-210j</guid>
      <description>&lt;p&gt;A lightweight Raft implementation designed for embedding into Rust applications — the consensus layer for building reliable distributed systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with a simple vision&lt;/strong&gt;: make distributed coordination accessible - cheap to run, simple to use. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built on a core philosophy&lt;/strong&gt;: choose simple architectures over complex ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;In my experience, adding distributed coordination to applications was always expensive – existing solutions like etcd are either too slow when embedded (gRPC overhead) or require running separate 3-node clusters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;d-engine&lt;/a&gt; aims to change this: make coordination cheap enough that you don't hesitate to add it when you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single-threaded event loop:&lt;/strong&gt; No race conditions, strict ordering. CPU-bound on single core—scale horizontally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role separation (SRP):&lt;/strong&gt; Leader/Follower/Candidate/Learner each handle their own logic. Main loop just routes events.&lt;/p&gt;

&lt;p&gt;Standard design for Raft implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Integration Modes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Embedded mode&lt;/strong&gt; - runs inside your Rust process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;EmbeddedEngine&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="nf"&gt;.wait_ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="nf"&gt;.client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"key"&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;b"value"&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;0.1ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Direct memory access via mpsc channels (no serialization)&lt;/li&gt;
&lt;li&gt;&amp;lt;0.1ms latency for local operations&lt;/li&gt;
&lt;li&gt;Single binary deployment&lt;/li&gt;
&lt;li&gt;Benchmark: 203K writes/s, 279K reads/s (M2 Mac)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Standalone mode&lt;/strong&gt; - separate server process with gRPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="n"&gt;d-engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;default-features&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Language-agnostic (Go, Python, Java, Rust)&lt;/li&gt;
&lt;li&gt;Independent scaling&lt;/li&gt;
&lt;li&gt;Benchmark: 64K writes/s, 12K reads/s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose based on your language and latency requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;Embedded mode (3-node cluster, M2 Mac localhost):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;203K writes/s, 279K reads/s (linearizable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standalone mode (gRPC):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;64K writes/s, 12K reads/s (linearizable)&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F1mzw06weicknyej2vcef.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.amazonaws.com%2Fuploads%2Farticles%2F1mzw06weicknyej2vcef.png" alt="d-engine_bench_comparison_v0.2" width="799" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lab numbers only—production performance varies by workload and hardware.&lt;br&gt;
Full details in &lt;a href="https://github.com/deventlab/d-engine/tree/main/benches/embedded-bench/reports/v0.2.2" rel="noopener noreferrer"&gt;benches/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Design: Traits for Extension
&lt;/h2&gt;

&lt;p&gt;d-engine provides working implementations (RocksDB storage, KV operations, gRPC transport). When defaults don't fit, implement the traits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;StorageEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LogStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LogStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MetaStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MetaStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;StateMachine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;apply_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Examples in repo show Sled storage backend, custom HTTP handlers with HAProxy for HA deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency Model
&lt;/h2&gt;

&lt;p&gt;Three read policies (configurable per-operation or server-wide):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LinearizableRead&lt;/strong&gt;: Strongest guarantee, verify quorum (~2ms)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeaseRead&lt;/strong&gt;: Leader lease-based, fast path (~0.3ms, requires NTP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventualConsistency&lt;/strong&gt;: Local read, may be stale (~0.1ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trade-offs documented. Defaults are sane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Simple, Scale When Needed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Start with 1 node (auto-elected leader)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;EmbeddedEngine&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Scale to 3 nodes later: update config, zero code changes&lt;/span&gt;
&lt;span class="c1"&gt;// See examples/single-node-expansion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Kubernetes, no complex setup. Just Rust + config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Is
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Raft consensus implementation&lt;/li&gt;
&lt;li&gt;Pluggable storage (RocksDB, Sled, custom)&lt;/li&gt;
&lt;li&gt;Flexible consistency (linearizable/lease/eventual)&lt;/li&gt;
&lt;li&gt;Production-ready core, API stabilizing toward v1.0&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current State &amp;amp; Direction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Version 0.2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Core Raft engine is solid (1000+ tests, d-engine 0.1.x version Jepsen validated).&lt;br&gt;
APIs are stabilizing toward v1.0. Pre-1.0 means breaking changes are possible&lt;br&gt;
if they improve design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No production users yet&lt;/strong&gt; – looking for early adopters to validate real-world use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future Direction:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud-native storage backends (Cloudflare, AWS, GCP)&lt;/li&gt;
&lt;li&gt;Exploring: etcd-compatible API layer&lt;/li&gt;
&lt;li&gt;Timeline depends on community feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;d-engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/deventlab/d-engine" rel="noopener noreferrer"&gt;https://github.com/deventlab/d-engine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.rs/d-engine" rel="noopener noreferrer"&gt;https://docs.rs/d-engine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Examples: &lt;a href="https://github.com/deventlab/d-engine/tree/main/examples" rel="noopener noreferrer"&gt;https://github.com/deventlab/d-engine/tree/main/examples&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building distributed systems in Rust and need consensus, the code is there. Examples show embedded mode, standalone mode, custom storage, HA deployments with HAProxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for Early Adopters
&lt;/h2&gt;

&lt;p&gt;I'm looking for Rust developers building distributed systems who have &lt;strong&gt;real problems to solve&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coordination bottlenecks in your architecture (slow consensus, expensive etcd clusters)&lt;/li&gt;
&lt;li&gt;Specific use cases where strong consistency matters (leader election, distributed locks, metadata stores)&lt;/li&gt;
&lt;li&gt;Production deployments where you need cheap, simple, and reliable coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Particularly interested in:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams with real coordination problems (not just kicking the tires)&lt;/li&gt;
&lt;li&gt;Cloud-native scenarios (Cloudflare Workers, AWS Lambda, serverless)&lt;/li&gt;
&lt;li&gt;Cost-sensitive use cases where etcd feels like overkill&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I'm offering:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free architecture review for your use case&lt;/li&gt;
&lt;li&gt;Direct access to roadmap planning&lt;/li&gt;
&lt;li&gt;Quick response to GitHub issues/questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a specific problem: &lt;a href="https://github.com/deventlab/d-engine/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; with your use case, or &lt;a href="mailto:deventlab@gmail.com"&gt;reach out directly&lt;/a&gt;.&lt;br&gt;
Show me what's broken, what's expensive, what's too complex. Let's see if d-engine can solve it cheaply.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;License&lt;/strong&gt;: MIT or Apache-2.0 | &lt;strong&gt;Platforms&lt;/strong&gt;: Linux, macOS | &lt;strong&gt;MSRV&lt;/strong&gt;: Rust 1.88&lt;/p&gt;

</description>
      <category>raft</category>
      <category>rust</category>
      <category>distributedsystems</category>
      <category>consensus</category>
    </item>
  </channel>
</rss>
