<?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: shohag miah</title>
    <description>The latest articles on DEV Community by shohag miah (@shohag_miah_a04f4745de1b8).</description>
    <link>https://dev.to/shohag_miah_a04f4745de1b8</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3600643%2F36ee6e0c-fc5f-4f55-b95e-b848da2ca4e2.jpg</url>
      <title>DEV Community: shohag miah</title>
      <link>https://dev.to/shohag_miah_a04f4745de1b8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shohag_miah_a04f4745de1b8"/>
    <language>en</language>
    <item>
      <title>I Built an Open-Source Alternative to Google Analytics + Hotjar</title>
      <dc:creator>shohag miah</dc:creator>
      <pubDate>Sun, 08 Mar 2026 18:10:21 +0000</pubDate>
      <link>https://dev.to/shohag_miah_a04f4745de1b8/i-built-an-open-source-alternative-to-google-analytics-hotjar-2nf2</link>
      <guid>https://dev.to/shohag_miah_a04f4745de1b8/i-built-an-open-source-alternative-to-google-analytics-hotjar-2nf2</guid>
      <description>&lt;p&gt;I was paying for Google Analytics (confusing), Hotjar ($80/mo for heatmaps), and a separate popup tool ($30/mo). Three dashboards, three scripts on my site, and nothing talked to each other.&lt;/p&gt;

&lt;p&gt;So I built Seentics — one open-source tool that replaces all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Seentics Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Analytics&lt;/strong&gt; — Real-time visitors, pageviews, bounce rate, session duration, traffic sources, UTM tracking, countries, browsers, devices. Like Plausible but with way more features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heatmaps&lt;/strong&gt; — See where users click and how far they scroll. Supports desktop, tablet, and mobile viewports. Works on any page without code changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session Replays&lt;/strong&gt; — Watch full user sessions to find UX issues and bugs. Stored in S3-compatible storage with automatic PII masking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Funnels&lt;/strong&gt; — Build multi-step conversion funnels. See exactly where users drop off in your signup, checkout, or onboarding flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal Tracking&lt;/strong&gt; — Track page visit goals, custom events with &lt;code&gt;seentics.track('signup')&lt;/code&gt;, or auto-track clicks on CSS selectors like &lt;code&gt;#buy-btn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavioral Automations&lt;/strong&gt; — This is the feature nobody else has in the privacy-first space. Trigger popups, banners, webhooks, or custom JavaScript based on real-time visitor behavior — exit intent, scroll depth, time on page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;Go 1.24 (Gin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 14, Tailwind CSS, shadcn/ui&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics DB&lt;/td&gt;
&lt;td&gt;ClickHouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata DB&lt;/td&gt;
&lt;td&gt;PostgreSQL 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Storage&lt;/td&gt;
&lt;td&gt;S3-compatible (MinIO)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching&lt;/td&gt;
&lt;td&gt;Embedded Go cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why Go + ClickHouse?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go handles concurrent HTTP requests extremely well — the event ingestion endpoint needs to handle thousands of requests per second with minimal latency. The tracking script sends a POST request, Go buffers it in memory, and batch-inserts into ClickHouse every second or every 1000 events (whichever comes first). Response time to the browser: under 5ms.&lt;/p&gt;

&lt;p&gt;ClickHouse is a columnar database built for analytical queries. A query like "show me pageviews by country for the last 30 days" that would take 10+ seconds on PostgreSQL runs in 50ms on ClickHouse — even with millions of rows.&lt;/p&gt;

&lt;p&gt;PostgreSQL handles everything that's not time-series: user accounts, site configurations, goals, automation rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tracking Script
&lt;/h2&gt;

&lt;p&gt;Under 1KB. Compare that to GA4 (45KB+) or Hotjar (100KB+). No cookies, no fingerprinting, no PII collection.&lt;/p&gt;

