<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Raft on Krishnatejaswi S</title>
    <link>https://KTS-o7.github.io/bear/tags/raft/</link>
    <description>Recent content in Raft on Krishnatejaswi S</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</managingEditor>
    <webMaster>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</webMaster>
    <copyright>Krishnatejaswi S</copyright>
    <lastBuildDate>Sun, 03 May 2026 00:00:00 +0530</lastBuildDate>
    <atom:link href="https://KTS-o7.github.io/bear/tags/raft/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Raft Log Compaction: Keeping the Log from Growing Forever</title>
      <link>https://KTS-o7.github.io/bear/systems/raft_log_compaction/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/raft_log_compaction/</guid>
      <description>&lt;p&gt;Raft&amp;rsquo;s correctness relies on the log. Every state change is appended to it, replicated to a majority, and then applied to the state machine. This works perfectly until you&amp;rsquo;ve been running for a few months and the log has millions of entries. Replaying the full log on every restart is slow. A new node joining the cluster would have to receive and apply every entry since the beginning of time.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Raft&rsquo;s correctness relies on the log. Every state change is appended to it, replicated to a majority, and then applied to the state machine. This works perfectly until you&rsquo;ve been running for a few months and the log has millions of entries. Replaying the full log on every restart is slow. A new node joining the cluster would have to receive and apply every entry since the beginning of time.</p>
<p>Log compaction is how you fix this. The idea is simple: once a prefix of the log has been applied to the state machine, you don&rsquo;t need the individual entries anymore &ndash; just a snapshot of the state at that point.</p>
<h2 id="snapshots">Snapshots</h2>
<p>A snapshot captures the complete state machine state at a given log index. Once the snapshot is written, all log entries up to and including that index can be discarded.</p>





<pre tabindex="0"><code>Before compaction:
  Log: [1: set x=1] [2: set y=2] [3: set x=3] [4: del y] [5: set z=1]
  Applied through index 4
  State machine: {x: 3, z: 1}

After compaction:
  Snapshot at index 4: {x: 3}  (z not in snapshot -- set z=1 not yet applied)
  Remaining log: [5: set z=1]</code></pre><p>Wait &ndash; index 5 is retained because it hasn&rsquo;t been applied yet when the snapshot is taken. The snapshot covers only committed, applied entries.</p>
