{"id":"design-sales-rank","title":"Design Amazon's sales rank by category feature","sections":[{"id":"overview","title":"Overview","html":"<p><em>Note: This document links directly to relevant areas found in the <a href=\"/\">system design topics</a> to avoid duplication.  Refer to the linked content for general talking points, tradeoffs, and alternatives.</em></p>","headings":[]},{"id":"step-1-outline-use-cases-and-constraints","title":"Step 1: Outline use cases and constraints","html":"<h2 id=\"step-1-outline-use-cases-and-constraints\">Step 1: Outline use cases and constraints</h2>\n<blockquote>\n<p>Gather requirements and scope the problem.\nAsk questions to clarify use cases and constraints.\nDiscuss assumptions.</p>\n</blockquote>\n<p>Without an interviewer to address clarifying questions, we'll define some use cases and constraints.</p>\n<h3 id=\"use-cases\">Use cases</h3>\n<h4 id=\"well-scope-the-problem-to-handle-only-the-following-use-case\">We'll scope the problem to handle only the following use case</h4>\n<ul>\n<li><strong>Service</strong> calculates the past week's most popular products by category</li>\n<li><strong>User</strong> views the past week's most popular products by category</li>\n<li><strong>Service</strong> has high availability</li>\n</ul>\n<h4 id=\"out-of-scope\">Out of scope</h4>\n<ul>\n<li>The general e-commerce site\n<ul>\n<li>Design components only for calculating sales rank</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"constraints-and-assumptions\">Constraints and assumptions</h3>\n<h4 id=\"state-assumptions\">State assumptions</h4>\n<ul>\n<li>Traffic is not evenly distributed</li>\n<li>Items can be in multiple categories</li>\n<li>Items cannot change categories</li>\n<li>There are no subcategories ie <code>foo/bar/baz</code></li>\n<li>Results must be updated hourly\n<ul>\n<li>More popular products might need to be updated more frequently</li>\n</ul>\n</li>\n<li>10 million products</li>\n<li>1000 categories</li>\n<li>1 billion transactions per month</li>\n<li>100 billion read requests per month</li>\n<li>100:1 read to write ratio</li>\n</ul>\n<h4 id=\"calculate-usage\">Calculate usage</h4>\n<p><strong>Clarify with your interviewer if you should run back-of-the-envelope usage calculations.</strong></p>\n<ul>\n<li>Size per transaction:\n<ul>\n<li><code>created_at</code> - 5 bytes</li>\n<li><code>product_id</code> - 8 bytes</li>\n<li><code>category_id</code> - 4 bytes</li>\n<li><code>seller_id</code> - 8 bytes</li>\n<li><code>buyer_id</code> - 8 bytes</li>\n<li><code>quantity</code> - 4 bytes</li>\n<li><code>total_price</code> - 5 bytes</li>\n<li>Total: ~40 bytes</li>\n</ul>\n</li>\n<li>40 GB of new transaction content per month\n<ul>\n<li>40 bytes per transaction * 1 billion transactions per month</li>\n<li>1.44 TB of new transaction content in 3 years</li>\n<li>Assume most are new transactions instead of updates to existing ones</li>\n</ul>\n</li>\n<li>400 transactions per second on average</li>\n<li>40,000 read requests per second on average</li>\n</ul>\n<p>Handy conversion guide:</p>\n<ul>\n<li>2.5 million seconds per month</li>\n<li>1 request per second = 2.5 million requests per month</li>\n<li>40 requests per second = 100 million requests per month</li>\n<li>400 requests per second = 1 billion requests per month</li>\n</ul>","headings":[{"id":"use-cases","title":"Use cases","depth":3},{"id":"well-scope-the-problem-to-handle-only-the-following-use-case","title":"We'll scope the problem to handle only the following use case","depth":4},{"id":"out-of-scope","title":"Out of scope","depth":4},{"id":"constraints-and-assumptions","title":"Constraints and assumptions","depth":3},{"id":"state-assumptions","title":"State assumptions","depth":4},{"id":"calculate-usage","title":"Calculate usage","depth":4}]},{"id":"step-2-create-a-high-level-design","title":"Step 2: Create a high level design","html":"<h2 id=\"step-2-create-a-high-level-design\">Step 2: Create a high level design</h2>\n<blockquote>\n<p>Outline a high level design with all important components.</p>\n</blockquote>\n<p><img src=\"/repo/solutions/system_design/sales_rank/sales_rank_basic.png\" alt=\"Diagram\" loading=\"lazy\" decoding=\"async\"></p>","headings":[]},{"id":"step-3-design-core-components","title":"Step 3: Design core components","html":"<h2 id=\"step-3-design-core-components\">Step 3: Design core components</h2>\n<blockquote>\n<p>Dive into details for each core component.</p>\n</blockquote>\n<h3 id=\"use-case-service-calculates-the-past-weeks-most-popular-products-by-category\">Use case: Service calculates the past week's most popular products by category</h3>\n<p>We could store the raw <strong>Sales API</strong> server log files on a managed <strong>Object Store</strong> such as Amazon S3, rather than managing our own distributed file system.</p>\n<p><strong>Clarify with your interviewer how much code you are expected to write</strong>.</p>\n<p>We'll assume this is a sample log entry, tab delimited:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>timestamp   product_id  category_id    qty     total_price   seller_id    buyer_id</span></span>\n<span class=\"line\"><span>t1          product1    category1      2       20.00         1            1</span></span>\n<span class=\"line\"><span>t2          product1    category2      2       20.00         2            2</span></span>\n<span class=\"line\"><span>t2          product1    category2      1       10.00         2            3</span></span>\n<span class=\"line\"><span>t3          product2    category1      3        7.00         3            4</span></span>\n<span class=\"line\"><span>t4          product3    category2      7        2.00         4            5</span></span>\n<span class=\"line\"><span>t5          product4    category1      1        5.00         5            6</span></span>\n<span class=\"line\"><span>...</span></span></code></pre>\n<p>The <strong>Sales Rank Service</strong> could use <strong>MapReduce</strong>, using the <strong>Sales API</strong> server log files as input and writing the results to an aggregate table <code>sales_rank</code> in a <strong>SQL Database</strong>.  We should discuss the <a href=\"/learn/database#sql-or-nosql\">use cases and tradeoffs between choosing SQL or NoSQL</a>.</p>\n<p>We'll use a multi-step <strong>MapReduce</strong>:</p>\n<ul>\n<li><strong>Step 1</strong> - Transform the data to <code>(category, product_id), sum(quantity)</code></li>\n<li><strong>Step 2</strong> - Perform a distributed sort</li>\n</ul>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> SalesRanker</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">MRJob</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> within_past_week</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, timestamp):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Return True if timestamp is within past week, False otherwise.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, _ line):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Parse each log line, extract and transform relevant lines.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Emit key value pairs of the form:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, product1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, product1), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product2), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, product3), 7</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product4), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        timestamp, product_id, category_id, quantity, total_price, seller_id, \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            buyer_id </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> line.split(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\t</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.within_past_week(timestamp):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category_id, product_id), quantity</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, value):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Sum values for each key.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, product1), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product2), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, product3), 7</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, product4), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">sum</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(values)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper_sort</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, value):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Construct key to ensure proper sorting.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Transform key and value to the form:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 2), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 3), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 3), product2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 7), product3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 1), product4</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        The shuffle/sort step of MapReduce will then do a</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        distributed sort on the keys, resulting in:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 1), product4</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 2), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 3), product2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 3), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 7), product3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        category_id, product_id </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        quantity </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category_id, quantity), product_id</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer_identity</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, value):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, value</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> steps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Run the map and reduce steps.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mr(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">mapper</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mapper,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">                    reducer</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reducer),</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mr(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">mapper</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mapper_sort,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">                    reducer</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reducer_identity),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        ]</span></span></code></pre>\n<p>The result would be the following sorted list, which we could insert into the <code>sales_rank</code> table:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>(category1, 1), product4</span></span>\n<span class=\"line\"><span>(category1, 2), product1</span></span>\n<span class=\"line\"><span>(category1, 3), product2</span></span>\n<span class=\"line\"><span>(category2, 3), product1</span></span>\n<span class=\"line\"><span>(category2, 7), product3</span></span></code></pre>\n<p>The <code>sales_rank</code> table could have the following structure:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>id int NOT NULL AUTO_INCREMENT</span></span>\n<span class=\"line\"><span>category_id int NOT NULL</span></span>\n<span class=\"line\"><span>total_sold int NOT NULL</span></span>\n<span class=\"line\"><span>product_id int NOT NULL</span></span>\n<span class=\"line\"><span>PRIMARY KEY(id)</span></span>\n<span class=\"line\"><span>FOREIGN KEY(category_id) REFERENCES Categories(id)</span></span>\n<span class=\"line\"><span>FOREIGN KEY(product_id) REFERENCES Products(id)</span></span></code></pre>\n<p>We'll create an <a href=\"/learn/database#use-good-indices\">index</a> on <code>id </code>, <code>category_id</code>, and <code>product_id</code> to speed up lookups (log-time instead of scanning the entire table) and to keep the data in memory.  Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.<sup>&#x3C;a href=<a href=\"https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know%3E1\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know>1</a></sup></p>\n<h3 id=\"use-case-user-views-the-past-weeks-most-popular-products-by-category\">Use case: User views the past week's most popular products by category</h3>\n<ul>\n<li>The <strong>Client</strong> sends a request to the <strong>Web Server</strong>, running as a <a href=\"/learn/reverse-proxy-web-server\">reverse proxy</a></li>\n<li>The <strong>Web Server</strong> forwards the request to the <strong>Read API</strong> server</li>\n<li>The <strong>Read API</strong> server reads from the <strong>SQL Database</strong> <code>sales_rank</code> table</li>\n</ul>\n<p>We'll use a public <a href=\"/learn/communication#representational-state-transfer-rest\"><strong>REST API</strong></a>:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>$ curl https://amazon.com/api/v1/popular?category_id=1234</span></span></code></pre>\n<p>Response:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"id\": \"100\",</span></span>\n<span class=\"line\"><span>    \"category_id\": \"1234\",</span></span>\n<span class=\"line\"><span>    \"total_sold\": \"100000\",</span></span>\n<span class=\"line\"><span>    \"product_id\": \"50\",</span></span>\n<span class=\"line\"><span>},</span></span>\n<span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"id\": \"53\",</span></span>\n<span class=\"line\"><span>    \"category_id\": \"1234\",</span></span>\n<span class=\"line\"><span>    \"total_sold\": \"90000\",</span></span>\n<span class=\"line\"><span>    \"product_id\": \"200\",</span></span>\n<span class=\"line\"><span>},</span></span>\n<span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"id\": \"75\",</span></span>\n<span class=\"line\"><span>    \"category_id\": \"1234\",</span></span>\n<span class=\"line\"><span>    \"total_sold\": \"80000\",</span></span>\n<span class=\"line\"><span>    \"product_id\": \"3\",</span></span>\n<span class=\"line\"><span>},</span></span></code></pre>\n<p>For internal communications, we could use <a href=\"/learn/communication#remote-procedure-call-rpc\">Remote Procedure Calls</a>.</p>","headings":[{"id":"use-case-service-calculates-the-past-weeks-most-popular-products-by-category","title":"Use case: Service calculates the past week's most popular products by category","depth":3},{"id":"use-case-user-views-the-past-weeks-most-popular-products-by-category","title":"Use case: User views the past week's most popular products by category","depth":3}]},{"id":"step-4-scale-the-design","title":"Step 4: Scale the design","html":"<h2 id=\"step-4-scale-the-design\">Step 4: Scale the design</h2>\n<blockquote>\n<p>Identify and address bottlenecks, given the constraints.</p>\n</blockquote>\n<p><img src=\"/repo/images/MzExP06.png\" alt=\"Diagram\" loading=\"lazy\" decoding=\"async\"></p>\n<p><strong>Important: Do not simply jump right into the final design from the initial design!</strong></p>\n<p>State you would 1) <strong>Benchmark/Load Test</strong>, 2) <strong>Profile</strong> for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat.  See <a href=\"/learn/design-scaling-aws\">Design a system that scales to millions of users on AWS</a> as a sample on how to iteratively scale the initial design.</p>\n<p>It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them.  For example, what issues are addressed by adding a <strong>Load Balancer</strong> with multiple <strong>Web Servers</strong>?  <strong>CDN</strong>?  <strong>Master-Slave Replicas</strong>?  What are the alternatives and <strong>Trade-Offs</strong> for each?</p>\n<p>We'll introduce some components to complete the design and to address scalability issues.  Internal load balancers are not shown to reduce clutter.</p>\n<p><em>To avoid repeating discussions</em>, refer to the following <a href=\"/\">system design topics</a> for main talking points, tradeoffs, and alternatives:</p>\n<ul>\n<li><a href=\"/learn/domain-name-system\">DNS</a></li>\n<li><a href=\"/learn/content-delivery-network\">CDN</a></li>\n<li><a href=\"/learn/load-balancer\">Load balancer</a></li>\n<li><a href=\"/learn/load-balancer#horizontal-scaling\">Horizontal scaling</a></li>\n<li><a href=\"/learn/reverse-proxy-web-server\">Web server (reverse proxy)</a></li>\n<li><a href=\"/learn/application-layer\">API server (application layer)</a></li>\n<li><a href=\"/learn/cache\">Cache</a></li>\n<li><a href=\"/learn/database#relational-database-management-system-rdbms\">Relational database management system (RDBMS)</a></li>\n<li><a href=\"/learn/availability-patterns#fail-over\">SQL write master-slave failover</a></li>\n<li><a href=\"/learn/database#master-slave-replication\">Master-slave replication</a></li>\n<li><a href=\"/learn/consistency-patterns\">Consistency patterns</a></li>\n<li><a href=\"/learn/availability-patterns\">Availability patterns</a></li>\n</ul>\n<p>The <strong>Analytics Database</strong> could use a data warehousing solution such as Amazon Redshift or Google BigQuery.</p>\n<p>We might only want to store a limited time period of data in the database, while storing the rest in a data warehouse or in an <strong>Object Store</strong>.  An <strong>Object Store</strong> such as Amazon S3 can comfortably handle the constraint of 40 GB of new content per month.</p>\n<p>To address the 40,000 <em>average</em> read requests per second (higher at peak), traffic for popular content (and their sales rank) should be handled by the <strong>Memory Cache</strong> instead of the database.  The <strong>Memory Cache</strong> is also useful for handling the unevenly distributed traffic and traffic spikes.  With the large volume of reads, the <strong>SQL Read Replicas</strong> might not be able to handle the cache misses.  We'll probably need to employ additional SQL scaling patterns.</p>\n<p>400 <em>average</em> writes per second (higher at peak) might be tough for a single <strong>SQL Write Master-Slave</strong>, also pointing to a need for additional scaling techniques.</p>\n<p>SQL scaling patterns include:</p>\n<ul>\n<li><a href=\"/learn/database#federation\">Federation</a></li>\n<li><a href=\"/learn/database#sharding\">Sharding</a></li>\n<li><a href=\"/learn/database#denormalization\">Denormalization</a></li>\n<li><a href=\"/learn/database#sql-tuning\">SQL Tuning</a></li>\n</ul>\n<p>We should also consider moving some data to a <strong>NoSQL Database</strong>.</p>","headings":[]},{"id":"additional-talking-points","title":"Additional talking points","html":"<h2 id=\"additional-talking-points\">Additional talking points</h2>\n<blockquote>\n<p>Additional topics to dive into, depending on the problem scope and time remaining.</p>\n</blockquote>\n<h4 id=\"nosql\">NoSQL</h4>\n<ul>\n<li><a href=\"/learn/database#key-value-store\">Key-value store</a></li>\n<li><a href=\"/learn/database#document-store\">Document store</a></li>\n<li><a href=\"/learn/database#wide-column-store\">Wide column store</a></li>\n<li><a href=\"/learn/database#graph-database\">Graph database</a></li>\n<li><a href=\"/learn/database#sql-or-nosql\">SQL vs NoSQL</a></li>\n</ul>\n<h3 id=\"caching\">Caching</h3>\n<ul>\n<li>Where to cache\n<ul>\n<li><a href=\"/learn/cache#client-caching\">Client caching</a></li>\n<li><a href=\"/learn/cache#cdn-caching\">CDN caching</a></li>\n<li><a href=\"/learn/cache#web-server-caching\">Web server caching</a></li>\n<li><a href=\"/learn/cache#database-caching\">Database caching</a></li>\n<li><a href=\"/learn/cache#application-caching\">Application caching</a></li>\n</ul>\n</li>\n<li>What to cache\n<ul>\n<li><a href=\"/learn/cache#caching-at-the-database-query-level\">Caching at the database query level</a></li>\n<li><a href=\"/learn/cache#caching-at-the-object-level\">Caching at the object level</a></li>\n</ul>\n</li>\n<li>When to update the cache\n<ul>\n<li><a href=\"/learn/cache#cache-aside\">Cache-aside</a></li>\n<li><a href=\"/learn/cache#write-through\">Write-through</a></li>\n<li><a href=\"/learn/cache#write-behind-write-back\">Write-behind (write-back)</a></li>\n<li><a href=\"/learn/cache#refresh-ahead\">Refresh ahead</a></li>\n</ul>\n</li>\n</ul>\n<h3 id=\"asynchronism-and-microservices\">Asynchronism and microservices</h3>\n<ul>\n<li><a href=\"/learn/asynchronism#message-queues\">Message queues</a></li>\n<li><a href=\"/learn/asynchronism#task-queues\">Task queues</a></li>\n<li><a href=\"/learn/asynchronism#back-pressure\">Back pressure</a></li>\n<li><a href=\"/learn/application-layer#microservices\">Microservices</a></li>\n</ul>\n<h3 id=\"communications\">Communications</h3>\n<ul>\n<li>Discuss tradeoffs:\n<ul>\n<li>External communication with clients - <a href=\"/learn/communication#representational-state-transfer-rest\">HTTP APIs following REST</a></li>\n<li>Internal communications - <a href=\"/learn/communication#remote-procedure-call-rpc\">RPC</a></li>\n</ul>\n</li>\n<li><a href=\"/learn/application-layer#service-discovery\">Service discovery</a></li>\n</ul>\n<h3 id=\"security\">Security</h3>\n<p>Refer to the <a href=\"/learn/security\">security section</a>.</p>\n<h3 id=\"latency-numbers\">Latency numbers</h3>\n<p>See <a href=\"/learn/appendix#latency-numbers-every-programmer-should-know\">Latency numbers every programmer should know</a>.</p>\n<h3 id=\"ongoing\">Ongoing</h3>\n<ul>\n<li>Continue benchmarking and monitoring your system to address bottlenecks as they come up</li>\n<li>Scaling is an iterative process</li>\n</ul>","headings":[{"id":"nosql","title":"NoSQL","depth":4},{"id":"caching","title":"Caching","depth":3},{"id":"asynchronism-and-microservices","title":"Asynchronism and microservices","depth":3},{"id":"communications","title":"Communications","depth":3},{"id":"security","title":"Security","depth":3},{"id":"latency-numbers","title":"Latency numbers","depth":3},{"id":"ongoing","title":"Ongoing","depth":3}]}],"sourceFiles":[{"name":"sales_rank_mapreduce.py","url":"https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/sales_rank/sales_rank_mapreduce.py","html":"<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># -*- coding: utf-8 -*-</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mrjob.job </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> MRJob</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> SalesRanker</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">MRJob</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> within_past_week</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, timestamp):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Return True if timestamp is within past week, False otherwise.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, _, line):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Parse each log line, extract and transform relevant lines.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Emit key value pairs of the form:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, p1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, p1), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p2), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, p3), 10</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p4), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        timestamp, product_id, category, quantity </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> line.split(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">\\t</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.within_past_week(timestamp):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category, product_id), quantity</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, values):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Sum values for each key.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p1), 2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, p1), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p2), 3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, p3), 10</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, p4), 1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">sum</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(values)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper_sort</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, value):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Construct key to ensure proper sorting.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Transform key and value to the form:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, 2), p1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, 3), p1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, 3), p2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (bar, 10), p3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (foo, 1), p4</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        The shuffle/sort step of MapReduce will then do a</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        distributed sort on the keys, resulting in:</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 1), product4</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 2), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category1, 3), product2</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 3), product1</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        (category2, 7), product3</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        category, product_id </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        quantity </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (category, quantity), product_id</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer_identity</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, value):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, value</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> steps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Run the map and reduce steps.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mr(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">mapper</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mapper,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">                    reducer</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reducer),</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mr(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">mapper</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mapper_sort,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">                    reducer</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reducer_identity),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        ]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __name__</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> ==</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> '__main__'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    SalesRanker.run()</span></span></code></pre>"}]}