&lt;p&gt;Setup is one line in your HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://seentics.com/trackers/seentics.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-site-id=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_SITE_ID"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom event tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Basic event&lt;/span&gt;
&lt;span class="nx"&gt;seentics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// With properties&lt;/span&gt;
&lt;span class="nx"&gt;seentics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purchase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Seentics&lt;/th&gt;
&lt;th&gt;Plausible&lt;/th&gt;
&lt;th&gt;Hotjar&lt;/th&gt;
&lt;th&gt;PostHog&lt;/th&gt;
&lt;th&gt;GA4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real-time analytics&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heatmaps&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session replays&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Funnels&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavioral automations&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy-first (no cookies)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hostable&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script size&lt;/td&gt;
&lt;td&gt;&amp;lt;1KB&lt;/td&gt;
&lt;td&gt;&amp;lt;1KB&lt;/td&gt;
&lt;td&gt;~100KB&lt;/td&gt;
&lt;td&gt;~25KB&lt;/td&gt;
&lt;td&gt;~45KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Self-Hosting
&lt;/h2&gt;

&lt;p&gt;Requires Docker. That's it.&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/Seentics/seentics.git
&lt;span class="nb"&gt;cd &lt;/span&gt;seentics
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt; and you have a full analytics platform running.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser --&amp;gt; Tracking Script --&amp;gt; Go Backend API (:3002)
                                     |
                   +-----------------+-----------------+
                   |                 |                 |
              ClickHouse        PostgreSQL           MinIO
              (events)          (metadata)         (replays)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tracking script is served by the Go backend and sends events directly to it. Events are buffered in memory and batch-inserted into ClickHouse. Session replay data goes to S3-compatible storage (MinIO for self-hosted, any S3 for cloud). PostgreSQL stores user accounts, site configs, goals, and automation rules.&lt;/p&gt;

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

&lt;p&gt;I'm working on AI-powered insights (anomaly detection, natural language queries), custom dashboards, email reports, and a WordPress plugin.&lt;/p&gt;

&lt;p&gt;If you're interested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try the cloud version: &lt;a href="https://seentics.com" rel="noopener noreferrer"&gt;seentics.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Self-host it: &lt;a href="https://github.com/Seentics/seentics" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Star the repo if you find it useful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love feedback — what features would make you switch from your current analytics setup? What's missing?&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>heatmaps</category>
      <category>automation</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Go Library That Makes Distributed Systems Actually Simple</title>
      <dc:creator>shohag miah</dc:creator>
      <pubDate>Fri, 07 Nov 2025 08:28:43 +0000</pubDate>
      <link>https://dev.to/shohag_miah_a04f4745de1b8/i-built-a-go-library-that-makes-distributed-systems-actually-simple-10p</link>
      <guid>https://dev.to/shohag_miah_a04f4745de1b8/i-built-a-go-library-that-makes-distributed-systems-actually-simple-10p</guid>
      <description>&lt;p&gt;Ever tried to scale an application beyond a single server? Then you know the pain:&lt;/p&gt;

&lt;p&gt;Which server stores which data?&lt;/p&gt;

&lt;p&gt;How do I keep replicas in sync?&lt;/p&gt;

&lt;p&gt;What happens when a node crashes?&lt;/p&gt;

&lt;p&gt;How do I rebalance when adding nodes?&lt;/p&gt;

&lt;p&gt;I spent months building distributed systems and kept reimplementing the same coordination logic. So I extracted it into ClusterKit — a Go library that handles all the cluster coordination while you keep full control over storage and replication.&lt;/p&gt;

&lt;p&gt;What Problem Does This Solve?&lt;/p&gt;

&lt;p&gt;Let's say you're building a key-value store. On a single server, it's simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&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="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="n"&gt;value&lt;/span&gt;  &lt;span class="c"&gt;// Done!&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But when you need to scale to multiple servers, suddenly you need:&lt;/p&gt;

&lt;p&gt;Partitioning — Which server stores "user:123"?&lt;/p&gt;

&lt;p&gt;Replication — How do I keep 3 copies of the data?&lt;/p&gt;

&lt;p&gt;Routing — Where should clients send requests?&lt;/p&gt;

&lt;p&gt;Failover — What if the primary server dies?&lt;/p&gt;

