<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>System-Design | Haihao Sun's Homepage</title><link>https://h3sun.github.io/tags/system-design/</link><atom:link href="https://h3sun.github.io/tags/system-design/index.xml" rel="self" type="application/rss+xml"/><description>System-Design</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Mon, 07 Oct 2024 00:00:00 +0000</lastBuildDate><image><url>https://h3sun.github.io/media/icon_hu68170e94a17a2a43d6dcb45cf0e8e589_3079_512x512_fill_lanczos_center_3.png</url><title>System-Design</title><link>https://h3sun.github.io/tags/system-design/</link></image><item><title>DDIA Chapter 7 - Transaction</title><link>https://h3sun.github.io/post/2024-10-06-ddia-chapter-7-transaction/</link><pubDate>Mon, 07 Oct 2024 00:00:00 +0000</pubDate><guid>https://h3sun.github.io/post/2024-10-06-ddia-chapter-7-transaction/</guid><description>&lt;h1 id="acid">ACID&lt;/h1>
&lt;ul>
&lt;li>Atomicity: All or nothing.&lt;/li>
&lt;li>Consistency: Invariants must always be true (This is from application perspective).&lt;/li>
&lt;li>Isolation: Transactions are executed in a way that their operations do not interfere with each other.&lt;/li>
&lt;li>Durability: Once commited, data will not be forgetten.&lt;/li>
&lt;/ul>
&lt;h1 id="weak-isolation-levels">Weak Isolation Levels&lt;/h1>
&lt;h2 id="read-committed-to-combat-dirty-read">Read Committed (To combat dirty read)&lt;/h2>
&lt;h3 id="what-is-dirty-read">What is dirty read?&lt;/h3>
&lt;ul>
&lt;li>Read a data that has not been committed by a transaction.&lt;/li>
&lt;/ul>
&lt;h3 id="implement-read-committed">Implement Read Committed&lt;/h3>
&lt;ul>
&lt;li>Lock on write.&lt;/li>
&lt;li>Lock on read (performance hit).&lt;/li>
&lt;li>Remember old + new value.&lt;/li>
&lt;/ul>
&lt;h2 id="snapshot-isolation-and-repeatable-read-to-combat-nonrepeatable-read">Snapshot Isolation and Repeatable Read (To combat nonrepeatable read)&lt;/h2>
&lt;h3 id="what-is-nonrepetabale-read">What is nonrepetabale read?&lt;/h3>
&lt;ul>
&lt;li>Alice read account A (50).&lt;/li>
&lt;li>Alice executes A = A + 10.&lt;/li>
&lt;li>Alice executes B = B - 10.&lt;/li>
&lt;li>Alice read account B (40).&lt;/li>
&lt;li>Sum is 50 + 40 which is not 100.&lt;/li>
&lt;/ul>
&lt;h3 id="snapshot-isoltion">Snapshot Isoltion&lt;/h3>
&lt;ul>
&lt;li>Each transaction sees the snapshot of the database at the start of the transaction.&lt;/li>
&lt;li>MVCC(Multi-version concurrency control).&lt;/li>
&lt;/ul>
&lt;h2 id="preventing-lost-updates">Preventing Lost Updates&lt;/h2>
&lt;ul>
&lt;li>Atomic write operations.&lt;/li>
&lt;li>Explicit locking.&lt;/li>
&lt;li>Automotically deleting lost updates.&lt;/li>
&lt;li>Compare and set.&lt;/li>
&lt;li>Conflict resolution and replica.&lt;/li>
&lt;/ul>
&lt;h1 id="serializability-to-combat-phantom">Serializability (To combat phantom)&lt;/h1>
&lt;h2 id="write-skew-and-phantoms">Write Skew and Phantoms&lt;/h2>
&lt;p>oncall-rotation in emergency room.&lt;/p>
&lt;ul>
&lt;li>Pattern&lt;/li>
&lt;/ul>
&lt;ol>
&lt;li>select&lt;/li>
&lt;li>decide how to continue based on select&lt;/li>
&lt;li>INSERT, UPDATE, DELTE&lt;/li>
&lt;/ol>
&lt;h2 id="actual-serial-execution">Actual Serial Execution&lt;/h2>
&lt;ul>
&lt;li>Every transaction must be small and fast for performance.&lt;/li>
&lt;li>It is limited to use cases where the active dataset can fit in memory for performance.&lt;/li>
&lt;li>Write throughput must be low enough to be handled on a single CPU core, or else transactions need to be partitioned without requiring cross-partition coordination.&lt;/li>
&lt;li>Cross-partition transactions are possible, but there is a hard limit to the extent to which they can be used.&lt;/li>
&lt;/ul>
&lt;h2 id="two-phase-locking">Two-Phase Locking&lt;/h2>
&lt;ul>
&lt;li>read: acquire the lock in shared mode, if exclusive exists, wait.&lt;/li>
&lt;li>write: acquire the lock in exclusive mode.&lt;/li>
&lt;li>read then write: acquire in shared first, then exclusive.&lt;/li>
&lt;li>commit or abort: hold until abort or commit&lt;/li>
&lt;/ul>
&lt;h2 id="serializable-snapshot-isolation-ssi">Serializable Snapshot Isolation (SSI)&lt;/h2>
&lt;ul>
&lt;li>Goal of SSI: full serializability with small perf penalty compared to snapshot isolation.&lt;/li>
&lt;li>Used in single-node (PostgreSQL 9.1) and distributed, may become the new default.&lt;/li>
&lt;/ul>
&lt;h3 id="pessimistic-versus-optimistic-concurrency-control">Pessimistic versus optimistic concurrency control&lt;/h3>
&lt;ul>
&lt;li>2PL is pessimistic&lt;/li>
&lt;li>SSI is optimistic: When a transaction wants to commit, the database checks isolation violations&lt;/li>
&lt;li>If there is enough spare capacity, and if contention between transactions is not too high, optimistic concurrency control techniques perform better&lt;/li>
&lt;li>On top of snapshot isolation, SSI adds an algorithm for detecting serialization conflicts among writes and determining which transactions to abort.&lt;/li>
&lt;/ul>
&lt;p>Decisions based on an outdated premise
A premise is a fact that was true at the beginning of the transaction. How does the database know if a query result might have changed?&lt;/p>
&lt;h3 id="detecting-stale-mvcc-reads">Detecting stale MVCC reads&lt;/h3>
&lt;ul>
&lt;li>The database needs to track when a transaction ignores another transaction’s writes due to MVCC visibility rules.&lt;/li>
&lt;li>Check any of the ignored writes has been committed and abort if so.&lt;/li>
&lt;li>Wait until committing because read-only won&amp;rsquo;t write skew, and other write transactions might be aborted.&lt;/li>
&lt;li>Key is to avoid unnecessary aborts.&lt;/li>
&lt;/ul>
&lt;h3 id="detecting-writes-that-affect-prior-reads">Detecting writes that affect prior reads&lt;/h3>
&lt;ul>
&lt;li>DB track ephemeral information about transactions reading data on index, or at the table level.&lt;/li>
&lt;li>When a transaction writes, notify existing reader of that data&lt;/li>
&lt;li>Check other writes commit when commit reader.&lt;/li>
&lt;li>(Question: why not notify existing reader when committing the other writer transaction?)&lt;/li>
&lt;/ul>
&lt;h3 id="performance-of-serializable-snapshot-isolation">Performance of serializable snapshot isolation&lt;/h3>
&lt;ul>
&lt;li>The rate of aborts significantly affects the overall performance of SSI. So SSI requires short read-write transactions, but it&amp;rsquo;s less sensitive to slow transactions than two-phase locking or serial execution.&lt;/li>
&lt;/ul>
&lt;p>source:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://xgwang.me/posts/ddia-7-transactions/">https://xgwang.me/posts/ddia-7-transactions/&lt;/a>&lt;/li>
&lt;li>ddia&lt;/li>
&lt;/ul></description></item><item><title>DDIA Chapter 6 - Partition</title><link>https://h3sun.github.io/post/2024-10-06-ddia-chapter-6-partition/</link><pubDate>Sun, 06 Oct 2024 00:00:00 +0000</pubDate><guid>https://h3sun.github.io/post/2024-10-06-ddia-chapter-6-partition/</guid><description>&lt;h2 id="two-types-of-partition-techniques">Two types of partition techniques&lt;/h2>
&lt;h3 id="key-range-partition">Key Range Partition&lt;/h3>
&lt;ul>
&lt;li>How: [a-c], [d-e], &amp;hellip;&lt;/li>
&lt;li>Pros: Efficient for range query.&lt;/li>
&lt;li>Cons: Hot spot.&lt;/li>
&lt;/ul>
&lt;h3 id="hash-partition">Hash Partition&lt;/h3>
&lt;ul>
&lt;li>How: hash first, then use range.&lt;/li>
&lt;li>Pros: Distribute load evenly.&lt;/li>
&lt;li>Cons: Range query is inefficient.&lt;/li>
&lt;/ul>
&lt;h3 id="hybrid">Hybrid&lt;/h3>
&lt;ul>
&lt;li>How: one part -&amp;gt; partition, the other part -&amp;gt; sort order.&lt;/li>
&lt;/ul>
&lt;h2 id="partitioning-secondary-indexes">Partitioning Secondary Indexes&lt;/h2>
&lt;h3 id="partitioning-secondary-indexes-by-document">Partitioning Secondary Indexes by Document&lt;/h3>
&lt;ul>
&lt;li>Database can perform indexing automatically.&lt;/li>
&lt;li>Note: in KV-only DB, application level secondary indexing is prone to inconsistency.&lt;/li>
&lt;li>Each partition maintains its own secondary indexes.&lt;/li>
&lt;li>A.K.A. local index.&lt;/li>
&lt;li>Need to send the query to all partitions, and combine all the results you get back (scatter/gather).&lt;/li>
&lt;li>Read queries are expensive, and prone to latency amplification. But widely used.&lt;/li>
&lt;li>Recommend structure the partitioning scheme so that secondary index queries can be served from a single partition.&lt;/li>
&lt;/ul>
&lt;h3 id="partitioning-secondary-indexes-by-term">Partitioning Secondary Indexes by Term&lt;/h3>
&lt;ul>
&lt;li>A global index must also be partitioned (term-partitioned).&lt;/li>
&lt;li>Partition the index by the term itself, or using a hash of the term.&lt;/li>
&lt;li>Pro: more efficient read (no scatter/gather).&lt;/li>
&lt;li>Con: write slower and more complicated, require distributed transaction.&lt;/li>
&lt;li>Updates to global secondary indexes are asynchronous, e.g., Amazon DynamoDB.&lt;/li>
&lt;/ul>
&lt;h2 id="strategies-for-rebalancing">Strategies for Rebalancing&lt;/h2>
&lt;h3 id="how-not-to-do-it-hash-mod-n">How not to do it: hash mod N&lt;/h3>
&lt;p>Problem: move data more than necessary&lt;/p>
&lt;h3 id="fixed-number-of-partitions">Fixed number of partitions&lt;/h3>
&lt;ul>
&lt;li>Create many more partitions than there are nodes, and assign several partitions to each node.&lt;/li>
&lt;li>The new node can steal a few partitions from every existing node.&lt;/li>
&lt;li>The number of partitions is usually fixed when the database is first set up and not changed afterward.&lt;/li>
&lt;li>If partitions are very large, rebalancing and recovery from node failures become expensive. But if partitions are too small, they incur too much overhead.&lt;/li>
&lt;/ul>
&lt;h2 id="request-routing">Request Routing&lt;/h2>
&lt;h3 id="strategy-for-routing">Strategy for routing&lt;/h3>
&lt;ol>
&lt;li>Send requests to all nodes, and if we do not get result, reoute to next node.&lt;/li>
&lt;li>Implement a routing tier which knows which node has the specific partition.&lt;/li>
&lt;li>Client knows the partition.&lt;/li>
&lt;/ol>
&lt;h3 id="how-does-the-component-make-the-routing-decision">How does the component make the routing decision&lt;/h3>
&lt;ol>
&lt;li>Zookeeper which is a service that knows the partition.&lt;/li>
&lt;li>Gossip protocol.&lt;/li>
&lt;li>Mannual rebalance.&lt;/li>
&lt;/ol>
&lt;p>source:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://xgwang.me/posts/ddia-6-partitioning/#intro">https://xgwang.me/posts/ddia-6-partitioning/#intro&lt;/a>&lt;/li>
&lt;li>ddia&lt;/li>
&lt;/ul></description></item><item><title>DDIA Chapter 5 - Repliation</title><link>https://h3sun.github.io/post/2024-10-05-ddia-chapter-5-replication/</link><pubDate>Sat, 05 Oct 2024 00:00:00 +0000</pubDate><guid>https://h3sun.github.io/post/2024-10-05-ddia-chapter-5-replication/</guid><description>&lt;h1 id="importance-of-replication">Importance of Replication&lt;/h1>
&lt;ul>
&lt;li>Fault Tolerance: keep the system running, even when one machine (or several machines) go down.&lt;/li>
&lt;li>Disconnected operation: Allow an application to continue to work when there is a network interruption.&lt;/li>
&lt;li>Latency: Placing data geographically close to users, so users can interact with it with a lower latency.&lt;/li>
&lt;li>Scalability: Being able to handle higher volume of reads than a single machine could handle.&lt;/li>
&lt;/ul>
&lt;h1 id="three-main-approaches-to-replication">Three main approaches to replication&lt;/h1>
&lt;ol>
&lt;li>Single-leader replication: Clients send write operation to a single node(leader), which sends a stream of data change to other replicas(followers). Reads can be performed on any replica, but reads from followers might be stale.&lt;/li>
&lt;li>Multi-leader replication: Clients send each write to onoe of several leader nodes, any of which can accept writes. The leaders send streams of data change events to each other and any follower nodes.&lt;/li>
&lt;li>Leaderless replication: Clients send each write to several nodes, and read from several nodes in parallel in order to detect and correct nodes with stale data. w + r &amp;gt; n.&lt;/li>
&lt;/ol>
&lt;h1 id="three-interesting-effects-can-be-caused-by-replication-lag">Three interesting effects can be caused by replication lag&lt;/h1>
&lt;ol>
&lt;li>Read-after-write consistency&lt;/li>
&lt;/ol>
&lt;p>Users should always see data that they submit themselves.&lt;/p>
&lt;p>Solution:&lt;/p>
&lt;ul>
&lt;li>When read something that user can modify, read from leader.&lt;/li>
&lt;li>Track update time t, and from t to t + 1 minutes, read from leader(essentially wait for replica picking up the data from leader).&lt;/li>
&lt;li>Track the logical timestamp and compare it with follower node, if smaller, than find another node.&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Monotonic reads&lt;/li>
&lt;/ol>
&lt;p>After users have seen the data at one point in time, they shouldn&amp;rsquo;t later see the data from ealier point in time.&lt;/p>
&lt;p>Solution:&lt;/p>
&lt;ul>
&lt;li>Always read from a single replica.&lt;/li>
&lt;/ul>
&lt;ol start="3">
&lt;li>Consistent prefix reads&lt;/li>
&lt;/ol>
&lt;p>Users should see data in a state that makes causal sense: for example, an answer should come after a question.&lt;/p>
&lt;p>Solution:&lt;/p>
&lt;ul>
&lt;li>Write to the same partition.&lt;/li>
&lt;/ul>
&lt;p>Finally, we discussed the concurrency issues that are inherent in multi-leader and leaderless replication approaches: because such systems allow multiple writes to happen concurrently, conflicts may occur. We examined an algorithm that a database might use to determine whether one operation happened before another, or whether they happened concurrently. We also touched on methods for resolving conflicts by merging together concurrently written values.
In the next chapter we will continue looking at data that is distributed across multiple machines, through the counterpart of replication: splitting a large dataset into partitions.&lt;/p>
&lt;p>From DDIA chapter 5.&lt;/p></description></item></channel></rss>