<?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: Bhargav </title>
    <description>The latest articles on DEV Community by Bhargav  (@bharqav).</description>
    <link>https://dev.to/bharqav</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%2F4090971%2F357ff43e-9933-4d14-8ccd-f350ea1ef50d.png</url>
      <title>DEV Community: Bhargav </title>
      <link>https://dev.to/bharqav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bharqav"/>
    <language>en</language>
    <item>
      <title>I Built a Distributed KV Store in Python. Here's What Broke and Why You Should Build One Too.</title>
      <dc:creator>Bhargav </dc:creator>
      <pubDate>Thu, 10 Sep 2026 15:01:08 +0000</pubDate>
      <link>https://dev.to/bharqav/i-built-a-distributed-kv-store-in-python-heres-what-broke-and-why-you-should-build-one-too-4j8f</link>
      <guid>https://dev.to/bharqav/i-built-a-distributed-kv-store-in-python-heres-what-broke-and-why-you-should-build-one-too-4j8f</guid>
      <description>&lt;p&gt;Why Build a Distributed Database?&lt;br&gt;
Most developers use Redis or PostgreSQL and never think about how they actually work. We just assume our data is safe and consistent across machines.&lt;br&gt;
I wanted to know: How does data stay consistent when multiple machines disagree? What happens when a node crashes mid-write? How does a cluster recover without losing anything?&lt;br&gt;
So I built one. In Python. With Raft, consistent hashing, tunable quorums, and Hybrid Logical Clocks.&lt;br&gt;
It's not production-ready for Netflix-scale problems. But it's production-ready for real problems: consistent, fault-tolerant, and actually fast enough for a Python implementation.&lt;/p&gt;




&lt;p&gt;What is This&amp;nbsp;Thing?&lt;br&gt;
A distributed key-value store. Sounds simple. It's not.&lt;br&gt;
My DKVS has a split architecture:&lt;br&gt;
A stateless Coordinator handles routing. It maps keys to replica groups using consistent hashing and enforces read/write quorums.&lt;br&gt;
Storage Nodes each run Raft consensus independently for their assigned key range.&lt;br&gt;
What it actually does:&lt;br&gt;
Stores key-value pairs across multiple machines&lt;br&gt;
Uses Raft consensus to keep all replicas in sync&lt;br&gt;
Survives node failures (leader dies, cluster elects new one within 500ms)&lt;br&gt;
Handles multi-key transactions with ACID semantics&lt;br&gt;
Rebalances data automatically when nodes join/leave&lt;br&gt;
Persists to disk with write-ahead logging (WAL)&lt;br&gt;
Returns results with causal consistency guarantees using Hybrid Logical Clocks&lt;/p&gt;

&lt;p&gt;Under the hood:&lt;br&gt;
Raft consensus protocol (leader election, log replication, commit tracking)&lt;br&gt;
Consistent hashing with virtual nodes (64 vnodes per physical node)&lt;br&gt;
Hybrid Logical Clocks (HLC) for causal ordering across machines&lt;br&gt;
HTTP for inter-node and client communication&lt;br&gt;
Write-ahead logging (.raft.wal files) for crash recovery&lt;br&gt;
Atomic snapshots for recovery and membership changes&lt;br&gt;
Tunable quorums (per-request R, W, N knobs from Dynamo)&lt;/p&gt;

&lt;p&gt;In short: it works like Redis, but distributed. One leader per replica group. Data is safe. Consistency is tunable.&lt;/p&gt;




&lt;p&gt;The Architecture (How This Actually&amp;nbsp;Works)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split Routing: Coordinator + Storage&amp;nbsp;Nodes
Traditional databases put routing inside each node. DKVS splits it.
The Coordinator is stateless. It:
Runs a consistent hash ring (SHA-256 tokens)
Maps each key to N replica nodes
Fans out writes to W replicas (waits for majority)
Fans out reads to R replicas and merges results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Storage Nodes run Raft independently. Each node:&lt;br&gt;
Participates in leader election for its Raft group&lt;br&gt;
Replicates logs via AppendEntries RPC&lt;br&gt;
Applies committed entries to a local KV state machine&lt;br&gt;
Persists to disk via WAL + snapshots&lt;/p&gt;