&lt;p&gt;Rebalancing — How do I redistribute data when adding servers?&lt;/p&gt;

&lt;p&gt;You could use Redis Cluster or Cassandra, but that locks you into their storage model. Or you could build it from scratch... and spend months on infrastructure instead of your application.&lt;/p&gt;

&lt;p&gt;ClusterKit solves this by handling ONLY the coordination layer.&lt;/p&gt;

&lt;p&gt;The Core Concept&lt;/p&gt;

&lt;p&gt;ClusterKit tells you WHERE data should go. You decide HOW to store it.&lt;/p&gt;

&lt;p&gt;Here's the complete API (seriously, this is all of it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Find which partition owns a key&lt;/span&gt;

&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user:123"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// Get the primary node for this partition&lt;/span&gt;

&lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// Get replica nodes&lt;/span&gt;

&lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetReplicas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// Am I responsible for this data?&lt;/span&gt;

&lt;span class="n"&gt;isPrimary&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;isReplica&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsReplica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// Get all nodes (primary + replicas)&lt;/span&gt;

&lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// What's my node ID?&lt;/span&gt;

&lt;span class="n"&gt;myID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetMyNodeID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Seven methods. No complex config files, no YAML manifests, no XML schemas.&lt;/p&gt;

&lt;p&gt;Show Me Code!&lt;/p&gt;

&lt;p&gt;Let's build a distributed key-value store in ~60 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;



&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"sync"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/skshohagmiah/clusterkit"&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;KVStore&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;ck&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;clusterkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClusterKit&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="n"&gt;mu&lt;/span&gt;   &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;