<p>The snapshot metadata includes the last included index and last included term. These replace the log prefix. When AppendEntries includes a consistency check against the entry before the new one, the leader uses the snapshot&rsquo;s last index and term as the baseline when the follower&rsquo;s log has been compacted past that point.</p>
<h2 id="when-to-snapshot">When to Snapshot</h2>
<p>Most implementations trigger a snapshot when the log grows past a size threshold &ndash; say 64MB or 256MB. The Raft paper leaves this as an implementation detail. etcd uses 100MB by default. Smaller thresholds mean more frequent snapshots and less log replay on restart, but snapshot creation has a cost.</p>
<p>Taking a snapshot of a large state machine can be expensive. If you&rsquo;re snapshotting a database with gigabytes of data, the snapshot itself might take seconds. During that time the state machine is typically paused or copy-on-write semantics are used to take a consistent point-in-time snapshot without stopping serving. Most production implementations use copy-on-write (fork on Linux makes this cheap for process-based state machines).</p>
<h2 id="installsnapshot-rpc">InstallSnapshot RPC</h2>
<p>Snapshots matter most for a new node joining the cluster or a node that has been down for a long time and fallen significantly behind.</p>
<p>If a follower is so far behind that the leader has already compacted the log entries it needs, AppendEntries won&rsquo;t work &ndash; those entries are gone. Instead the leader sends the snapshot directly with InstallSnapshot RPC.</p>
<p>The follower receives the snapshot, writes it to stable storage, discards its entire existing log, and resets its state machine to the snapshot state. After that it can receive normal AppendEntries for the remaining log entries.</p>
<p>InstallSnapshot is also used when a brand new node joins the cluster. Rather than replaying potentially years of log history, the leader sends the current snapshot and the follower catches up from there.</p>
<h2 id="etcd-in-practice">etcd in Practice</h2>
<p>etcd runs a snapshot at 100MB by default (configurable with <code>--snapshot-count</code>, which actually counts committed entries, not bytes &ndash; the default is 10,000 entries). After snapshotting, etcd retains the last 5 snapshots and the WAL (write-ahead log) segments needed to replay from the oldest retained snapshot. This gives you a window of history for debugging without storing the full log.</p>
<p>etcd also exposes the snapshot mechanism for operator use. <code>etcdctl snapshot save</code> takes a snapshot of the current state to a file. This is the recommended backup mechanism for etcd clusters &ndash; you back up the snapshot, not the raw data directory.</p>
<h2 id="leader-leases-and-linearizable-reads">Leader Leases and Linearizable Reads</h2>
<p>One related problem: reads in a basic Raft cluster go through the leader, which needs to confirm it&rsquo;s still the leader before responding. The way to confirm is to send a heartbeat round to a majority before responding to a read &ndash; which is an extra round-trip per read, expensive under load.</p>
<p>Leader leases are the optimization. When a leader wins an election and sends its first heartbeats, it records the time and grants itself a lease for a fixed duration (typically the election timeout). During that window, it&rsquo;s guaranteed to still be the leader &ndash; any new leader would need a full election timeout to emerge. So it can serve reads locally without a heartbeat round.</p>
<p>The caveat: lease-based reads depend on the leader&rsquo;s clock being accurate relative to other nodes. If a leader&rsquo;s clock runs fast and its lease hasn&rsquo;t actually expired on other nodes&rsquo; clocks while it thinks it has, you can serve stale reads. etcd uses clock bounds and makes the lease duration conservative to avoid this. In practice, lease reads are the default in most etcd deployments because the latency benefit outweighs the theoretical risk on well-synchronized hardware clocks.</p>
<p>Raft&rsquo;s log compaction is not exciting to implement but it&rsquo;s essential for production deployments. A cluster without snapshot support will eventually fall over under its own log weight &ndash; or force every new node to replay history from the beginning, which is equally untenable.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Raft: Consensus for the Rest of Us</title>
      <link>https://KTS-o7.github.io/bear/systems/raft_consensus/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/raft_consensus/</guid>
      <description>&lt;p&gt;Paxos is the algorithm everyone cites and almost nobody implements correctly. The gap between the paper and a working, production-safe implementation is wide enough that teams have shipped subtly broken versions without knowing it for months.&lt;/p&gt;&#xA;&lt;p&gt;Raft was built as a direct response to that. Same safety guarantees, designed from scratch for understandability &amp;ndash; the explicit goal was that a student should be able to implement it correctly from the paper alone.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Paxos is the algorithm everyone cites and almost nobody implements correctly. The gap between the paper and a working, production-safe implementation is wide enough that teams have shipped subtly broken versions without knowing it for months.</p>
<p>Raft was built as a direct response to that. Same safety guarantees, designed from scratch for understandability &ndash; the explicit goal was that a student should be able to implement it correctly from the paper alone.</p>
<p>It works. etcd runs on it. Kubernetes&rsquo; entire cluster state lives in etcd. CockroachDB uses a Raft group per range. TiKV, Consul &ndash; Raft is everywhere Paxos was supposed to be.</p>
<h2 id="the-decomposition">The Decomposition</h2>
<p>Raft&rsquo;s core move is breaking consensus into three subproblems that can be understood and implemented independently:</p>
<ol>
<li>Leader election &ndash; pick one node as leader at any time</li>
<li>Log replication &ndash; leader accepts entries, replicates to followers</li>
<li>Safety &ndash; no two nodes ever commit conflicting entries</li>
</ol>
<p>Everything flows through a single leader. There&rsquo;s no &ldquo;any node can propose&rdquo; ambiguity like in basic Paxos. The leader is the sole source of truth for what enters the log. That constraint makes the rest of the protocol tractable.</p>
<h2 id="terms-and-roles">Terms and Roles</h2>
<p>Raft uses a logical clock called a <strong>term</strong> &ndash; a monotonically increasing integer. Each election starts a new term. If a node sees a message from a higher term than its own, it immediately updates and steps down if it was leader. Terms are how stale leaders get detected and evicted.</p>
<p>Every node is one of three things at any moment:</p>
<ul>
<li><strong>Leader</strong> &ndash; accepts client requests, replicates entries, sends heartbeats to prevent new elections</li>
<li><strong>Follower</strong> &ndash; passive, responds to RPCs from leaders and candidates</li>
<li><strong>Candidate</strong> &ndash; transitional state during an election</li>
</ul>
<p>Every node starts as a follower.</p>
<h2 id="leader-election">Leader Election</h2>
<p>Followers expect periodic heartbeats from the leader via AppendEntries RPCs (sent even when there are no entries to replicate). If an election timeout fires without a heartbeat, the follower converts to a candidate:</p>
<ol>
<li>Increment current term, vote for self</li>
<li>Send RequestVote to all peers</li>
<li>A peer grants its vote if it hasn&rsquo;t voted this term and the candidate&rsquo;s log is at least as up-to-date as its own</li>
<li>First candidate to collect a majority wins, becomes leader, starts sending heartbeats immediately</li>
</ol>
<p>Split votes happen when two candidates start elections simultaneously and each gets half the cluster. Neither reaches majority. Both time out and retry with a new term. Raft handles this with randomized election timeouts (typically 150&ndash;300ms) &ndash; making simultaneous timeouts unlikely without coordination overhead.</p>
<h2 id="log-replication">Log Replication</h2>
<p>Once a leader is elected:</p>
<ol>
<li>Client sends a command to the leader</li>
<li>Leader appends the entry to its local log (not yet committed)</li>
<li>Leader sends AppendEntries to all followers in parallel</li>
<li>Once a majority have written the entry, the leader marks it committed</li>
<li>Leader applies the entry to its state machine, replies to client</li>
<li>On the next heartbeat, followers learn the commit index and apply the entry too</li>
</ol>