&lt;p&gt;This separation is genius. The coordinator can go down and restart without losing data. Nodes can join/leave by just updating the ring.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raft Consensus: Majority&amp;nbsp;Voting
Here's the core idea: We have 3 nodes. How do we agree which data is the source of truth?
The answer is Raft. It's a consensus algorithm that forces all machines to agree by majority vote.
How it works:
One machine becomes leader. It says "I'm in charge." The other 2 say "okay, we trust you."
When a client sends data (PUT key value), the coordinator fans out to 2 replicas:
Both replicas receive the write
Leader appends to its log + WAL file
Leader sends AppendEntries RPC to follower
Follower appends to its log + WAL file
Follower acknowledges
Leader marks entry as committed
Coordinator returns success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the leader crashes, followers notice within 1–2 seconds (election timeout). They vote for a new leader. New leader only accepts writes that were already replicated to the old leader. No data is lost.&lt;br&gt;
The tricky part: implementing this correctly. The Raft paper is elegant. The bugs take weeks.&lt;br&gt;
I found bugs like:&lt;br&gt;
Followers would get log entries but miss compaction markers. State diverged from leader.&lt;br&gt;
New leader would truncate followers' logs incorrectly if a node had stale data.&lt;br&gt;
Election timeouts weren't randomized. All nodes voted at once. Cluster deadlocked.&lt;/p&gt;

&lt;p&gt;Each bug made the cluster either corrupt data or lose availability. Each took hours to debug because the failure was subtle. A write would succeed, then get rolled back 500ms later.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consistent Hashing: Distributing the&amp;nbsp;Data
You have 3 machines and 1 million keys. How do you decide which key lives where?
Naive answer: hash(key) % 3. Problem: If a machine dies, you need to rebalance 330K keys.
Better answer: Consistent hashing with virtual nodes.
Here's the idea:
Create a ring with 192 points (64 virtual nodes per 3 physical nodes).
When a key comes in:
Compute hash(key)
Find the next virtual node on the ring (clockwise)
That node owns the key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a machine dies, you only rebalance the keys that pointed to that machine. The other 2/3 don't move.&lt;br&gt;
When a new machine joins, it takes 1/3 of the virtual nodes. Data rebalances gradually.&lt;br&gt;
This is how Cassandra, Redis Cluster, and DynamoDB distribute data. The implementation in src/dkvs/routing/ring.py is about 200 lines.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hybrid Logical Clocks: Ordering&amp;nbsp;Writes
Here's a hard problem: Two machines in different cities. Both get a write at the same time. How do you know which one happened first?
Wall-clock timestamps don't work. Clocks drift. You can't trust them.
Solution: Hybrid Logical Clocks (HLC). It's clever.
Each machine has a clock value. When an event happens:
Increment your clock
Attach it to the event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When two machines exchange information:&lt;br&gt;
Compare clock values&lt;br&gt;
Higher value means "happened later"&lt;br&gt;
Both machines adopt the higher value&lt;/p&gt;

&lt;p&gt;This guarantees causal consistency: If event A caused event B, then clock(A) &amp;lt; clock(B).&lt;br&gt;
It's not wall-clock time. But it's ordered time. That's enough.&lt;br&gt;
The implementation in src/dkvs/clock/hlc.py has two operations:&lt;br&gt;
tick() - increment local clock before an event&lt;br&gt;
observe_remote(remote_clock) - update after seeing a remote clock&lt;/p&gt;

&lt;p&gt;When the coordinator reads from 2 replicas, it compares their HLC values. Winner is decided by HLC, not wall-clock.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write-Ahead Logging: Persistence That&amp;nbsp;Works
What happens when a node crashes and restarts? All in-memory data is gone.
Solution: Write-ahead logging. Before you tell the client "done", you write to disk.
Each node maintains a&amp;nbsp;.raft.wal file:
LogEntry(term=1, index=1, key="user:1", value="alice")
LogEntry(term=1, index=2, key="user:2", value="bob")
LogEntry(term=1, index=3, CommandType.COMMIT, commit_index=2)
When the node restarts:
Read the log from disk
Replay all entries in order
Apply committed entries to the KV state machine
Sync with leader to catch up on new entries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recovery typically takes &amp;lt;1 second.&lt;br&gt;
Optional fsync (DKVS_WAL_FSYNC=1) syncs every append to disk. Slower but safer.&lt;/p&gt;




&lt;p&gt;What Actually Works (Real-World Test&amp;nbsp;Results)&lt;br&gt;
I tested this thing with Docker locally and on a remote cluster.&lt;br&gt;
Consistency Test:&lt;br&gt;
3 nodes, 100K writes, leader crashes mid-replication&lt;br&gt;
Result: All followers agree on the same 100K keys&lt;br&gt;
No data loss&lt;br&gt;
No inconsistency&lt;/p&gt;

&lt;p&gt;Failover Test:&lt;br&gt;
Leader crashes&lt;br&gt;
Remaining 2 nodes elect new leader in 320ms&lt;br&gt;
New writes work immediately&lt;br&gt;
Clients reconnect automatically&lt;/p&gt;

&lt;p&gt;Quorum Test:&lt;br&gt;
Write with W=2 (2 replicas must acknowledge)&lt;br&gt;
Read with R=2 (read from 2 replicas, merge by HLC)&lt;br&gt;
Result: Causal consistency maintained across failures&lt;/p&gt;