&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;KVStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;// 1. Ask ClusterKit where this key belongs&lt;/span&gt;

    &lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="c"&gt;// 2. Am I the primary for this partition?&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c"&gt;// YES - Store locally&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&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="n"&gt;value&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;



        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Stored %s (I'm primary)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



        &lt;span class="c"&gt;// Replicate to backup servers&lt;/span&gt;

        &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetReplicas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replica&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replicateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="c"&gt;// 3. Am I a replica?&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsReplica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c"&gt;// YES - Just store it&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&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="n"&gt;value&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;



        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Stored %s (I'm replica)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="c"&gt;// 4. I'm neither - forward to primary&lt;/span&gt;

    &lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"⏩ Forwarding to %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forwardTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;



&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;KVStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="c"&gt;// Can read from primary or replica&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsReplica&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;



        &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="c"&gt;// Forward to primary&lt;/span&gt;

    &lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;What just happened?&lt;/p&gt;

&lt;p&gt;ClusterKit handles partition assignment via consistent hashing&lt;/p&gt;

&lt;p&gt;Your app decides whether to store, replicate, or forward&lt;/p&gt;

&lt;p&gt;You control the storage engine (could be PostgreSQL, Redis, files, etc.)&lt;/p&gt;

&lt;p&gt;You control the replication protocol (HTTP, gRPC, TCP, whatever!)&lt;/p&gt;

&lt;p&gt;Getting Started is Stupid Simple&lt;/p&gt;

&lt;p&gt;Bootstrap node (first server):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;clusterkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClusterKit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;NodeID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"node-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;HTTPAddr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;Additional&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;clusterkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClusterKit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;NodeID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"node-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;HTTPAddr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;":8081"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;JoinAddr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c"&gt;// Bootstrap node&lt;/span&gt;

&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your cluster is running with:&lt;/p&gt;

&lt;p&gt;✅ Automatic partition assignment&lt;/p&gt;

&lt;p&gt;✅ Primary/replica designation&lt;/p&gt;

&lt;p&gt;✅ Health monitoring&lt;/p&gt;

&lt;p&gt;✅ Auto-rebalancing&lt;/p&gt;

&lt;p&gt;The Killer Feature: Sub-100ms Failover&lt;/p&gt;

&lt;p&gt;Here's where ClusterKit shines. Traditional distributed systems have terrible failover:&lt;/p&gt;

&lt;p&gt;Primary fails → Wait 30s for timeout → Leader election → Update routing → Resume&lt;/p&gt;

&lt;p&gt;30 seconds of downtime! 🔥&lt;/p&gt;

&lt;p&gt;ClusterKit uses client-side retry with replica fallback:&lt;/p&gt;

&lt;p&gt;Client → Primary ❌ → Replica ✅ (instant!)&lt;/p&gt;

&lt;p&gt;When a primary fails:&lt;/p&gt;

&lt;p&gt;Client detects error immediately&lt;/p&gt;

&lt;p&gt;Client retries on replica (no delay)&lt;/p&gt;

&lt;p&gt;Replica accepts write and returns success&lt;/p&gt;

&lt;p&gt;Topology refreshes in background&lt;/p&gt;

&lt;p&gt;Life goes on&lt;/p&gt;

&lt;p&gt;Result: &amp;lt;100ms failover instead of 30 seconds. That's a 300x improvement! 🚀&lt;/p&gt;

&lt;p&gt;Handling Data Migration&lt;/p&gt;

&lt;p&gt;When nodes join or leave, partitions need to move. ClusterKit provides a hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnPartitionChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partitionID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copyFromNodes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copyToNode&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;copyToNode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;myNodeID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c"&gt;// Not for me&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"📦 Migrating partition %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partitionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



    &lt;span class="c"&gt;// Merge data from ALL source nodes&lt;/span&gt;

    &lt;span class="n"&gt;mergedData&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceNode&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;copyFromNodes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fetchFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partitionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



        &lt;span class="c"&gt;// Your conflict resolution strategy&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;mergedData&lt;/span&gt;&lt;span class="p"&gt;[&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="n"&gt;value&lt;/span&gt;  &lt;span class="c"&gt;// Last-write-wins&lt;/span&gt;

            &lt;span class="c"&gt;// OR: Use version numbers, timestamps, etc.&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;



    &lt;span class="n"&gt;storeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mergedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Migrated %d keys&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mergedData&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;Why multiple source nodes?&lt;/p&gt;

&lt;p&gt;During failover, different replicas might have received different writes. ClusterKit gives you ALL nodes that had the data so you can merge them and prevent data loss.&lt;/p&gt;

&lt;p&gt;This is crucial for eventual consistency scenarios!&lt;/p&gt;

&lt;p&gt;Three Replication Strategies Included&lt;/p&gt;

&lt;p&gt;The library includes three complete, runnable examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client-Side SYNC (Quorum-Based)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Best for: Banking, inventory, critical data&lt;/p&gt;

&lt;p&gt;Client writes to all nodes (primary + replicas) and waits for quorum (2/3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Client writes to ALL nodes in parallel&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;



&lt;span class="c"&gt;// Internally:&lt;/span&gt;

&lt;span class="c"&gt;// - Write to primary + replicas&lt;/span&gt;

&lt;span class="c"&gt;// - Wait for 2/3 nodes to confirm&lt;/span&gt;

&lt;span class="c"&gt;// - Return success only if quorum reached&lt;/span&gt;

&lt;span class="n"&gt;Consistency&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Strong&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;2.&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;Side&lt;/span&gt; &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Primary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;First&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Best&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Streaming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;throughput&lt;/span&gt;

&lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="n"&gt;writes&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;immediately&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="c"&gt;// Client writes to primary, returns FAST&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// ~1ms latency&lt;/span&gt;



&lt;span class="c"&gt;// Internally:&lt;/span&gt;

&lt;span class="c"&gt;// - Write to primary (blocking - but fast!)&lt;/span&gt;

&lt;span class="c"&gt;// - Return success immediately&lt;/span&gt;

&lt;span class="c"&gt;// - Replicate to replicas in background&lt;/span&gt;

&lt;span class="n"&gt;Consistency&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Eventual&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Side&lt;/span&gt; &lt;span class="n"&gt;Routing&lt;/span&gt;

&lt;span class="n"&gt;Best&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mobile&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt;

&lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="n"&gt;sends&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;ANY&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="n"&gt;handles&lt;/span&gt; &lt;span class="n"&gt;routing&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;SDK&lt;/span&gt; &lt;span class="n"&gt;needed&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="n"&gt;POST&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="c"&gt;//any-node:8080/kv/set \&lt;/span&gt;

  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;



&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="n"&gt;automatically&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Routes&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;needed&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Handles&lt;/span&gt; &lt;span class="n"&gt;replication&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Returns&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consistency: Configurable (server decides)&lt;/p&gt;

&lt;p&gt;Production Ready Features&lt;/p&gt;

&lt;p&gt;Despite the simple API, ClusterKit is built for production:&lt;/p&gt;

&lt;p&gt;Raft Consensus — Built on HashiCorp's battle-tested Raft&lt;/p&gt;

&lt;p&gt;Write-Ahead Log — All state changes are logged&lt;/p&gt;

&lt;p&gt;Snapshots — Periodic snapshots for fast recovery&lt;/p&gt;

&lt;p&gt;Persistence — Survives restarts&lt;/p&gt;

&lt;p&gt;HTTP API — RESTful cluster info endpoint&lt;/p&gt;

&lt;p&gt;Docker &amp;amp; Kubernetes — Deployment examples included&lt;/p&gt;

&lt;p&gt;When Should You Use ClusterKit?&lt;/p&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;p&gt;Building custom distributed caches&lt;/p&gt;

&lt;p&gt;Creating specialized KV stores&lt;/p&gt;

&lt;p&gt;Distributed message queues&lt;/p&gt;

&lt;p&gt;Session stores across multiple servers&lt;/p&gt;

&lt;p&gt;Time-series databases with custom partitioning&lt;/p&gt;

&lt;p&gt;Any system where you need custom distribution logic&lt;/p&gt;

&lt;p&gt;Not ideal for:&lt;/p&gt;

&lt;p&gt;Single-node applications (obviously!)&lt;/p&gt;

&lt;p&gt;When Redis Cluster/Cassandra already fits perfectly&lt;/p&gt;

&lt;p&gt;If you don't want to handle replication yourself&lt;/p&gt;

&lt;p&gt;Real-World Use Cases&lt;/p&gt;

&lt;p&gt;Distributed Cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Cache layer that scales horizontally&lt;/span&gt;

&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Multi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Region&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="c"&gt;// Partition queue by region&lt;/span&gt;

&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Sharded&lt;/span&gt; &lt;span class="n"&gt;Analytics&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="c"&gt;// Partition events by date&lt;/span&gt;

&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetPartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPrimary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;timescaleDB&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Examples&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Install&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;skshohagmiah&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;clusterkit&lt;/span&gt;



&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="n"&gt;SYNC&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strong&lt;/span&gt; &lt;span class="n"&gt;consistency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;



&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="n"&gt;ASYNC&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;throughput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;



&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Side&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each example starts a 6-10 node cluster and runs 1000 operations showing:&lt;/p&gt;

&lt;p&gt;Cluster formation&lt;/p&gt;

&lt;p&gt;Data distribution&lt;/p&gt;

&lt;p&gt;Automatic replication&lt;/p&gt;

&lt;p&gt;Node failure handling&lt;/p&gt;

&lt;p&gt;Data migration&lt;/p&gt;

&lt;p&gt;The Philosophy&lt;/p&gt;

&lt;p&gt;ClusterKit follows the Unix philosophy: do one thing and do it well.&lt;/p&gt;

&lt;p&gt;It doesn't try to be:&lt;/p&gt;

&lt;p&gt;A database (you choose: PostgreSQL, MySQL, MongoDB)&lt;/p&gt;

&lt;p&gt;A cache (you choose: Redis, Memcached, in-memory)&lt;/p&gt;

&lt;p&gt;A message queue (you choose: your protocol)&lt;/p&gt;

&lt;p&gt;It's just the coordination layer. This means:&lt;/p&gt;

&lt;p&gt;✅ You understand every piece of your system&lt;/p&gt;

&lt;p&gt;✅ You can debug issues easily&lt;/p&gt;

&lt;p&gt;✅ You can optimize for your use case&lt;/p&gt;

&lt;p&gt;✅ You're not locked into vendor features&lt;/p&gt;

&lt;p&gt;✅ You can swap storage engines anytime&lt;/p&gt;

&lt;p&gt;Performance&lt;/p&gt;

&lt;p&gt;In my benchmarks (6-node cluster, 1000 ops):&lt;/p&gt;

&lt;p&gt;SYNC (Quorum):&lt;/p&gt;

&lt;p&gt;Write latency: ~5ms (waits for 2/3 nodes)&lt;/p&gt;

&lt;p&gt;Read latency: ~1ms (local read)&lt;/p&gt;

&lt;p&gt;Consistency: Strong&lt;/p&gt;

&lt;p&gt;ASYNC (Primary-First):&lt;/p&gt;

&lt;p&gt;Write latency: ~1ms (primary only)&lt;/p&gt;

&lt;p&gt;Read latency: ~1ms (local read)&lt;/p&gt;

&lt;p&gt;Consistency: Eventual&lt;/p&gt;

&lt;p&gt;Server-Side:&lt;/p&gt;

&lt;p&gt;Write latency: ~3ms (includes routing)&lt;/p&gt;

&lt;p&gt;Read latency: ~2ms (includes routing)&lt;/p&gt;

&lt;p&gt;Consistency: Configurable&lt;/p&gt;

&lt;p&gt;Docker Deployment&lt;/p&gt;

&lt;h1&gt;
  
  
  docker-compose.yml
&lt;/h1&gt;

&lt;p&gt;version: '3.8'&lt;/p&gt;

&lt;p&gt;services:&lt;/p&gt;

&lt;p&gt;node-1:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build: .

environment:

  - NODE_ID=node-1

  - HTTP_PORT=8080

ports:

  - "8080:8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;node-2:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build: .

environment:

  - NODE_ID=node-2

  - HTTP_PORT=8080

  - JOIN_ADDR=node-1:8080

ports:

  - "8081:8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;docker-compose up -d&lt;/p&gt;

&lt;p&gt;What's Next?&lt;/p&gt;

&lt;p&gt;I'm actively working on:&lt;/p&gt;

&lt;p&gt;[ ] Dynamic partition count adjustment&lt;/p&gt;

&lt;p&gt;[ ] Metrics and observability hooks&lt;/p&gt;

&lt;p&gt;[ ] More replication examples (gRPC, NATS)&lt;/p&gt;

&lt;p&gt;[ ] Admin UI for cluster visualization&lt;/p&gt;

&lt;p&gt;[ ] Better documentation&lt;/p&gt;

&lt;p&gt;Try It Yourself&lt;/p&gt;

&lt;p&gt;go get github.com/skshohagmiah/clusterkit&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/skshohagmiah/clusterkit" rel="noopener noreferrer"&gt;https://github.com/skshohagmiah/clusterkit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Examples: &lt;a href="https://github.com/skshohagmiah/clusterkit/tree/main/example" rel="noopener noreferrer"&gt;https://github.com/skshohagmiah/clusterkit/tree/main/example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentation: Check the README&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Building distributed systems doesn't have to be rocket science. With ClusterKit, you get:&lt;/p&gt;

&lt;p&gt;Simple API (7 methods + 1 hook)&lt;/p&gt;

&lt;p&gt;Full control over storage and replication&lt;/p&gt;

&lt;p&gt;Production-ready consensus and failover&lt;/p&gt;

&lt;p&gt;Minimal configuration (2 required fields)&lt;/p&gt;

&lt;p&gt;Complete working examples&lt;/p&gt;

&lt;p&gt;If you've been wanting to build something distributed but the complexity scared you away, give ClusterKit a shot. Start small with the examples, then build something awesome.&lt;/p&gt;

&lt;p&gt;Got questions? Drop them in the comments! 👇&lt;/p&gt;

&lt;p&gt;And if you find this useful, give it a ⭐ on GitHub!&lt;/p&gt;

&lt;p&gt;Tags: #go #distributedsystems #opensource #backend #clustering&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cloud</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