<pre tabindex="0"><code>Client → [write x=5] → Leader
Leader  → AppendEntries(x=5) → Follower A  ✓
Leader  → AppendEntries(x=5) → Follower B  ✓
Leader  → AppendEntries(x=5) → Follower C  (unreachable)
[Majority = Leader + A + B → COMMIT] → apply x=5</code></pre><p>Follower C is behind but not a problem. When it reconnects, the leader sends it the missing entries.</p>
<h2 id="safety-log-matching">Safety: Log Matching</h2>
<p>Raft&rsquo;s key safety property: if two logs contain an entry with the same index and term, all preceding entries are identical.</p>
<p>This is enforced by the AppendEntries consistency check. When the leader sends an entry, it includes the index and term of the entry immediately before it. A follower rejects the append if that preceding entry doesn&rsquo;t match its own log. The leader then backs up and resends from an earlier point until it finds a common prefix, then overwrites the diverged entries.</p>
<p>The vote restriction reinforces this: a node won&rsquo;t vote for a candidate whose log is less up-to-date than its own. This guarantees the new leader always has all committed entries &ndash; it never needs to fetch missing entries from other nodes after winning an election.</p>
<h2 id="leader-failure">Leader Failure</h2>
<p>If the leader crashes, the election timeout fires on one of the followers and a new election starts. The vote restriction ensures the winner has the most complete log among the majority. No repair phase, no querying all nodes &ndash; safety comes from the constraint, not from post-election cleanup.</p>
<h2 id="raft-vs-paxos">Raft vs. Paxos</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Multi-Paxos</th>
          <th>Raft</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Design goal</td>
          <td>Correctness</td>
          <td>Correctness + understandability</td>
      </tr>
      <tr>
          <td>Leader</td>
          <td>Distinguished proposer (informal)</td>
          <td>Explicit, enforced by protocol</td>
      </tr>
      <tr>
          <td>Log holes</td>
          <td>Can have gaps, complex to fill</td>
          <td>No gaps, entries apply in order</td>
      </tr>
      <tr>
          <td>Config changes</td>
          <td>Ad hoc</td>
          <td>Joint consensus built in</td>
      </tr>
      <tr>
          <td>Implementations</td>
          <td>Chubby, Zab (variants)</td>
          <td>etcd, CockroachDB, TiKV, Consul</td>
      </tr>
  </tbody>
</table>
<p>Both tolerate up to (N-1)/2 failures in an N-node cluster. The fault tolerance is identical. The difference is that Raft implementations are actually correct in practice because the protocol is debuggable.</p>
<h2 id="where-it-runs">Where It Runs</h2>
<ul>
<li><strong>etcd</strong> &ndash; Kubernetes&rsquo; backing store; the entire cluster state (pods, services, configs) is a Raft log</li>
<li><strong>CockroachDB</strong> &ndash; Raft per range; each 512MB range is an independent 3&ndash;5 node Raft group</li>
<li><strong>TiKV</strong> &ndash; Raft groups per region, underpins TiDB</li>
<li><strong>Consul</strong> &ndash; service discovery and distributed KV</li>
</ul>
<p>If you&rsquo;re building anything that needs replicated state and strong consistency, Raft is the default answer. The paper is readable in an afternoon. The TLA+ spec exists if you want to verify your implementation. Start there.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