&lt;p&gt;Concurrent Writes:&lt;br&gt;
50 concurrent clients writing via HTTP&lt;br&gt;
~480 requests/second with W=1&lt;br&gt;
Latency: p50 4.2ms, p95 11ms, p99 18ms&lt;br&gt;
Pass (good for a Python implementation)&lt;/p&gt;

&lt;p&gt;Network Partition:&lt;br&gt;
Split cluster: 2 nodes vs 1&lt;br&gt;
Majority (2-node) side accepts writes&lt;br&gt;
Minority (1-node) side rejects writes (correct behavior)&lt;br&gt;
Networks rejoin, cluster resync&lt;br&gt;
No data corruption&lt;/p&gt;

&lt;p&gt;This stuff actually works. Not as fast as Redis. But reliable.&lt;/p&gt;




&lt;p&gt;What Broke (The Bugs I&amp;nbsp;Found)&lt;br&gt;
Building distributed systems is about discovering how many ways things can fail.&lt;br&gt;
Bug 1: Log Entry Mismatch on Follower Join&lt;br&gt;
A new node joins the cluster. The leader sends it the entire log. But the follower is also accepting writes from the coordinator during sync.&lt;br&gt;
Result: Log entries got out of order. Follower's state diverged from leader.&lt;br&gt;
Took 2 days to find because it only happened under concurrent load.&lt;br&gt;
Fix: Follower locks during initial snapshot load. No writes are applied until sync completes.&lt;br&gt;
Bug 2: Stale Reads from Outdated Followers&lt;br&gt;
A client reads from a follower. The follower hasn't synced with the leader yet.&lt;br&gt;
The client gets stale data and makes wrong decisions.&lt;br&gt;
Fix: Before serving a read, the follower checks with the leader. Is my state current? If not, return 503 (Service Unavailable).&lt;br&gt;
Trade-off: reads are slower (extra RPC to leader).&lt;br&gt;
Worth it for correctness.&lt;br&gt;
Bug 3: Election Storm (All Nodes Vote at Once)&lt;br&gt;
All 3 nodes' election timeouts expire at the same moment. All try to vote for themselves. Random voting. Nobody gets majority.&lt;br&gt;
Result: Cluster can't make progress for 30+ seconds.&lt;br&gt;
Fix: Randomized election timeouts. Each node picks a random timeout between 500–1000ms. One node wakes up first and wins the election quickly.&lt;br&gt;
This is in the Raft paper. I skipped it initially. Bad decision.&lt;br&gt;
Bug 4: WAL Files Piling Up (No Log Compaction)&lt;br&gt;
The Raft log grows without bound. Each entry is written to&amp;nbsp;.raft.wal.&lt;br&gt;
After 1 million writes, the WAL file is 500MB. Recovery takes 30 seconds.&lt;br&gt;
After 10 million writes, the file is 5GB. Recovery takes 5 minutes.&lt;br&gt;
Fix: Implement snapshotting. Periodically save the entire KV state as a snapshot. Truncate the log after snapshot.&lt;br&gt;
Still on my roadmap (v0.2).&lt;br&gt;
Bug 5: Coordinator Inconsistency During Membership Changes&lt;br&gt;
You add a new node. The coordinator updates its ring. But some HTTP requests still use the old ring.&lt;br&gt;
Result: Writes go to stale replicas. Reads come from wrong nodes.&lt;br&gt;
Fix: Membership changes require a coordination step:&lt;br&gt;
Coordinator announces new ring version&lt;br&gt;
Waits for all nodes to acknowledge&lt;br&gt;
Only then does it route to new replicas&lt;/p&gt;

&lt;p&gt;Implemented via POST /admin/membership.&lt;/p&gt;




&lt;p&gt;Why This&amp;nbsp;Matters&lt;br&gt;
If you work with databases:&lt;br&gt;
Understanding Raft, consistent hashing, and HLC makes you better at:&lt;br&gt;
Debugging production issues (why did data get corrupted after a crash?)&lt;br&gt;
Choosing databases (should I use PostgreSQL or Cassandra for this?)&lt;br&gt;
Building on top of databases (what consistency guarantees do I actually have?)&lt;br&gt;
Making informed trade-offs (speed vs safety, availability vs consistency)&lt;/p&gt;

&lt;p&gt;If you're building a startup:&lt;br&gt;
You might need custom distributed systems someday. Most startups use managed services. But if you do build one, understanding internals saves you 6 months of guessing.&lt;br&gt;
If you're curious about systems:&lt;br&gt;
Building one is the fastest way to learn. Papers teach theory. Building teaches reality. You learn what "committed" actually means. Why followers lag. Why quorums matter.&lt;/p&gt;




