<?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> Nosql on Hemant Sethi</title>
    <link>https://www.sethihemant.com/tags/-nosql/</link>
    <description>Recent content in  Nosql on Hemant Sethi</description>
    <generator>Hugo -- 0.146.0</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 28 Jan 2026 21:24:47 -0800</lastBuildDate>
    <atom:link href="https://www.sethihemant.com/tags/-nosql/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Spanner</title>
      <link>https://www.sethihemant.com/notes/spanner-2012/</link>
      <pubDate>Sat, 14 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/spanner-2012/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf&#34;&gt;Spanner&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;spanner-googles-globally-distributed-database&#34;&gt;Spanner: Google’s Globally-Distributed Database&lt;/h2&gt;
&lt;h3 id=&#34;abstract&#34;&gt;Abstract&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spanner is Google’s scalable, multi-version, globally-distributed, and synchronously-replicated database which supports &lt;strong&gt;externally-consistent&lt;/strong&gt;(&lt;strong&gt;Linearizable&lt;/strong&gt;) distributed transactions.&lt;/li&gt;
&lt;li&gt;Paper describes how Spanner is Structured, feature set, rationale behind various design decisions, and a Novel Time API that exposes clock certainty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spanner shards data across many sets of Paxos state machines in DCs spread across the world.&lt;/li&gt;
&lt;li&gt;Replication for global availability and geographic locality, clients automatically failover between replicas.&lt;/li&gt;
&lt;li&gt;Automatically reshards data across machines as the amount of data or the number of servers changes, and it automatically migrates data across machines (even across datacenters) to balance load and in response to failures.&lt;/li&gt;
&lt;li&gt;Designed to scale up to millions of machines across hundreds of data centers and trillions of database rows.&lt;/li&gt;
&lt;li&gt;Applications can use Spanner for high availability, even in the face of wide-area natural disasters, by replicating their data within or even across continents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BigTable problems&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Megastore&lt;/strong&gt; supports semi-relational data model and synchronous replication, despite its relatively poor write throughput.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spanner&lt;/strong&gt; has evolved from a Bigtable-like versioned key-value store into a &lt;strong&gt;temporal multi-version database&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Globally distributed features:&lt;/li&gt;
&lt;li&gt;TrueTime API and its implementation(&lt;strong&gt;Key enabler&lt;/strong&gt; of the above properties)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;spanner-implementation&#34;&gt;Spanner Implementation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Directory abstraction(unit of data movement)&lt;/strong&gt; to manage replication and locality.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf">Spanner</a></p>
<hr>
<h2 id="spanner-googles-globally-distributed-database">Spanner: Google’s Globally-Distributed Database</h2>
<h3 id="abstract">Abstract</h3>
<ul>
<li>Spanner is Google’s scalable, multi-version, globally-distributed, and synchronously-replicated database which supports <strong>externally-consistent</strong>(<strong>Linearizable</strong>) distributed transactions.</li>
<li>Paper describes how Spanner is Structured, feature set, rationale behind various design decisions, and a Novel Time API that exposes clock certainty.</li>
</ul>
<h3 id="introduction">Introduction</h3>
<ul>
<li>Spanner shards data across many sets of Paxos state machines in DCs spread across the world.</li>
<li>Replication for global availability and geographic locality, clients automatically failover between replicas.</li>
<li>Automatically reshards data across machines as the amount of data or the number of servers changes, and it automatically migrates data across machines (even across datacenters) to balance load and in response to failures.</li>
<li>Designed to scale up to millions of machines across hundreds of data centers and trillions of database rows.</li>
<li>Applications can use Spanner for high availability, even in the face of wide-area natural disasters, by replicating their data within or even across continents.</li>
<li><strong>BigTable problems</strong></li>
<li><strong>Megastore</strong> supports semi-relational data model and synchronous replication, despite its relatively poor write throughput.</li>
<li><strong>Spanner</strong> has evolved from a Bigtable-like versioned key-value store into a <strong>temporal multi-version database</strong>.</li>
<li>Globally distributed features:</li>
<li>TrueTime API and its implementation(<strong>Key enabler</strong> of the above properties)</li>
</ul>
<h3 id="spanner-implementation">Spanner Implementation</h3>
<ul>
<li>
<p><strong>Directory abstraction(unit of data movement)</strong> to manage replication and locality.</p>
</li>
<li>
<p><strong>Data model</strong>. Spanner looks like a relational database instead of a key-value store.</p>
</li>
<li>
<p>Applications can control <strong>data locality.</strong></p>
</li>
<li>
<p>A Spanner deployment is called a <strong>universe.</strong></p>
</li>
<li>
<p>Spanner is organized as a set of <strong>zones.</strong></p>
</li>
<li>
<p>A <strong>zone</strong> has **one zonemaster(**assigns data to spanservers) and between one hundred and several thousand <strong>spanservers(<strong>serve data to clients)</strong>.</strong></p>
</li>
<li>
<p>Per-zone <strong>location proxies</strong> are used by clients to locate the spanservers assigned to serve their data.
<img loading="lazy" src="/images/notes/spanner-2012/image-1.png"></p>
</li>
<li>
<p><strong>Universe master(Singleton)</strong> is primarily a console that displays status information about all the zones for interactive debugging</p>
</li>
<li>
<p><strong>Placement driver(Singleton)</strong> handles automated movement of data across zones on the timescale of minutes.</p>
</li>
</ul>
<h3 id="spanserver-software-stack">SpanServer Software Stack</h3>
<ul>
<li>
<p>Spanserver implementation to illustrate how <strong>replication</strong> and <strong>distributed transactions</strong> have been layered onto our Bigtable-based implementation.</p>
</li>
<li>
<p>Each <strong>spanserver</strong> is responsible for between 100 and 1000 instances of a data structure called a tablet.</p>
</li>
<li>
<p>A tablet is similar to Bigtable’s tablet abstraction, in that it implements a bag of the following mappings</p>
</li>
<li>
<p>Unlike <strong>Bigtable</strong>, <strong>Spanner</strong> assigns <strong>timestamps</strong> to data which is why</p>
</li>
<li>
<p>A <strong>Spanner’s</strong> <strong>tablet’s</strong> state is stored in a set of <strong>B-tree-like</strong> files and a <strong>write-ahead log</strong>, all on a distributed file system called <strong>Colossus</strong> (the successor to the Google File System.</p>
</li>
<li>
<p>To support <strong>replication</strong>, each spanserver implements a <strong>single Paxos state machine</strong> on top of each tablet.</p>
</li>
<li>
<p>Each state machine stores its <strong>metadata</strong> and <strong>log</strong> in its corresponding tablet</p>
</li>
<li>
<p><strong>Paxos</strong> implementation supports <strong>long-lived leaders</strong> with <strong>time-based</strong> <strong>leases</strong>(D: 10s).</p>
</li>
<li>
<p>Current Spanner implementation <strong>logs every Paxos write twice</strong>:tablet’s &amp; Paxos log.</p>
</li>
<li>
<p>Implementation of Paxos is <strong>pipelined</strong>, so as to improve Spanner’s throughput in the presence of WAN latencies; but writes are applied by Paxos in order.</p>
</li>
<li>
<p>The <strong>Paxos state machines</strong> are used to implement a consistently <strong>replicated bag of mappings</strong>.</p>
</li>
<li>
<p><strong>Writes</strong> must <strong>initiate</strong> the Paxos protocol at the leader;</p>
</li>
<li>
<p><strong>Reads</strong> access state directly from the underlying tablet(<strong>sufficiently up-to-date)</strong>.</p>
</li>
<li>
<p>Set of replicas is collectively a <strong>Paxos group</strong>.</p>
</li>
<li>
<p>At <strong>leader replica</strong>, each spanserver implements a <strong>lock table</strong> for concurrency control.
<img loading="lazy" src="/images/notes/spanner-2012/image-2.png"></p>
</li>
<li>
<p>Bigtable and Spanner are designed for <strong>long-lived transactions</strong>(e.g. for report generation, which might take on the order of minutes) which <strong>perform poorly under optimistic concurrency control in the presence of conflicts</strong>.(<strong>What?</strong>)</p>
</li>
<li>
<p><strong>Operations</strong> that require <strong>synchronization</strong>, such as <strong>transactional reads</strong>, acquire locks in the lock table; other operations bypass the lock table.</p>
</li>
<li>
<p>Each spanserver(at <strong>leader replica)</strong> implements a <strong>transaction manager</strong> to support distributed transactions.</p>
</li>
<li>
<p>If a transaction involves only one Paxos group (as is the case for most transactions), it can bypass the transaction manager, since the lock table and Paxos together provide transactionality.</p>
</li>
<li>
<p>If a transaction involves more than one Paxos group, those groups’ leaders coordinate to perform a <strong>two-phase commit</strong>.</p>
</li>
<li>
<p>The state of each transaction manager is stored in the underlying Paxos group (and therefore is replicated).</p>
</li>
</ul>
<h3 id="directories-and-placement">Directories and Placement</h3>
<ul>
<li>
<p>On top of the <strong>bag of key-value mappings</strong>, the Spanner implementation supports a <strong>bucketing abstraction</strong>(<strong>Directory)</strong>, which is a <strong>set of contiguous keys that share a common prefix</strong>.</p>
</li>
<li>
<p><strong>A directory is the unit of data placement</strong>.</p>
</li>
<li>
<p>The fact that a Paxos group may contain multiple directories implies that a <strong>Spanner tablet</strong> is different from a <strong>Bigtable tablet</strong>. Former is not necessarily a single lexicographically contiguous partition of the row space.
<img loading="lazy" src="/images/notes/spanner-2012/image-3.png"></p>
</li>
<li>
<p><strong>Movedir</strong> is the background task used to move directories between Paxos groups.</p>
</li>
<li>
<p>Application specifies a <strong>directory’s</strong> geographic-replication placement.</p>
</li>
<li>
<p>The design of <strong>placement-specification language</strong> separates responsibilities for <strong>managing replication configurations</strong>.</p>
</li>
<li>
<p>An application controls how data is replicated, by <strong>tagging each database</strong> and/or <strong>individual directories</strong> with a combination of those options.</p>
</li>
<li>
<p>Spanner will <strong>Shard/Partition</strong> a directory into multiple <strong>fragments</strong> if it grows too large.</p>
</li>
</ul>
<h3 id="data-model">Data Model</h3>
<ul>
<li>
<p>Spanner offers a</p>
</li>
<li>
<p><strong>DataModel Use Case:</strong>
<img loading="lazy" src="/images/notes/spanner-2012/image-4.png"></p>
</li>
<li>
<p>This interleaving of tables to form directories is significant because it allows clients to describe the locality relationships that exist between multiple tables, which is necessary for good performance in a sharded, distributed database. Without it, Spanner would not know the most important locality relationships.</p>
</li>
</ul>
<h3 id="truetime">TrueTime</h3>
<ul>
<li>
<p><strong>TrueTime</strong> explicitly represents time as a <strong>TTinterval</strong>, which is an interval with bounded time uncertainty(unlike standard time interfaces that give clients no notion of uncertainty).
<img loading="lazy" src="/images/notes/spanner-2012/image-5.png"></p>
</li>
<li>
<p>The endpoints of a <strong>TTinterval</strong> are of type <strong>TTstamp.</strong></p>
</li>
<li>
<p>The time epoch is analogous to UNIX time with <strong>leap-second smearing</strong>.</p>
</li>
<li>
<p>The underlying time references used by <strong>TrueTime</strong> are <strong>GPS</strong> and <strong>atomic clocks</strong> because they have different failure modes.</p>
</li>
<li>
<p><strong>TrueTime</strong> is implemented by a <strong>set of time master machines</strong> per datacenter and a <strong>timeslave daemon</strong> per machine.</p>
</li>
<li>
<p>All masters’ time references are regularly compared against each other.</p>
</li>
<li>
<p>Every daemon polls a variety of masters to reduce vulnerability to errors from any one master.</p>
</li>
<li>
<p>Uncertainty Range 1-7 ms with 4 ms most of the time at a Daemon poll interval of 30 sec and current drift applied rate is 200 microseconds/second.</p>
</li>
</ul>
<h3 id="concurrency-control">Concurrency Control</h3>
<ul>
<li>TrueTime API is used to guarantee <strong>correctness properties</strong> around concurrency control, and how those properties are used to implement features such as <strong>externally consistent transactions</strong>, <strong>lock-free read-only transactions</strong>, and <strong>non-blocking reads</strong> in the past.</li>
<li>Important to distinguish writes as seen by <strong>Paxos Writes</strong> vs <strong>Spanner client writes.</strong></li>
</ul>
<h3 id="timestamp-management">Timestamp Management</h3>
<ul>
<li>Spanner supports:
<img loading="lazy" src="/images/notes/spanner-2012/image-6.png"></li>
</ul>
<h3 id="paxos-leader-leases">Paxos Leader Leases</h3>
<ul>
<li>Spanner’s Paxos implementation uses <strong>timed leases</strong> to make leadership long-lived (10 seconds by default).</li>
<li><strong>Leader Election</strong></li>
</ul>
<h3 id="assigning-timestamps-to-rw-transactions">Assigning Timestamps to RW Transactions</h3>
<ul>
<li>Transactional reads and writes use <strong>two-phase locking(Reads &amp; Writes block each other).</strong></li>
<li>As a result, they can be assigned timestamps at any time when all locks have been acquired, but before any locks have been released.</li>
<li>For a given transaction, Spanner assigns it the timestamp that Paxos assigns to the Paxos write that represents the transaction commit.</li>
<li><strong>Spanner</strong> depends on the following <strong>monotonicity invariant</strong>: within each Paxos group, Spanner assigns <strong>timestamps</strong> to Paxos writes in <strong>monotonically increasing</strong> order, <strong>even across leaders</strong>.</li>
<li><strong>Spanner</strong> also enforces the following <strong>external-consistency invariant</strong>: if the start of a transaction T2 occurs after commit of a transaction T1, then the commit timestamp of T2 must be greater than the commit timestamp of T1.</li>
</ul>
<h3 id="serving-reads-at-a-timestamp">Serving Reads at a Timestamp</h3>
<h3 id="assigning-timestamps-to-ro-transactions">Assigning Timestamps to RO Transactions</h3>
<h3 id="details">Details</h3>
<h3 id="read-write-transactions">Read Write Transactions</h3>
<ul>
<li>Like <strong>Bigtable</strong>, writes that occur in a transaction are buffered at the client until commit.</li>
<li>As a result, reads in a transaction do not see the effects of the transaction’s writes. This design works well in Spanner because a read returns the timestamps of any data read, and uncommitted writes have not yet been assigned timestamps.</li>
<li>Reads within read-write transactions use wound-wait to avoid deadlocks.</li>
</ul>
<h3 id="jordan-google-spanner2013">Jordan: Google Spanner(2013)</h3>
<ul>
<li>
<p>Strongly consistent SQL Database via Paxos.</p>
</li>
<li>
<p>Supports <strong>causally consistent</strong> Non-Blocking read-only Snapshots <strong>over</strong> <strong>multiple nodes</strong> at a time even though they are <strong>distributed</strong>. This is not something you could do in your traditional database.
Causal Consistency</p>
</li>
<li>
<p>Write B is causally dependent on Write A if</p>
</li>
<li>
<p>Can be achieved by using Lamport Clocks.</p>
</li>
<li>
<p>Spanner is both externally and causally consistent.</p>
</li>
<li>
<p>The order of writes to the database is the order in which the events actually happened.</p>
</li>
<li>
<p>Formally:</p>
</li>
</ul>
<h3 id="spanner-details">Spanner Details</h3>
<ul>
<li>Spanner’s Design looks similar to Megastore to ensure strong consistency.</li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf">https://static.googleusercontent.com/media/research.google.com/en//archive/spanner-osdi2012.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>DynamoDB</title>
      <link>https://www.sethihemant.com/notes/dynamodb-2022/</link>
      <pubDate>Wed, 11 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/dynamodb-2022/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://www.usenix.org/conference/atc22/presentation/elhemali&#34;&gt;DynamoDB&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;dynamodb&#34;&gt;DynamoDB&lt;/h2&gt;
&lt;h3 id=&#34;summaryabstract&#34;&gt;Summary/Abstract&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Amazon DynamoDB is a NoSQL cloud database service that provides consistent performance at any scale.&lt;/li&gt;
&lt;li&gt;Fundamental properties: &lt;strong&gt;consistent performance&lt;/strong&gt;, &lt;strong&gt;availability&lt;/strong&gt;, &lt;strong&gt;durability&lt;/strong&gt;, and a &lt;strong&gt;fully managed serverless&lt;/strong&gt; experience.&lt;/li&gt;
&lt;li&gt;In 2021, during the 66-hour Amazon Prime Day shopping event, &lt;strong&gt;89.2 million requests per second&lt;/strong&gt;, while experiencing high availability with &lt;strong&gt;single-digit millisecond&lt;/strong&gt; performance.&lt;/li&gt;
&lt;li&gt;Design and implementation of &lt;strong&gt;DynamoDB&lt;/strong&gt; have evolved since the first launch in 2012. The system has successfully dealt with issues related to &lt;strong&gt;fairness&lt;/strong&gt;, &lt;strong&gt;traffic imbalance across partitions&lt;/strong&gt;, &lt;strong&gt;monitoring&lt;/strong&gt;, and &lt;strong&gt;automated system operations&lt;/strong&gt; without impacting availability or performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;goal of the design&lt;/strong&gt; of DynamoDB is to complete all requests with &lt;strong&gt;low single-digit millisecond&lt;/strong&gt; latencies.&lt;/li&gt;
&lt;li&gt;DynamoDB uniquely integrates the following six fundamental system properties:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB is a fully managed cloud service.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB employs a multi-tenant architecture.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB provides predictable performance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB is highly available.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB supports flexible use cases.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;DynamoDB evolved as a distributed database service to meet the needs of its customers without losing its key aspect of providing a single-tenant experience to every customer using a multi-tenant architecture.&lt;/li&gt;
&lt;li&gt;The paper explains the challenges faced by the system and how the service evolved to handle those challenges while &lt;strong&gt;connecting&lt;/strong&gt; the required changes to a common theme of durability, availability, scalability, and predictable performance.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/dynamodb-2022/image-1.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;history&#34;&gt;History&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Design of DynamoDB was motivated by our experiences with its predecessor &lt;strong&gt;Dynamo. Dynamo&lt;/strong&gt; was created in response to the need for a highly scalable, available, and durable key-value database for shopping cart data&lt;/li&gt;
&lt;li&gt;Amazon learned that providing applications with direct access to traditional enterprise database instances led to scal- ing bottlenecks such as connection management, interference between concurrent workloads, and operational problems with tasks such as schema upgrades.&lt;/li&gt;
&lt;li&gt;Service Oriented Architecture was adopted to encapsulate an application’s data behind service-level APIs that allowed sufficient decoupling to address tasks like reconfiguration without having to disrupt clients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt; took the principles from &lt;strong&gt;Dynamo&lt;/strong&gt;(which was being run as Self-hosted DB but created operational burden for developers) &amp;amp; &lt;strong&gt;Simple DB&lt;/strong&gt;, a fully managed elastic NoSQL database service, but the data model couldn’t scale to the demands of the large Tables which DDB needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamo Limitations:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SimpleDB limitations&lt;/strong&gt;:&lt;/li&gt;
&lt;li&gt;Amazon concluded that a better solution would combine the best parts of the original Dynamo design (incremental scalability and predictable high performance) with the best parts of SimpleDB (ease of administration of a cloud service, consistency, and a table-based data model that is richer than a pure key-value store)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;architecture&#34;&gt;Architecture&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A DynamoDB table is a collection of items.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://www.usenix.org/conference/atc22/presentation/elhemali">DynamoDB</a></p>
<hr>
<h2 id="dynamodb">DynamoDB</h2>
<h3 id="summaryabstract">Summary/Abstract</h3>
<ul>
<li>Amazon DynamoDB is a NoSQL cloud database service that provides consistent performance at any scale.</li>
<li>Fundamental properties: <strong>consistent performance</strong>, <strong>availability</strong>, <strong>durability</strong>, and a <strong>fully managed serverless</strong> experience.</li>
<li>In 2021, during the 66-hour Amazon Prime Day shopping event, <strong>89.2 million requests per second</strong>, while experiencing high availability with <strong>single-digit millisecond</strong> performance.</li>
<li>Design and implementation of <strong>DynamoDB</strong> have evolved since the first launch in 2012. The system has successfully dealt with issues related to <strong>fairness</strong>, <strong>traffic imbalance across partitions</strong>, <strong>monitoring</strong>, and <strong>automated system operations</strong> without impacting availability or performance.</li>
</ul>
<h3 id="introduction">Introduction</h3>
<ul>
<li>The <strong>goal of the design</strong> of DynamoDB is to complete all requests with <strong>low single-digit millisecond</strong> latencies.</li>
<li>DynamoDB uniquely integrates the following six fundamental system properties:</li>
<li><strong>DynamoDB is a fully managed cloud service.</strong></li>
<li><strong>DynamoDB employs a multi-tenant architecture.</strong></li>
<li><strong>DynamoDB provides predictable performance</strong></li>
<li><strong>DynamoDB is highly available.</strong></li>
<li><strong>DynamoDB supports flexible use cases.</strong></li>
<li>DynamoDB evolved as a distributed database service to meet the needs of its customers without losing its key aspect of providing a single-tenant experience to every customer using a multi-tenant architecture.</li>
<li>The paper explains the challenges faced by the system and how the service evolved to handle those challenges while <strong>connecting</strong> the required changes to a common theme of durability, availability, scalability, and predictable performance.
<img loading="lazy" src="/images/notes/dynamodb-2022/image-1.png"></li>
</ul>
<h3 id="history">History</h3>
<ul>
<li>Design of DynamoDB was motivated by our experiences with its predecessor <strong>Dynamo. Dynamo</strong> was created in response to the need for a highly scalable, available, and durable key-value database for shopping cart data</li>
<li>Amazon learned that providing applications with direct access to traditional enterprise database instances led to scal- ing bottlenecks such as connection management, interference between concurrent workloads, and operational problems with tasks such as schema upgrades.</li>
<li>Service Oriented Architecture was adopted to encapsulate an application’s data behind service-level APIs that allowed sufficient decoupling to address tasks like reconfiguration without having to disrupt clients.</li>
<li><strong>DynamoDB</strong> took the principles from <strong>Dynamo</strong>(which was being run as Self-hosted DB but created operational burden for developers) &amp; <strong>Simple DB</strong>, a fully managed elastic NoSQL database service, but the data model couldn’t scale to the demands of the large Tables which DDB needed.</li>
<li><strong>Dynamo Limitations:</strong></li>
<li><strong>SimpleDB limitations</strong>:</li>
<li>Amazon concluded that a better solution would combine the best parts of the original Dynamo design (incremental scalability and predictable high performance) with the best parts of SimpleDB (ease of administration of a cloud service, consistency, and a table-based data model that is richer than a pure key-value store)</li>
</ul>
<h3 id="architecture">Architecture</h3>
<ul>
<li>
<p>A DynamoDB table is a collection of items.</p>
</li>
<li>
<p>Each item is a collection of attributes. Uniquely identified by a primary key.</p>
</li>
<li>
<p>Schema of the primary key is specified at the table creation time.</p>
</li>
<li>
<p>The partition key’s value is always used as an input to an internal hash function.</p>
</li>
<li>
<p>The output from the hash function and the sort key value (if present) determines where the item will be stored.</p>
</li>
<li>
<p>Multiple items can have the same partition key value in a table with a composite primary key. However, those items must have different sort key values.</p>
</li>
<li>
<p>Supports secondary indexes to provide enhanced querying capability, which allows querying the data in the table using an alternate key.</p>
</li>
<li>
<p>DynamoDB provides a simple interface to store or retrieve items from a table or an index.
<img loading="lazy" src="/images/notes/dynamodb-2022/image-2.png"></p>
</li>
<li>
<p>DynamoDB supports ACID transactions for multi-item updates w/o affecting scalability/availability/performance.</p>
</li>
<li>
<p>A DynamoDB table is divided into multiple partitions.</p>
</li>
<li>
<p>Each partition of the table hosts a disjoint and contiguous part of the table’s key-range and has multiple replicas(<strong>replication Group</strong>) distributed across different Availability Zones for high availability and durability.</p>
</li>
<li>
<p>The Replication Group uses Multi-Paxos for leader election and consensus.</p>
</li>
<li>
<p>Any replica can trigger a round of the election.</p>
</li>
<li>
<p>Once elected leader, a replica can maintain leadership as long as it periodically renews its leadership lease.</p>
</li>
<li>
<p>Only the leader replica can serve <strong>write</strong> and <strong>strongly consistent read</strong> requests.</p>
</li>
<li>
<p>Leader generates a write-ahead log record and sends it to its peers.</p>
</li>
<li>
<p><strong>Write</strong> is acknowledged to the application once a quorum of peers persists the log record to their local write-ahead logs.</p>
</li>
<li>
<p>DynamoDB supports <strong>strong(Leader Read)</strong> and <strong>eventually consistent(Replica read)</strong> reads.</p>
</li>
<li>
<p>The leader of the group extends its leadership using a lease mechanism.</p>
</li>
<li>
<p>If the leader of the group is detected as a failure (considered unhealthy or unavailable) by any of its peers, the peer can propose a new round of the election to elect itself as the new leader. The new leader won’t serve any writes or consistent reads until the previous leader’s lease expires.</p>
</li>
<li>
<p>Partitioning/Replication Group
<img loading="lazy" src="/images/notes/dynamodb-2022/image-3.png"></p>
</li>
<li>
<p><strong>Log Replica/Node</strong> - Write Ahead Log(replicated) for High Availability and Durability.
<img loading="lazy" src="/images/notes/dynamodb-2022/image-4.png"></p>
</li>
<li>
<p>Multi-Paxos Leader Election and Consensus.</p>
</li>
<li>
<p>Writes and Strongly/Eventually Consistent Reads</p>
</li>
<li>
<p>Microservice architecture
<img loading="lazy" src="/images/notes/dynamodb-2022/image-5.png"></p>
</li>
<li>
<p><strong>Metadata Service</strong></p>
</li>
<li>
<p><strong>Request Router Service</strong></p>
</li>
<li>
<p><strong>Auto-Admin Service(Central Nervous System of DDB)</strong></p>
</li>
<li>
<p><strong>Storage Service</strong></p>
</li>
<li>
<p><strong>Features supported by other Services</strong></p>
</li>
</ul>
<h3 id="journey-from-provisioned-to-on-demand">Journey from Provisioned to On-Demand</h3>
<ul>
<li>
<p>DDB was launched with <strong>Partitions</strong> as an internal abstraction, as a way to dynamically scale both the <strong>capacity</strong> and <strong>performance</strong> of tables.</p>
</li>
<li>
<p>Customers explicitly specified the throughput that a table required in terms of read capacity units (RCUs) and write capacity units (WCUs). RCUs and WCUs collectively are called <strong>provisioned</strong> throughput.</p>
</li>
<li>
<p>As the demands from a table changed (because it grew in size or because the load increased), partitions could be further split and migrated to allow the table to scale elastically. <strong>Partition</strong> abstraction proved to be really valuable and continues to be central to the design of DynamoDB.</p>
</li>
<li>
<p><strong>[Challenge] This early version tightly coupled the assignment of both capacity and performance to individual partitions, which led to challenges</strong></p>
</li>
<li>
<p>DynamoDB uses <strong>admission control</strong> to ensure that storage nodes don’t become overloaded, to avoid interference between co-resident table partitions, and to enforce the throughput limits requested by customers.</p>
</li>
<li>
<p><strong>Admission control</strong> was the shared responsibility of all storage nodes for a table. Storage nodes independently performed admission control based on the allocations of their locally stored partitions.</p>
</li>
<li>
<p>Allocated throughput of each partition was used to isolate the workloads. DynamoDB enforced a cap on the maximum throughput that could be allocated to a single partition. Total throughput of all the partitions hosted by a storage node is less than or equal to the maximum allowed throughput on the node as determined by the physical characteristics of its storage drives.</p>
</li>
<li>
<p>The throughput allocated to partitions was adjusted when the overall table’s throughput was changed or its partitions were split into child partitions.</p>
</li>
<li>
<p>When a partition was split for size, the allocated throughput of the parent partition was equally divided among the child partitions and was allocated based on the table’s provisioned throughput.</p>
</li>
<li>
<p>E.g. Assume that a partition can accommodate a maximum provisioned throughput of 1000 WCUs. When a table is created with 3200 WCUs, DynamoDB created four partitions that each would be allocated 800 WCUs. If the table’s provisioned throughput was increased to 3600 WCUs, then each partition’s capacity would increase to 900 WCUs. If the table’s provisioned throughput was increased to 6000 WCUs, then the partitions would be split to create eight child partitions, and each partition would be allocated 750 WCUs. If the table’s capacity was decreased to 5000 WCUs, then each partition’s capacity would be decreased to 675 WCUs</p>
</li>
<li>
<p>The uniform distribution of throughput across partitions is based on the assumptions that an application uniformly accesses keys in a table and the splitting a partition for size equally splits the performance.</p>
</li>
<li>
<p>However, it was discovered that <strong>application workloads frequently have non-uniform access patterns both over time and over key ranges</strong>.</p>
</li>
<li>
<p><strong>Hot Partition Worsening with Split</strong>: When the request rate within a table is non-uniform, splitting a partition and dividing performance allocation proportionately can result in the hot portion of the partition having less available performance than it did before the split.</p>
</li>
<li>
<p><strong>[Single Hot Partition]</strong> Since throughput was allocated statically and enforced at a partition level, these non-uniform workloads occasionally resulted in an application’s reads and writes being rejected, called throttling, even though the total provisioned throughput of the table was sufficient to meet its needs.
Common Challenges faced by the applications were:</p>
</li>
<li>
<p><strong>Hot Partition</strong></p>
</li>
<li>
<p><strong>Throughput Dilution.</strong></p>
</li>
<li>
<p>Customers would increase the provisioned throughput of the table(even if they were under the limit overall), which caused poor performance. It was difficult to estimate the correct provisioned throughput.</p>
</li>
<li>
<p><strong>Hot partitions</strong> and <strong>throughput dilution</strong> stemmed from tightly coupling a rigid performance allocation to each partition, and dividing that allocation as partitions split. <strong>Bursting</strong> and <strong>Adaptive Capacity</strong> to address these concerns.</p>
</li>
</ul>
<h3 id="improvements-to-admission-control">Improvements to Admission Control:</h3>
<h3 id="key-observations">Key Observations:</h3>
<ul>
<li>Partitions had non-uniform access/traffic.</li>
<li>Not all partitions hosted by a storage node used their allocated throughput simultaneously.</li>
</ul>
<h3 id="bursting">Bursting</h3>
<ul>
<li>The idea behind <strong>Bursting</strong> was to let applications tap into the unused capacity at a partition level on a best effort basis to absorb short-lived spikes.</li>
<li>DynamoDB retained a portion of a partition’s unused capacity for later bursts of throughput usage for up to 300 seconds and utilized it when consumed capacity exceeded the provisioned capacity of the partition.</li>
<li>DynamoDB still maintained <strong>workload isolation</strong> by ensuring that a partition could only burst if there was <strong>unused throughput at the node level</strong>. The capacity was managed on the storage node using <strong>multiple token buckets</strong> to provide admission control:</li>
<li>**[Partition Token + Node Token]**When a read or write request arrives on a storage node, if there were tokens in the partition’s allocated token bucket, then the request was admitted and tokens were deducted from the partition and node level bucket.</li>
<li><strong>[Burst Token + Node Token]</strong> Once a partition had exhausted all the provisioned tokens, requests were allowed to burst only when tokens were available both in the burst token bucket and the node level token bucket.</li>
<li>Read requests were accepted based on the local token buckets.</li>
<li><strong>[Replica node’s Token Bucket for Write]</strong> Write requests using burst capacity require an additional check on the node-level token bucket of other member replicas of the partition.</li>
<li>The leader replica of the partition periodically collected information about each of the members node-level capacity.</li>
</ul>
<h3 id="adaptive-capacity">Adaptive Capacity</h3>
<ul>
<li>DynamoDB launched adaptive capacity to better <strong>absorb long-lived spikes</strong> that cannot be absorbed by the burst capacity.</li>
<li>Better absorb work-loads that had heavily skewed access patterns across partitions.</li>
<li>Adaptive capacity actively monitored the provisioned and consumed capacity of all the tables.</li>
<li>If a table experienced throttling and the table level throughput was not exceeded, then it would automatically increase (boost) the allocated throughput of the partitions of the table using a <strong>proportional control algorithm.</strong></li>
<li>The <strong>autoadmin system</strong> ensured that partitions receiving boost were relocated to an appropriate node that had the capacity to serve the increased throughput, however like bursting, adaptive capacity was also <strong>best-effort</strong> but <strong>eliminated over 99.99%</strong> of the throttling due to skewed access pattern.</li>
</ul>
<h3 id="global-admission-control">Global Admission Control</h3>
<ul>
<li>Even though Bursting and Adaptive Capacity significantly reduced throughput problems for non-uniform access, they had <strong>limitations</strong>.</li>
<li>Takeaway from bursting and adaptive capacity was that we had <strong>tightly coupled partition level capacity</strong> to <strong>admission control</strong>.</li>
<li>Admission control was <strong>distributed and performed at a partition level.</strong></li>
<li>DynamoDB realized <strong>it would be beneficial to remove admission control from the partition</strong> and <strong>let the partition always burst while providing workload isolation</strong>.</li>
<li>DynamoDB replaced <strong>adaptive capacity</strong> with <strong>global admission control (GAC)</strong>.</li>
<li>GAC builds on the same idea of <strong>Token Bucket</strong>.</li>
<li>The GAC service <strong>centrally tracks the total consumption of the table</strong> capacity in terms of tokens.</li>
<li>Each request router maintains a <strong>local token bucket</strong> to make admission decisions and communicates with <strong>GAC to replenish tokens at regular intervals</strong> (in the order of a few seconds).</li>
<li><strong>[Important Design Consideration] Each GAC server can be stopped and restarted without any impact on the overall operation of the service</strong>.</li>
<li>Each GAC server can track one or more token buckets configured independently.</li>
<li>All the GAC servers are part of an independent hash ring.</li>
<li><strong>Request routers manage several time-limited tokens locally</strong>. When a request from the application arrives, the request router deducts tokens. Eventually, the request router will run out of tokens because of consumption or expiry. <strong>When the request router runs off of tokens, it requests more tokens from GAC.</strong></li>
<li>The GAC instance uses the information provided by the client to <strong>estimate the global token consumption and vends tokens available for the next time unit to the client’s share of overall tokens</strong>.</li>
<li>Thus, it ensures that non-uniform workloads that send traffic to only a subset of items can execute up to the maximum partition capacity.</li>
<li>In addition to the global admission control scheme, the partition-level token buckets were retained for defense in-depth. The capacity of these token buckets is then capped to ensure that one application doesn’t consume all or a significant share of the resources on the storage nodes.</li>
</ul>
<h3 id="balancing-consumed-capacity">Balancing Consumed Capacity</h3>
<ul>
<li>Letting partitions burst(always) required DynamoDB to manage burst capacity effectively.</li>
<li><strong>Colocation</strong> was a straightforward problem with provisioned throughput tables because of static partitions.</li>
</ul>
<h3 id="splitting-for-consumption">Splitting for Consumption</h3>
<ul>
<li><strong>[Problem]</strong> Even with GAC and the ability for partitions to always burst, tables could experience throttling if their traffic was skewed to a specific set of items.</li>
<li><strong>[Solution]</strong></li>
<li>DynamoDB automatically scales out partitions once the consumed throughput of a partition crosses a certain threshold.</li>
<li>The split point in the key range is chosen based on key distribution the partition has observed.</li>
<li>The observed key distribution serves as a proxy for the application’s access pattern and is more effective than splitting the key range in the middle.</li>
<li>Partition splits usually complete in the order of minutes.</li>
<li><strong>[Catch]</strong> Still class of workloads exist that cannot benefit from split for consumption. E.g. a partition receiving high traffic to a single item or a partition where the key range is accessed sequentially will not benefit from split. DDB avoids splitting the partition.</li>
</ul>
<h3 id="on-demand-provisioning">On Demand Provisioning</h3>
<ul>
<li><strong>[Context]</strong></li>
<li>Initially, applications migrated to DDB, were on self provisioned servers either on-prem or on self-hosted databases.</li>
<li>DynamoDB provides a simplified serverless operational model and a new model for provisioning - read and write capacity units.</li>
<li><strong>[Problem]</strong></li>
<li>The concept of capacity units was new to customers, some found it challenging to forecast the provisioned throughput.</li>
<li>Customers either over provisioned(Low utilization) or under provisioned(Throttling).</li>
<li><strong>[Solution]</strong> To improve the customer experience for spiky workloads, DDB launched <strong>On-Demand Tables</strong>.</li>
<li>DynamoDB provisions the on-demand tables <strong>based on the consumed capacity</strong> by <strong>collecting the signal of reads and writes</strong> and instantly accommodates <strong>up to double the previous peak traffic</strong> on the table.</li>
<li>On-demand scales a table by splitting partitions for consumption. The split decision algorithm is based on traffic.</li>
<li>GAC allows DynamoDB to monitor and protect the system from one application consuming all the resources.</li>
</ul>
<h3 id="durability-and-correctness">Durability and Correctness</h3>
<ul>
<li>Data loss can occur because of hardware failures, software bugs, or hardware bugs.</li>
<li>DynamoDB is designed for high durability by having mechanisms to prevent, detect, and correct any potential data losses.</li>
</ul>
<h3 id="hardware-failures">Hardware Failures</h3>
<ul>
<li><strong>Write-ahead logs(WAL)</strong> in DynamoDB are central for providing durability and crash recovery. Write ahead logs are stored in all three replicas of a partition.</li>
<li>For higher durability, the write ahead logs are periodically archived to S3, an object store that is designed for 11 nines(99.999999999) of durability.</li>
<li>The unarchived logs are typically a few hundred megabytes in size.</li>
<li>When a node fails, all replication groups hosted on the node are down to two copies.</li>
<li>The process of healing a storage replica can take several minutes because the repair process involves copying the <strong>B-tree</strong> and <strong>write-ahead logs</strong>.</li>
<li><strong>[Solution]</strong> Upon detecting an unhealthy storage replica, the leader of a replication group adds a <strong>log replica</strong> to ensure there is no impact on durability.</li>
<li>Adding a log replica takes only a few seconds because the system has to copy only the recent write-ahead logs from a healthy replica to the new replica without the B-tree. Quick healing of impacted replication groups using log replicas ensures high durability of most recent writes.</li>
</ul>
<h3 id="silent-data-errors">Silent Data Errors</h3>
<ul>
<li><strong>[Problem]</strong> Some hardware failures can cause incorrect data to be stored . These errors can happen because of the storage media, CPU, or memory.</li>
<li>It&rsquo;s very difficult to detect these and they can happen anywhere in the system.</li>
<li><strong>[Solution]</strong> DynamoDB makes extensive use of <strong>checksums</strong> to detect silent errors.</li>
<li>By maintaining checksums within every log entry, message, and log file, DynamoDB validates data integrity for every data transfer between two nodes.</li>
<li><strong>Checksums</strong> serve as guardrails to prevent errors from spreading to the rest of the system.</li>
<li>Every log file that is archived to S3 has a <strong>manifest</strong> that contains information about the log, such as a table, partition and start and end markers for the data stored in the log file.</li>
<li>The agent responsible for archiving log files to S3 performs various checks before uploading the data. These include and are not limited to verification of every log entry to ensure that it belongs to the correct table and partition, verification of checksums to detect any silent errors, and verification that the log file doesn’t have any holes in the sequence numbers.</li>
<li>Once all the checks are passed, the log file and its manifest are archived. Log archival agents run on all three replicas of the replication group. If one of the agents finds that a log file is already archived, the agent downloads the uploaded file to verify the integrity of the data by comparing it with its local write-ahead log.</li>
<li>Every log file and manifest file are uploaded to <strong>S3 with a content checksum</strong>. The <strong>content checksum is checked by S3 as part of the put operation</strong>, which guards against any <strong>errors during data transit to S3.</strong></li>
</ul>
<h3 id="continuous-verification">Continuous Verification</h3>
<ul>
<li>DynamoDB also continuously verifies data at rest. Our goal is to detect any silent data errors or bit rot in the system. An example of such a continuous verification system is the <strong>scrub process</strong>.</li>
<li>The goal of <strong>scrub</strong> is to detect errors that we had not anticipated, such as <strong>bit rot</strong>.</li>
<li>The <strong>scrub process</strong> runs and verifies two things:</li>
<li>The verification is done by computing the checksum of the live replica and matching that with a snapshot of one generated from the log entries archived in S3.</li>
<li><strong>Scrub</strong> acts as a defense in depth <strong>to detect divergences</strong> between the <strong>live storage replicas</strong> with the <strong>replicas built using the history of logs</strong> from the inception of the table.</li>
<li>A similar technique of continuous verification is used to verify replicas of <strong>global tables.</strong></li>
<li>We have learned that continuous verification of data-at-rest is the most reliable method of protecting against hardware failures, silent data corruption, and even software bugs.</li>
</ul>
<h3 id="software-bugs">Software Bugs</h3>
<ul>
<li><strong>[Problem]</strong> DDB is a complex Distributed Key Value store. High complexity increases the probability of human error in design, code, and operations. Errors in the system could cause loss or corruption of data, or violate other interface contracts that our customers depend on.</li>
<li><strong>[Solution]</strong> DDB uses formal methods extensively to ensure the correctness of our replication protocols. The core replication protocol was specified using TLA+.</li>
<li>When new features that affect the replication protocol are added, they are incorporated into the specification and model checked.</li>
<li>Model checking has allowed us to catch subtle bugs that could have led to durability and correctness issues before the code went into production. S3 also uses Model Checking.</li>
<li><strong>Extensive failure injection testing</strong> and <strong>stress testing</strong> to ensure the correctness of every piece of software deployed.</li>
<li>In addition to testing and verification of the replication protocol of the data plane, <strong>formal methods have also been used to verify the correctness of our control plane and features such as distributed transactions</strong>.</li>
</ul>
<h3 id="backups-and-restore">Backups and Restore</h3>
<ul>
<li>In addition to guarding against physical media corruption, DynamoDB also supports backup and restore <strong>to protect against any logical corruption</strong> <strong>due to a bug in a customer’s application</strong>. Backups or restores don’t affect performance or availability of the table as they are built using the write-ahead logs that are archived in S3.</li>
<li>The backups are consistent across multiple partitions up to the nearest second.</li>
<li>The backups are full copies of DynamoDB tables and are stored in an Amazon S3 bucket.</li>
<li>DynamoDB also supports <strong>point-in-time restore</strong> where customers can <strong>restore the contents of a table that existed at any time in the previous 35 days to a different DynamoDB table in the same region.</strong></li>
<li>For tables with the point-in-time restore enabled, DynamoDB creates <strong>periodic</strong>(<strong>based on the amount of write-ahead logs accumulated for the partition</strong>) snapshots of the partitions that belong to the table and uploads them to S3.</li>
<li>Snapshots, in conjunction to write-ahead logs, are used to do point-in-time restore.</li>
<li><strong>[Workflow]</strong> When a point-in-time restore is requested for a table,</li>
</ul>
<h3 id="availability">Availability</h3>
<ul>
<li>To achieve high availability, DynamoDB tables are distributed and replicated across multiple Availability Zones (AZ) in a Region. DynamoDB regularly tests resilience to node, rack, and AZ failures.</li>
<li>To test the availability and durability of the overall service, power-off tests are exercised. Using realistic simulated traffic, random nodes are powered off using a job scheduler. At the end of all the power-off tests, the test tools verify that the data stored in the database is logically valid and not corrupted.</li>
</ul>
<h3 id="write-and-consistent-read-availability">Write and Consistent Read Availability</h3>
<ul>
<li>A partition’s <strong>write availability</strong> depends on its ability to have a <strong>healthy leader</strong> and a <strong>healthy write quorum.</strong></li>
<li>A <strong>healthy write quorum</strong> in the case of DynamoDB consists of two out of the three replicas from different AZs.</li>
<li>A partition remains available as long as there are enough healthy replicas for a write quorum and a leader</li>
<li>A partition will become unavailable for writes if the number of replicas needed to achieve the minimum quorum are unavailable</li>
<li>The leader replica serves <strong>consistent reads</strong>.</li>
<li>Introducing <strong>log replicas</strong> was a big change to the system, and the formally proven implementation of Paxos provided us the confidence to safely tweak and experiment with the system to achieve higher availability</li>
<li>Eventually consistent reads can be served by any of the replicas.</li>
<li>In case a leader replica fails, other replicas detect its failure and elect a new leader to minimize disruptions to the availability of consistent reads.</li>
</ul>
<h3 id="failure-detection">Failure Detection</h3>
<ul>
<li><strong>[Problem]</strong> A newly elected leader will have to wait for the expiry of the old leader’s lease before serving any traffic. While this only takes a couple of seconds, the elected leader cannot accept any new writes or consistent read traffic during that period, thus disrupting availability.</li>
<li>Failure detection must be quick and robust to minimize disruptions. False positives in failure detection can lead to more disruptions in availability. Failure detection works well for failure scenarios where every replica of the group loses connection to the leader.</li>
<li>However, nodes can experience gray network failures(Gray Failure).</li>
<li>Gray network failures can happen because of communication issues between a leader and follower, issues with outbound or inbound communication of a node, or front-end routers facing communication issues with the leader even though the leader and followers can communicate with each other.</li>
<li>Gray failures can disrupt availability because there might be a false positive in failure detection or no failure detection</li>
<li>For example, a replica that isn’t receiving heartbeats from a leader will try to elect a new leader. This can disrupt availability.</li>
<li><strong>[Solution]</strong> To solve the availability problem caused by gray failures, a follower that wants to trigger a failover sends a message to other replicas in the replication group asking if they can communicate with the leader. If replicas respond with a healthy leader message, the follower drops its attempt to trigger a leader election. This change in the failure detection algorithm used by DynamoDB significantly minimized the number of false positives in the system, and hence the number of spurious leader elections.</li>
</ul>
<h3 id="measuring-availability">Measuring Availability</h3>
<ul>
<li>DynamoDB is designed for <strong>99.999(5-9s)</strong> percent availability for global tables and 99.99**(4-9s)** percent availability for regional tables.</li>
<li>To ensure these goals are being met, DynamoDB continuously monitors availability at service and table levels. The tracked availability data is used to analyze customer perceived availability trends and trigger alarms if customers see errors above a certain threshold. These alarms are called customer-facing alarms (CFA) to report any availability-related problems and proactively mitigate the problem either automatically or through operator intervention.</li>
<li>In addition to real time monitoring of availability, the system runs daily jobs that trigger aggregation to calculate aggregate availability metrics per customer.</li>
<li>DynamoDB also measures and alarms on availability observed on the client-side. There are <strong>two sets of clients</strong> used to measure the <strong>user-perceived availability</strong>.</li>
<li>Real application traffic allows us to reason about DynamoDB availability and latencies as seen by our customers and catch gray failures.</li>
</ul>
<h3 id="deployments">Deployments</h3>
<ul>
<li>Unlike a traditional relational database, <strong>DynamoDB takes care of deployments without the need for maintenance windows</strong> and without impacting the performance and availability that customers experience.</li>
<li>The rollback procedure is often missed in testing and can lead to customer impact. DynamoDB runs a suite of <strong>upgrade and downgrade tests</strong> at a component level before every deployment.</li>
<li><strong>[Problem]</strong> Deployments are not atomic in a distributed system. At any given time, there will be software running the old code on some nodes and new code on other parts of the fleet.</li>
<li>New software might introduce a new type of message or change the protocol in a way that old software in the system doesn’t understand.</li>
<li><strong>[Solution]</strong> DynamoDB handles these kinds of changes with <strong>read-write deployments</strong>. Read-write deployment is completed as a multi-step process.</li>
<li>The first step is to deploy the software to read the new message format or protocol. Once all the nodes can handle the new message, the software is updated to send new messages.</li>
<li><strong>Read-write</strong> deployments ensure that both types of messages can coexist in the system. Even in the case of rollbacks, the system can understand both old and new messages.</li>
<li><strong>[OneBox]</strong> Deployments are done on a small set of nodes before pushing them to the entire fleet of nodes. The strategy reduces the potential impact of faulty deployments.</li>
<li>[<strong>AutoRollback AlarmWatcher/ApprovalWorkflow</strong>] DynamoDB sets alarm thresholds on availability metrics. If error rates or latency exceed the threshold values during deployments, the system triggers automatic rollbacks.</li>
<li>**[Problem]**Software deployments to storage nodes trigger <strong>leader failovers</strong> that are designed to avoid any impact to availability.</li>
</ul>
<h3 id="dependencies-on-external-services">Dependencies on External Services</h3>
<ul>
<li>To ensure high availability, all the services that DynamoDB depends on in the request path should be more highly available than DynamoDB.</li>
<li>Alternatively, DynamoDB should be able to continue to operate even when the services on which it depends are impaired.</li>
<li>Examples of services DynamoDB depends on for the request path include AWS Identity and Access Management Services (IAM), and AWS Key Management Service (AWS KMS) for tables encrypted using customer keys. DynamoDB uses IAM and AWS KMS to authenticate every customer request.</li>
<li>While these services are highly available, DynamoDB is designed to operate when these services are unavailable without sacrificing any of the security properties that these systems provide.</li>
<li>In the case of IAM and AWS KMS, DynamoDB employs a <strong>statically stable design</strong>, where the overall system keeps working even when a dependency becomes impaired.</li>
<li>Perhaps the system doesn’t see any updated information that its dependency was supposed to have delivered. However, everything before the dependency became impaired continues to work despite the impaired dependency.</li>
<li>DynamoDB caches result from IAM and AWS KMS in the request routers that perform the authentication of every request. DynamoDB periodically refreshes the cached results asynchronously.</li>
<li>If AWS IAM or KMS were to become unavailable, the routers will continue to use the cached results for a predetermined extended period.</li>
<li>Caches improve response times by removing the need to do an off-box call, which is especially valuable when the system is under high load.</li>
</ul>
<h3 id="metadata-availability">Metadata Availability</h3>
<ul>
<li>One of the most important pieces of metadata the request routers needs is the mapping between a table’s primary keys and storage nodes.</li>
<li>[<strong>Metadata Storage</strong>]At launch, DynamoDB stored the metadata in DynamoDB itself.</li>
<li>[<strong>Routing Schema</strong>] This routing information consists of all the partitions for a table, the key range of each partition, and the storage nodes hosting the partition.</li>
<li>[<strong>Router Metadata Caching</strong>] When a router received a request for a table it had not seen before, it downloaded the routing information for the entire table and cached it locally. Since the configuration information about partition replicas rarely changes, the <strong>cache hit rate</strong> was approximately <strong>99.75 percent</strong>.</li>
</ul>
<h3></h3>
<h3 id="dynamodb-limits">DynamoDB Limits</h3>
<ul>
<li>**Per Partition Read and Write Capacity Units - **<strong>Ref</strong></li>
<li><strong>1 MB limit</strong> on the size of data returned by a single Query, <strong>Scan</strong>/<strong>GetItem Op</strong>.</li>
<li><strong>BatchGetItem</strong> operation can return up to <strong>16MB</strong> of data - <strong>Ref</strong></li>
<li><strong>Item Size Limit</strong>: Ref</li>
<li>**Secondary Indexes - **<strong>Ref</strong></li>
<li><strong>Transactions:</strong></li>
</ul>
<h3 id="microbenchmarks">MicroBenchmarks</h3>
<ul>
<li>To show that scale doesn’t affect the latencies observed by applications, we ran YCSB [8] workloads of types A (50 percent reads and 50 percent updates) and B (95 percent reads and 5 percent updates)</li>
<li>Both benchmarks used a uniform key distribution and items of size 900 bytes.</li>
<li>The workloads were scaled from 100 thousand total operations per second to 1 million total operations per second.</li>
<li>The purpose of the graph is to show, even at different throughput, DynamoDB read latencies show very little variance and remain identical even as the throughput of the workload is increased.
<img loading="lazy" src="/images/notes/dynamodb-2022/image-6.png"></li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://www.usenix.org/conference/atc22/presentation/elhemali">https://www.usenix.org/conference/atc22/presentation/elhemali</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>BigTable</title>
      <link>https://www.sethihemant.com/notes/bigtable-2006/</link>
      <pubDate>Mon, 09 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/bigtable-2006/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf&#34;&gt;BigTable&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;bigtablewide-column-storage-system&#34;&gt;BigTable/Wide Column Storage System&lt;/h2&gt;
&lt;h3 id=&#34;goal&#34;&gt;Goal&lt;/h3&gt;
&lt;p&gt;Design a &lt;strong&gt;distributed&lt;/strong&gt; and &lt;strong&gt;scalable&lt;/strong&gt; system that can store a &lt;strong&gt;huge amount of semi-structured data&lt;/strong&gt;. The data will be indexed by a &lt;strong&gt;row key&lt;/strong&gt; where each row can have an &lt;strong&gt;unbounded number of columns&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&#34;what-is-bigtable&#34;&gt;What is BigTable&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;BigTable is a &lt;strong&gt;distributed&lt;/strong&gt; and massively &lt;strong&gt;scalable&lt;/strong&gt; &lt;strong&gt;wide-column store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Designed to store huge sets of &lt;strong&gt;structured&lt;/strong&gt; data.&lt;/li&gt;
&lt;li&gt;Provides storage for very big tables (often in the &lt;strong&gt;terabyte&lt;/strong&gt; range)&lt;/li&gt;
&lt;li&gt;BigTable is a &lt;strong&gt;CP system&lt;/strong&gt;, i.e., it has &lt;strong&gt;strongly consistent&lt;/strong&gt; reads and writes.&lt;/li&gt;
&lt;li&gt;BigTable can be used as an input source or output destination for MapReduce.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Developed at Google in 2005 and used in dozens of Google services.&lt;/li&gt;
&lt;li&gt;Google couldn’t use external commercial databases because of its large scale services, and costs would have been too high. So they built an in-house solution, custom built for their use case and traffic patterns.&lt;/li&gt;
&lt;li&gt;BigTable is &lt;strong&gt;highly available(?? With consistency??)&lt;/strong&gt; and high-performing database that powers multiple applications across Google — where each application has different needs in terms of the size of data to be stored and latency with which results are expected.&lt;/li&gt;
&lt;li&gt;BigTable inspired various open source databases like Cassandra(borrow BigTable’s DataModel), HBase(Distributed Non-Relational Database) and HyperTable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;bigtable-usecases&#34;&gt;BigTable UseCases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Google built &lt;strong&gt;BigTable&lt;/strong&gt; to store large amounts of data and perform thousands of queries per second on that data.&lt;/li&gt;
&lt;li&gt;Examples of BigTable data are billions of URLs with many versions per page, petabytes of Google Earth data, and billions of users’ search data.&lt;/li&gt;
&lt;li&gt;BigTable is suitable to store &lt;strong&gt;large datasets&lt;/strong&gt; that are &lt;strong&gt;greater than one TB&lt;/strong&gt; where &lt;strong&gt;each row is less than 10MB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Since BigTable &lt;strong&gt;does not provide ACID&lt;/strong&gt; properties or &lt;strong&gt;transaction support(Across Rows or Tables)&lt;/strong&gt;, OLTP &lt;strong&gt;applications&lt;/strong&gt; should not use &lt;strong&gt;BigTable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Data should be structured in the form of key-value pairs or rows-columns.&lt;/li&gt;
&lt;li&gt;Non-structured data like images or movies should not be stored in BigTable.&lt;/li&gt;
&lt;li&gt;Google examples:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BigTable&lt;/strong&gt; can be used to store the following types of data:&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;big-table-data-model&#34;&gt;Big Table Data Model&lt;/h3&gt;
&lt;h3 id=&#34;agenda&#34;&gt;Agenda&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Rows&lt;/li&gt;
&lt;li&gt;Column families&lt;/li&gt;
&lt;li&gt;Columns&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;details&#34;&gt;Details&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;BigTable can be characterized as a sparse, distributed, persistent, multidimensional, sorted map.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf">BigTable</a></p>
<hr>
<h2 id="bigtablewide-column-storage-system">BigTable/Wide Column Storage System</h2>
<h3 id="goal">Goal</h3>
<p>Design a <strong>distributed</strong> and <strong>scalable</strong> system that can store a <strong>huge amount of semi-structured data</strong>. The data will be indexed by a <strong>row key</strong> where each row can have an <strong>unbounded number of columns</strong>.</p>
<h3 id="what-is-bigtable">What is BigTable</h3>
<ul>
<li>BigTable is a <strong>distributed</strong> and massively <strong>scalable</strong> <strong>wide-column store</strong>.</li>
<li>Designed to store huge sets of <strong>structured</strong> data.</li>
<li>Provides storage for very big tables (often in the <strong>terabyte</strong> range)</li>
<li>BigTable is a <strong>CP system</strong>, i.e., it has <strong>strongly consistent</strong> reads and writes.</li>
<li>BigTable can be used as an input source or output destination for MapReduce.</li>
</ul>
<h3 id="background">Background</h3>
<ul>
<li>Developed at Google in 2005 and used in dozens of Google services.</li>
<li>Google couldn’t use external commercial databases because of its large scale services, and costs would have been too high. So they built an in-house solution, custom built for their use case and traffic patterns.</li>
<li>BigTable is <strong>highly available(?? With consistency??)</strong> and high-performing database that powers multiple applications across Google — where each application has different needs in terms of the size of data to be stored and latency with which results are expected.</li>
<li>BigTable inspired various open source databases like Cassandra(borrow BigTable’s DataModel), HBase(Distributed Non-Relational Database) and HyperTable.</li>
</ul>
<h3 id="bigtable-usecases">BigTable UseCases</h3>
<ul>
<li>Google built <strong>BigTable</strong> to store large amounts of data and perform thousands of queries per second on that data.</li>
<li>Examples of BigTable data are billions of URLs with many versions per page, petabytes of Google Earth data, and billions of users’ search data.</li>
<li>BigTable is suitable to store <strong>large datasets</strong> that are <strong>greater than one TB</strong> where <strong>each row is less than 10MB</strong>.</li>
<li>Since BigTable <strong>does not provide ACID</strong> properties or <strong>transaction support(Across Rows or Tables)</strong>, OLTP <strong>applications</strong> should not use <strong>BigTable</strong>.</li>
<li>Data should be structured in the form of key-value pairs or rows-columns.</li>
<li>Non-structured data like images or movies should not be stored in BigTable.</li>
<li>Google examples:</li>
<li><strong>BigTable</strong> can be used to store the following types of data:</li>
</ul>
<h3 id="big-table-data-model">Big Table Data Model</h3>
<h3 id="agenda">Agenda</h3>
<ul>
<li>Rows</li>
<li>Column families</li>
<li>Columns</li>
<li>Timestamps</li>
</ul>
<h3 id="details">Details</h3>
<ul>
<li>
<p>BigTable can be characterized as a sparse, distributed, persistent, multidimensional, sorted map.</p>
</li>
<li>
<p>Traditional DBs have a two-dimensional layout of the data, where each cell value is identified by the <strong>‘Row ID’</strong> and <strong>‘Column Name’</strong>.
<img loading="lazy" src="/images/notes/bigtable-2006/image-1.png"></p>
</li>
<li>
<p>BigTable has a <strong>four-dimensional data model</strong>. The four dimensions are:
<img loading="lazy" src="/images/notes/bigtable-2006/image-2.png"></p>
</li>
<li>
<p>The data is indexed (or sorted) by <strong>row key</strong>, <strong>column key</strong>, and a <strong>timestamp</strong>. Therefore, to access a cell’s contents, we need values for all of them.</p>
</li>
<li>
<p>If no timestamp is specified, BigTable retrieves the most recent version.
<img loading="lazy" src="/images/notes/bigtable-2006/image-3.png"></p>
</li>
</ul>
<h3 id="rows">Rows</h3>
<ul>
<li>Each <strong>row</strong> in the table is uniquely identified by an <strong>associated row key(internally represented as String)</strong> that is an arbitrary string of up to <strong>64 kilobytes</strong> in size (although most keys are significantly smaller).</li>
<li>Every read or write of data under a single row is <strong>atomic</strong>.</li>
<li>Atomicity across rows is not guaranteed, e.g., when updating two rows, one might succeed, and the other might fail.</li>
<li>Each table’s data is only indexed by <strong>row key</strong>, <strong>column key</strong>, and <strong>timestamp</strong>. There are no secondary indices.</li>
<li>A <strong>column</strong> is a <strong>key-value</strong> pair where the key is represented as ‘<strong>column key’</strong> and the value as ‘<strong>column value</strong>.’</li>
</ul>
<h3 id="column-families">Column families</h3>
<ul>
<li>Column keys are grouped into sets called column families. All data stored in a column family is usually of the same type. This is for <strong>compression purposes</strong>.</li>
<li>The number of distinct column families in a table should be small (in the <strong>hundreds</strong> <strong>at maximum</strong>), and families <strong>should rarely change</strong> during operation.</li>
<li><strong>Access control</strong> as well as both <strong>disk</strong> and <strong>memory</strong> <strong>accounting</strong> are performed at the column-family level.</li>
<li>All rows have the same set of column families.</li>
<li>BigTable can retrieve data from the same column family efficiently.</li>
<li>Short Column family names are better as names are included in the data transfer.
<img loading="lazy" src="/images/notes/bigtable-2006/image-4.png"></li>
</ul>
<h3 id="columns">Columns</h3>
<ul>
<li>Columns are <strong>units</strong> within a column family.</li>
<li>A BigTable may have an <strong>unbounded number of columns</strong>.</li>
<li>New columns can be added on the fly.</li>
<li><strong>Short column names are better</strong> as names are <strong>passed</strong> in each <strong>data transfer</strong>, e.g., ColumnFamily:ColumnName =&gt; Work:Dept</li>
<li>BigTable is quite suitable for <strong>sparse data</strong>(Empty columns are not stored).</li>
</ul>
<h3 id="timestamps">Timestamps</h3>
<ul>
<li>Each column cell can contain multiple versions of the content.</li>
<li>A <strong>64-bit timestamp</strong> identifies each version that either represents real time or a custom value assigned by the client.</li>
<li>While reading, if no timestamp is specified, BigTable returns the most recent version.</li>
<li>If the client specifies a timestamp, the latest version that is earlier than the specified timestamp is returned.</li>
<li>BigTable supports two <strong>per-column-family settings to garbage-collect cell versions automatically</strong></li>
</ul>
<h3 id="system-apis">System APIs</h3>
<p>BigTable provides APIs for two types of operations:</p>
<ul>
<li>Metadata operations</li>
<li>Data operations</li>
</ul>
<h3 id="metadata-operations">Metadata operations</h3>
<ul>
<li>APIs for creating and deleting tables and column families.</li>
<li>Functions for changing cluster, table, and column family metadata, such as access control rights.</li>
</ul>
<h3 id="data-operations">Data operations</h3>
<ul>
<li>Clients can insert, modify, or delete values in BigTable.</li>
<li>Clients can also lookup values from individual rows or iterate over a subset of the data in a table.</li>
<li>BigTable supports <strong>single-row transactions(Single row atomic read/writes)</strong>, which can be used to perform <strong>atomic read-modify-write</strong> sequences on data stored under a single row key.</li>
<li>Bigtable <strong>does not support transactions across row keys</strong>, but provides a client interface for <strong>batch writing</strong> across row keys.</li>
<li>BigTable allows <strong>cells</strong> to be used as integer counters.</li>
<li>A set of wrappers allow a <strong>BigTable</strong> to be used both as an <strong>input source</strong> and as an <strong>output target</strong> for MapReduce jobs.</li>
<li>Clients can also write scripts in <strong>Sawzall</strong>(a language developed at Google) to instruct server-side data processing (transform, filter, aggregate) prior to the network fetch.</li>
<li>APIs for write operations:</li>
<li>A read or scan operation can read arbitrary cells in a BigTable:</li>
</ul>
<h3 id="partitioning-and-high-level-architecture">Partitioning and High Level Architecture</h3>
<h3 id="table-partitioning">Table Partitioning</h3>
<ul>
<li>A single instance of a BigTable implementation is known as a cluster.</li>
<li>Each cluster can store a number of tables where <strong>each table is split into multiple Tablets</strong>, each around <strong>100–200 MB</strong> in size.</li>
<li>Tables broken into <strong>Tablets(row boundary)</strong> which hold a <strong>contiguous</strong> <strong>range</strong> of rows.</li>
<li>Initially, each table consists of only one Tablet. As the table grows, multiple Tablets are created. By default, a table is split at around 100 to 200 MB.</li>
<li><strong>Tablets</strong> are the unit of distribution and load balancing.</li>
<li>Since the table is sorted by row, reads of short ranges of rows(within a small number of Tablets) are always efficient. This means selecting a row key with a high degree of locality is very important.</li>
<li>Each Tablet is assigned to a Tablet server, which manages all read/write requests of that Tablet.</li>
</ul>
<h3 id="high-level-architecture">High Level Architecture</h3>
<p>Big Table cluster consists of 3 major components:</p>
<ul>
<li>
<p><strong>Client Library</strong>: Application talks to BigTable using client library.</p>
</li>
<li>
<p><strong>One master server</strong>: For doing metadata operations, managing Tablets and assigning Tablets to Tablet servers.</p>
</li>
<li>
<p><strong>Many Tablet servers</strong>: Each Tablet server serves read and write of the data to the Tablets it is assigned.
BigTable is built on top of several <strong>other pieces from Google infrastructure</strong>:</p>
</li>
<li>
<p><strong>GFS</strong>: BigTable uses the Google File System to store its data and log files.</p>
</li>
<li>
<p><strong>SSTable</strong>: Google’s Sorted String Table file format is used to store BigTable data.</p>
</li>
<li>
<p><strong>Chubby</strong>: BigTable uses a highly available and persistent distributed lock service called Chubby to handle synchronization issues and store configuration information.</p>
</li>
<li>
<p><strong>Cluster Scheduling System</strong>: Google has a cluster management system that schedules, monitors, and manages the Bigtable’s cluster.
<img loading="lazy" src="/images/notes/bigtable-2006/image-5.png"></p>
</li>
</ul>
<h3 id="sstables">SSTables</h3>
<h3 id="how-are-tablets-stored-in-gfs">How are Tablets stored in GFS?</h3>
<ul>
<li>
<p>BigTable uses Google File System (GFS), a persistent distributed file storage system to store data as files.</p>
</li>
<li>
<p>The file format used by BigTable to store its files is called <strong>SSTable</strong>.</p>
</li>
<li>
<p><strong>SSTables</strong> are persisted, <strong>ordered maps of keys to values</strong>, where both keys and values are arbitrary byte strings.</p>
</li>
<li>
<p>Each Tablet is stored in GFS as a sequence of files called SSTables.</p>
</li>
<li>
<p>An SSTable consists of a sequence of <strong>data blocks</strong> (typically 64KB in size).
<img loading="lazy" src="/images/notes/bigtable-2006/image-6.png"></p>
</li>
<li>
<p>A <strong>block index</strong> is used to locate blocks; the index is loaded into memory when the SSTable is opened.
<img loading="lazy" src="/images/notes/bigtable-2006/image-7.png"></p>
</li>
<li>
<p>An SSTable lookup can be performed with a <strong>single disk seek</strong>. We first find the appropriate block by performing a binary search in the in-memory index, and then reading the appropriate block from the disk.</p>
</li>
<li>
<p>To read data from an <strong>SSTable</strong>, it can either be copied from disk to memory as a whole or can be done via just the index. The former approach avoids subsequent disk seeks for lookups, while the latter requires a single disk seek for each lookup.</p>
</li>
<li>
<p><strong>SSTables provide two operations:</strong></p>
</li>
<li>
<p>SSTable is immutable once written to GFS. If new data is added, a new SSTable is created. Once an old SSTable is no longer needed, it is set out for garbage collection.</p>
</li>
<li>
<p>SSTable <strong>immutability</strong> is at the core of BigTable’s <strong>data checkpointing</strong> and <strong>recovery routines</strong>.</p>
</li>
<li>
<p>Advantages of SSTable’s immutability:</p>
</li>
</ul>
<h3 id="table-vs-tablet-vs-sstable">Table vs Tablet vs SSTable</h3>
<ul>
<li>
<p>Multiple Tablets make up a table.</p>
</li>
<li>
<p>SSTables can be shared by multiple Tablets. [<strong>Why?]</strong></p>
</li>
<li>
<p>Tablets do not overlap, SSTables can overlap.
<img loading="lazy" src="/images/notes/bigtable-2006/image-8.png"></p>
</li>
<li>
<p>To improve performance, BigTable uses an <strong>in-memory</strong>, <strong>mutable sorted buffer</strong> called <strong>MemTable</strong> to store recent updates.</p>
</li>
<li>
<p>As more writes are performed, MemTable size increases, and when it reaches a threshold, the MemTable is frozen, a new MemTable is created, and the frozen MemTable is converted to an SSTable and written to GFS.</p>
</li>
<li>
<p>Each data update is also written to a <strong>commit-log(Write Ahead Log WAL)</strong> which is also stored in GFS. This log contains redo records used for recovery if a Tablet server fails before committing a MemTable to SSTable.</p>
</li>
<li>
<p>While reading, the data can be in <strong>MemTables</strong> or <strong>SSTables</strong>. Since both these tables are sorted, it is easy to find the most recent data.
<img loading="lazy" src="/images/notes/bigtable-2006/image-9.png"></p>
</li>
</ul>
<h3 id="gfs-and-chubby">GFS and Chubby</h3>
<h3 id="gfs">GFS</h3>
<ul>
<li>GFS files are broken down into fixed-size blocks called chunks.</li>
<li>SSTables are divided into fixed-size blocks and these blocks are stored on the chunk servers. Each Chunk is replicated across multiple chunk servers for reliability.</li>
<li>Clients interact with master for metadata, and chunk servers directly for SSTable data files.
<img loading="lazy" src="/images/notes/bigtable-2006/image-10.png"></li>
</ul>
<h3 id="chubby">Chubby</h3>
<p><strong>Chubby Recap:</strong></p>
<ul>
<li>
<p>Chubby is a highly available and persistent distributed locking service.</p>
</li>
<li>
<p>Chubby usually runs with five active replicas, one of which is elected as the master to serve requests. To remain alive, a majority of Chubby replicas must be running.</p>
</li>
<li>
<p>BigTable depends on Chubby so much that if Chubby is unavailable for an extended period of time, BigTable will also become unavailable.</p>
</li>
<li>
<p>Chubby uses the Paxos algorithm to keep its replicas consistent in the face of failure.</p>
</li>
<li>
<p>Chubby provides a namespace consisting of files and directories. Each file or directory can be used as a lock. Read and write access to a Chubby file is atomic.
<strong>In BigTable, Chubby is used to:</strong></p>
</li>
<li>
<p>Allows a <strong>multi-thousand node Bigtable cluster</strong> to stay <strong>coordinated</strong>.</p>
</li>
<li>
<p>Ensure there is only one active master. The master maintains a session lease with Chubby and periodically renews it to retain the status of the master.</p>
</li>
<li>
<p>Store the bootstrap location of BigTable data.</p>
</li>
<li>
<p>Discover new Tablet servers as well as the failure of existing ones.</p>
</li>
<li>
<p>Store BigTable schema information (the column family information for each table)</p>
</li>
<li>
<p>Store Access Control Lists (ACLs).
<img loading="lazy" src="/images/notes/bigtable-2006/image-11.png"></p>
</li>
</ul>
<h3 id="bigtable-components">BigTable Components</h3>
<p>A BigTable cluster consists of three major components:</p>
<ul>
<li>A library component that is linked into every client.</li>
<li>One master server.</li>
<li>Many Tablet servers.
<img loading="lazy" src="/images/notes/bigtable-2006/image-12.png"></li>
</ul>
<h3 id="bigtable-master-server">BigTable Master Server</h3>
<p>There is <strong>only one master server</strong> in a BigTable cluster, and it is responsible for:</p>
<ul>
<li>Assigning Tablets to Tablet servers and ensuring effective load balancing.</li>
<li>Monitoring the status of Tablet servers and managing the joining or failure of Tablet Servers.</li>
<li>Garbage collection of the underlying files stored in GFS</li>
<li>Handling metadata operations such as table and column family creations.</li>
<li>Bigtable master is not involved in the core task of mapping tablets onto the underlying files in GFS (Tablet servers handle this).</li>
<li>This means that Bigtable clients do not have to communicate with the master at all.<strong>(What?)</strong></li>
<li>This design decision significantly reduces the load on the master and the possibility of the master becoming a bottleneck.</li>
</ul>
<h3 id="tablet-server">Tablet Server</h3>
<ul>
<li>Each Tablet server is assigned ownership of a number of Tablets (typically 10-1000 Tablets per server) by the master server.</li>
<li>Each Tablet server serves read and write requests of the data of the Tablets it is assigned.</li>
<li>The client communicates directly with the Tablet servers for reads/writes.</li>
<li>Tablet servers can be added or removed dynamically from a cluster to accommodate changes in the workloads.</li>
<li>Tablet creation, deletion, or merging is initiated by the master server, while <strong>Tablet partition or splitting(too Large)</strong> is handled by Tablet servers who notify the master.</li>
</ul>
<h3 id="working-with-tablets">Working with Tablets</h3>
<h3 id="agenda-1">Agenda</h3>
<ul>
<li>Locating Tablets</li>
<li>Assigning Tablets</li>
<li>Monitoring Tablet Servers</li>
<li>Load-balancing Tablet servers</li>
</ul>
<h3 id="locating-tablets">Locating Tablets</h3>
<ul>
<li>
<p>Since Tablets move around from server to server (due to load balancing, Tablet server failures, etc.), <strong>given a row, how do we find the correct Tablet server?</strong></p>
</li>
<li>
<p>To answer this, we need to find the <strong>Tablet whose row range covers the target row</strong>.</p>
</li>
<li>
<p>BigTable maintains a <strong>3-level hierarchy</strong>, analogous to that of a <strong>B+ tree</strong>, to store Tablet location information.</p>
</li>
<li>
<p>BigTable creates a special table, called <strong>Metadata table</strong>, to store Tablet locations.</p>
</li>
<li>
<p>This <strong>Metadata table</strong> contains <strong>one row per Tablet</strong> that tells us which Tablet server is serving this Tablet.</p>
</li>
<li>
<p>Each row in the METADATA table stores a Tablet’s location under a row key that is an encoding of the Tablet’s table identifier and its end row.
<img loading="lazy" src="/images/notes/bigtable-2006/image-13.png"></p>
</li>
<li>
<p>BigTable stores the information about the Metadata table in two parts:
<img loading="lazy" src="/images/notes/bigtable-2006/image-14.png"></p>
</li>
<li>
<p>A BigTable client seeking the location of a Tablet starts the search by looking up a particular file in Chubby that is known to hold the location of the Meta- 0 Tablet.</p>
</li>
<li>
<p>This Meta-0 Tablet contains information about other metadata Tablets, which in turn contain the location of the actual data Tablets.</p>
</li>
<li>
<p>With this scheme, the depth of the tree is limited to <strong>three</strong>. For efficiency, the client library caches Tablet locations and also prefetch metadata associated with other Tablets whenever it reads the METADATA table
<img loading="lazy" src="/images/notes/bigtable-2006/image-15.png"></p>
</li>
</ul>
<p><img loading="lazy" src="/images/notes/bigtable-2006/image-16.png"></p>
<h3 id="assigning-tablets">Assigning Tablets</h3>
<ul>
<li>A Tablet is assigned to only one Tablet server at any time.</li>
<li>The master keeps track of the set of live Tablet servers and the mapping of Tablets to Tablet servers.</li>
<li>The master also keeps track of any unassigned Tablets and assigns them to Tablet servers with sufficient room.</li>
<li>When a Tablet server starts, it creates and acquires an exclusive lock on a uniquely named file in Chubby’s “servers” directory. This mechanism is used to tell the master that the Tablet server is alive.</li>
<li>During <strong>Master restarts(or startup)</strong>, following things happens:</li>
</ul>
<h3 id="monitoring-tablet-serverstablet-failures-or-network-partitions">Monitoring Tablet servers(Tablet Failures or Network Partitions)</h3>
<ul>
<li>BigTable maintains a ‘Servers’ directory in Chubby, which contains one file for each live Tablet server.</li>
<li>Whenever a new <strong>Tablet server comes online</strong>, it creates a new file in this directory to signal its availability and obtains an exclusive lock on this file. As long as a Tablet server retains the lock on its Chubby file, it is considered alive.</li>
<li>BigTable’s master keeps monitoring the ‘Servers’ directory, and whenever it sees a new file in this directory, it knows that a new Tablet server has become available and is ready to be assigned Tablets.</li>
<li>Master regularly checks the status of the lock. If the lock is lost, the master assumes that there is a problem either with the Tablet server or the Chubby.</li>
<li>In such a case, the master tries to acquire the lock, and if it succeeds, it concludes that Chubby is working fine, and the Tablet server is having problems.</li>
<li>The master, in this case, <strong>deletes the Tablet server’s Chubby lock file</strong> and <strong>reassigns</strong> the tablets of the failing Tablet server</li>
<li>The <strong>deletion of the file works as a signal for the failing Tablet server</strong> to <strong>terminate itself</strong> and <strong>stop serving the Tablets</strong>.</li>
<li>It tries to acquire the lock again, and if it succeeds, it considers it a temporary network problem and starts serving the Tablets again.</li>
<li>If the file gets deleted, then the Tablet server terminates itself to start afresh.</li>
</ul>
<h3 id="load-balancing-tablet-servers">Load-balancing Tablet servers</h3>
<ul>
<li>Master periodically asks Tablet servers about their current load. All this information gives the master a global view of the cluster and helps assign and load-balance Tablets.</li>
</ul>
<h3 id="life-of-bigtables-read-and-write-operations">Life of BigTables Read and Write Operations</h3>
<h3 id="write-request">Write Request</h3>
<p>Upon receiving a write request, Tablet server performs the following steps</p>
<ul>
<li><strong>Validate request</strong> to be well formed</li>
<li>Does sender <strong>Authorization</strong> to perform mutation <strong>using ACLs in Chubby</strong>.</li>
<li>If authorized, mutation is written to <strong>commit-log</strong> in GFS that stores redo records.</li>
<li>Once committed to commit-log, request contents are stored in memory in a sorted buffer called <strong>MemTable</strong>.</li>
<li>After inserting data into MemTable, success acknowledgement is sent to the client.</li>
<li>Periodically, MemTables are flushed to SSTables, and SSTables in the background are merged using Compaction.
<img loading="lazy" src="/images/notes/bigtable-2006/image-17.png"></li>
</ul>
<h3 id="read-request">Read Request</h3>
<p>Upon receiving a read request, Tablet server performs following steps:</p>
<ul>
<li>Validate request is well formed and sender is authorized.</li>
<li>Return rows if they are available in cache.</li>
<li>Read MemTable to find the required rows.</li>
<li>Read SSTable Indexes that are loaded in memory to find SSTables that will have the required data, then read those rows from SSTables.</li>
<li>Merge rows read from MemTable and SSTable to find the required version of data.</li>
<li>Since MemTable and SSTables are sorted, merged view can be formed efficiently.
<img loading="lazy" src="/images/notes/bigtable-2006/image-18.png"></li>
</ul>
<h3></h3>
<h3></h3>
<h3 id="fault-tolerance-and-compaction">Fault Tolerance and Compaction</h3>
<h3 id="agenda-2">Agenda</h3>
<ul>
<li>Fault tolerance and replication</li>
<li>Compaction</li>
</ul>
<h3 id="fault-tolerance-and-replication">Fault tolerance and replication</h3>
<h3 id="fault-tolerance-in-chubby-and-gfs">Fault tolerance in Chubby and GFS</h3>
<ul>
<li>Both the systems employ a replication strategy for fault tolerance and high availability, that minimizes downtime for Chubby. Similarly, GFS replication creates multiple copies of the data to avoid data loss.</li>
</ul>
<h3 id="fault-tolerance-for-tablet-server">Fault tolerance for Tablet server</h3>
<ul>
<li>BigTable’s master is responsible for monitoring the Tablet servers.</li>
<li>The master does this by periodically checking the status of the Chubby lock against each Tablet server.</li>
<li>When the master finds out that a Tablet server has gone dead, it reassigns the tablets of the failing Tablet server.</li>
</ul>
<h3 id="fault-tolerance-for-the-master">Fault tolerance for the Master</h3>
<ul>
<li>The master acquires a lock in a Chubby file and maintains a lease.</li>
<li>If, at any time, the master’s lease expires, it kills itself.</li>
<li>When Google’s Cluster Management System finds out that there is no active master, it starts one up.</li>
<li>The new master has to acquire the lock on the Chubby file before acting as the master.</li>
</ul>
<h3 id="compaction">Compaction</h3>
<p>Mutations in BigTable take up extra space till compaction happens. BigTable manages compaction behind the scenes. List of compactions:</p>
<ul>
<li><strong>Minor Compaction(MemTable Written to SSTables)</strong></li>
<li><strong>Merging Compaction(SSTables + MemTable compacted to Larger SSTable)</strong></li>
<li><strong>Major Compaction</strong>(All SSTables - &gt;Single SS Table)
<img loading="lazy" src="/images/notes/bigtable-2006/image-19.png"></li>
</ul>
<h3 id="bigtable-refinements">BigTable refinements</h3>
<p>BigTable implemented certain refinements to achieve high performance, availability, and reliability.</p>
<h3 id="agenda-3">Agenda</h3>
<ul>
<li>Locality groups</li>
<li>Compression</li>
<li>Caching</li>
<li>Bloom Filters</li>
<li>Unified commit Log</li>
<li>Speeding up Tablet recovery</li>
</ul>
<h3 id="locality-groups">Locality groups</h3>
<ul>
<li>BigTable uses column-oriented storage.</li>
<li>Clients can club together multiple column families into a locality group.</li>
<li>BigTable generates separate SSTables for each locality group.</li>
<li>This has few benefits:
<img loading="lazy" src="/images/notes/bigtable-2006/image-20.png"></li>
</ul>
<h3 id="compression">Compression</h3>
<ul>
<li>Clients can choose to compress the SSTable for a locality group to save space.</li>
<li>BigTable allows its clients to choose compression techniques based on their application requirements.</li>
<li>The compression ratio gets even better when multiple versions of the same data are stored.</li>
<li>Compression is applied to each SSTable block separately.</li>
</ul>
<h3 id="caching">Caching</h3>
<ul>
<li>To improve read performance, Tablet servers employ two levels of caching:</li>
</ul>
<h3 id="bloom-filters">Bloom Filters</h3>
<ul>
<li>Any read operation has to read from all SSTables that make up a Tablet.</li>
<li>These SSTables are not in memory, thus the read operation needs to do many disk accesses. To reduce the number of disk accesses BigTable uses <strong>Bloom Filters</strong>.</li>
<li>Bloom Filters are created for SSTables (particularly for the locality groups).</li>
<li>They help to reduce the number of disk accesses by <strong>predicting if an SSTable does “not” contain data corresponding to a particular (row, column) pair</strong>.</li>
<li>Bloom filters take a small amount of memory but can improve the read performance drastically.</li>
</ul>
<h3 id="unified-commit-log">Unified commit Log</h3>
<ul>
<li>Instead of maintaining separate commit log files for each Tablet, BigTable maintains one log file for a Tablet server. This gives better write performance.</li>
<li>Since each write has to go to the commit log, writing to a large number of log files would be slow as it could cause a large number of disk seeks.</li>
<li>One disadvantage of having a single log file is that it complicates the Tablet recovery process.</li>
<li>When a Tablet server dies, the Tablets that it served will be moved to other Tablet servers.</li>
<li>To recover the state for a Tablet, the new Tablet server needs to reapply the mutations for that Tablet from the commit log written by the original Tablet server.</li>
<li>However, the mutations for these Tablets were co-mingled in the same physical log file. One approach would be for each new Tablet server to read this full commit log file and apply just the entries needed for the Tablets it needs to recover.</li>
<li>However, under such a scheme, if 100 machines were each assigned a single Tablet from a failed Tablet server, then the log file would be read 100 times.</li>
<li>BigTable avoids duplicating log reads by first sorting the commit log entries in order of the keys &lt;table, row name, log sequence number&gt;.</li>
<li>In the sorted output, all mutations for a particular Tablet are contiguous and can therefore be read efficiently.</li>
<li>To further improve the performance, each Tablet server maintains two log writing threads — each writing to its own and separate log file.</li>
<li>Only one of the threads is active at a time. If one of the threads is performing poorly (say, due to network congestion), the writing switches to the other thread. Log entries have sequence numbers to allow the recovery process
<img loading="lazy" src="/images/notes/bigtable-2006/image-21.png"></li>
</ul>
<h3 id="speeding-up-tablet-recovery">Speeding up Tablet recovery</h3>
<ul>
<li>One of the complicated and time-consuming tasks while loading Tablets is to ensure that the Tablet server loads all entries from the commit log.</li>
<li>When the master moves a Tablet from one Tablet server to another, the source Tablet server performs compactions to ensure that the destination Tablet server does not have to read the commit log. This is done in 3 steps:</li>
</ul>
<h3 id="tablet-splitting">Tablet Splitting</h3>
<p><img loading="lazy" src="/images/notes/bigtable-2006/image-22.png"></p>
<h3 id="concurrency-on-memtable">Concurrency on MemTable</h3>
<ul>
<li>Want to avoid read-contention when writes are also happening on the same rows.</li>
<li>Use <strong>Copy-on-write</strong> semantics on a per-row basis.</li>
</ul>
<h3 id="performance-observations">Performance Observations</h3>
<p><img loading="lazy" src="/images/notes/bigtable-2006/image-23.png"></p>
<h3 id="bigtable-characteristics">BigTable Characteristics</h3>
<h3 id="bigtable-performanceand-popularity">BigTable performance(and Popularity)</h3>
<ul>
<li><strong>Distributed multi-level map</strong>: BigTable can run on a large number of machines.</li>
<li><strong>Scalable</strong> means that BigTable can be easily scaled horizontally by adding more nodes to the cluster without any performance impact. No manual intervention or rebalancing is required. BigTable achieves linear scalability and proven fault tolerance on commodity hardware</li>
<li><strong>Fault-tolerant and reliable</strong>: Since data is replicated to multiple nodes, fault tolerance is pretty high.</li>
<li><strong>Durable</strong>: BigTable stores data permanently.</li>
<li><strong>Centralized</strong>: BigTable adopts a single-master approach to maintain data consistency and a centralized view of the state of the system.</li>
<li><strong>Separation between control and data</strong>: BigTable maintains a strict separation between control and data flow. Clients talk to the Master for all metadata operations, whereas all data access happens directly between the Clients and the Tablet servers.</li>
</ul>
<h3 id="dynamo-vs-bigtable">Dynamo vs. BigTable</h3>
<p><img loading="lazy" src="/images/notes/bigtable-2006/image-24.png"></p>
<p><img loading="lazy" src="/images/notes/bigtable-2006/image-25.png"></p>
<h3 id="datastores-developed-on-the-principles-of-bigtable">Datastores developed on the principles of BigTable</h3>
<p>Google’s BigTable has inspired many NoSQL systems. Here is a list of a few famous ones:</p>
<ul>
<li><strong>HBase</strong>: HBase is an open-source, distributed non-relational database modeled after BigTable. It is built on top of the Hadoop Distributed File System (HDFS).</li>
<li><strong>Hypertable</strong>: Similar to HBase, Hypertable is an open-source implementation of BigTable and is written in C++. Unlike BigTable, which uses only one storage layer (i.e., GFS), Hypertable is capable of running on top of any file system (e.g., HDFS, GlusterFS, or the CloudStore ). To achieve this, the system has abstracted the interface to the file system by sending all data requests through a Distributed File System broker process.</li>
<li><strong>Cassandra</strong>: Cassandra is a distributed, decentralized, and highly available NoSQL database. Its architecture is based on Dynamo and BigTable. Cassandra can be described as a BigTable-like datastore running on a Dynamo-like infrastructure. Cassandra is also a wide-column store and utilizes the storage model of BigTable, i.e., SSTables and MemTables.</li>
</ul>
<h3 id="summary">Summary</h3>
<ul>
<li>BigTable is a <strong>Distributed</strong> <strong>wide column</strong> storage system designed to manage large amounts of <strong>semi-structured data</strong> with High Availability, Low Latency, Scalability, and Fault tolerance.</li>
<li>It is a <strong>sparse</strong>, <strong>distributed</strong>, <strong>persistent</strong>, <strong>Multi Dimensional</strong> <strong>sorted map</strong>.</li>
<li>Map is indexed by a unique key made up of <strong>Row Key(<strong>up to 64 KB</strong>)</strong>, <strong>Column key</strong>, and a <strong>timestamp(64-bit integer)</strong>.</li>
<li>Columns are grouped into Column families. RowKey and Column key uniquely identifies a Column data cell. Within each cell, data is further indexed by timestamps to store multiple versions of the data.</li>
<li>Each read/write to a row is atomic. Atomicity across rows is not guaranteed.</li>
<li>A BigTable’s Table could be a multi-TB table. A Table is broken into a smaller range of rows called <strong>Tablets</strong>.</li>
<li>One Master server and multiple <strong>Tablet Servers</strong>.</li>
<li>Master does metadata management, Assigns Tablets to Tablet servers, does Tablet rebalancing etc.</li>
<li>Read/Write of data goes directly to the tablet servers.</li>
<li>Tablet servers store each tablet as a set of Immutable <strong>SSTable</strong> files, each of which is further divided into <strong>64KB Data Blocks.</strong> SStables are stored as Chunks in GFS and replicated to different chunk servers.</li>
<li>To enhance read performance, especially reducing disk seeks while trying to check for existence of a Key from each of SSTable, Bloom filters are used to check for existence.</li>
<li>BigTable relies on Chubby for master server selection(and Failover), using Locks, and also master check if the Tablet servers are alive, since they take a lock on the Chubby’s server directory.</li>
<li>Writes first go to a Commit Log(WAL) for failure recovery, then to In-Memory MemTable(where it&rsquo;s kept as a Sorted Map), and when it breaches threshold, its written to SSTable.</li>
<li>MemTables, SSTables merged and SSTables are compacted to bigger SSTable in background using compactions.</li>
<li>All the read operations are served from a Merged view of MemTable and All SSTables.</li>
</ul>
<h3 id="reference">Reference</h3>
<ul>
<li>BigTable</li>
<li>SSTable(LSM Trees)</li>
<li>Amazon Dynamo</li>
<li>Cassandra</li>
<li>HBase</li>
<li>Jordan BigTable</li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf">https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Chubby</title>
      <link>https://www.sethihemant.com/notes/chubby-2006/</link>
      <pubDate>Sat, 07 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/chubby-2006/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf&#34;&gt;Chubby&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;chubby--distributed-locking-service&#34;&gt;Chubby / Distributed Locking Service&lt;/h2&gt;
&lt;h3 id=&#34;goal&#34;&gt;Goal&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Design a &lt;strong&gt;highly available&lt;/strong&gt; and &lt;strong&gt;consistent service&lt;/strong&gt; that can store small objects and provide a &lt;strong&gt;locking mechanism&lt;/strong&gt; on those objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-is-chubby&#34;&gt;What is Chubby?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby is a service that provides a &lt;strong&gt;distributed locking&lt;/strong&gt; mechanism and also &lt;strong&gt;stores small files&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Internally, it is &lt;strong&gt;implemented as a key/value store&lt;/strong&gt; that also provides a &lt;strong&gt;locking mechanism on each object&lt;/strong&gt; stored in it.&lt;/li&gt;
&lt;li&gt;Extensively used in various systems inside Google to &lt;strong&gt;provide&lt;/strong&gt; &lt;strong&gt;storage and coordination services&lt;/strong&gt; for systems like &lt;strong&gt;GFS&lt;/strong&gt; and &lt;strong&gt;BigTable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Apache ZooKeeper is the open-source alternative to Chubby.&lt;/li&gt;
&lt;li&gt;Chubby is a centralized service offering &lt;strong&gt;developer-friendly interfaces (to acquire/release locks and create/read/delete small files)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It does all this with just a few extra lines of code to any existing application without a lot of modification to application logic.&lt;/li&gt;
&lt;li&gt;At a high level, Chubby provides a framework for &lt;strong&gt;distributed consensus&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-use-cases&#34;&gt;Chubby Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Primarily Chubby was developed to provide a reliable locking service. Other use cases evolved like:&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;leader-election&#34;&gt;Leader Election&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Any lock service can be seen as a &lt;strong&gt;consensus service&lt;/strong&gt;, as it converts the problem of reaching consensus to handing out locks.&lt;/li&gt;
&lt;li&gt;A set of distributed applications compete to acquire a lock, and whoever gets the lock first gets the resource.&lt;/li&gt;
&lt;li&gt;Similarly, an application can have multiple replicas running and wants one of them to be chosen as the leader. Chubby can be used for leader election among a set of replicas.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-1.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;naming-servicelike-dns&#34;&gt;Naming Service(Like DNS)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;It is hard to make faster updates to DNS due to its time-based caching nature, which means there is generally a potential delay before the latest DNS mapping is effective.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-2.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;storagesmall-objects-that-rarely-change&#34;&gt;Storage(Small Objects that rarely change)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby provides a &lt;strong&gt;Unix-style interface&lt;/strong&gt; to reliably &lt;strong&gt;store small files&lt;/strong&gt; that &lt;strong&gt;do not change frequently&lt;/strong&gt; (complementing the service offered by GFS).&lt;/li&gt;
&lt;li&gt;Applications can then use these files for any usage like DNS, configs, etc.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-3.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;distributed-locking-mechanism&#34;&gt;Distributed Locking Mechanism&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby provides a &lt;strong&gt;developer-friendly interface&lt;/strong&gt; for &lt;strong&gt;coarse-grained distributed locks&lt;/strong&gt; (as opposed to fine-grained locks) to &lt;strong&gt;synchronize distributed activities&lt;/strong&gt; in a distributed environment.&lt;/li&gt;
&lt;li&gt;Application needs a few lines, and chubby can take care of all lock management so that devs can focus on business logic, and not solve distributed Locking problems in a Distributed system’s setting.&lt;/li&gt;
&lt;li&gt;We can say that Chubby provides mechanisms like &lt;strong&gt;semaphores&lt;/strong&gt; and &lt;strong&gt;mutexes&lt;/strong&gt; for a distributed environment.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-4.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;when-not-to-use-chubby&#34;&gt;When Not to Use Chubby?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Bulk Storage is needed&lt;/li&gt;
&lt;li&gt;Data update rate is high.&lt;/li&gt;
&lt;li&gt;Locks are acquired/released frequently.&lt;/li&gt;
&lt;li&gt;Usage is more like a publish/subscribe model.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby is neither really a research effort nor does it claim to introduce any new algorithms.&lt;/li&gt;
&lt;li&gt;Rather, Chubby describes a &lt;strong&gt;certain design and implementation&lt;/strong&gt; done at Google in order to &lt;strong&gt;provide a way for its clients to synchronize their activities&lt;/strong&gt; and &lt;strong&gt;agree(Consensus)&lt;/strong&gt; on basic information about their environment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-and-paxos&#34;&gt;Chubby and Paxos&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby uses Paxos underneath to manage the state of the Chubby system at any point in time.&lt;/li&gt;
&lt;li&gt;Getting all nodes in a distributed system to agree on anything (e.g., election of primary among peers) is basically a kind of &lt;strong&gt;distributed consensus&lt;/strong&gt; problem.&lt;/li&gt;
&lt;li&gt;Distributed consensus using Asynchronous Communication is already solved by &lt;strong&gt;Paxos&lt;/strong&gt; protocol.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-5.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-common-terms&#34;&gt;Chubby Common Terms&lt;/h3&gt;
&lt;h3 id=&#34;chubby-cell&#34;&gt;Chubby Cell&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby cell is a &lt;strong&gt;Chubby Cluster&lt;/strong&gt;. Most Chubby Cells are &lt;strong&gt;single Data Center&lt;/strong&gt;(DC) but there can be some configuration where Chubby replicas exist Cross DC as well.&lt;/li&gt;
&lt;li&gt;Chubby cell has two main components, &lt;strong&gt;server&lt;/strong&gt; and &lt;strong&gt;client&lt;/strong&gt;, that communicate via remote procedure call (&lt;strong&gt;RPC&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-servers&#34;&gt;Chubby Servers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A Chubby Cell consists of a small set of servers(typically 5) known as Replicas.&lt;/li&gt;
&lt;li&gt;Using Paxos, one of the servers is selected as Master which handles all client requests. Fails over to another replica if the master fails.&lt;/li&gt;
&lt;li&gt;Each replica maintains a small database to store files/directories/locks.&lt;/li&gt;
&lt;li&gt;The master writes directly to its own local database, which gets synced asynchronously to all the replicas(Reliability).&lt;/li&gt;
&lt;li&gt;For Fault Tolerance, replicas are placed on different racks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-client-library&#34;&gt;Chubby Client Library&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Client applications use a Chubby library to communicate with the replicas in the chubby cell using RPC.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/chubby-2006/image-6.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;chubby-api&#34;&gt;Chubby API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chubby exports a &lt;strong&gt;unix-like&lt;/strong&gt; file system interface similar to &lt;strong&gt;POSIX&lt;/strong&gt; but simpler.&lt;/li&gt;
&lt;li&gt;It consists of &lt;strong&gt;a strict tree of files and directories&lt;/strong&gt; with name components separated by slashes. E.g. &lt;strong&gt;File format: /ls/chubby_cell/directory_name/&amp;hellip;/file_name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A special name, &lt;strong&gt;/ls/local&lt;/strong&gt;, will be resolved to the most local cell relative to the calling application or service. &lt;strong&gt;What is the most local Cell?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Chubby can be used for &lt;strong&gt;locking&lt;/strong&gt; or &lt;strong&gt;storing a small amount of data&lt;/strong&gt; or &lt;strong&gt;both&lt;/strong&gt;, i.e., &lt;strong&gt;storing small files with locks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Categories&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;general&#34;&gt;General&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open()&lt;/strong&gt; : Opens a given named file or directory and returns a handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Close()&lt;/strong&gt; : Closes an open handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poison()&lt;/strong&gt; : Allows a client to cancel all Chubby calls made by other threads without fear of deallocating the memory being accessed by them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete()&lt;/strong&gt; : Deletes the file or directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;file&#34;&gt;File&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GetContentsAndStat()&lt;/strong&gt; : Returns (atomically) the &lt;strong&gt;whole file contents and metadata&lt;/strong&gt; associated with the file. This approach of reading the whole file is &lt;strong&gt;designed to discourage the creation of large files&lt;/strong&gt;, as it is not the intended use of Chubby.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GetStat()&lt;/strong&gt; : Returns just the metadata.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ReadDir()&lt;/strong&gt; : Returns the contents of a directory – that is, names and metadata of all children.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SetContents()&lt;/strong&gt; : Writes the whole contents of a file (atomically).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SetACL()&lt;/strong&gt; : Writes new access control list information.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;locking&#34;&gt;Locking&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Acquire()&lt;/strong&gt; : Acquires a lock on a file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TryAcquire()&lt;/strong&gt; : Tries to acquire a lock on a file; it is a non-blocking variant of Acquire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release()&lt;/strong&gt; : Releases a lock.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;sequencer&#34;&gt;Sequencer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GetSequencer()&lt;/strong&gt; : Get the sequencer of a lock. A sequencer is a string representation of a lock.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SetSequencer()&lt;/strong&gt; : Associate a sequencer with a handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CheckSequencer()&lt;/strong&gt; : Check whether a sequencer is valid.
Chubby does not support operations like append, seek, move files between directories, or making symbolic or hard links.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Files can only be completely read or completely written/overwritten. This makes it practical only for storing very small files.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf">Chubby</a></p>
<hr>
<h2 id="chubby--distributed-locking-service">Chubby / Distributed Locking Service</h2>
<h3 id="goal">Goal</h3>
<ul>
<li>Design a <strong>highly available</strong> and <strong>consistent service</strong> that can store small objects and provide a <strong>locking mechanism</strong> on those objects.</li>
</ul>
<h3 id="what-is-chubby">What is Chubby?</h3>
<ul>
<li>Chubby is a service that provides a <strong>distributed locking</strong> mechanism and also <strong>stores small files</strong>.</li>
<li>Internally, it is <strong>implemented as a key/value store</strong> that also provides a <strong>locking mechanism on each object</strong> stored in it.</li>
<li>Extensively used in various systems inside Google to <strong>provide</strong> <strong>storage and coordination services</strong> for systems like <strong>GFS</strong> and <strong>BigTable</strong>.</li>
<li>Apache ZooKeeper is the open-source alternative to Chubby.</li>
<li>Chubby is a centralized service offering <strong>developer-friendly interfaces (to acquire/release locks and create/read/delete small files)</strong>.</li>
<li>It does all this with just a few extra lines of code to any existing application without a lot of modification to application logic.</li>
<li>At a high level, Chubby provides a framework for <strong>distributed consensus</strong>.</li>
</ul>
<h3 id="chubby-use-cases">Chubby Use Cases</h3>
<ul>
<li>Primarily Chubby was developed to provide a reliable locking service. Other use cases evolved like:</li>
</ul>
<h3 id="leader-election">Leader Election</h3>
<ul>
<li>Any lock service can be seen as a <strong>consensus service</strong>, as it converts the problem of reaching consensus to handing out locks.</li>
<li>A set of distributed applications compete to acquire a lock, and whoever gets the lock first gets the resource.</li>
<li>Similarly, an application can have multiple replicas running and wants one of them to be chosen as the leader. Chubby can be used for leader election among a set of replicas.
<img loading="lazy" src="/images/notes/chubby-2006/image-1.png"></li>
</ul>
<h3 id="naming-servicelike-dns">Naming Service(Like DNS)</h3>
<ul>
<li>It is hard to make faster updates to DNS due to its time-based caching nature, which means there is generally a potential delay before the latest DNS mapping is effective.
<img loading="lazy" src="/images/notes/chubby-2006/image-2.png"></li>
</ul>
<h3 id="storagesmall-objects-that-rarely-change">Storage(Small Objects that rarely change)</h3>
<ul>
<li>Chubby provides a <strong>Unix-style interface</strong> to reliably <strong>store small files</strong> that <strong>do not change frequently</strong> (complementing the service offered by GFS).</li>
<li>Applications can then use these files for any usage like DNS, configs, etc.
<img loading="lazy" src="/images/notes/chubby-2006/image-3.png"></li>
</ul>
<h3 id="distributed-locking-mechanism">Distributed Locking Mechanism</h3>
<ul>
<li>Chubby provides a <strong>developer-friendly interface</strong> for <strong>coarse-grained distributed locks</strong> (as opposed to fine-grained locks) to <strong>synchronize distributed activities</strong> in a distributed environment.</li>
<li>Application needs a few lines, and chubby can take care of all lock management so that devs can focus on business logic, and not solve distributed Locking problems in a Distributed system’s setting.</li>
<li>We can say that Chubby provides mechanisms like <strong>semaphores</strong> and <strong>mutexes</strong> for a distributed environment.
<img loading="lazy" src="/images/notes/chubby-2006/image-4.png"></li>
</ul>
<h3 id="when-not-to-use-chubby">When Not to Use Chubby?</h3>
<ul>
<li>Bulk Storage is needed</li>
<li>Data update rate is high.</li>
<li>Locks are acquired/released frequently.</li>
<li>Usage is more like a publish/subscribe model.</li>
</ul>
<h3 id="background">Background</h3>
<ul>
<li>Chubby is neither really a research effort nor does it claim to introduce any new algorithms.</li>
<li>Rather, Chubby describes a <strong>certain design and implementation</strong> done at Google in order to <strong>provide a way for its clients to synchronize their activities</strong> and <strong>agree(Consensus)</strong> on basic information about their environment</li>
</ul>
<h3 id="chubby-and-paxos">Chubby and Paxos</h3>
<ul>
<li>Chubby uses Paxos underneath to manage the state of the Chubby system at any point in time.</li>
<li>Getting all nodes in a distributed system to agree on anything (e.g., election of primary among peers) is basically a kind of <strong>distributed consensus</strong> problem.</li>
<li>Distributed consensus using Asynchronous Communication is already solved by <strong>Paxos</strong> protocol.
<img loading="lazy" src="/images/notes/chubby-2006/image-5.png"></li>
</ul>
<h3 id="chubby-common-terms">Chubby Common Terms</h3>
<h3 id="chubby-cell">Chubby Cell</h3>
<ul>
<li>Chubby cell is a <strong>Chubby Cluster</strong>. Most Chubby Cells are <strong>single Data Center</strong>(DC) but there can be some configuration where Chubby replicas exist Cross DC as well.</li>
<li>Chubby cell has two main components, <strong>server</strong> and <strong>client</strong>, that communicate via remote procedure call (<strong>RPC</strong>).</li>
</ul>
<h3 id="chubby-servers">Chubby Servers</h3>
<ul>
<li>A Chubby Cell consists of a small set of servers(typically 5) known as Replicas.</li>
<li>Using Paxos, one of the servers is selected as Master which handles all client requests. Fails over to another replica if the master fails.</li>
<li>Each replica maintains a small database to store files/directories/locks.</li>
<li>The master writes directly to its own local database, which gets synced asynchronously to all the replicas(Reliability).</li>
<li>For Fault Tolerance, replicas are placed on different racks.</li>
</ul>
<h3 id="chubby-client-library">Chubby Client Library</h3>
<ul>
<li>Client applications use a Chubby library to communicate with the replicas in the chubby cell using RPC.
<img loading="lazy" src="/images/notes/chubby-2006/image-6.png"></li>
</ul>
<h3 id="chubby-api">Chubby API</h3>
<ul>
<li>Chubby exports a <strong>unix-like</strong> file system interface similar to <strong>POSIX</strong> but simpler.</li>
<li>It consists of <strong>a strict tree of files and directories</strong> with name components separated by slashes. E.g. <strong>File format: /ls/chubby_cell/directory_name/&hellip;/file_name</strong></li>
<li>A special name, <strong>/ls/local</strong>, will be resolved to the most local cell relative to the calling application or service. <strong>What is the most local Cell?</strong></li>
<li>Chubby can be used for <strong>locking</strong> or <strong>storing a small amount of data</strong> or <strong>both</strong>, i.e., <strong>storing small files with locks</strong>.</li>
<li><strong>API Categories</strong></li>
</ul>
<h3 id="general">General</h3>
<ul>
<li><strong>Open()</strong> : Opens a given named file or directory and returns a handle.</li>
<li><strong>Close()</strong> : Closes an open handle.</li>
<li><strong>Poison()</strong> : Allows a client to cancel all Chubby calls made by other threads without fear of deallocating the memory being accessed by them.</li>
<li><strong>Delete()</strong> : Deletes the file or directory.</li>
</ul>
<h3 id="file">File</h3>
<ul>
<li><strong>GetContentsAndStat()</strong> : Returns (atomically) the <strong>whole file contents and metadata</strong> associated with the file. This approach of reading the whole file is <strong>designed to discourage the creation of large files</strong>, as it is not the intended use of Chubby.</li>
<li><strong>GetStat()</strong> : Returns just the metadata.</li>
<li><strong>ReadDir()</strong> : Returns the contents of a directory – that is, names and metadata of all children.</li>
<li><strong>SetContents()</strong> : Writes the whole contents of a file (atomically).</li>
<li><strong>SetACL()</strong> : Writes new access control list information.</li>
</ul>
<h3 id="locking">Locking</h3>
<ul>
<li><strong>Acquire()</strong> : Acquires a lock on a file.</li>
<li><strong>TryAcquire()</strong> : Tries to acquire a lock on a file; it is a non-blocking variant of Acquire.</li>
<li><strong>Release()</strong> : Releases a lock.</li>
</ul>
<h3 id="sequencer">Sequencer</h3>
<ul>
<li><strong>GetSequencer()</strong> : Get the sequencer of a lock. A sequencer is a string representation of a lock.</li>
<li><strong>SetSequencer()</strong> : Associate a sequencer with a handle.</li>
<li><strong>CheckSequencer()</strong> : Check whether a sequencer is valid.
Chubby does not support operations like append, seek, move files between directories, or making symbolic or hard links.</li>
</ul>
<p>Files can only be completely read or completely written/overwritten. This makes it practical only for storing very small files.</p>
<h3 id="design-rationale">Design Rationale</h3>
<h3 id="agenda">Agenda</h3>
<ul>
<li>Why was chubby built as a service?</li>
<li>Why coarse-grained locks?</li>
<li>Why advisory locks?</li>
<li>Why does Chubby need storage?</li>
<li>Why does Chubby exports like a unix-like file system interface?</li>
<li>High Availability and reliability</li>
</ul>
<h3 id="why-was-chubby-built-as-a-service-rather-than-a-distributed-client-library-doing-paxos">Why was chubby built as a service rather than a distributed client library doing Paxos?</h3>
<ul>
<li>Reasons behind building a distributed service instead of having a client library that only provides Paxos distributed consensus? A lock service has clear advantages over a client library:</li>
</ul>
<h3 id="why-coarse-grained-locks">Why coarse-grained locks?</h3>
<p>Chubby locks usage is not expected to be fine-grained in which they might be held for only a short period (i.e., seconds or less). <strong>For example,</strong> electing a leader is not a frequent event. Reasons why only coarse grained locks ar supported:</p>
<ul>
<li><strong>Less load on the lock server</strong></li>
<li><strong>Survive Lock server failures</strong></li>
<li><strong>Fewer lock servers are needed:</strong></li>
<li><strong>Implement a fine-grained locking system</strong> on top of this coarse grained locking system Chubby.
<img loading="lazy" src="/images/notes/chubby-2006/image-7.png"></li>
</ul>
<h3 id="why-advisory-locks">Why advisory locks?</h3>
<ul>
<li>Chubby locks are advisory, which means it is up to the application to honor the lock. Chubby doesn’t make locked objects inaccessible to clients not holding their locks.</li>
<li>Chubby gave following reasons for <strong>not having mandatory locks</strong>:</li>
</ul>
<h3 id="why-does-chubby-need-storage">Why does Chubby need storage?</h3>
<ul>
<li>To provide a Consistent view of the system to various distributed entities in some use cases like:</li>
</ul>
<h3 id="why-does-chubby-exports-like-a-unix-like-file-system-interface">Why does Chubby exports like a unix-like file system interface?</h3>
<ul>
<li>It significantly reduces the effort needed to write basic browsing and namespace manipulation tools, and reduces the need to educate casual Chubby users.
<img loading="lazy" src="/images/notes/chubby-2006/image-8.png"></li>
</ul>
<h3 id="high-availability-and-reliability">High Availability and reliability</h3>
<ul>
<li>Chubby compromises on performance in favor of availability and consistency. <strong>What?</strong></li>
</ul>
<h3 id="how-chubby-works">How Chubby Works?</h3>
<h3 id="agenda-1">Agenda</h3>
<ul>
<li>Service Initialization</li>
<li>Client Initialization</li>
<li>Leader Election example using Chubby</li>
</ul>
<h3 id="service-initialization">Service Initialization</h3>
<ul>
<li>A master is chosen among chubby replicas using Paxos.</li>
<li>Current master information is persisted in storage and all replicas become aware of the master.</li>
</ul>
<h3 id="client-initialization">Client Initialization</h3>
<ul>
<li>Client contacts DNS to know listed Chubby replicas.</li>
<li>Client calls Chubby Server directly via Remote Procedure Call(RPC)</li>
<li>If that replica is not the master, it will return the address of the current master.</li>
<li>Once the master is located, the client maintains a session with it and sends all requests to it until it indicates that it is not the master anymore or stops responding.
<img loading="lazy" src="/images/notes/chubby-2006/image-9.png"></li>
</ul>
<h3 id="leader-election-example-using-chubby">Leader Election example using Chubby</h3>
<p>Example of application that uses Chubby to elect a single master from a bunch of instances of the same application.</p>
<p><img loading="lazy" src="/images/notes/chubby-2006/image-10.png"></p>
<h3 id="sample-pseudocode-for-leader-election-from-client-application">Sample Pseudocode for leader election from client application.</h3>
<p><img loading="lazy" src="/images/notes/chubby-2006/image-11.png"></p>
<h3 id="files-directories-and-handles">Files, Directories and Handles</h3>
<h3 id="agenda-2">Agenda</h3>
<ul>
<li>Nodes</li>
<li>Metadata</li>
<li>Handles
Chubby file system interface is a tree of files and directories(which can have sub-directories but not files), each of which is called a node.</li>
</ul>
<p><img loading="lazy" src="/images/notes/chubby-2006/image-12.png"></p>
<h3 id="nodes">Nodes</h3>
<ul>
<li>Any node can act as an advisory reader/writer lock.</li>
<li>Nodes can be ephemeral or permanent.</li>
<li>Ephemeral files are used as temporary files and act as an indicator to others that a client is alive.</li>
<li>Ephemeral files are also deleted if no client has them open.</li>
<li>Ephemeral directories are also deleted if they are empty.</li>
<li>Any node can be explicitly deleted.</li>
</ul>
<h3 id="metadata">Metadata</h3>
<ul>
<li>Metadata for each node includes ACL(Access control list), 4 monotonically increasing 64-bit numbers, and a checksum.</li>
<li><strong>ACL</strong></li>
<li><strong>Monotonically increasing 64-bit numbers</strong>: These numbers allow clients to detect changes easily.</li>
<li><strong>Checksum :</strong> Chubby exposes a 64-bit file-content checksum so clients may tell whether files differ.</li>
</ul>
<h3 id="handles">Handles</h3>
<ul>
<li>Clients open nodes to obtain <strong>handles</strong>(similar to <strong>Unix File Descriptors</strong>). Handles include:</li>
</ul>
<h3 id="locks-sequencers-and-lock-delays">Locks Sequencers and Lock-Delays</h3>
<h3 id="agenda-3">Agenda</h3>
<ul>
<li>Locks</li>
<li>Sequencer</li>
<li>Lock-Delay</li>
</ul>
<h3 id="locks">Locks</h3>
<ul>
<li>Each chubby node can act as a reader-writer lock in the following two ways:</li>
</ul>
<h3 id="sequencer-1">Sequencer</h3>
<ul>
<li>With distributed systems, receiving messages out of order is a problem.</li>
<li>Chubby uses sequence numbers to solve this problem.</li>
<li>So below what we are basically trying to do is, trying to do distributed consensus(total order broadcast) on a bunch of application servers(using Leader election on those servers) by using a Distributed Lock Service(Chubby) which uses Paxos to help provide distributed consensus within application servers.</li>
<li>After acquiring a lock on a file, a client can immediately request a <strong>Sequencer</strong>, which is an opaque byte string describing the state of the lock.</li>
<li>An application’s master server can generate a <strong>sequencer</strong> and send it with any internal order to other application servers.</li>
<li>Application servers that receive orders from a primary can check with Chubby if the sequencer is still good and does not belong to a stale primary (to handle the ‘Brain split’ scenario).
<img loading="lazy" src="/images/notes/chubby-2006/image-13.png"></li>
</ul>
<h3 id="lock-delay">Lock-Delay</h3>
<ul>
<li>For file servers(or external services) that do not support sequencers**(or Fencing Tokens** to protect against delayed packets belonging to an older lock**)**, Chubby provides a lock-delay period to protect against message delays and server restarts.</li>
<li>If a client releases a lock in the normal way, it is immediately available for other clients to claim, as one would expect.</li>
<li>However, if a lock becomes free because the holder has failed or become inaccessible, the lock server will prevent other clients from claiming the lock for a period called the lock- delay.</li>
<li>While imperfect, the lock-delay protects unmodified servers and clients from everyday problems caused by message delays and restarts.</li>
</ul>
<h3 id="session-and-events">Session and Events</h3>
<h3 id="agenda-4">Agenda</h3>
<ul>
<li>What is a Chubby Session?</li>
<li>Session Protocol</li>
<li>What is Keep Alive</li>
<li>Session Optimization</li>
<li>Failovers</li>
</ul>
<h3 id="what-is-a-chubby-session">What is a Chubby Session?</h3>
<ul>
<li>A relationship b/w Chubby Cell and a Client.</li>
<li>It exists for some interval of time and is maintained by periodic handshakes called <strong>keepalives</strong>.</li>
<li>Clients’ handles, locks, and cached data only remain valid provided its session remains valid.</li>
</ul>
<h3 id="session-protocol">Session Protocol</h3>
<ul>
<li>Client requests a new session from Chubby cells’s master.</li>
<li>Session ends if the client explicitly ends it or it has been idle.</li>
<li>Each session has an associated lease, which is the time interval during which the master guarantees not to terminate the session unilaterally. End of this interval is called <strong>Session Lease Timeout</strong>.</li>
<li>Master advances session lease timeout in 3 circumstances:</li>
</ul>
<h3 id="what-is-keep-alive">What is Keep Alive</h3>
<ul>
<li>Keepalive is a way for a client to maintain a constant session with Chubby Cell.</li>
<li>Steps:</li>
<li>Google experimentation showed that <strong>93% of RPC requests</strong> are KeepAlives.</li>
<li>How can we reduce the keepalives?
<img loading="lazy" src="/images/notes/chubby-2006/image-14.png"></li>
</ul>
<h3 id="session-optimization">Session Optimization</h3>
<ul>
<li><strong>Piggybacking events(using a different event to transmit some additional detail)</strong></li>
<li><strong>Local Lease</strong></li>
<li><strong>Jeopardy</strong></li>
<li><strong>Grace Period</strong></li>
<li><strong>Original(Initial chubby session):</strong></li>
<li><strong>Optimization Attempt 1:</strong>
<img loading="lazy" src="/images/notes/chubby-2006/image-15.png"></li>
</ul>
<h3 id="failovers">Failovers</h3>
<ul>
<li>
<p>Failover happens when the master fails or otherwise loses membership. Chubby typically takes b/w 5-30 seconds for fail-over.</p>
</li>
<li>
<p>Summary of things that happen in a master failover.
<img loading="lazy" src="/images/notes/chubby-2006/image-16.png"></p>
</li>
<li>
<p>Client has lease M1 (&amp; local lease C1) with master and pending KeepAlive request.</p>
</li>
<li>
<p>Master starts lease M2 and replies to the KeepAlive request.</p>
</li>
<li>
<p>Client extends the local lease to C2 and makes a new KeepAlive call. Master dies before replying to the next KeepAlive. So, no new leases can be assigned. Client’s C2 lease expires, and the client library flushes its cache and informs the application that it has entered jeopardy. The grace period starts on the client.</p>
</li>
<li>
<p>Eventually, a new master is elected and initially uses a conservative approximation M3 of the session lease that its predecessor may have had for the client. Client sends KeepAlive to new master (4).</p>
</li>
<li>
<p>The first KeepAlive request from the client to the new master is rejected (5) because it has the wrong master epoch number (described in the next section).</p>
</li>
<li>
<p>Client retries with another KeepAlive request.</p>
</li>
<li>
<p>Re-tried KeepAlive succeeds. Client extends its lease to C3 and optionally informs the application that its session is no longer in jeopardy (session is in the safe mode now).</p>
</li>
<li>
<p>Client makes a new KeepAlive call, and the normal protocol works from this point onwards.</p>
</li>
<li>
<p>Because the grace period was long enough to cover the interval between the end of lease C2 and the beginning of lease C3, the client saw nothing but a delay. If the grace period was less than that interval, the client would have abandoned the session and reported the failure to the application.</p>
</li>
</ul>
<h3 id="master-election-and-chubby-events">Master Election and Chubby Events?</h3>
<h3 id="initializing-a-newly-elected-master">Initializing a newly elected Master</h3>
<ul>
<li>A newly elected master proceeds as follows:</li>
<li><strong>Picks a new Epoch Number</strong>: To differentiate itself from the previous master. Clients are required to present an epoch number on every call. Master rejects calls from clients using older epoch numbers. This ensures that the new master will not respond to a very old packet that was sent to the previous master.</li>
<li><strong>Responds to master-location requests</strong>: but doesn’t respond to session related operations yet.</li>
<li><strong>Build in-memory data structures</strong>:</li>
<li><strong>Let clients perform keep-alives:</strong></li>
<li><strong>Emits a fail-over event to each session</strong>:</li>
<li><strong>Wait</strong>: Master waits until each session acknowledges the fail-over event or lets its session expire.</li>
<li><strong>Allow all operations to proceed</strong>.</li>
<li><strong>Honor older handles by clients</strong>:</li>
<li><strong>Delete Ephemeral files</strong>:</li>
</ul>
<h3 id="chubby-events">Chubby Events</h3>
<ul>
<li>Chubby supports a <strong>simple event mechanism</strong> to let its clients <strong>subscribe</strong> to events.</li>
<li>Events are <strong>delivered asynchronously</strong> via <strong>callbacks</strong> from the chubby library.</li>
<li>Clients subscribe to a range of events while creating a handle.</li>
<li>Example of events from Server to Chubby Client:</li>
<li>Additionally Chubby client sends the following session events to the application:</li>
</ul>
<h3 id="caching">Caching</h3>
<h3 id="chubby-cache">Chubby Cache</h3>
<ul>
<li>Caching is important since it is used for read heavy purposes rather than write heavy.</li>
<li>Chubby clients cache <strong>file contents</strong>, <strong>node metadata</strong>, and information on <strong>open handles</strong> in a <strong>consistent</strong>, <strong>write-through cache</strong> in clients’ memory.</li>
<li>Chubby must maintain <strong>consistency</strong> b/w <strong>file</strong>, its <strong>replicas</strong>, and <strong>cache</strong> as well.</li>
<li>Clients maintain their cache by a <strong>lease mechanism</strong>, and <strong>flush</strong> the cache when the lease expires.</li>
</ul>
<h3 id="cache-invalidation">Cache Invalidation</h3>
<ul>
<li>Protocol for cache invalidation when file data or metadata is changed:
<img loading="lazy" src="/images/notes/chubby-2006/image-17.png"></li>
</ul>
<p><strong>Question:</strong> While the master is waiting for acknowledgments, are other clients allowed to read the file?</p>
<ul>
<li>
<p><strong>Answer:</strong> During the time the master is waiting for the acknowledgments from clients, the file is treated as ‘uncachable.’ This means that the clients can still read the file but will not cache it. This approach ensures that reads always get processed without any delay. This is useful because reads outnumber writes.
<strong>Question:</strong> Are clients allowed to cache locks? If yes, how is it used?</p>
</li>
<li>
<p><strong>Answer:</strong> Chubby allows its clients to cache locks, which means the client can hold locks longer than necessary, hoping that they can be used again by the same client.
<strong>Question:</strong> Are clients allowed to cache open handles?</p>
</li>
<li>
<p><strong>Answer:</strong> Chubby allows its clients to cache open handles. This way, if a client tries to open a file it has opened previously, only the first open() call goes to the master.</p>
</li>
</ul>
<h3 id="database">Database</h3>
<h3 id="agenda-5">Agenda</h3>
<ul>
<li>
<p>Backup</p>
</li>
<li>
<p>Mirroring
How chubby uses a database for storage.</p>
</li>
<li>
<p>Initially, Chubby used a replicated version of Berkeley DB to store its data. Later, the Chubby team felt that using Berkeley DB exposes Chubby to more risks, so they decided to write a simplified custom database with the following characteristics:</p>
</li>
</ul>
<h3 id="backup">Backup</h3>
<ul>
<li>For recovery in case of failure, all database transactions are stored in a <strong>transaction log</strong> (<strong>a write-ahead log</strong>).</li>
<li>As this transaction log can become very large over time, every few hours, t<strong>he master of each Chubby cell writes a snapshot of its database to a GFS server</strong> in a different building.</li>
<li>The use of a separate building ensures both that the backup will survive building damage, and that the backups introduce no cyclic dependencies in the system;</li>
<li><strong>Once a snapshot is taken, the previous transaction log is deleted</strong>. Therefore, at any time, the complete state of the system is determined by the last snapshot together with the set of transactions from the transaction log.</li>
<li>Backup databases are used for disaster recovery and to initialize the database of a newly replaced replica without placing a load on other replicas.</li>
</ul>
<h3 id="mirroring">Mirroring</h3>
<ul>
<li>Mirroring is a technique that allows a system to automatically maintain multiple copies. Chubby allows a collection of files to be mirrored from one cell to another.</li>
<li>Mirroring is fast because the files are small.</li>
<li>A special “global” cell subtree /ls/global/master that is mirrored to the subtree /ls/cell/replica in every other Chubby cell.</li>
<li>Various files in which Chubby cells and other systems advertise their presence to monitoring services.</li>
<li>Pointers to allow clients to locate large data sets such as Bigtable cells, and many configuration files for other systems.</li>
</ul>
<h3 id="scaling-chubby">Scaling Chubby</h3>
<h3 id="agenda-6">Agenda</h3>
<ul>
<li>Proxies</li>
<li>Partitioning</li>
<li>Learning
Chubby’s clients are individual processes, so Chubby handles more clients than expected. At Google, 90,000+ clients communicate with a single Chubby server.</li>
</ul>
<p>Techniques used to reduce communication with the master(since read heavy):</p>
<ul>
<li><strong>Minimize request rate</strong> by creating more chubby cells so that clients almost always use a nearby cell(found with DNS) to avoid reliance on remote machines.</li>
<li><strong>Minimize KeepAlives Load</strong>: KeepAlives are by far the dominant types of request.</li>
<li><strong>Caching:</strong> Clients cache file data, metadata, handles, locks etc.</li>
<li><strong>Simplified protocol conversions:</strong></li>
</ul>
<h3 id="proxies">Proxies</h3>
<ul>
<li>A proxy is an additional server that can act on behalf of the actual server.</li>
<li>A Chubby proxy can handle KeepAlives and read requests.</li>
<li>All writes and first-time reads pass through the cache to reach the master</li>
<li>Proxy responsible for invalidating client’s cache as well.
<img loading="lazy" src="/images/notes/chubby-2006/image-18.png"></li>
</ul>
<h3 id="partitioning">Partitioning</h3>
<ul>
<li>Need to support 100K clients. How would chubby do that?</li>
<li>Chubby’s interface (files &amp; directories) was designed such that namespaces can easily be partitioned between multiple Chubby cells if needed.</li>
<li>Chubby can partition nodes within a large directory(with lots of sub-directories).</li>
<li>Scenarios in which <strong>partitioning does not help scale</strong>:</li>
</ul>
<h3 id="learning">Learning</h3>
<ul>
<li><strong>Lack of aggressive caching</strong>: Initially, clients were not caching the absence of files or open file handles. An abusive client could write loops that retry indefinitely when a file is not present or poll a file by opening it and closing it repeatedly when one might expect they would open the file just once. Chubby educated its users to make use of aggressive caching for such scenarios.</li>
<li><strong>Lack of quotas</strong>: Chubby was never intended to be used as a storage system for large amounts of data, so it has no storage quotas. In hindsight, this was naive. To handle this, Chubby later introduced a limit on file size (256kBytes).</li>
<li><strong>Publish/subscribe</strong>: There have been several attempts to use Chubby’s event mechanism as a publish/subscribe system. Chubby is a strongly consistent system, and the way it maintains a consistent cache makes it a slow and inefficient choice for publish/subscribe. Chubby developers caught and stopped such uses early on.</li>
<li><strong>Developers rarely consider availability</strong>: Developers generally fail to think about failure probabilities and wrongly assume that Chubby will always be available. Chubby educated its clients to plan for short Chubby outages so that it has little or no effect on their applications.</li>
</ul>
<h3 id="chubby-as-a-name-service">Chubby as a Name Service?</h3>
<ul>
<li>
<p>Authors were surprised to find that Chubby was most popular for DNS.</p>
</li>
<li>
<p>Hard to pick a good value for TTL, since DNS uses TTL and may serve stale values for some time(up-to 60 secs).</p>
</li>
<li>
<p>Chubby however, via Client side Cache invalidation, provides <strong>Consistent Reads</strong>.</p>
</li>
<li>
<p>E.g. If starting a n processes where each process looks each other up(via DNS), that&rsquo;s N^2 DNS lookups.</p>
</li>
<li>
<p>Chubby sees a thundering herd from the reads at client startup(not cached).
<strong>Summary:</strong></p>
</li>
<li>
<p>Distributed Lock Service used inside Google.</p>
</li>
<li>
<p>Provides coarse-grained locking(for minutes, hours or days) and not recommended for fine-grained locking(seconds or less). Suited to read-heavy rather than write-heavy. Although you can build a fine-grained locking system on top of Chubby.</p>
</li>
<li>
<p>A Chubby cell is a Chubby Cluster(usually with 3 or 5 replicas).</p>
</li>
<li>
<p>Using Paxos, one replica in a Cell is chosen as master which handles all read/write requests. If the master fails, a fail-over is performed.</p>
</li>
<li>
<p>Each replica has a local database, for files/directories/locks etc. Master writes directly to its own database, which gets asynchronously replicated for Fault Tolerance.</p>
</li>
<li>
<p>Clients use a Chubby Library to communicate with Servers using RPC.</p>
</li>
<li>
<p>Chubby interface is a unix-like file system based, a tree of files and directories(which other sub-directories but not files).</p>
</li>
<li>
<p><strong>Locks</strong>: Each node(file/directory) can act as an advisory reader(<strong>shared</strong>)-writer(<strong>exclusive</strong>) lock.</p>
</li>
<li>
<p><strong>Ephemeral Nodes</strong> to indicate others that a client is alive.</p>
</li>
<li>
<p><strong>Metadata</strong> includes ACL, Monotonically increasing 64-bit numbers, and CheckSum.</p>
</li>
<li>
<p><strong>Events</strong> mechanism between Chubby Client and server and Client and application for a variety of events like, Lock Acquired, file edited, Jeopardy, Safe etc.</p>
</li>
<li>
<p><strong>Client Caching</strong> to reduce read traffic. Need consistency b/w File, Replica, and its client cache. <strong>Client cache invalidation</strong> using KeepAlive request/responses.</p>
</li>
<li>
<p>Clients maintain <strong>Sessions</strong> using KeepAlive RPCs.</p>
</li>
<li>
<p><strong>Backup</strong> Snapshot of database(<strong>Write-Ahead Log</strong>) to a GFS file server to different buildings.</p>
</li>
<li>
<p><strong>Mirroring</strong>:Collection of files synced from one cell to another.
<strong>System Design Patterns</strong>:</p>
</li>
<li>
<p><strong>Write-Ahead Log</strong>: For Fault Tolerance, to handle master crash, all database transactions stored in a transaction log**(on local drive or on a distributed GFS?)**</p>
</li>
<li>
<p><strong>Quorum:</strong> To ensure strong consistency. Master gets write ack from N replicas before responding back to client about write success.</p>
</li>
<li>
<p><strong>Generation Clock:</strong> Newly elected master uses Epoch number(monotonically increasing) to avoid split brain.</p>
</li>
<li>
<p><strong>Lease:</strong> Chubby Client maintains a Time bound session lease with Master.
<strong>References:</strong></p>
</li>
<li>
<p>Chubby Paper</p>
</li>
<li>
<p>Chubby Architecture video</p>
</li>
<li>
<p>Chubby vs ZooKeeper</p>
</li>
<li>
<p>Hierarchical Chubby</p>
</li>
<li>
<p>BigTable</p>
</li>
<li>
<p>GFS</p>
</li>
<li>
<p>Jordan Deep Dive</p>
</li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf">https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Kafka</title>
      <link>https://www.sethihemant.com/notes/kafka-2011/</link>
      <pubDate>Wed, 04 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/kafka-2011/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://notes.stephenholiday.com/Kafka.pdf&#34;&gt;Kafka&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;kafkadistributed-messaging-system&#34;&gt;Kafka/Distributed Messaging System&lt;/h2&gt;
&lt;h3 id=&#34;goal&#34;&gt;Goal&lt;/h3&gt;
&lt;p&gt;Design a &lt;strong&gt;distributed&lt;/strong&gt; &lt;strong&gt;messaging system&lt;/strong&gt; that can &lt;strong&gt;reliably&lt;/strong&gt; transfer a &lt;strong&gt;high throughput&lt;/strong&gt; of &lt;strong&gt;messages&lt;/strong&gt; between different entities.&lt;/p&gt;
&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;One common challenge in distributed systems is handling &lt;strong&gt;continuous influx of data from multiple sources&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;E.g. Imagine a &lt;strong&gt;log aggregation service&lt;/strong&gt; that can receive hundreds of log entries per second from different sources. Function of this log aggregation service is to store these logs on a disk at a shared server and build an index on top of these logs so that they can be searched later.&lt;/li&gt;
&lt;li&gt;Challenges of this service?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed Messaging Systems&lt;/strong&gt;(or Asynchronous processing paradigm) can help.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-is-a-messaging-system&#34;&gt;What is a messaging System?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;System responsible for &lt;strong&gt;transferring data amongst various disparate systems&lt;/strong&gt; like apps, services, processes, servers etc, w/o &lt;strong&gt;introducing additional coupling&lt;/strong&gt; b/w producers and consumers, and by providing &lt;strong&gt;asynchronous&lt;/strong&gt; way of communicating b/w sender and receiver.&lt;/li&gt;
&lt;li&gt;Two types of Messaging Systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;queue&#34;&gt;Queue&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A Particular message can be consumed by one consumer only.&lt;/li&gt;
&lt;li&gt;Once a message is consumed, it&amp;rsquo;s removed from the queue.&lt;/li&gt;
&lt;li&gt;Limits the system as the same messages can’t be read by the multiple consumers.
&lt;img loading=&#34;lazy&#34; src=&#34;https://www.sethihemant.com/images/notes/kafka-2011/image-1.png&#34;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;publish-subscribe-messaging-system&#34;&gt;Publish-Subscribe Messaging System&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the Pub-Sub model, messages are written into Partitions/Topics.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://notes.stephenholiday.com/Kafka.pdf">Kafka</a></p>
<hr>
<h2 id="kafkadistributed-messaging-system">Kafka/Distributed Messaging System</h2>
<h3 id="goal">Goal</h3>
<p>Design a <strong>distributed</strong> <strong>messaging system</strong> that can <strong>reliably</strong> transfer a <strong>high throughput</strong> of <strong>messages</strong> between different entities.</p>
<h3 id="background">Background</h3>
<ul>
<li>One common challenge in distributed systems is handling <strong>continuous influx of data from multiple sources</strong>.</li>
<li>E.g. Imagine a <strong>log aggregation service</strong> that can receive hundreds of log entries per second from different sources. Function of this log aggregation service is to store these logs on a disk at a shared server and build an index on top of these logs so that they can be searched later.</li>
<li>Challenges of this service?</li>
<li><strong>Distributed Messaging Systems</strong>(or Asynchronous processing paradigm) can help.</li>
</ul>
<h3 id="what-is-a-messaging-system">What is a messaging System?</h3>
<ul>
<li>System responsible for <strong>transferring data amongst various disparate systems</strong> like apps, services, processes, servers etc, w/o <strong>introducing additional coupling</strong> b/w producers and consumers, and by providing <strong>asynchronous</strong> way of communicating b/w sender and receiver.</li>
<li>Two types of Messaging Systems</li>
</ul>
<h3 id="queue">Queue</h3>
<ul>
<li>A Particular message can be consumed by one consumer only.</li>
<li>Once a message is consumed, it&rsquo;s removed from the queue.</li>
<li>Limits the system as the same messages can’t be read by the multiple consumers.
<img loading="lazy" src="/images/notes/kafka-2011/image-1.png"></li>
</ul>
<h3 id="publish-subscribe-messaging-system">Publish-Subscribe Messaging System</h3>
<ul>
<li>
<p>In the Pub-Sub model, messages are written into Partitions/Topics.</p>
</li>
<li>
<p>Producers write the messages to topics that get persisted in the messaging system.</p>
</li>
<li>
<p>Subscribers subscribe to those topics to receive each message that was published.</p>
</li>
<li>
<p>Pub-Sub model allows multiple consumers to read the same message.</p>
</li>
<li>
<p>Messaging system that stores and handles messages is called a Broker.
<img loading="lazy" src="/images/notes/kafka-2011/image-2.png"></p>
</li>
<li>
<p>Provides a loose coupling b/w producers and consumers so they don’t need to be synchronized. They can read and write <strong>messages at different rates</strong>.</p>
</li>
<li>
<p>Also provides <strong>fault-tolerance</strong>. Messages don’t get lost.</p>
</li>
<li>
<p>A messaging system can be deployed for various reasons:</p>
</li>
</ul>
<h3 id="kafka">Kafka</h3>
<h3 id="agenda">Agenda</h3>
<ul>
<li>What is Kafka</li>
<li>Background</li>
<li>Kafka Use Cases</li>
</ul>
<h3 id="what-is-kafka">What is Kafka?</h3>
<ul>
<li>Open source pub-sub messaging system</li>
<li>Can work as a message-queue as well.</li>
<li>Distributed, Fault tolerant, highly scalable by design.</li>
<li>Fundamentally a system that takes streams of messages from producers, store reliably on a central cluster(with a set of brokers), and allows those messages to be delivered to consumers.
<img loading="lazy" src="/images/notes/kafka-2011/image-3.png"></li>
</ul>
<h3 id="background-1">Background</h3>
<ul>
<li>
<p>Created at LinkedIn in 2010 to track <strong>Page Views(events)</strong>, <strong>Messages</strong> from Messaging Systems, and <strong>Logs</strong> from Various services.</p>
</li>
<li>
<p>Kafka is also known as a <strong>Distributed Commit log or Write Ahead Log or a Transaction Log.</strong></p>
</li>
<li>
<p><strong>Commit Log</strong> is an append-only data structure that can persistently store a sequence of records.</p>
</li>
<li>
<p>Records are always appended to the end of the log, and once added, records cannot be deleted or modified. Reading from a commit log always happens from left to right (or old to new).
<img loading="lazy" src="/images/notes/kafka-2011/image-4.png"></p>
</li>
<li>
<p>Stores all messages on disk and reads and writes take advantage of sequential disk reads/writes.</p>
</li>
</ul>
<h3 id="kafka-use-cases">Kafka Use Cases</h3>
<ul>
<li>Can be used for collecting huge amounts(Big Data) events and do real-time stream processing of those events.</li>
<li><strong>Metrics:</strong> Can collect and aggregate monitoring data. Different services can write their metrics which can be later pulled from Kafka to produce aggregate statistics.</li>
<li><strong>Log Aggregation:</strong> Collect logs from various sources, and make them available in standard format to multiple consumers.</li>
<li><strong>Stream Processing:</strong> Cases where data undergoes transformation after reading. E.g. Raw data consumed from the topic is transformed, enriched, aggregated, and pushed to a new topic for further consumption. Sort of creating a derived view of data from the source of record data.</li>
<li><strong>Commit Log:</strong> Can be used as an external commit log for distributed systems which can keep track of their states.</li>
<li><strong>Website Activity Tracking:</strong> One of the original use cases was to build a User-Activity tracking pipeline. Like Page clicks searches, are published to separate topics. These topics are made available for later processing like Loading data into Hadoop(for Batch Processing), Data Warehousing systems for Analytics or reporting. Can also be fed into <strong>Product Suggestion or Recommendations systems</strong> which can power, Similar Products that you may like, or people have bought etc.</li>
</ul>
<h3 id="high-level-architecture">High Level Architecture</h3>
<h3 id="agenda-1">Agenda</h3>
<ul>
<li>Kafka Common Terms</li>
<li>High-Level Architecture</li>
</ul>
<h3 id="kafka-common-terms">Kafka Common Terms</h3>
<ul>
<li>
<p><strong>Brokers</strong></p>
</li>
<li>
<p><strong>Records</strong>
<img loading="lazy" src="/images/notes/kafka-2011/image-5.png"></p>
</li>
<li>
<p><strong>Topics</strong>
<img loading="lazy" src="/images/notes/kafka-2011/image-6.png"></p>
</li>
<li>
<p><strong>Producers</strong></p>
</li>
<li>
<p><strong>Consumers</strong></p>
</li>
<li>
<p>In Kafka, producers and consumers are fully decoupled and agnostic of each other, which is a key design element to achieve the high scalability that Kafka is known for. For example, producers never need to wait for consumers.</p>
</li>
</ul>
<h3 id="high-level-architecture-1">High Level Architecture</h3>
<h3 id="kafka-cluster">Kafka Cluster</h3>
<ul>
<li>Kafka is run as a cluster of one or more servers, where each server is responsible for running one Kafka broker.</li>
</ul>
<h3 id="zookeeper">ZooKeeper</h3>
<ul>
<li><strong>ZooKeeper</strong> is a <strong>highly read optimized</strong> <strong>distributed key-value store</strong> and is used for coordination and storing configurations.</li>
<li>In Original Version of Kafka, Kafka had used Zookeeper to coordinate between Kafka brokers; ZooKeeper maintains metadata information about the Kafka cluster
<img loading="lazy" src="/images/notes/kafka-2011/image-7.png"></li>
</ul>
<h3 id="kafka-deep-dive">Kafka Deep Dive</h3>
<p>Related Notes: Alex XU II</p>
<h3 id="agenda-2">Agenda</h3>
<ul>
<li>Topic Partitions</li>
<li>High-Water Mark
Kafka is simply a collection of topics. As topics can get quite big, they are split into partitions of a smaller size for better performance and scalability.</li>
</ul>
<h3 id="topic-partitions">Topic Partitions</h3>
<ul>
<li>
<p>Kafka Topics are partitioned, and these partitions are placed on separate nodes/brokers.</p>
</li>
<li>
<p>When a new message is published to a topic, it gets appended to one of the topic’s partitions, usually decided by using the Customer specified Partition Key.</p>
</li>
<li>
<p>A <strong>partition</strong> is an ordered sequence of messages.</p>
</li>
<li>
<p>Kafka Guarantees <strong>FIFO Ordering</strong> between messages of a single partition. No ordering guarantees across partitions or at a topic level.
<img loading="lazy" src="/images/notes/kafka-2011/image-8.png"></p>
</li>
<li>
<p>A Unique Sequence ID called a <strong>Partition Offset</strong> gets assigned to every message added to a partition. Used to identify a message’s sequential position within a partition.</p>
</li>
<li>
<p>Offset sequences are unique to a single partition. Messages are uniquely located using <strong>(Topic, Partition, Offset)</strong>.</p>
</li>
<li>
<p>Producers can choose to publish messages to any partition. If Ordering within a partition is not needed, a <strong>Round-Robin strategy</strong> can be used for evenly partitioning data across nodes.</p>
</li>
<li>
<p>Placing partitions on separate brokers allows for multiple consumers to read from a topic in a parallel, i.e. Different consumers can concurrently read different partitions on separate brokers. However, for multiple consumers within the same Consumer Group, only 1 consumer from a consumer Group can read the data from a Partition at any time.</p>
</li>
<li>
<p>Messages once written to a partition are immutable(Append Only Log).</p>
</li>
<li>
<p>Producer specifies a <strong>Partition Key</strong>, to any message that it publishes so that data is written to the same partition.</p>
</li>
<li>
<p>Each broker can manage a set of partitions from across various topics.
<img loading="lazy" src="/images/notes/kafka-2011/image-9.png"></p>
</li>
<li>
<p>Follows the principle of <strong>Dumb Broker</strong> and <strong>Smart Consumer</strong>.</p>
</li>
<li>
<p>Kafka doesn’t keep a record of what records are read by the consumer. Consumers poll kafka for new messages and specify which records(specified by partition Offset) they want to read from the topic.</p>
</li>
<li>
<p>Consumers are allowed to increment/decrement the offset to replay and reprocess the messages.</p>
</li>
<li>
<p>Each Topic partition has one leader broker and multiple replica(followers) brokers.</p>
</li>
</ul>
<h3 id="leaders-and-followers">Leaders and Followers</h3>
<ul>
<li>A <strong>leader</strong> is the node responsible for all reads and writes for the given partition. Every partition has one Kafka broker acting as a leader.</li>
<li>To handle Single Point of Failure and to enable Fault Tolerance, Kafka replicates partitions and distributes them across multiple brokers.</li>
<li>Each follower’s responsibility is to replicate the leader&rsquo;s data to serve as a backup partition.</li>
<li>A follower can take over the leadership if the leader of a partition goes down.</li>
<li>Kafka stores the location of the leader of each partition in ZooKeeper</li>
<li>As all writes/reads happen at/from the leader, producers and consumers directly talk to ZooKeeper to find a partition leader.
<img loading="lazy" src="/images/notes/kafka-2011/image-10.png"></li>
</ul>
<h3 id="in-sync-replicasisr">In Sync Replicas(ISR)</h3>
<ul>
<li>An in-sync replica (ISR) is a broker that has the latest data for a given partition.</li>
<li>A follower is an in-sync replica only if it has fully caught up to the partition it is following.</li>
<li><strong>Only ISRs are eligible to become partition leaders.</strong></li>
<li>Kafka can choose the minimum number of ISRs required before the data becomes available for consumers to read.</li>
</ul>
<h3 id="high-water-mark">High Water Mark</h3>
<ul>
<li>To ensure data consistency, the leader broker never returns (or exposes) messages which have not been replicated to a minimum set of ISRs.</li>
<li>Broker uses High Water Mark which is the <strong>highest offset that all ISRs of a particular partition share</strong>.</li>
<li>The leader exposes data only up to the high-water mark offset and propagates the high-water mark offset to all followers.</li>
<li>This avoids the case of a <strong>Non-Repeatable read</strong> in case the Leader crashes before Replicas get the latest messages.
<img loading="lazy" src="/images/notes/kafka-2011/image-11.png"></li>
</ul>
<h3 id="consumer-groups">Consumer Groups</h3>
<h3 id="agenda-3">Agenda</h3>
<ul>
<li>What is a Consumer Group?</li>
<li>Distributing Partitions to a consumer within Consumer Groups.</li>
</ul>
<h3 id="what-is-a-consumer-group">What is a Consumer Group?</h3>
<ul>
<li>A consumer group is basically a set of one or more consumers working together in parallel to consume messages from topic partitions.</li>
<li>No two consumers within the same Consumer group can attach to the same partition at a time. Thus no two consumers within CG receive the same message.</li>
</ul>
<h3 id="distributing-partitions-to-a-consumer-within-consumer-groups">Distributing Partitions to a consumer within Consumer Groups.</h3>
<ul>
<li>
<p>Kafka ensures that only a single consumer reads messages from any partition within a consumer group</p>
</li>
<li>
<p>Topic partitions are a unit of parallelism</p>
</li>
<li>
<p>If a consumer stops, Kafka spreads partitions across the remaining consumers in the same consumer group</p>
</li>
<li>
<p>Every time a consumer is added to or removed from a group, the consumption is rebalanced within the group.
<img loading="lazy" src="/images/notes/kafka-2011/image-12.png"></p>
</li>
<li>
<p>Parallelizing processing across multiple partitions of a topic, helps support very high Throughput.</p>
</li>
<li>
<p>Kafka stores the current offset per consumer group per topic per partition? <strong>What? Initially we said Kafka is DUMB and that Consumer tracks the offset? [Research]</strong></p>
</li>
<li>
<p>Kafka uses any unused consumers as failovers when there are more consumers than partitions. Extra Consumers are idle in the meantime.</p>
</li>
<li>
<p>Rebalancing happens as Consumers are added and removed from the ConsumerGroups.</p>
</li>
</ul>
<h3 id="kafka-workflow">Kafka Workflow</h3>
<h3 id="agenda-4">Agenda</h3>
<ul>
<li>Kafka Workflow as Pub-Sub messaging</li>
<li>Kafka Workflow for Consumer Group</li>
<li>Kafka provides both pub-sub and queue-based messaging systems in a fast, reliable, persisted, fault-tolerance, and zero downtime manner.</li>
<li>In both cases, producers simply send the message to a topic, and consumers can choose any one type of messaging system depending on their need</li>
</ul>
<h3 id="kafka-workflow-as-pub-sub-messaging">Kafka Workflow as Pub-Sub Messaging</h3>
<ul>
<li>Producer publishes a message to a topic.</li>
<li>Broker stores messages in the partitions configured for that topic. If no partition keys were specified, Broker spreads the messages evenly across partitions.</li>
<li>Consumer subscribes to a specific Topic. Broker provides the current offset of that Topic back to Consumer and saves that Offset to ZooKeeper.</li>
<li>Consumers will request Brokers at regular intervals for new messages and process it once kafka sends those messages.</li>
<li>Once the consumer processes the message, it sends an acknowledgement back to the broker. Broker Updates the processed offsets in the ZooKeeper.</li>
<li>Consumers can rewind/skip to the desired offset and read subsequent messages.</li>
</ul>
<h3 id="role-of-zookeeper">Role of Zookeeper</h3>
<h3 id="agenda-5">Agenda</h3>
<ul>
<li>What is ZooKeeper?</li>
<li>ZooKeeper as Central Coordinator.</li>
</ul>
<h3 id="what-is-zookeeper">What is ZooKeeper?</h3>
<ul>
<li>Distributed configuration and synchronization service.</li>
<li>Serves as the coordination interface between the Kafka brokers, producers, and consumers.</li>
<li>Kafka stores basic metadata in ZooKeeper, such as information about brokers, topics, partitions, partition leader/followers, consumer offsets.
<img loading="lazy" src="/images/notes/kafka-2011/image-13.png"></li>
</ul>
<h3 id="zookeeper-as-the-central-coordinatormight-be-stale-info">ZooKeeper as the central coordinator(Might be Stale info)</h3>
<ul>
<li>Kafka brokers are stateless; they rely on ZooKeeper to maintain and coordinate brokers, such as notifying consumers and producers of the arrival of a new broker or failure of an existing broker, as well as routing all requests to partition leaders.</li>
<li>Stores all sorts of Metadata about the Kafka Cluster</li>
</ul>
<h3 id="how-do-producers-or-consumers-find-out-who-the-leader-of-a-partition-is">How do producers or consumers find out who the leader of a partition is?</h3>
<ul>
<li>
<p>In the older versions of Kafka, all clients (i.e., producers and consumers) used to <strong>directly talk to ZooKeeper</strong> to find the partition leader.</p>
</li>
<li>
<p>Kafka has moved away from this coupling, and in Kafka’s latest releases, <strong>clients fetch metadata information from Kafka brokers directly</strong>;
<img loading="lazy" src="/images/notes/kafka-2011/image-14.png"></p>
</li>
<li>
<p>All the critical information is stored in the ZooKeeper and ZooKeeper replicates this data across its cluster, therefore, failure of Kafka broker (or ZooKeeper itself) does not affect the state of the Kafka cluster.</p>
</li>
<li>
<p>Zookeeper is also responsible for <strong>coordinating the partition leader election</strong> between the Kafka brokers in case of leader failure.</p>
</li>
</ul>
<h3 id="controller-broker">Controller Broker</h3>
<h3 id="agenda-6">Agenda</h3>
<ul>
<li>What is a Controller Broker?</li>
<li>Split Brain.</li>
<li>Generation Clock.</li>
</ul>
<h3 id="what-is-a-controller-broker">What is a Controller Broker?</h3>
<ul>
<li>Within the Kafka cluster, one broker is elected as the Controller.</li>
<li>Controller broker is responsible for admin operations, such as creating/deleting a topic, adding partitions, assigning leaders to partitions, monitoring broker failures by doing health checks on other brokers.</li>
<li>Communicates the result of the partition leader election to other brokers in the system.</li>
</ul>
<h3 id="split-brain">Split Brain</h3>
<ul>
<li>When a controller node dies, kafka elects a new controller. One of the problems is that we cannot truly know if the leader has stopped for good(Crash Stop) or has experienced intermittent failures like Stop the World GC or process Pause, or a temporary network disruption.</li>
<li>Two split-brain controllers would be giving out conflicting commands in parallel. If something like this happens in a cluster, it can result in major inconsistencies. How do we handle this?</li>
</ul>
<h3 id="generation-clock">Generation Clock?</h3>
<ul>
<li>Split-brain is commonly solved with a generation clock, which is simply a monotonically increasing number to indicate a server’s generation.</li>
<li>In Kafka, the generation clock is implemented through an epoch number, Old leader = epoch 1, and new leader = epoch 2.</li>
<li>This epoch is included in every request that is sent from the Controller to other brokers.</li>
<li>Brokers can now easily differentiate the real Controller by simply trusting the Controller with the highest number.</li>
<li>This epoch number is stored in ZooKeeper.</li>
</ul>
<h3 id="kafka-delivery-semantics">Kafka Delivery Semantics?</h3>
<h3 id="agenda-7">Agenda</h3>
<ul>
<li>Producer Delivery Semantics</li>
<li>Consumer Delivery Semantics</li>
</ul>
<h3 id="producer-delivery-semantics">Producer Delivery Semantics</h3>
<ul>
<li>A producer writes only to the leader broker, and the followers asynchronously replicate the data.</li>
<li>How can a producer know that the data is successfully stored at the leader or that the followers are keeping up with the leader?</li>
<li>Kafka offers <strong>three options</strong> to denote the number of brokers that must receive the record before the producer considers the write as successful:</li>
</ul>
<h3 id="consumer-delivery-semantics">Consumer Delivery Semantics</h3>
<ul>
<li>A consumer can read only those messages that have been written to a set of in-sync replicas(<strong>High Water Mark</strong>).</li>
<li>There are three ways of providing consistency to the consumer:</li>
</ul>
<h3 id="kafka-characteristics">Kafka Characteristics</h3>
<h3 id="agenda-8">Agenda</h3>
<ul>
<li>Storing messages to disks</li>
<li>Record Retention in Kafka</li>
<li>Client Quota</li>
<li>Kafka Performance</li>
</ul>
<h3 id="storing-messages-to-disks">Storing messages to disks</h3>
<ul>
<li>Kafka writes its messages to the local disk and does not keep anything in RAM. Disk storage is important for durability so that the messages will not disappear if the system dies and restarts.</li>
<li>Even though disk access is generally considered to be slow, there is a huge performance difference b/w Random Block Access and Sequential Access.</li>
<li>Random block access is slower because of numerous disk seeks, whereas the sequential nature of writing or reading, enables disk operations to be thousands of times faster than random access.</li>
<li>Because all writes and reads happen sequentially, <strong>Kafka has a very high throughput</strong>.</li>
<li>Writing or reading sequentially from disks are <strong>heavily optimized</strong> by the OS, via <strong>read-ahead</strong> (prefetch large block multiples) and <strong>write-behind</strong> (group small logical writes into big physical writes) techniques.</li>
<li>Also, modern operating systems cache the disk in free RAM. This is called Pagecache.</li>
<li>Since Kafka stores messages in a standardized binary format unmodified throughout the whole flow (producer → broker → consumer), it can make use of the zero-copy optimization.</li>
<li>Kafka has a protocol that groups messages together. This allows network requests to group messages together and reduces network overhead.</li>
</ul>
<h3 id="record-retention-in-kafka">Record Retention in Kafka</h3>
<ul>
<li>By default, Kafka retains records until it runs out of disk space. We can set time-based limits (configurable retention period), size-based limits (configurable based on size), or compaction (keeps the latest version of record using the key).</li>
<li>For example, we can set a retention policy of three days, or two weeks, or a month, etc.</li>
<li>The records in the topic are available for consumption until discarded by time, size, or compaction.</li>
</ul>
<h3 id="client-quota">Client Quota</h3>
<ul>
<li>Heavy Hitters(Noisy Neighbours) can exhaust broker resources, or can cause network saturation to multi-tenant kafka clusters, which can deny service to other clients and broker themselves.</li>
<li>In Kafka, quotas are <strong>byte-rate</strong> thresholds defined per <strong>client-ID(application).</strong></li>
<li>The broker does not return an error when a client exceeds its quota but instead attempts to slow the client down by holding the client’s response for enough time to keep the client under the quota.</li>
<li>This also prevents clients from having to implement special back-off and retry behavior.</li>
</ul>
<h3 id="kafka-performance">Kafka Performance</h3>
<ul>
<li>Scalability</li>
<li>Fault Tolerance and Reliability</li>
<li>Throughput</li>
<li>Low Latency?</li>
</ul>
<h3 id="system-design-pattern">System Design Pattern:</h3>
<ul>
<li>
<p><strong>High Water Mark</strong> - To deal with Non-Repeatable reads and data consistency.</p>
</li>
<li>
<p><strong>Leader and Follower</strong> - Leader serves read/writes. Followers do replication.</p>
</li>
<li>
<p><strong>Split-Brain</strong> - Multiple Controller nodes active at a time(due to Zombie Controller). Generational Epoch number to resolve.</p>
</li>
<li>
<p><strong>Segmented Log</strong> - Log segmentation to implement storage for its partitions.
<strong>References:</strong></p>
</li>
<li>
<p>Confluent Docs</p>
</li>
<li>
<p>NYTimes usecase</p>
</li>
<li>
<p>Kafka Summit 2019</p>
</li>
<li>
<p>Kafka Acks explained(TODO)</p>
</li>
<li>
<p>Kafka as distributed log</p>
</li>
<li>
<p>Minimizing Kafka Latency(TODO)</p>
</li>
<li>
<p>Kafka Internal Storage(TODO)</p>
</li>
<li>
<p>Exactly once semantics(TODO)</p>
</li>
<li>
<p>Split Brain(TODO)
<strong>Open Questions:</strong></p>
</li>
<li>
<p>Kafka stores the current offset per consumer group per topic per partition? <strong>What? Initially we said Kafka is DUMB and that Consumer tracks the offset? [Research]</strong></p>
</li>
<li>
<p>In At-most-once consumer delivery semantics, Why can’t the consumer read from the previous offset? Why are messages said to be lost?[Research]</p>
</li>
<li>
<p>Exactly once semantics? How would transactions happen across 2 systems(consumer processing + Kafka Offset Commit). How are they suggesting the transaction would be rolled back?</p>
</li>
<li>
<p>Zero Copy Optimization</p>
</li>
<li>
<p>Page Cache optimization Kafka</p>
</li>
<li>
<p>How does replication internal work b/w leader follower?</p>
</li>
<li>
<p>Tombstoning in Kafka.</p>
</li>
</ul>
<h2 id="1-zero-copy-optimizations--page-cache-in-kafka-and-other-systems"><strong>1️⃣ Zero Copy Optimizations &amp; Page Cache in Kafka and Other Systems</strong></h2>
<h3 id="-what-is-zero-copy"><strong>📌 What is Zero Copy?</strong></h3>
<p>Zero Copy is a <strong>kernel-level optimization</strong> that allows data to be transferred between disk and network <strong>without passing through user-space memory</strong>, <strong>reducing CPU overhead and increasing throughput</strong>.</p>
<p>🚀 <strong>Why is Zero Copy important?</strong> ✔ <strong>Reduces CPU usage</strong> (since data isn’t copied multiple times).</p>
<p>✔ <strong>Minimizes context switches</strong> (between user and kernel space).</p>
<p>✔ <strong>Improves I/O throughput</strong> (as memory copying is avoided).</p>
<h3 id="-how-kafka-uses-zero-copy-sendfile-optimization"><strong>📌 How Kafka Uses Zero Copy (Sendfile Optimization)</strong></h3>
<p>Kafka uses <strong>Zero Copy via the sendfile system call</strong> in Linux.</p>
<p>🔹 <strong>Without Zero Copy (Traditional Path)</strong></p>
<ol>
<li>
<p>Kafka reads a log file from disk → <strong>(Disk → Kernel Space).</strong></p>
</li>
<li>
<p>The kernel copies data to Kafka’s user-space buffer → <strong>(Kernel Space → User Space).</strong></p>
</li>
<li>
<p>Kafka writes the buffer to a network socket → <strong>(User Space → Kernel Space → Network).</strong></p>
</li>
<li>
<p>The kernel sends data over the network.
🔹 <strong>With Zero Copy (Optimized Path)</strong></p>
</li>
<li>
<p>Kafka calls sendfile() → <strong>Kernel directly transfers a log file to the network socket</strong>.</p>
</li>
<li>
<p><strong>No user-space buffer required</strong> → Data goes <strong>directly from disk to network</strong>.
✔ <strong>Avoids unnecessary copies in user-space.</strong>✔ <strong>Greatly improves throughput</strong> (Kafka can achieve <strong>millions of messages per second</strong>).</p>
</li>
</ol>
<h3 id="-zero-copy-optimizations-in-other-systems"><strong>📌 Zero Copy Optimizations in Other Systems</strong></h3>
<p><em>[Table content - requires manual formatting]</em></p>
<h3 id="-what-is-page-cache-and-how-kafka-optimizes-it"><strong>📌 What is Page Cache and How Kafka Optimizes It?</strong></h3>
<p>Kafka <strong>doesn’t need a traditional database cache</strong>. Instead, it relies on the <strong>OS page cache</strong> for fast reads.</p>
<p>✔ <strong>Page Cache:</strong> The Linux kernel automatically caches recently read disk pages in memory.</p>
<p>✔ <strong>Kafka uses the Page Cache to serve reads directly from memory without hitting disk.</strong></p>
<p>🔹 <strong>How Page Cache Works in Kafka:</strong></p>
<ol>
<li>When a consumer reads a message, Kafka <strong>first checks the OS page cache</strong>.</li>
<li>If the data is cached, it is served <strong>directly from memory</strong> (zero disk I/O).</li>
<li>If the data isn’t in cache, Kafka reads it from disk, and the OS automatically caches it.
🚀 <strong>Optimizations in Kafka:</strong> ✔ <strong>Uses sendfile() to directly transfer from Page Cache to network.</strong>✔ <strong>Leverages sequential disk access (append-only logs) for high read efficiency.</strong>✔ <strong>Minimizes JVM heap memory usage by relying on OS caching.</strong></li>
</ol>
<h2 id="2-how-replication-works-between-leaders-and-followers-in-kafka"><strong>2️⃣ How Replication Works Between Leaders and Followers in Kafka</strong></h2>
<p>Kafka ensures <strong>fault tolerance</strong> and <strong>high availability</strong> using <strong>replication</strong>.</p>
<h3 id="-basics-of-kafka-replication"><strong>📌 Basics of Kafka Replication</strong></h3>
<p>✔ Each <strong>Kafka topic is partitioned</strong>, and each partition has:</p>
<ul>
<li><strong>One Leader</strong> (handles all reads &amp; writes).</li>
<li><strong>One or more Followers</strong> (replicas of the leader’s data).</li>
</ul>
<h3 id="-steps-in-kafka-replication"><strong>📌 Steps in Kafka Replication</strong></h3>
<p>1️⃣ <strong>Producer writes data to the Leader Partition</strong>.</p>
<p>2️⃣ <strong>Leader appends data to its local log segment</strong>.</p>
<p>3️⃣ <strong>Followers fetch new data from the leader</strong>.</p>
<p>4️⃣ <strong>Followers append data to their own log segment</strong>.</p>
<p>5️⃣ **Followers send an acknowledgment (ACK) once they persist the data.**6️⃣ <strong>If a majority of followers acknowledge, Kafka considers the message committed</strong>.</p>
<h3 id="-leader-and-follower-sync-mechanism"><strong>📌 Leader and Follower Sync Mechanism</strong></h3>
<p>✔ <strong>Kafka uses a pull-based replication model</strong> → Followers <strong>poll</strong> the leader to fetch new data.</p>
<p>✔ <strong>Offset tracking:</strong> Followers maintain an <strong>offset</strong> to track the latest committed message.</p>
<p>✔ <strong>ISR (In-Sync Replicas):</strong> Only replicas in sync with the leader are part of the ISR.</p>
<h3 id="-how-a-new-leader-is-elected"><strong>📌 How a New Leader is Elected?</strong></h3>
<p>✔ If the Leader fails, <strong>one of the ISR replicas is promoted</strong>.</p>
<p>✔ The new Leader <strong>starts serving read and write requests</strong>.</p>
<p>✔ If no ISR exists, the partition becomes <strong>temporarily unavailable</strong> until a new Leader is available.</p>
<h3 id="-replication-strategies"><strong>📌 Replication Strategies</strong></h3>
<p><em>[Table content - requires manual formatting]</em></p>
<p>🚀 <strong>Tuning Replication Settings for Performance</strong> ✔ <strong>min.insync.replicas = 2</strong> → Ensures durability (at least two replicas must ACK).</p>
<p>✔ <strong>unclean.leader.election = false</strong> → Prevents unsafe leader elections (data loss risk).</p>
<p>✔ <strong>replica.lag.time.max.ms = 10,000</strong> → Defines when a slow follower is removed from ISR.</p>
<h2 id="3-tombstoning-in-kafka"><strong>3️⃣ Tombstoning in Kafka</strong></h2>
<p>Kafka <strong>Tombstoning</strong> is used for <strong>deleting records in log-compacted topics</strong>.</p>
<h3 id="-why-is-tombstoning-needed"><strong>📌 Why is Tombstoning Needed?</strong></h3>
<p>✔ Kafka <strong>never deletes data immediately</strong>.</p>
<p>✔ Instead, Kafka <strong>marks the record as deleted (tombstone message)</strong>.</p>
<p>✔ The actual data is <strong>removed later during log compaction</strong>.</p>
<h3 id="-how-tombstoning-works"><strong>📌 How Tombstoning Works</strong></h3>
<ol>
<li>Producer sends a <strong>null value for a key</strong> (marks it as deleted).</li>
<li>Kafka appends this <strong>tombstone message</strong> to the log.</li>
<li>The <strong>consumer sees the tombstone event</strong> and removes the record from its own storage.</li>
<li>Kafka’s <strong>log compaction</strong> eventually purges the tombstone message and the original record.</li>
</ol>
<h3 id="-example-of-tombstone-message"><strong>📌 Example of Tombstone Message</strong></h3>
<p>{</p>
<p>&ldquo;key&rdquo;: &ldquo;user_123&rdquo;,</p>
<p>&ldquo;value&rdquo;: null,</p>
<p>&ldquo;timestamp&rdquo;: 1700000000</p>
<p>}</p>
<hr>
<p>✔ This <strong>soft deletes</strong> &ldquo;user_123&rdquo;.</p>
<p>✔ <strong>Log compaction</strong> later removes both the <strong>original record and the tombstone</strong>.</p>
<h3 id="-how-log-compaction-works"><strong>📌 How Log Compaction Works</strong></h3>
<ul>
<li><strong>Log compaction keeps only the latest value for each key.</strong></li>
<li><strong>Tombstones stay in the log until Kafka compacts the segment.</strong></li>
<li><strong>Kafka guarantees at least one copy of the latest record is retained</strong> (even after compaction).
🚀 <strong>Tuning Log Compaction</strong> ✔ <strong>log.cleanup.policy = compact</strong> → Enables log compaction.</li>
</ul>
<p>✔ <strong>delete.retention.ms = 86400000</strong> → Keeps tombstones for 24 hours before purging.</p>
<p>✔ <strong>log.segment.bytes</strong> → Defines when segments are compacted.</p>
<h2 id="-summary--key-takeaways"><strong>🔹 Summary &amp; Key Takeaways</strong></h2>
<h3 id="zero-copy--page-cache"><strong>Zero Copy &amp; Page Cache</strong></h3>
<p>✔ <strong>Kafka uses sendfile() for Zero Copy, avoiding unnecessary memory copies.</strong>✔ <strong>Page Cache stores recent messages, reducing disk I/O.</strong></p>
<h3 id="replication-between-leaders-and-followers"><strong>Replication Between Leaders and Followers</strong></h3>
<p>✔ <strong>Kafka uses asynchronous, pull-based replication for performance.</strong>✔ <strong>ISR (In-Sync Replicas) ensures durability.</strong>✔ <strong>New leaders are elected from ISR in case of failure.</strong></p>
<h3 id="tombstoning--log-compaction"><strong>Tombstoning &amp; Log Compaction</strong></h3>
<p>✔ <strong>Kafka uses tombstones (null values) for soft deletes.</strong>✔ <strong>Log compaction removes older records but keeps the latest one per key.</strong></p>
<hr>
<p><strong>Paper Link:</strong> <a href="https://notes.stephenholiday.com/Kafka.pdf">https://notes.stephenholiday.com/Kafka.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Cassandra</title>
      <link>https://www.sethihemant.com/notes/cassandra-2009/</link>
      <pubDate>Tue, 03 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/cassandra-2009/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf&#34;&gt;Cassandra&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;cassandra--distributed-wide-column-nosql-database&#34;&gt;Cassandra / Distributed Wide Column NoSQL Database&lt;/h2&gt;
&lt;h3 id=&#34;goal&#34;&gt;Goal&lt;/h3&gt;
&lt;p&gt;Design a &lt;strong&gt;distributed&lt;/strong&gt; and &lt;strong&gt;scalable&lt;/strong&gt; system that can store a &lt;strong&gt;huge amount of semi-structured data&lt;/strong&gt;, which is &lt;strong&gt;indexed by a row key&lt;/strong&gt; where each row can have an &lt;strong&gt;unbounded number of columns.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Open source Apache Project developed at FB in 2007 for Inbox Search feature.&lt;/li&gt;
&lt;li&gt;Designed to provide &lt;strong&gt;Scalability&lt;/strong&gt;, &lt;strong&gt;Availability&lt;/strong&gt;, &lt;strong&gt;Reliability&lt;/strong&gt; to store &lt;strong&gt;large amounts&lt;/strong&gt; of data.&lt;/li&gt;
&lt;li&gt;Combines &lt;strong&gt;distributed&lt;/strong&gt; nature of Amazon’s Dynamo(K-V store) and &lt;strong&gt;DataModel&lt;/strong&gt; for Google’s BigTable which is a Column based store.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decentralized&lt;/strong&gt; architecture with &lt;strong&gt;no Single Point of Failure&lt;/strong&gt;(SPOF), Performance can &lt;strong&gt;scale linearly&lt;/strong&gt; with addition of nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-is-cassandra&#34;&gt;What is Cassandra?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cassandra is typically classified as an &lt;strong&gt;AP&lt;/strong&gt; (i.e., &lt;strong&gt;Available&lt;/strong&gt; and &lt;strong&gt;Partition Tolerant&lt;/strong&gt;) system which means that &lt;strong&gt;availability&lt;/strong&gt; and &lt;strong&gt;partition tolerance&lt;/strong&gt; are generally considered more important than the &lt;strong&gt;consistency&lt;/strong&gt;. &lt;strong&gt;Eventually Consistent&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Similar to Dynamo, Cassandra can be tuned with &lt;strong&gt;replication-factor&lt;/strong&gt; and &lt;strong&gt;consistency levels&lt;/strong&gt; to meet strong consistency requirements, but this comes with a performance cost.&lt;/li&gt;
&lt;li&gt;Uses &lt;strong&gt;peer-to-peer&lt;/strong&gt; architecture where each node communicates to all other nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;cassandra-use-cases&#34;&gt;Cassandra Use Cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Any application where &lt;strong&gt;eventual consistency&lt;/strong&gt; is not a concern can utilize Cassandra.&lt;/li&gt;
&lt;li&gt;Cassandra is optimized for &lt;strong&gt;high throughput&lt;/strong&gt; &lt;strong&gt;writes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Can be used for collecting big data for performing real-time analysis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storing key-value data with high availability&lt;/strong&gt;(Reddit/Dig) because of linear scaling w/o downtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time Series Data Model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write Heavy Applications&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NoSQL&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;high-level-architecture&#34;&gt;High Level Architecture&lt;/h3&gt;
&lt;h3 id=&#34;agenda&#34;&gt;Agenda&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cassandra Common Terms&lt;/li&gt;
&lt;li&gt;High Level Architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;cassandra-common-terms&#34;&gt;Cassandra Common Terms&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Column&lt;/strong&gt;: A Key-Value pair. Most basic unit of data structure in Cassandra.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf">Cassandra</a></p>
<hr>
<h2 id="cassandra--distributed-wide-column-nosql-database">Cassandra / Distributed Wide Column NoSQL Database</h2>
<h3 id="goal">Goal</h3>
<p>Design a <strong>distributed</strong> and <strong>scalable</strong> system that can store a <strong>huge amount of semi-structured data</strong>, which is <strong>indexed by a row key</strong> where each row can have an <strong>unbounded number of columns.</strong></p>
<h3 id="background">Background</h3>
<ul>
<li>Open source Apache Project developed at FB in 2007 for Inbox Search feature.</li>
<li>Designed to provide <strong>Scalability</strong>, <strong>Availability</strong>, <strong>Reliability</strong> to store <strong>large amounts</strong> of data.</li>
<li>Combines <strong>distributed</strong> nature of Amazon’s Dynamo(K-V store) and <strong>DataModel</strong> for Google’s BigTable which is a Column based store.</li>
<li><strong>Decentralized</strong> architecture with <strong>no Single Point of Failure</strong>(SPOF), Performance can <strong>scale linearly</strong> with addition of nodes.</li>
</ul>
<h3 id="what-is-cassandra">What is Cassandra?</h3>
<ul>
<li>Cassandra is typically classified as an <strong>AP</strong> (i.e., <strong>Available</strong> and <strong>Partition Tolerant</strong>) system which means that <strong>availability</strong> and <strong>partition tolerance</strong> are generally considered more important than the <strong>consistency</strong>. <strong>Eventually Consistent</strong></li>
<li>Similar to Dynamo, Cassandra can be tuned with <strong>replication-factor</strong> and <strong>consistency levels</strong> to meet strong consistency requirements, but this comes with a performance cost.</li>
<li>Uses <strong>peer-to-peer</strong> architecture where each node communicates to all other nodes.</li>
</ul>
<h3 id="cassandra-use-cases">Cassandra Use Cases</h3>
<ul>
<li>Any application where <strong>eventual consistency</strong> is not a concern can utilize Cassandra.</li>
<li>Cassandra is optimized for <strong>high throughput</strong> <strong>writes.</strong></li>
<li>Can be used for collecting big data for performing real-time analysis.</li>
<li><strong>Storing key-value data with high availability</strong>(Reddit/Dig) because of linear scaling w/o downtime.</li>
<li><strong>Time Series Data Model</strong></li>
<li><strong>Write Heavy Applications</strong></li>
<li><strong>NoSQL</strong></li>
</ul>
<h3 id="high-level-architecture">High Level Architecture</h3>
<h3 id="agenda">Agenda</h3>
<ul>
<li>Cassandra Common Terms</li>
<li>High Level Architecture</li>
</ul>
<h3 id="cassandra-common-terms">Cassandra Common Terms</h3>
<ul>
<li>
<p><strong>Column</strong>: A Key-Value pair. Most basic unit of data structure in Cassandra.</p>
</li>
<li>
<p><strong>Row:</strong> Container for columns referenced by the primary key.
<img loading="lazy" src="/images/notes/cassandra-2009/image-1.png"></p>
</li>
<li>
<p><strong>Table:</strong> Container of rows.</p>
</li>
<li>
<p><strong>KeySpace:</strong> Container for tables that span over one or more cassandra nodes.</p>
</li>
<li>
<p><strong>Cluster</strong> : Container of KeySpaces.</p>
</li>
<li>
<p><strong>Node:</strong> Computer system running cassandra instance. Physical Host, or VM, or even Docker container.</p>
</li>
</ul>
<h3 id="data-partitioning">Data Partitioning</h3>
<ul>
<li>Cassandra uses Consistent Hashing similar to Dynamo.</li>
</ul>
<h3 id="cassandra-keys">Cassandra Keys</h3>
<ul>
<li>Mechanisms used by Cassandra to uniquely identify the rows.</li>
<li>Primary Key uniquely identifies each row of a table.</li>
<li><strong>Primary Key = Partition Key + Clustering Key</strong>
<img loading="lazy" src="/images/notes/cassandra-2009/image-2.png"></li>
</ul>
<h3 id="clustering-keys">Clustering Keys</h3>
<ul>
<li><strong>Clustering keys</strong> define how the data is stored within a node. Can have multiple clustering keys.</li>
</ul>
<h3 id="partitioner">Partitioner</h3>
<ul>
<li>
<p>Component which is responsible for determining how the data is distributed on the consistent hashing ring.</p>
</li>
<li>
<p>When cassandra inserts some data, partitioning applies a hashing algorithm to the partition Key to determine which range(and the corresponding node) the data lies.
<img loading="lazy" src="/images/notes/cassandra-2009/image-3.png"></p>
</li>
<li>
<p>Cassandra uses Murmer3 hashing function(Default).</p>
</li>
<li>
<p>In cassandra’s default configuration, a token is a 64-bit integer. Gives possible token ranges from [-2^63 , 2^63 + 1]. How does it differ from Dynamo?</p>
</li>
<li>
<p>All nodes learn about token assignment of other nodes through <strong>Gossip</strong>.
<img loading="lazy" src="/images/notes/cassandra-2009/image-4.png"></p>
</li>
</ul>
<h3 id="replication">Replication</h3>
<h3 id="agenda-1">Agenda</h3>
<ul>
<li>Replication Factor</li>
<li>Replication Strategy.</li>
<li>Each node in Cassandra serves as a replica for a different range of data. Replication factor decides how many replicas the system would have, which is the number of nodes that will receive the copy of the same data.</li>
<li>The node that owns the range in which hash of the partition key falls is the first replica. All additional replicas are placed on the consecutive nodes in a clockwise manner.</li>
<li><strong>Simple Replication Strategy</strong></li>
<li><strong>Network Topology Strategy</strong>
<img loading="lazy" src="/images/notes/cassandra-2009/image-5.png"></li>
</ul>
<h3 id="cassandra-consistency-levels">Cassandra Consistency Levels</h3>
<h3 id="agenda-2">Agenda</h3>
<ul>
<li>Cassandra Consistency Levels</li>
<li>Write Consistency Levels</li>
<li>Read consistency level</li>
<li>Snitch</li>
</ul>
<h3 id="cassandra-consistency-levels-1">Cassandra Consistency Levels</h3>
<ul>
<li><strong>Minimum</strong> number of Cassandra nodes that must fulfill a read or write operation before the operation can be considered successful.</li>
<li>Has Tunable Consistency levels for reads and writes.</li>
<li>Tradeoff b/w Consistency and performance.</li>
</ul>
<h3 id="write-consistency-levels">Write Consistency Levels</h3>
<ul>
<li><strong>One</strong> or <strong>Two</strong> or <strong>Three :</strong> Success acknowledgement from specified number of nodes**.**</li>
<li><strong>Quorum:</strong> Data must be written to at least the Majority Quorum of nodes.</li>
<li><strong>All:</strong> Data is written to all nodes.</li>
<li><strong>Local Quorum</strong>: Data is written to the Quorum of nodes in the same data center as the coordinator. Don&rsquo;t wait for responses from other Data Centers.</li>
<li><strong>Each Quorum:</strong> Data written to the Quorum of nodes in each data center.</li>
<li><strong>Any:</strong> Data written to at least one node**.**</li>
<li><strong>Performing Write Operation?</strong>
<strong>Hinted Handoff</strong>?</li>
</ul>
<p><img loading="lazy" src="/images/notes/cassandra-2009/image-6.png"></p>
<ul>
<li>When the node where the data was supposed to be written for Quorum was down comes online again, how should we write data to it? Cassandra accomplishes this through a <strong>Hinted handoff</strong>.</li>
<li>[FAILURE MODE] When a node is down or does not respond to a write request, the <strong>coordinator node writes a hint in a text file on the local disk</strong>. This <strong>hint contains the data itself along with information about which node the data belongs to</strong>. When the coordinator node discovers(using <strong>Gossip Protocol</strong>) that a node for which it holds hints has recovered, it forwards the write requests for each hint to the target. Furthermore, each node every ten minutes checks to see if the failing node, for which it is holding any hints, has recovered.</li>
<li>[FAILURE MODE] If a node is offline for some time, the hints can build up considerably on other nodes. Now, when the failed node comes back online, other nodes tend to flood that node with write requests. This can cause issues on the node, as it is already trying to come back after a failure.</li>
<li>Cassandra by <strong>default stores hints for 3 hours.</strong> After 3 hours, older hints are removed , if the failed node comes back up(hinted handoff won’t happen), and the node would contain stale data. Stale data can be fixed by <strong>Read-Repair</strong>(<strong>Read Path)</strong></li>
<li>When the cluster cannot meet the client’s consistency level, cassandra fails the write request, and doesn’t store a hint.</li>
</ul>
<h3 id="read-consistency-levels">Read Consistency Levels</h3>
<ul>
<li>Specifies how many replica nodes must respond to a read request before returning the data.</li>
<li>Same levels as Write operations except(<strong>Each Quorum</strong>) because Expensive.</li>
<li>R + W &gt; Replication Factor can give Strong consistency levels in Cassandra?[Research]</li>
<li>Cassandra uses <strong>Snitch</strong>, an application that determines the proximity of nodes within the ring and also tells which nodes are faster and cassandra uses this to route read/write requests.
<img loading="lazy" src="/images/notes/cassandra-2009/image-7.png"></li>
</ul>
<h3 id="how-does-cassandra-perform-a-read-operation">How does Cassandra perform a Read Operation?</h3>
<ul>
<li>
<p>Coordinator sends the read request to the fastest node(using Snitch).</p>
</li>
<li>
<p>E.g. Quorum R = 2, sends the request to the fastest node, and <strong>digest of the data</strong> from the second fastest node.</p>
</li>
<li>
<p>If the digest does not match, it means some replicas do not have the latest version of the data. In this case, the coordinator reads the data from all the replicas to determine the latest data.</p>
</li>
<li>
<p>The coordinator then returns the latest data to the client and initiates a read repair request.
<img loading="lazy" src="/images/notes/cassandra-2009/image-8.png"></p>
</li>
<li>
<p>The latest write-timestamp is used as a marker for the correct version of data[Research?] in Cassandra? Conflict resolution? Last write wins or Vector Clocks? Data Loss?</p>
</li>
<li>
<p>The read repair operation is performed only in a portion of the total reads to avoid performance degradation.</p>
</li>
<li>
<p>By default, Cassandra tries to <strong>read-repair</strong> 10% of all requests with DC local read repair.</p>
</li>
</ul>
<h3 id="snitch">Snitch</h3>
<ul>
<li>Snitch keeps track of <strong>network topology</strong> of Cassandra nodes. It determines which data center and racks nodes belong to and uses this info to route requests efficiently.</li>
<li><strong>Functions</strong> of Snitch in Cassandra?</li>
</ul>
<h3 id="gossiper">Gossiper</h3>
<ul>
<li><strong>How does Cassandra use the Gossip protocol?</strong></li>
<li><strong>Node failure detection?</strong></li>
</ul>
<h3 id="how-does-cassandra-use-the-gossip-protocol">How does Cassandra use the Gossip Protocol?</h3>
<ul>
<li>
<p>Allows each node to keep track of state information about other nodes in the cluster.</p>
</li>
<li>
<p>Gossip protocol is a peer-to-peer communication mechanism in which nodes periodically exchange state information about themselves and other nodes they know about.</p>
</li>
<li>
<p>Each node initiates a gossip round every second to exchange state information about themselves (and other nodes) with <strong>one to three other random nodes</strong>.</p>
</li>
<li>
<p>Each <strong>Gossip message has a version</strong> associated with it, so that during gossip exchange, older information is overwritten with the most current state for a particular node.</p>
</li>
<li>
<p><strong>Generation Number:</strong> Each node tracks a generation number which increments every time a node restarts.
<img loading="lazy" src="/images/notes/cassandra-2009/image-9.png"></p>
</li>
<li>
<p><strong>Seed Nodes</strong>?</p>
</li>
</ul>
<h3 id="node-failure-detection">Node Failure Detection?</h3>
<ul>
<li>Accurately detecting failures is a hard problem to solve. We cannot say with 100% accuracy if a node is actually down or is just very slow to respond due to heavy load, network congestion, GC/process pauses etc.</li>
<li><strong>Heart Beating(Boolean Failure detector, Yes or No)</strong> uses a <strong>fixed timeout</strong>, and if there is no heartbeat from a server, the system, after the timeout, assumes that the server has crashed. Here the value of the timeout is critical.</li>
<li>Cassandra uses an Adaptive failure detection mechanism, <strong>Phi Accrual Failure Detector</strong></li>
<li>A generic Accrual Failure Detector, instead of telling if the server is alive or not, outputs the suspicion level about a server; a higher suspicion level means there are higher chances that the server is down.</li>
<li><strong>Phi Accrual Failure Detector</strong>, if a node does not respond, its suspicion level is increased and could be declared dead later.</li>
</ul>
<h3 id="anatomy-of-cassandras-write-operation">Anatomy of Cassandra’s write operation</h3>
<h3 id="agenda-3">Agenda</h3>
<ul>
<li>CommitLog</li>
<li>MemTable</li>
<li>SSTable</li>
<li>Cassandra stores data both in-memory and on-disk to provide both high performance and durability. Every write includes a timestamp. The <strong>Write-Path</strong> involves a lot of components.</li>
<li><strong>Cassandra’s Write path Summary</strong>:</li>
</ul>
<h3 id="commit-log">Commit Log</h3>
<ul>
<li>When a node receives a write request, it immediately writes the data to a commit log.</li>
<li>The commit log is a write-ahead log and is stored on disk.</li>
<li>Used as a Crash-Recovery mechanism for Cassandra’s Durability goals.</li>
<li>A write on the node isn’t considered successful until it’s written to the commit log.
<img loading="lazy" src="/images/notes/cassandra-2009/image-10.png"></li>
</ul>
<h3 id="memtable">MemTable</h3>
<ul>
<li>After a <strong>Write</strong> is persisted to CommitLog, it is then written to the memory-resident data structure which is MemTable.</li>
<li>Each Cassandra node has an In-Memory MemTable for each Table. It resembles that data in that Table it represents.</li>
<li><strong>Accrues writes and provides reads for data not yet flushed to disk.</strong></li>
<li>Commit Log stores all the writes in sequential Order(append only log) whereas <strong>MemTable stores data in sorted order of PartitionKey, and Clustering Columns</strong>.</li>
<li>After data is written to Commit-Log and MemTable, node sends <strong>success acknowledgement</strong> to the Coordinator.
<img loading="lazy" src="/images/notes/cassandra-2009/image-11.png"></li>
</ul>
<h3 id="sstablesorted-string-table">SSTable(Sorted String Table)</h3>
<ul>
<li>When the number of objects stored in the MemTable reaches a Threshold, the contents of the MemTable are flushed to disk in a file called <strong>SSTable</strong>.</li>
<li>New MemTable is created to serve in-memory requests for subsequent data.</li>
<li>Flushing of MemTables is a Non-Blocking operation.</li>
<li>Multiple MemTables may exist for a single Table, one current, and others waiting to be flushed.</li>
<li>SSTable contains data for a specific Table.</li>
<li>When the MemTable is flushed to SStables, corresponding entries in the Commit Log are removed.</li>
<li>The Term <strong>SSTable</strong> first appeared in Google’s Bigtable which is also a storage system. Cassandra borrowed this term even though it does not store data as strings on the disk.</li>
<li>Once a MemTable is flushed to disk as an SSTable, it is immutable and cannot be changed by the application.</li>
<li>If we are not allowed to update SSTables, <strong>how do we delete or update a column?</strong></li>
<li>The current data state of a Cassandra table consists of its MemTables in memory and SSTables on the disk.</li>
<li>On reads, Cassandra will first read MemTables, and then subsequently SSTables(if MemTables Does Not contain the key) to find data values, as the MemTable may still contain values that have not yet been flushed to the disk.</li>
<li>MemTable works as a WriteBack cache that Cassandra looks up by Key.</li>
<li><strong>Generation Number</strong>: an Index number that is incremented every time a new SSTable is created for a Table. Uniquely identifies an SSTable.
<img loading="lazy" src="/images/notes/cassandra-2009/image-12.png"></li>
</ul>
<h3 id="anatomy-of-cassandras-read-operation">Anatomy of Cassandra’s read operation</h3>
<h3 id="agenda-4">Agenda</h3>
<ul>
<li>Caching</li>
<li>Reading from MemTable</li>
<li>Reading from SSTable</li>
</ul>
<h3 id="caching">Caching</h3>
<ul>
<li>To boost read performance, Cassandra provides 3 optional forms of caching:</li>
</ul>
<h3 id="reading-from-memtable">Reading from MemTable</h3>
<ul>
<li>Data is sorted by the partition key and the clustering columns.</li>
<li>When a read request comes in, the node performs a binary search on the partition key to find the required partition and then returns the row.
<img loading="lazy" src="/images/notes/cassandra-2009/image-13.png"></li>
</ul>
<p><img loading="lazy" src="/images/notes/cassandra-2009/image-14.png"></p>
<h3 id="reading-from-sstables">Reading from SSTables</h3>
<h3 id="bloom-filters">Bloom Filters</h3>
<ul>
<li>Each SStable has a <strong>Bloom filter</strong> associated with it, which tells(probabilistic) if a particular key is present in it or not for <strong>boosting read performance</strong>.</li>
<li>Bloom filters are very fast, non-deterministic algorithms for testing whether an element is a member of a set.</li>
<li>Bloom filters work by mapping the values in a data set into a <strong>bit array</strong> and condensing a larger data set into a <strong>digest</strong> <strong>string</strong> using a <strong>hash function.</strong></li>
<li>The filters are stored in memory and are used to improve performance by reducing the need for disk access on key lookups since disk access is much slower.</li>
<li>Because <strong>false negatives</strong> are not possible:</li>
</ul>
<h3 id="how-are-sstables-stored-on-disk">How are SSTables Stored on Disk?</h3>
<ul>
<li>
<p>Each SSTable Consists of Two Files:
<img loading="lazy" src="/images/notes/cassandra-2009/image-15.png"></p>
</li>
<li>
<p><strong>Partition Index Summary File</strong>
<img loading="lazy" src="/images/notes/cassandra-2009/image-16.png"></p>
</li>
<li>
<p>If we want to read data for key=12, here are the steps we need to follow (also shown in the figure below):</p>
</li>
</ul>
<h3 id="reading-sstable-through-key-cache">Reading SSTable through Key Cache</h3>
<ul>
<li>
<p>As the Key Cache stores a map of recently read partition keys to their SSTable offsets, it is the fastest way to find the required row in the SSTable.
<img loading="lazy" src="/images/notes/cassandra-2009/image-17.png"></p>
</li>
<li>
<p><strong>Summary of Read Operation:</strong>
<img loading="lazy" src="/images/notes/cassandra-2009/image-18.png"></p>
</li>
</ul>
<h3 id="compaction">Compaction</h3>
<h3 id="agenda-5">Agenda</h3>
<ul>
<li>How does compaction work in Cassandra?</li>
<li>Compaction Strategies?</li>
<li>Sequential Writes?</li>
</ul>
<h3 id="how-does-compaction-work-in-cassandra">How does compaction work in Cassandra?</h3>
<ul>
<li>SSTables are immutable(Append Only Log), which helps Cassandra achieve such high write speeds.</li>
<li>Flushing of MemTable to SStable is a continuous process. This means we can have a large number of SStables lying on the disk. While reading, it is tedious to scan all these SStables. So, to improve the read performance, we need <strong>compaction.</strong></li>
<li><strong>Compaction</strong> refers to the operation of merging multiple related SSTables into a single new one.</li>
<li>During compaction, the data in SSTables is merged: the keys are merged, columns are combined, obsolete values are discarded, and a new index is created.</li>
<li>On compaction, the merged data is sorted, a new index is created over the sorted data, and this freshly merged, sorted, and indexed data is written to a single new SSTable.</li>
<li>Compaction will reduce the number of SSTables to consult and therefore improve read performance.</li>
<li>Compaction will also reclaim space taken by obsolete(Tombstoned or overwritten) data in SSTables.
<img loading="lazy" src="/images/notes/cassandra-2009/image-19.png"></li>
</ul>
<h3 id="compaction-strategies">Compaction Strategies</h3>
<ul>
<li><strong>Size Tiered(Default, Write Optimized)</strong></li>
<li><strong>Levelled(Read Optimized)</strong></li>
<li><strong>Time Window(Time Series Optimized)</strong></li>
</ul>
<h3 id="sequential-writes">Sequential Writes</h3>
<ul>
<li>Sequential writes are the primary reason that writes perform so well in Cassandra.</li>
<li>No <strong>reads</strong> or <strong>seeks</strong> of any kind are required for <strong>writing</strong> a value to Cassandra because writes are <strong>append-only</strong> operations.</li>
<li>Write speed of the disk becomes a performance bottleneck.</li>
<li>Compaction is intended to amortize the reorganization of data, but it uses sequential I/O to do so, which makes it efficient.</li>
<li>If Cassandra naively inserted values where they ultimately belonged, writing clients would pay for seeks upfront.</li>
</ul>
<h3 id="tombstones">Tombstones</h3>
<ul>
<li>An interesting case with Cassandra can be when we delete some data for a node that is down or unreachable, that node could miss a delete. When that node comes back online later and a repair occurs, the node could “resurrect” the data that had been previously deleted by re-sharing it with other nodes.</li>
<li>To prevent deleted data from being reintroduced, Cassandra uses a concept called a <strong>tombstone</strong> Which is similar to a “soft delete” from the Relational databases world.</li>
<li>When we delete data, Cassandra does not delete it right away, instead associates a <strong>tombstone</strong> with it, with a time to <strong>expiry</strong>.</li>
<li>The purpose of this delay is to give a node that is unavailable time to recover.</li>
<li><strong>Tombstones</strong> are removed as part of <strong>compaction</strong>. During <strong>compaction</strong>, any row with an <strong>expired tombstone</strong> will not be propagated further.</li>
</ul>
<h3 id="common-problems-associated-with-tombstones">Common Problems associated with Tombstones?</h3>
<ul>
<li>
<p><strong>Tombstones make Cassandra’s writes efficient</strong> because the data is not removed right away when deleted. Instead, it is removed later during compaction.</p>
</li>
<li>
<p><strong>Problems?</strong></p>
</li>
<li>
<p><strong>Slower Reads</strong>
<strong>Indexes?</strong></p>
</li>
<li>
<p>Cassandra uses clustering keys to create indexes of data within a partition.</p>
</li>
<li>
<p>These are only local indexes, not global indexes.</p>
</li>
<li>
<p>If you have many clustering keys in order to achieve multiple different sort orders, Cassandra will de-normalize the data such that it keeps two copies of it.
<strong>Cassandra Pitfalls?</strong></p>
</li>
<li>
<p>Lack of Strong Consistency even with Quorums(say Sloppy Quorum or hinted handoffs) which can create race conditions amongst concurrent writes.</p>
</li>
<li>
<p>Lack of ability to support data relationships(outside of sorting data within a partition)</p>
</li>
<li>
<p>Lack of Global Secondary Indexes if needed for Read Heavy applications where read cache may not work.</p>
</li>
</ul>
<h3 id="summary">Summary</h3>
<ul>
<li>Cassandra is <strong>Distributed</strong>, <strong>Decentralized</strong>(Leaderless), <strong>Scalable</strong>, <strong>Highly Available, Eventually Consistent NoSQL</strong> datastore.</li>
<li>Was designed with <strong>Fault-Tolerance</strong> in mind(hardware/software failures can and do happen).</li>
<li><strong>Peer-to-Peer(Gossip) distributed System</strong>, with no Leader/Follower nodes. All nodes are equal except some are tagged seed nodes, for bootstrapping gossip to the nodes added to the cluster.</li>
<li>Data is automatically <strong>Partitioned</strong> across nodes using <strong>Consistent Hashing</strong> as well as Replicated for <strong>Fault Tolerance</strong> and <strong>redundancy</strong>.</li>
<li>Combines Distributed Nature of Amazon’s Dynamo(Consistent Hashing, Replication, Partitioning), with DataModel of Google’s BigTable, i.e. SSTable/MemTable.</li>
<li>Offers <strong>Tunable Consistency</strong>(Default AP) but can be made strongly consistent(<strong>CP</strong>) but with performance implications.</li>
<li>Uses <strong>Gossip protocol</strong> for Inter-Node communication.</li>
<li>Supports <strong>Geographical Distribution</strong> of data across multiple clouds and data centers?</li>
</ul>
<h3 id="system-design-patterns-used">System Design Patterns Used?</h3>
<ul>
<li><strong>Consistent Hashing</strong> : Data Partitioning</li>
<li><strong>Quorum</strong> : Data Consistency</li>
<li><strong>Write Ahead Log</strong> : Durability</li>
<li><strong>Segmented Log</strong>: Splits its commit log into multiple smaller files instead of single large file for easier operation.</li>
<li><strong>Gossip Protocol</strong>: Membership or Cluster State information, Failure Detection?</li>
<li><strong>Phi Accrual Failure Detector</strong>: Adaptive Failure Detection using suspicion levels.</li>
<li><strong>Bloom Filters</strong>: Check for partition Key presence in SSTable(Read Optimized).</li>
<li><strong>Hinted Handoff:</strong> Sloppy Quorum?? and High Availability.</li>
<li><strong>Read Repair:</strong> Fix Stale values on Read?</li>
</ul>
<h3 id="references">References:</h3>
<ul>
<li>
<p>DataStax Docs</p>
</li>
<li>
<p>Cassandra Tombstone issues</p>
</li>
<li>
<p>BigTable</p>
</li>
<li>
<p>Dynamo</p>
</li>
<li>
<p>PhiAccrual Failure Detector(Akka)
<strong>Open Questions?</strong></p>
</li>
<li>
<p>What is a <strong>Murmer3</strong> hashing function? How does it compare to <strong>MD5</strong>? Why <strong>Murmur</strong>?</p>
</li>
<li>
<p>Why 64 bit Token range? How does that compare to Dynamo?</p>
</li>
<li>
<p>R + W &gt; Replication Factor can give Strong consistency levels in Cassandra?[Research]</p>
</li>
<li>
<p>What happens if the coordinator node which wrote the Hint on the local disk crashes? How does the hinted handoff process complete? [Research]</p>
</li>
<li>
<p>The latest write-timestamp is used as a marker for the correct version of data[Research?] in Cassandra? Conflict resolution? Last write wins or Vector Clocks? Data Loss?</p>
</li>
<li>
<p><strong>Phi Accrual Failure Detector?</strong></p>
</li>
<li>
<p>Write Ahead Log? Cassandra?</p>
</li>
<li>
<p>KeyCache and Row Cache in Cassandra? How is it used? How is it invalidated or kept in Sync?</p>
</li>
<li>
<p>Bloom Filters details?</p>
</li>
<li>
<p>Why is each compaction Strategy Size-Tiered or Levelled Compaction a good strategy for its corresponding workload?</p>
</li>
<li>
<p>Anti-Entropy in Cassandra?</p>
</li>
<li>
<p>Geographical replication of data?</p>
</li>
<li>
<p>Read up on Various company blogs on Cassandra?</p>
</li>
<li>
<p><strong>Last Write Wins and Conflict Resolution?</strong></p>
</li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf">https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Dynamo </title>
      <link>https://www.sethihemant.com/notes/dynamo-2007/</link>
      <pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://www.sethihemant.com/notes/dynamo-2007/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Paper:&lt;/strong&gt; &lt;a href=&#34;https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf&#34;&gt;Dynamo &lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;dynamo--distributed-key-value-store&#34;&gt;Dynamo / Distributed Key Value Store&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Design a &lt;strong&gt;distributed key-value store(or Distributed Hash Table)&lt;/strong&gt; that is highly available (i.e., reliable), highly scalable, and completely decentralized.&lt;/p&gt;
&lt;h3 id=&#34;features&#34;&gt;Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Highly available Key-Value Store.&lt;/li&gt;
&lt;li&gt;Shopping Cart, Bestseller Lists, Sales Rank, Product Catalog, etc which &lt;strong&gt;needs only primary-key access to data&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Multi-table RDBMS would limit scalability and availability.&lt;/li&gt;
&lt;li&gt;Can choose desired Level of &lt;strong&gt;Availability&lt;/strong&gt; and &lt;strong&gt;Consistency&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;background&#34;&gt;Background?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Designed for **high availability(**at a massive scale) and &lt;strong&gt;partition tolerance&lt;/strong&gt; at the expense of &lt;strong&gt;strong consistency&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Primary Motivation for being optimized for High Availability(Over consistency) was to be always up for serving customer requests to provide better customer experience.&lt;/li&gt;
&lt;li&gt;Dynamo design inspired various NoSQL Databases, &lt;strong&gt;Cassandra&lt;/strong&gt;, &lt;strong&gt;Riak&lt;/strong&gt;, &lt;strong&gt;VoldemortDB&lt;/strong&gt;, &lt;strong&gt;DynamoDB&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;design-goals&#34;&gt;Design Goals?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Highly Available&lt;/li&gt;
&lt;li&gt;Reliability&lt;/li&gt;
&lt;li&gt;Highly Scalable&lt;/li&gt;
&lt;li&gt;Decentralized&lt;/li&gt;
&lt;li&gt;Eventually Consistent(EC) - Weaker Consistency model than Strong Consistency(Linearizability)&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Notes:&lt;/strong&gt; ) Latency Requirements?&lt;/li&gt;
&lt;li&gt;(&lt;strong&gt;Notes:&lt;/strong&gt; ) Geographical Distribution of Data?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;use-cases&#34;&gt;Use cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Dynamo can achieve strong consistency, but it comes with a performance impact. If Strong Consistency is a requirement, Dynamo is not the best option.&lt;/li&gt;
&lt;li&gt;Applications that need tight control over the trade-offs between availability, consistency, cost-effectiveness, and performance.&lt;/li&gt;
&lt;li&gt;Services that need only Primary Key access to the data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;system-apis&#34;&gt;System APIs:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;get(key)&lt;/strong&gt; : T… Object, Context&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;put(key, context, object)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Dynamo treats both the &lt;strong&gt;object&lt;/strong&gt; and &lt;strong&gt;the key&lt;/strong&gt; as an arbitrary &lt;strong&gt;array of bytes&lt;/strong&gt; (typically &lt;strong&gt;less than 1 MB&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Uses &lt;strong&gt;MD5 Hashing algorithm&lt;/strong&gt; on the key to generate &lt;strong&gt;128-bit HashID&lt;/strong&gt;, which is used to determine the storage nodes that are responsible for serving the key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;high-level-architecture&#34;&gt;High Level Architecture&lt;/h3&gt;
&lt;h3 id=&#34;agenda&#34;&gt;Agenda&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Data Distribution(Partitioning)&lt;/li&gt;
&lt;li&gt;Data Replication and Consistency&lt;/li&gt;
&lt;li&gt;Handing Temporary Failures(Fault Tolerance)&lt;/li&gt;
&lt;li&gt;Inter-Node communication(Unreliable Network) and Failure Detection&lt;/li&gt;
&lt;li&gt;High Availability&lt;/li&gt;
&lt;li&gt;Conflict resolution and handling permanent failures.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;data-partitioning&#34;&gt;Data Partitioning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Distributing data across a set of nodes is called &lt;strong&gt;data partitioning&lt;/strong&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>Paper:</strong> <a href="https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf">Dynamo </a></p>
<hr>
<h2 id="dynamo--distributed-key-value-store">Dynamo / Distributed Key Value Store</h2>
<p><strong>Problem:</strong> Design a <strong>distributed key-value store(or Distributed Hash Table)</strong> that is highly available (i.e., reliable), highly scalable, and completely decentralized.</p>
<h3 id="features">Features</h3>
<ul>
<li>Highly available Key-Value Store.</li>
<li>Shopping Cart, Bestseller Lists, Sales Rank, Product Catalog, etc which <strong>needs only primary-key access to data</strong>.</li>
<li>Multi-table RDBMS would limit scalability and availability.</li>
<li>Can choose desired Level of <strong>Availability</strong> and <strong>Consistency</strong>.</li>
</ul>
<h3 id="background">Background?</h3>
<ul>
<li>Designed for **high availability(**at a massive scale) and <strong>partition tolerance</strong> at the expense of <strong>strong consistency</strong>.</li>
<li>Primary Motivation for being optimized for High Availability(Over consistency) was to be always up for serving customer requests to provide better customer experience.</li>
<li>Dynamo design inspired various NoSQL Databases, <strong>Cassandra</strong>, <strong>Riak</strong>, <strong>VoldemortDB</strong>, <strong>DynamoDB</strong>.</li>
</ul>
<h3 id="design-goals">Design Goals?</h3>
<ul>
<li>Highly Available</li>
<li>Reliability</li>
<li>Highly Scalable</li>
<li>Decentralized</li>
<li>Eventually Consistent(EC) - Weaker Consistency model than Strong Consistency(Linearizability)</li>
<li>(<strong>Notes:</strong> ) Latency Requirements?</li>
<li>(<strong>Notes:</strong> ) Geographical Distribution of Data?</li>
</ul>
<h3 id="use-cases">Use cases</h3>
<ul>
<li>Dynamo can achieve strong consistency, but it comes with a performance impact. If Strong Consistency is a requirement, Dynamo is not the best option.</li>
<li>Applications that need tight control over the trade-offs between availability, consistency, cost-effectiveness, and performance.</li>
<li>Services that need only Primary Key access to the data.</li>
</ul>
<h3 id="system-apis">System APIs:</h3>
<ul>
<li><strong>get(key)</strong> : T… Object, Context</li>
<li><strong>put(key, context, object)</strong></li>
<li>Dynamo treats both the <strong>object</strong> and <strong>the key</strong> as an arbitrary <strong>array of bytes</strong> (typically <strong>less than 1 MB</strong>).</li>
<li>Uses <strong>MD5 Hashing algorithm</strong> on the key to generate <strong>128-bit HashID</strong>, which is used to determine the storage nodes that are responsible for serving the key.</li>
</ul>
<h3 id="high-level-architecture">High Level Architecture</h3>
<h3 id="agenda">Agenda</h3>
<ul>
<li>Data Distribution(Partitioning)</li>
<li>Data Replication and Consistency</li>
<li>Handing Temporary Failures(Fault Tolerance)</li>
<li>Inter-Node communication(Unreliable Network) and Failure Detection</li>
<li>High Availability</li>
<li>Conflict resolution and handling permanent failures.</li>
</ul>
<h3 id="data-partitioning">Data Partitioning</h3>
<ul>
<li>
<p>Distributing data across a set of nodes is called <strong>data partitioning</strong>.</p>
</li>
<li>
<p><strong>Challenges with Partitioning?</strong></p>
</li>
<li>
<p><strong>Naive Approach(Modulo Hashing)</strong></p>
</li>
<li>
<p><strong>Better Approach(Consistent Hashing)</strong>
<img loading="lazy" src="/images/notes/dynamo-2007/image-1.png"></p>
</li>
<li>
<p>Consistent hashing represents the data managed by a cluster as a <strong>ring</strong>. The ring is divided into smaller <strong>predefined ranges</strong>. Each node in the ring is assigned a range of data. The <strong>start of the range</strong> is called a <strong>token</strong>(each node is assigned one token).
<img loading="lazy" src="/images/notes/dynamo-2007/image-2.png"></p>
</li>
<li>
<p>Above works great when a node is added or removed from the ring; as only the next node is affected in these scenarios</p>
</li>
<li>
<p>The basic Consistent Hashing algorithm assigns a single token (or a consecutive hash range) to each physical node and does a <strong>static division of ranges</strong> that requires calculating tokens based on a given number of nodes.</p>
</li>
<li>
<p>Dynamo efficiently handles these scenarios(node addition/removal) through the use of <strong>Virtual Nodes</strong>(or <strong>Vnodes</strong>). New scheme for distributing Tokens to physical nodes.</p>
</li>
<li>
<p>Instead of assigning a single token to a node, the hash range is divided into multiple smaller ranges, and each physical node is assigned multiple of these smaller ranges. Each of these subranges is called a <strong>Vnode</strong>.
<img loading="lazy" src="/images/notes/dynamo-2007/image-3.png"></p>
</li>
<li>
<p><strong>Vnodes</strong> are randomly distributed across the cluster and are generally non-contiguous so that no two neighboring <strong>Vnodes</strong> are assigned to the same physical node.</p>
</li>
<li>
<p>Nodes also carry replicas of other nodes for <strong>fault-tolerance</strong>.
<img loading="lazy" src="/images/notes/dynamo-2007/image-4.png"></p>
</li>
<li>
<p>Since there can be <strong>heterogeneous</strong> machines in the clusters, some servers might hold more Vnodes than others.</p>
</li>
<li>
<p><strong>Advantages of VNodes:</strong></p>
</li>
</ul>
<h3 id="data-replication">Data Replication</h3>
<p>Agenda</p>
<ul>
<li>
<p>Optimistic replication</p>
</li>
<li>
<p>Preference List</p>
</li>
<li>
<p>Sloppy Quorum and Handling of Temporary failures</p>
</li>
<li>
<p>Hinted Handoff
<strong>Optimistic replication</strong></p>
</li>
<li>
<p>Replicates each data item on N nodes(N = Replication Factor, configurable per Dynamo instance).</p>
</li>
<li>
<p>Each key is assigned a <strong>Coordinator node</strong>(node that falls first in the hash range), which stores the data locally and <strong>replicates</strong> <strong>asynchronously(What?? or Synchronously?)</strong> to <strong>N-1 Clockwise successor</strong> nodes in the ring(eventually consistent) called <strong>Optimistic replication</strong>.
<img loading="lazy" src="/images/notes/dynamo-2007/image-5.png"></p>
</li>
<li>
<p>As Dynamo stores N copies of data spread across different nodes, if one node is down, other replicas can respond to queries for that range of data.</p>
</li>
<li>
<p>If a client cannot contact the coordinator node, it sends the request to a node holding a replica.
<strong>Preference List</strong></p>
</li>
<li>
<p>The list of nodes responsible for storing a particular key is called the preference list.</p>
</li>
<li>
<p>Dynamo is designed so that <strong>every node in the system can determine which nodes should be in this list for any specific key</strong>.</p>
</li>
<li>
<p>This list contains more than N nodes to account for failure and skip virtual nodes on the ring so that the list only contains distinct physical nodes.
<strong>Sloppy Quorum and handling of temporary failures</strong></p>
</li>
<li>
<p>Following <strong>traditional/strict</strong> quorum approaches, any distributed system becomes unavailable during server failures or network partitions and would have reduced availability even under simple failure conditions. Dynamo uses Sloppy Quorums.</p>
</li>
<li>
<p>With this approach, all read/write operations are performed on the first N healthy nodes from the preference list, which may not always be the first N nodes encountered while moving clockwise on the consistent hashing ring.</p>
</li>
<li>
<p><strong>Fault Tolerance with Sloppy Quorum</strong>.
<img loading="lazy" src="/images/notes/dynamo-2007/image-6.png"></p>
</li>
<li>
<p><strong>Hinted Handoff</strong></p>
</li>
</ul>
<h3 id="vector-clocks-and-conflicting-dataconflict-resolution">Vector Clocks and Conflicting Data(Conflict Resolution)</h3>
<p><strong>Agenda:</strong></p>
<ul>
<li>
<p>Clock Skew?</p>
</li>
<li>
<p>Vector Clock?</p>
</li>
<li>
<p>Conflict Free Replicated Data Types(CRDTs)</p>
</li>
<li>
<p>Last Write Wins(LWW)
Clock Skew</p>
</li>
<li>
<p>Physical clocks have clock skews, which is okay in single node systems, but can create concurrency updates in distributed systems, due to clock skews across different nodes.</p>
</li>
<li>
<p>Physical clocks are synchronized using NTP, but that still has skew, and 2 different nodes&rsquo; physical clocks can’t be accurately synchronized.</p>
</li>
<li>
<p>Using special hardware like GPS clocks and Atomic Clocks can reduce the clock skews, but doesn’t entirely eliminate it.</p>
</li>
<li>
<p>Physical clock has a problem with <strong>Causal Ordering</strong> of events(<strong>happens-before</strong> relationship).
Vector Clock?</p>
</li>
<li>
<p>Captures Causal ordering between events.</p>
</li>
<li>
<p>Vector clock is a (node, counter) pair. <strong>What? Isn’t it Lamport Clocks?</strong></p>
</li>
<li>
<p>Vector timestamps are attached to every version of the object stored in Dynamo.</p>
</li>
<li>
<p>One can determine whether two versions of an object are on parallel branches or have a causal ordering by examining their vector clocks.</p>
</li>
<li>
<p>If the counters on the first object’s clock are less-than-or-equal to all of the nodes in the second clock, then the first is an ancestor of the second and can be forgotten. Otherwise, the two changes are considered to be in conflict and require reconciliation. Dynamo resolves these conflicts at read-time.</p>
</li>
<li>
<p>Version branching may happen in the presence of failures combined with concurrent updates, resulting in conflicting versions of an object.</p>
</li>
<li>
<p>Dynamo <strong>truncates vector clocks (oldest first) when they grow too large</strong>. If Dynamo ends up deleting older vector clocks that are required to reconcile an object’s state, <strong>Dynamo would not be able to achieve eventual consistency</strong>.
<strong>Conflict Free Replicated Data Types</strong>?</p>
</li>
<li>
<p>To make use of CRDTs, we need to model our data in such a way that concurrent changes can be applied to the data in any order and will produce the same end result. This way, the system does not need to worry about any ordering guarantees.</p>
</li>
<li>
<p>The idea that any two nodes that have received the same set of updates will see the same end result is called <strong>strong eventual consistency</strong>.
<strong>Last Write Wins</strong></p>
</li>
<li>
<p>Dynamo(and Cassandra) also offer a way to do server side conflict resolution, <strong>LWW</strong>.</p>
</li>
<li>
<p>Uses <strong>Physical</strong>(Wall Clock/Time-Of-the-Day) Clocks.</p>
</li>
<li>
<p>Can potentially lead to Data-Loss during concurrent writes.</p>
</li>
</ul>
<h3 id="life-of-dynamos-put-and-get-operations">Life of Dynamo’s put() and get() operations.</h3>
<p><strong>Agenda:</strong></p>
<ul>
<li>Strategies for Coordinator selection</li>
<li>Consistency protocol</li>
<li>put() process</li>
<li>get() process</li>
<li>Request handling through a state machine.</li>
</ul>
<h3 id="strategies-for-choosing-coordinator">Strategies for choosing coordinator</h3>
<ul>
<li>Clients route request using Generic Load Balancer.</li>
<li>Clients use a <strong>partition-aware client library</strong> that routes requests to the appropriate coordinator with <strong>lower latency</strong>.
<img loading="lazy" src="/images/notes/dynamo-2007/image-7.png"></li>
</ul>
<h3 id="consistency-protocol">Consistency Protocol</h3>
<ul>
<li>
<p>Uses a consistency protocol similar to quorum systems.</p>
</li>
<li>
<p>R + W &gt; N ( R /W = minimum number of nodes to participate in Read/Write)</p>
</li>
<li>
<p>Common configurations(N, R, W) for Dynamo (3, 2, 2)</p>
</li>
<li>
<p>Latency of get() and put() depends upon the slowest of replicas.
Put() Process</p>
</li>
<li>
<p>Coordinator generates new data version and vector timestamp.</p>
</li>
<li>
<p>Saves data locally.</p>
</li>
<li>
<p>Sends write requests to N-1 highest ranked healthy nodes from the preference list.</p>
</li>
<li>
<p>Put() is considered successful after receiving W-1 confirmations.
Get() process</p>
</li>
<li>
<p>Coordinator requests the data version from N-1 highest ranked healthy nodes from the preference list.</p>
</li>
<li>
<p>Waits until R - 1 replies.</p>
</li>
<li>
<p>Coordinator handles causal data versioning using vector clocks/timestamps.</p>
</li>
<li>
<p>Returns all data versions to the caller.</p>
</li>
</ul>
<h3 id="request-handling-through-the-state-machine">Request handling through the state machine</h3>
<ul>
<li>Each client request results in creating a state machine on the node that received the client request.</li>
<li>The state machine contains all the logic for</li>
<li>Each state machine instance handles exactly one client request.</li>
<li>A <strong>read operation</strong> implements following <strong>state machine</strong>:</li>
<li><strong>Writes:</strong></li>
</ul>
<h3 id="anti-entropy-through-merkle-trees">Anti-Entropy through Merkle Trees</h3>
<ul>
<li>
<p>Dynamo uses Vector clocks to remove write conflicts(Read Repair) while serving read requests if it receives stale responses from some of the replicas.</p>
</li>
<li>
<p>If a replica fell significantly behind others, it might take a very long time to resolve conflicts using read repair(vector clocks), depending upon if those keys were read or not. It may happen that some of the keys are never accessed, and they cold remain stale for longer.</p>
</li>
<li>
<p>We need a mechanism to automatically reconcile replicas in the background(and do conflict resolution if any).</p>
</li>
<li>
<p>To do this, we need to quickly <strong>compare two copies of a range of data residing on different replicas</strong> and figure out exactly which parts are <strong>different</strong>.</p>
</li>
<li>
<p>Naively splitting up the entire data range for checksums is not very feasible; there is simply too much data to be transferred.(<strong>Transferred? How?</strong>)</p>
</li>
<li>
<p>Dynamo uses <strong>Merkle trees</strong> to compare replicas of a range.</p>
</li>
<li>
<p><strong>A Merkle tree</strong> is a <strong>binary tree of hashes</strong>, where each internal node is the hash of its two children, and each leaf node is a hash of a portion of the original data.
<img loading="lazy" src="/images/notes/dynamo-2007/image-8.png"></p>
</li>
<li>
<p>Now comparing the ranges of data on two replicas is equivalent to comparing two Merkle Trees</p>
</li>
<li>
<p>The principal advantage of using a Merkle tree is that each branch of the tree can be checked independently without requiring nodes to download the entire tree or the whole data set.</p>
</li>
<li>
<p>Merkle trees minimize the amount of data that needs to be transferred for synchronization and reduce the number of disk reads performed during the anti-entropy process.</p>
</li>
<li>
<p>The disadvantage of using Merkle trees is that many key ranges can change when a node joins or leaves, and as a result, the trees need to be recalculated.</p>
</li>
</ul>
<h3 id="gossip-protocol">Gossip Protocol</h3>
<h3 id="what-is-a-gossip-protocol">What is a Gossip Protocol?</h3>
<ul>
<li>How does <strong>Node Failure Detection</strong> happen in Dynamo?</li>
<li>Since we do not have any central node that keeps track of all nodes to know if a node is down or not, how does a node know every other node’s current state?</li>
<li><strong>Naive Approach:</strong> Each Node broadcast HeatBeat message to every other Node</li>
<li>Optimized Approach: <strong>Gossip Protocol</strong>
<img loading="lazy" src="/images/notes/dynamo-2007/image-9.png"></li>
</ul>
<h3 id="external-discovery-through-seed-nodes">External Discovery Through Seed Nodes?</h3>
<ul>
<li>Dynamo nodes use gossip protocol to find the current state of the ring. This can result in a logical partition of the cluster in a particular scenario.</li>
<li>An administrator joins node A to the ring and then joins node B to the ring. Nodes A and B consider themselves part of the ring, yet neither would be immediately aware of each other. To prevent these logical partitions, Dynamo introduced the concept of seed nodes.</li>
<li>Seed nodes are fully functional nodes and can be obtained either from a static configuration or a configuration service. This way, all nodes are aware of seed nodes.</li>
<li>Each node communicates with seed nodes through gossip protocol to reconcile membership changes; therefore, logical partitions are highly unlikely.</li>
</ul>
<h3 id="characteristics-and-criticism-of-dynamo">Characteristics and Criticism of Dynamo</h3>
<p>Responsibilities of a Dynamo Node</p>
<ul>
<li>Managing get() and put() requests via acting as a Coordinator(or request Forwarder).</li>
<li>Keeping track of membership(Hash ranges in a Ring) and detecting failures(Gossip)</li>
<li>Local Persistent Storage</li>
</ul>
<h3 id="characteristics-of-dynamo">Characteristics of Dynamo</h3>
<ul>
<li>Distributed(Can run across several machines)</li>
<li>Decentralized(No external coordinator, all nodes identical)</li>
<li>Scalable(Horizontally scaled on commodity hardware with Fault Tolerance. No Manual intervention/rebalancing required)</li>
<li>Highly Available</li>
<li>Fault Tolerant and Reliable</li>
<li>Tunable Consistency(Trade Offs b/w Availability and Consistency by adjusting the replication factor 3,2,2, or 3,1,3, or 3,3,1 etc).</li>
</ul>
<h3 id="criticism-on-dynamo-design">Criticism on Dynamo Design?</h3>
<ul>
<li>Each Dynamo node contains the entire routing table. Could affect scalability of the system as this routing table gets larger as more nodes are added to the system.</li>
<li>Dynamo seems to imply that it strives for symmetry(all nodes have the same set of responsibilities). But it does specify some nodes as seed nodes for external discovery to avoid logical partition. May violate Dynamo’s symmetry principle.</li>
<li>DHTs can be susceptible to Several different types of attack?[Research More?]</li>
<li>Dynamo’s design can be described as a <strong>Leaky Abstraction</strong>.</li>
</ul>
<h3 id="datastores-developed-on-principles-of-dynamo">DataStores developed on Principles of Dynamo</h3>
<ul>
<li><strong>Riak</strong> is a distributed NoSQL key-value data store that is highly available, scalable, fault-tolerant, and easy to operate.</li>
<li><strong>Cassandra</strong> is a distributed, decentralized, scalable, and highly available NoSQL wide-column database.
<img loading="lazy" src="/images/notes/dynamo-2007/image-10.png"></li>
</ul>
<h3 id="summary">Summary</h3>
<p><img loading="lazy" src="/images/notes/dynamo-2007/image-11.png"></p>
<p>Paper reading Video.</p>
<p>References:</p>
<ul>
<li>
<p><a href="https://www.allthingsdistributed.com/2007/10/amazons_dynamo.html">https://www.allthingsdistributed.com/2007/10/amazons_dynamo.html</a></p>
</li>
<li>
<p><a href="https://docs.riak.com/riak/kv/2.2.0/developing/data-types/">https://docs.riak.com/riak/kv/2.2.0/developing/data-types/</a></p>
</li>
<li>
<p><a href="https://research.google/pubs/bigtable-a-distributed-storage-system-for-structured-data/">https://research.google/pubs/bigtable-a-distributed-storage-system-for-structured-data/</a></p>
</li>
<li>
<p><a href="https://www.allthingsdistributed.com/2012/01/amazon-dynamodb.html">https://www.allthingsdistributed.com/2012/01/amazon-dynamodb.html</a></p>
</li>
<li>
<p><a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type</a></p>
</li>
<li>
<p><a href="https://www.allthingsdistributed.com/2017/10/a-decade-of-dynamo.html">https://www.allthingsdistributed.com/2017/10/a-decade-of-dynamo.html</a></p>
</li>
<li>
<p><a href="https://news.ycombinator.com/item?id=915212">https://news.ycombinator.com/item?id=915212</a>
<strong>Open Questions:</strong></p>
</li>
<li>
<p>Anti-Entropy and Merkle Trees</p>
</li>
<li>
<p>DHTs can be susceptible to Several different types of attack?[Research More?]</p>
</li>
<li>
<p>Underlying storage for Dynamo store. Berkeley, in-memory + persistent + more.</p>
</li>
<li>
<p>Why does it use MD5 hashing? Why not something else?</p>
</li>
<li>
<p>Logical partitioning and seed nodes?</p>
</li>
<li>
<p>New Features and revision in the design?</p>
</li>
</ul>
<hr>
<p><strong>Paper Link:</strong> <a href="https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf">https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf</a></p>
<hr>
<p><em>Last updated: March 15, 2026</em></p>
<p><em>Questions or discussion? <a href="mailto:sethi.hemant@gmail.com">Email me</a></em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