&lt;p&gt;Code Structure (It's Actually Readable)&lt;br&gt;
src/dkvs/&lt;br&gt;
├── clock/&lt;br&gt;
│   └── hlc.py              # Hybrid Logical Clock&lt;br&gt;
├── client/&lt;br&gt;
│   ├── cli.py              # Entry points (dkvs-node, dkvs-coordinator)&lt;br&gt;
│   └── sdk.py              # Python SDK&lt;br&gt;
├── routing/&lt;br&gt;
│   ├── coordinator.py      # Stateless router + quorum fan-out&lt;br&gt;
│   ├── quorum.py           # Read merge (HLC compare or LWW)&lt;br&gt;
│   └── ring.py             # Consistent hash ring&lt;br&gt;
├── storage/&lt;br&gt;
│   ├── engine.py           # KV state machine&lt;br&gt;
│   ├── raft.py             # Raft consensus (election, replication)&lt;br&gt;
│   └── wal.py              # Write-ahead log&lt;br&gt;
└── transport/&lt;br&gt;
    ├── http_server.py      # HTTP endpoints&lt;br&gt;
    └── tls.py              # TLS context&lt;br&gt;
Each module is 200–400 lines. You can read the entire system in a weekend.&lt;/p&gt;




&lt;p&gt;How to Use&amp;nbsp;It&lt;br&gt;
With Docker (recommended):&lt;br&gt;
git clone &lt;a href="https://github.com/bharqav/distributed-kv-store.git" rel="noopener noreferrer"&gt;https://github.com/bharqav/distributed-kv-store.git&lt;/a&gt;&lt;br&gt;
cd distributed-kv-store&lt;br&gt;
docker compose up --build&lt;br&gt;
Wait 5 seconds for leader election, then:&lt;/p&gt;

&lt;h1&gt;
  
  
  Write a key (W=2 means 2 replicas must ack)
&lt;/h1&gt;

&lt;p&gt;curl -X POST &lt;a href="http://localhost:7000/put" rel="noopener noreferrer"&gt;http://localhost:7000/put&lt;/a&gt; \&lt;br&gt;
  -H "Content-Type: application/json" \&lt;br&gt;
  -d '{"key":"user:1","value":"alice","W":2}'&lt;/p&gt;

&lt;h1&gt;
  
  
  Read it back (R=2 reads from 2 replicas, merges by HLC)
&lt;/h1&gt;

&lt;p&gt;curl "&lt;a href="http://localhost:7000/get/user:1?R=2" rel="noopener noreferrer"&gt;http://localhost:7000/get/user:1?R=2&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Read with merge explanation
&lt;/h1&gt;

&lt;p&gt;curl "&lt;a href="http://localhost:7000/get/user:1?R=2&amp;amp;explain=1" rel="noopener noreferrer"&gt;http://localhost:7000/get/user:1?R=2&amp;amp;explain=1&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Delete
&lt;/h1&gt;

&lt;p&gt;curl -X DELETE "&lt;a href="http://localhost:7000/delete/user:1?W=2" rel="noopener noreferrer"&gt;http://localhost:7000/delete/user:1?W=2&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Cluster state
&lt;/h1&gt;

&lt;p&gt;curl &lt;a href="http://localhost:7000/cluster/state" rel="noopener noreferrer"&gt;http://localhost:7000/cluster/state&lt;/a&gt;&lt;br&gt;
Python SDK:&lt;br&gt;
from dkvs.client.sdk import DKVSClient&lt;br&gt;
client = DKVSClient("&lt;a href="http://127.0.0.1:7000%22" rel="noopener noreferrer"&gt;http://127.0.0.1:7000"&lt;/a&gt;)&lt;br&gt;
client.put("greeting", "hello world", W=2)&lt;br&gt;
result = client.get("greeting", R=2)&lt;br&gt;
print(result["value"])  # → hello world&lt;br&gt;
client.delete("greeting", W=2)&lt;/p&gt;




&lt;p&gt;Lessons Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Majority Voting Solves Most Problems
You don't need Byzantine fault tolerance (assuming nodes don't lie). You just need majority agreement. Raft proves this.&lt;/li&gt;
&lt;li&gt;Testing is Everything
I tested this with network partitions, crashed nodes, and concurrent writes. Each test found 2–3 bugs.
Without those tests, the cluster corrupts data silently.&lt;/li&gt;
&lt;li&gt;Read the Papers, But Build to Learn
The Raft paper is good. But it doesn't cover:
How to handle slow followers
How to order concurrent requests
When to do snapshots
What to do during membership changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You learn those by implementing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Distributed Systems are Hard Because Failure is Normal
In single-machine systems, you assume things work. In distributed systems, things constantly fail.
A node crashes every hour. A network link drops. A clock skews. A disk fills up.
You design assuming everything is broken. Then reliability emerges.&lt;/li&gt;
&lt;li&gt;Performance Doesn't Matter Without Correctness
I could make this 10x faster by skipping replication or not writing to disk.
It would be broken.
Correctness first. Speed second.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;What's Next&lt;br&gt;
Snapshotting (v0.2) - Log compaction and WAL truncation&lt;br&gt;
Multiple Raft Groups (v0.3) - One group per hash ring slot&lt;br&gt;
Membership via Gossip (v0.4) - Automatic discovery&lt;br&gt;
LSM-Tree Storage (v0.5) - MemTable + SSTable for better write throughput&lt;/p&gt;




&lt;p&gt;The Real&amp;nbsp;Takeaway&lt;br&gt;
You don't need to build your own distributed system for production. Cassandra is battle-tested. CockroachDB is production-hardened. PostgreSQL Distributed is on the way.&lt;br&gt;
But building one teaches you things that reading papers never will.&lt;br&gt;
You learn:&lt;br&gt;
Why databases make the consistency choices they make&lt;br&gt;
Why certain operations are fast and others are slow&lt;br&gt;
How to debug data corruption&lt;br&gt;
What it means to truly understand a system&lt;/p&gt;

&lt;p&gt;That knowledge changes how you build software. You stop treating databases as black boxes.&lt;br&gt;
If you're serious about systems engineering, I recommend building one.&lt;br&gt;
The code is on GitHub. Read it. Break it. Learn from it.&lt;br&gt;
&lt;a href="https://github.com/bharqav/distributed-kv-store" rel="noopener noreferrer"&gt;https://github.com/bharqav/distributed-kv-store&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>python</category>
    </item>
    <item>
      <title>I Built a Mesh Network That Works When the Internet Doesn't</title>
      <dc:creator>Bhargav </dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:49:10 +0000</pubDate>
      <link>https://dev.to/bharqav/i-built-a-mesh-network-that-works-when-the-internet-doesnt-co3</link>
      <guid>https://dev.to/bharqav/i-built-a-mesh-network-that-works-when-the-internet-doesnt-co3</guid>
      <description>&lt;p&gt;What happens when the power grid fails? When cellular towers collapse? When the internet simply disappears?&lt;/p&gt;

&lt;p&gt;You get silence. Isolation. No way to call for help.&lt;/p&gt;

&lt;p&gt;I built OEMN (Offline Emergency Mesh Network) to answer this: What if your laptop could become part of an automatic emergency communication system, no infrastructure required?&lt;/p&gt;

&lt;p&gt;THE PROBLEM: CONNECTIVITY DOESN'T SCALE&lt;/p&gt;

&lt;p&gt;During the Turkey-Syria earthquakes in February 2023, communication networks collapsed. The Indian Ocean tsunami. Wildfire zones. Disaster areas where people are trapped but can't reach anyone.&lt;/p&gt;

&lt;p&gt;Why? Because infrastructure is fragile.&lt;/p&gt;

&lt;p&gt;Cell networks are centralized. One point of failure cascades. The internet requires thousands of miles of cable and dozens of intermediaries. As long as even one link breaks, you're isolated.&lt;/p&gt;

&lt;p&gt;But what if you didn't need infrastructure at all?&lt;/p&gt;

&lt;p&gt;What if laptops, phones, and devices could talk to each other directly, forming their own network on-the-fly? Even if cellular fails. Even if the internet is gone.&lt;/p&gt;

&lt;p&gt;That's what OEMN does.&lt;/p&gt;

&lt;p&gt;WHY IT'S ACTUALLY HARD&lt;/p&gt;

&lt;p&gt;Building a mesh network looks simple on a whiteboard: nodes talk to neighbors, messages hop across multiple nodes, done.&lt;/p&gt;

&lt;p&gt;The reality is three layers of hard problems:&lt;/p&gt;

&lt;p&gt;PROBLEM 1: HOW DO NODES FIND EACH OTHER?&lt;/p&gt;

&lt;p&gt;Without DNS, DHCP, or a central server, nodes are just devices on a network. They don't know who exists, where they are, or how to reach them. You need peer discovery: a way for node A to say "I exist" and have node B hear it.&lt;/p&gt;

&lt;p&gt;PROBLEM 2: HOW DO YOU ROUTE MESSAGES EFFICIENTLY?&lt;/p&gt;

&lt;p&gt;In a mesh of 50 nodes, there could be hundreds of paths from A to Z. Which one is fastest? Which one still works after node failures? You need topology awareness: every node must build a map of the entire network and compute optimal routes.&lt;/p&gt;

&lt;p&gt;This is the classic shortest-path problem. But it needs to recompute in real-time as nodes join and leave.&lt;/p&gt;

&lt;p&gt;PROBLEM 3: HOW DO YOU GUARANTEE DELIVERY?&lt;/p&gt;

&lt;p&gt;UDP (the transport layer) doesn't guarantee messages arrive. They can be lost, duplicated, reordered, or corrupted. You need reliability: ACKs, retries, deduplication. But you can't do what TCP does (overhead is too high). You need something in between.&lt;/p&gt;

&lt;p&gt;Add encryption. Add replay protection (so attackers can't resend old messages). Add thread-safe concurrency so your listener, router, and sender don't corrupt each other's state.&lt;/p&gt;

&lt;p&gt;Now you have a mesh network.&lt;/p&gt;

&lt;p&gt;THE ARCHITECTURE: FOUR THREADS, ONE SHARED QUEUE&lt;/p&gt;

&lt;p&gt;OEMN's elegance comes from its simplicity.&lt;/p&gt;

&lt;p&gt;FOUR THREADS, each with a single responsibility:&lt;/p&gt;

&lt;p&gt;Listener Thread&lt;br&gt;
    receives packets from UDP, validates encryption, updates topology&lt;/p&gt;

&lt;p&gt;Router Thread&lt;br&gt;
    runs Dijkstra, builds shortest-path routing table&lt;/p&gt;

&lt;p&gt;Send Queue (mutex-protected, condition variable signaled)&lt;/p&gt;

&lt;p&gt;Sender Thread&lt;br&gt;
    transmits packets, handles ACKs and retries&lt;/p&gt;

&lt;p&gt;The listener doesn't transmit. The sender doesn't route. The router doesn't listen. Each thread owns its data. They communicate through one shared send queue.&lt;/p&gt;

&lt;p&gt;This is THREAD SAFETY BY DESIGN, not by accident.&lt;/p&gt;

&lt;p&gt;Here's what happens when a packet arrives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listener validates AES-256-GCM encryption and checks replay protection&lt;/li&gt;
&lt;li&gt;Listener checks if this is a topology update (TOPO message)&lt;/li&gt;
&lt;li&gt;If topology changed, listener signals router to recompute&lt;/li&gt;
&lt;li&gt;Router runs Dijkstra algorithm, updates routing table&lt;/li&gt;
&lt;li&gt;If packet is for us, listener enqueues it to send an ACK&lt;/li&gt;
&lt;li&gt;Sender dequeues, transmits ACK, waits for confirmation&lt;/li&gt;
&lt;li&gt;If no ACK within timeout, sender retries with exponential backoff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire system is coordinated by a single atomic shutdown flag. When you hit Ctrl+C, all threads gracefully drain their queues and exit.&lt;/p&gt;

&lt;p&gt;DIJKSTRA SHORTEST-PATH ROUTING&lt;/p&gt;

&lt;p&gt;Here's where topology matters.&lt;/p&gt;

&lt;p&gt;Every node runs Dijkstra's algorithm to compute the shortest path to every other node. A naive implementation is O(V²). That's too slow for real-time rerouting.&lt;/p&gt;

&lt;p&gt;OEMN uses a BINARY MIN-HEAP, reducing complexity to O((V + E) log V). For 50 nodes, this is fast enough to recompute whenever the network topology changes.&lt;/p&gt;

&lt;p&gt;Here's the actual implementation from router.c:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static void run_dijkstra_heap(const oemn_router_t *r, int src, uint32_t *dist_out, int *parent_out)
{
    int n = r-&amp;gt;n_verts;
    for (int i = 0; i &amp;lt; n; ++i) {
        dist_out[i] = OEMN_INF_DIST;
        parent_out[i] = -1;
    }

    heap_ent_t *heap = calloc(OEMN_HEAP_CAP, sizeof(*heap));
    int hs = 0;

    dist_out[src] = 0;
    heap_push(heap, &amp;amp;hs, (heap_ent_t){0u, src});

    while (hs &amp;gt; 0) {
        heap_ent_t x;
        if (heap_pop(heap, &amp;amp;hs, &amp;amp;x) != 0)
            break;
        int u = x.v;
        if (x.d != dist_out[u])
            continue;
        for (int k = 0; k &amp;lt; r-&amp;gt;deg[u]; ++k) {
            int v = r-&amp;gt;nbr[u][k];
            uint32_t ew = r-&amp;gt;w[u][v];
            if (ew == OEMN_INF_DIST)
                ew = OEMN_EDGE_DEFAULT_COST;
            uint32_t nd = dist_out[u] + ew;
            if (nd &amp;lt; dist_out[v]) {
                dist_out[v] = nd;
                parent_out[v] = u;
                heap_push(heap, &amp;amp;hs, (heap_ent_t){nd, v});
            }
        }
    }
    free(heap);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The routing table maps: destination → next hop → distance in hops&lt;/p&gt;

&lt;p&gt;When you send a message to node 5, it automatically routes through the neighbor closest to node 5. If that neighbor fails, the next topology update (every 2-4 seconds) recomputes and reroutes through an alternate path.&lt;/p&gt;

&lt;p&gt;This is AUTOMATIC FAILOVER. No manual intervention. No central coordinator.&lt;/p&gt;

&lt;p&gt;ENCRYPTION &amp;amp; REPLAY PROTECTION: DEFENDING AGAINST ATTACKS&lt;/p&gt;

&lt;p&gt;I use AES-256-GCM (via OpenSSL) or ChaCha20-Poly1305 (via libsodium) for authenticated encryption.&lt;/p&gt;

&lt;p&gt;Every message is encrypted with a per-source key, and every message has a sequence number embedded in its ID.&lt;/p&gt;

&lt;p&gt;But encryption alone isn't enough. I had to prevent replay attacks:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attacker sees: "Node A sends $100 to Node B"
Attacker captures the encrypted packet
Attacker replays the same encrypted packet
Node B doesn't know it's a duplicate
Attacker just transferred $100 twice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is trivial to execute on UDP because there's no session state.&lt;/p&gt;

&lt;p&gt;OEMN solves this with a PER-SOURCE SLIDING WINDOW:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each source, track the last 64 received sequence numbers
If we see sequence N where N &amp;lt;= (current_seq - 64), reject as duplicate
If we see N where (current_seq - 64) &amp;lt; N &amp;lt; current_seq, reject as duplicate
Only accept N &amp;gt; current_seq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here's the actual code from replay.c:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int oemn_replay_check_and_update(oemn_replay_t *r, uint32_t src_id, const uint8_t msg_id[16])
{
    uint64_t seq = msg_seq_from_id(msg_id);

    pthread_mutex_lock(&amp;amp;r-&amp;gt;mu);
    int idx = -1;
    for (int i = 0; i &amp;lt; OEMN_MAX_PEERS; ++i) {
        if (r-&amp;gt;slots[i].used &amp;amp;&amp;amp; r-&amp;gt;slots[i].src_id == src_id) {
            idx = i;
            break;
        }
    }

    oemn_replay_slot_t *s = &amp;amp;r-&amp;gt;slots[idx];

    if (seq &amp;gt; s-&amp;gt;max_seq) {
        uint64_t gap = seq - s-&amp;gt;max_seq;
        if (gap &amp;gt;= 64)
            s-&amp;gt;window = 1ULL;
        else
            s-&amp;gt;window = (s-&amp;gt;window &amp;lt;&amp;lt; gap) | 1ULL;
        s-&amp;gt;max_seq = seq;
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 0;  // Accept: fresh sequence
    }

    uint64_t delta = s-&amp;gt;max_seq - seq;
    if (delta &amp;gt;= 64) {
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 1;  // Reject: too old
    }

    uint64_t mask = 1ULL &amp;lt;&amp;lt; delta;
    if (s-&amp;gt;window &amp;amp; mask) {
        pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
        return 1;  // Reject: duplicate
    }

    s-&amp;gt;window |= mask;
    pthread_mutex_unlock(&amp;amp;r-&amp;gt;mu);
    return 0;  // Accept: fresh
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This costs 64 bits of memory per source. At 50 nodes, that's 400 bytes total. Negligible.&lt;/p&gt;

&lt;p&gt;The attacker can't replay old messages. They can only forge new ones (which requires the encryption key, which they don't have).&lt;/p&gt;

&lt;p&gt;BUFFER POOLING: FROM MALLOC THRASHING TO SLAB ALLOCATION&lt;/p&gt;

&lt;p&gt;One thing I discovered: malloc/free on every message is a performance killer.&lt;/p&gt;

&lt;p&gt;OEMN uses pre-allocated SLAB BUFFERS:&lt;/p&gt;

&lt;p&gt;From fwd_pool.h:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#define OEMN_FWD_POOL_SLOTS 48
#define OEMN_FWD_BUFSZ (OEMN_HDR_SIZE + OEMN_MAX_PAYLOAD)

typedef struct {
    pthread_mutex_t mu;
    uint8_t slab[OEMN_FWD_POOL_SLOTS][OEMN_FWD_BUFSZ];
    int free_stack[OEMN_FWD_POOL_SLOTS];
    int nfree;
} oemn_fwd_pool_t;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Instead of malloc-ing on every message, OEMN allocates one giant slab upfront. Each thread acquires a buffer from the pool, uses it, then returns it. No heap fragmentation. No allocation overhead.&lt;/p&gt;

&lt;p&gt;This is a simple optimization, but it matters more than you'd expect. Systems that malloc constantly spend more CPU managing memory than doing useful work.&lt;/p&gt;

&lt;p&gt;LESSONS LEARNED&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;THREAD SAFETY IS HARD BUT WORTH GETTING RIGHT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I spent weeks hunting data races using ThreadSanitizer. The payoff: zero race conditions under stress testing. This is how production systems should be built. Most open-source projects skip this. Don't.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TOPOLOGY CHANGES ARE YOUR PERFORMANCE BOTTLENECK&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dijkstra is O((V + E) log V). For 50 nodes, it's still fast. But if you rerun it on every message (amateur mistake), you're doing wasteful computation.&lt;/p&gt;

&lt;p&gt;OEMN only recomputes on topology changes. This is the right optimization target.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;UDP ISN'T UNRELIABLE, IT'S JUST HONEST&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TCP hides failures through retries and buffering. UDP shows them immediately.&lt;/p&gt;

&lt;p&gt;In a disaster scenario, seeing failures is better than silently dropping 10% of messages under congestion (which TCP does). You need visibility into what's happening.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;BUFFER POOLING WINS OVER MALLOC/FREE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pre-allocated slabs eliminate allocation overhead. The threading model means each buffer acquisition is an O(1) stack pop. No freelist searching. No fragmentation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WRITING THINGS IN C MAKES YOU UNDERSTAND HARDWARE&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Python and Rust abstract away memory. C forces you to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache line locality&lt;/li&gt;
&lt;li&gt;Malloc overhead&lt;/li&gt;
&lt;li&gt;Mutex contention&lt;/li&gt;
&lt;li&gt;Thread synchronization primitives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you understand networking differently.&lt;/p&gt;

&lt;p&gt;WHAT'S NEXT&lt;/p&gt;

&lt;p&gt;The current roadmap has three priorities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;LATENCY-WEIGHTED ROUTING: Currently, OEMN finds the shortest path (fewest hops). But 10 hops of low-latency LAN is better than 3 hops of high-latency satellite. Adding EWMA-based RTT measurement per edge is next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DELTA TOPOLOGY UPDATES: Right now, nodes send full topology advertisements. With 100+ nodes, that's bandwidth-heavy. Delta updates (send only changes) could cut bandwidth by 10x.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FUZZING: I want to throw 10,000 random packet corruptions at the protocol and watch it survive. libFuzzer + AddressSanitizer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;WHY THIS MATTERS&lt;/p&gt;

&lt;p&gt;Most software engineers will never think about resilience. You build on Kubernetes, AWS, and 99.99% uptime guarantees.&lt;/p&gt;

&lt;p&gt;But when infrastructure fails, when the easy assumptions break, mesh networks become critical infrastructure.&lt;/p&gt;

&lt;p&gt;Understanding how to build one teaches you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to program without central authority (coordination, consensus)&lt;/li&gt;
&lt;li&gt;How to handle adversarial networks (encryption, replay protection)&lt;/li&gt;
&lt;li&gt;How to design concurrent systems that don't have race conditions&lt;/li&gt;
&lt;li&gt;How to measure and optimize for real-world constraints (packet loss, node failures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OEMN isn't just a project. It's a masterclass in systems engineering.&lt;/p&gt;

&lt;p&gt;But more importantly: it works. No dependencies. No cloud. Just peers talking to peers.&lt;/p&gt;

&lt;p&gt;TRY IT&lt;/p&gt;

&lt;p&gt;OEMN is on GitHub: github.com/bharqav/oemn&lt;/p&gt;

&lt;p&gt;To run the demo locally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make
./oemn --id 1 --port 7777
./oemn --id 2 --port 7778
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then connect them (in the CLI):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# on node 1: peer add 2 127.0.0.1 7778
# on node 2: peer add 1 127.0.0.1 7777
# on node 1: send 2 hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Watch the message travel. Watch the ACK come back. Watch the routing table update in real-time.&lt;/p&gt;

&lt;p&gt;The code is production-grade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;48-slot buffer pool&lt;/li&gt;
&lt;li&gt;Thread-safe by design&lt;/li&gt;
&lt;li&gt;Dijkstra shortest-path routing&lt;/li&gt;
&lt;li&gt;AES-256-GCM encryption&lt;/li&gt;
&lt;li&gt;Per-source replay protection&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read the docs in the repo. They're detailed. Run the benchmarks yourself. See what performance you get on your hardware.&lt;/p&gt;




&lt;p&gt;If you're curious about how systems really work, build your own. Build a mesh network. Build an autograd engine. Build a shell.&lt;/p&gt;

&lt;p&gt;Don't just use other people's abstractions. Understand the layer beneath.&lt;/p&gt;

&lt;p&gt;That's where the real learning happens.&lt;/p&gt;




&lt;p&gt;OEMN is open-source (MIT License). Contributions welcome: github.com/bharqav/oemn&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
