{"id":"design-query-cache","title":"Design a key-value cache to save the results of the most recent web server queries","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-cases\">We'll scope the problem to handle only the following use cases</h4>\n<ul>\n<li><strong>User</strong> sends a search request resulting in a cache hit</li>\n<li><strong>User</strong> sends a search request resulting in a cache miss</li>\n<li><strong>Service</strong> has high availability</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\n<ul>\n<li>Popular queries should almost always be in the cache</li>\n<li>Need to determine how to expire/refresh</li>\n</ul>\n</li>\n<li>Serving from cache requires fast lookups</li>\n<li>Low latency between machines</li>\n<li>Limited memory in cache\n<ul>\n<li>Need to determine what to keep/remove</li>\n<li>Need to cache millions of queries</li>\n</ul>\n</li>\n<li>10 million users</li>\n<li>10 billion queries per month</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>Cache stores ordered list of key: query, value: results\n<ul>\n<li><code>query</code> - 50 bytes</li>\n<li><code>title</code> - 20 bytes</li>\n<li><code>snippet</code> - 200 bytes</li>\n<li>Total: 270 bytes</li>\n</ul>\n</li>\n<li>2.7 TB of cache data per month if all 10 billion queries are unique and all are stored\n<ul>\n<li>270 bytes per search * 10 billion searches per month</li>\n<li>Assumptions state limited memory, need to determine how to expire contents</li>\n</ul>\n</li>\n<li>4,000 requests per second</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-cases","title":"We'll scope the problem to handle only the following use cases","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/query_cache/query_cache_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-user-sends-a-request-resulting-in-a-cache-hit\">Use case: User sends a request resulting in a cache hit</h3>\n<p>Popular queries can be served from a <strong>Memory Cache</strong> such as Redis or Memcached to reduce read latency and to avoid overloading the <strong>Reverse Index Service</strong> and <strong>Document Service</strong>.  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<p>Since the cache has limited capacity, we'll use a least recently used (LRU) approach to expire older entries.</p>\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>Query API</strong> server</li>\n<li>The <strong>Query API</strong> server does the following:\n<ul>\n<li>Parses the query\n<ul>\n<li>Removes markup</li>\n<li>Breaks up the text into terms</li>\n<li>Fixes typos</li>\n<li>Normalizes capitalization</li>\n<li>Converts the query to use boolean operations</li>\n</ul>\n</li>\n<li>Checks the <strong>Memory Cache</strong> for the content matching the query\n<ul>\n<li>If there's a hit in the <strong>Memory Cache</strong>, the <strong>Memory Cache</strong> does the following:\n<ul>\n<li>Updates the cached entry's position to the front of the LRU list</li>\n<li>Returns the cached contents</li>\n</ul>\n</li>\n<li>Else, the <strong>Query API</strong> does the following:\n<ul>\n<li>Uses the <strong>Reverse Index Service</strong> to find documents matching the query\n<ul>\n<li>The <strong>Reverse Index Service</strong> ranks the matching results and returns the top ones</li>\n</ul>\n</li>\n<li>Uses the <strong>Document Service</strong> to return titles and snippets</li>\n<li>Updates the <strong>Memory Cache</strong> with the contents, placing the entry at the front of the LRU list</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"cache-implementation\">Cache implementation</h4>\n<p>The cache can use a doubly-linked list: new items will be added to the head while items to expire will be removed from the tail.  We'll use a hash table for fast lookups to each linked list node.</p>\n<p><strong>Clarify with your interviewer how much code you are expected to write</strong>.</p>\n<p><strong>Query API Server</strong> implementation:</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 style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> QueryApi</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, memory_cache, reverse_index_service):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.memory_cache </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memory_cache</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reverse_index_service </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> reverse_index_service</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\"> parse_query</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Remove markup, break text into terms, deal with typos,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        normalize capitalization, convert to use boolean operations.</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</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\"> process_query</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query):</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        query </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\">.parse_query(query)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        results </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\">.memory_cache.get(query)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            results </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\">.reverse_index_service.process_search(query)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.memory_cache.set(query, results)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</span></span></code></pre>\n<p><strong>Node</strong> implementation:</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 style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Node</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query, results):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.query </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> query</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</span></span></code></pre>\n<p><strong>LinkedList</strong> implementation:</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 style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LinkedList</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.head </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.tail </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</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\"> move_to_front</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, node):</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\"> append_to_front</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, node):</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\"> remove_from_tail</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span></code></pre>\n<p><strong>Cache</strong> implementation:</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 style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Cache</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, MAX_SIZE):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">MAX_SIZE</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> MAX_SIZE</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.size </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {}  </span><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># key: query, value: node</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> LinkedList()</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\"> get</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query)</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Get the stored query result from the cache.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Accessing a node updates its position to the front of the LRU list.</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\">        node </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\">.lookup[query]</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            return</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.move_to_front(node)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node.results</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> set</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, results, query):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Set the result for the given query key in the cache.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        When updating an entry, updates its position to the front of the LRU list.</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        If the entry is new and the cache is at capacity, removes the oldest entry</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        before the new entry is added.</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\">        node </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\">.lookup[query]</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> not</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Key exists in cache, update the value</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            node.results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.move_to_front(node)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Key does not exist in cache</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\">.size </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\">.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">MAX_SIZE</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">                # Remove the oldest entry from the linked list and lookup</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup.pop(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.tail.query, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">None</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\">.linked_list.remove_from_tail()</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            else</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\">.size </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">+=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Add the new key and value</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            new_node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Node(query, results)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.append_to_front(new_node)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup[query] </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> new_node</span></span></code></pre>\n<h4 id=\"when-to-update-the-cache\">When to update the cache</h4>\n<p>The cache should be updated when:</p>\n<ul>\n<li>The page contents change</li>\n<li>The page is removed or a new page is added</li>\n<li>The page rank changes</li>\n</ul>\n<p>The most straightforward way to handle these cases is to simply set a max time that a cached entry can stay in the cache before it is updated, usually referred to as time to live (TTL).</p>\n<p>Refer to <a href=\"/learn/cache#when-to-update-the-cache\">When to update the cache</a> for tradeoffs and alternatives.  The approach above describes <a href=\"/learn/cache#cache-aside\">cache-aside</a>.</p>","headings":[{"id":"use-case-user-sends-a-request-resulting-in-a-cache-hit","title":"Use case: User sends a request resulting in a cache hit","depth":3},{"id":"cache-implementation","title":"Cache implementation","depth":4},{"id":"when-to-update-the-cache","title":"When to update the cache","depth":4}]},{"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/4j99mhe.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/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/consistency-patterns\">Consistency patterns</a></li>\n<li><a href=\"/learn/availability-patterns\">Availability patterns</a></li>\n</ul>\n<h3 id=\"expanding-the-memory-cache-to-many-machines\">Expanding the Memory Cache to many machines</h3>\n<p>To handle the heavy request load and the large amount of memory needed, we'll scale horizontally.  We have three main options on how to store the data on our <strong>Memory Cache</strong> cluster:</p>\n<ul>\n<li><strong>Each machine in the cache cluster has its own cache</strong> - Simple, although it will likely result in a low cache hit rate.</li>\n<li><strong>Each machine in the cache cluster has a copy of the cache</strong> - Simple, although it is an inefficient use of memory.</li>\n<li><strong>The cache is <a href=\"/learn/database#sharding\">sharded</a> across all machines in the cache cluster</strong> - More complex, although it is likely the best option.  We could use hashing to determine which machine could have the cached results of a query using <code>machine = hash(query)</code>.  We'll likely want to use <a href=\"/learn/about#under-development\">consistent hashing</a>.</li>\n</ul>","headings":[{"id":"expanding-the-memory-cache-to-many-machines","title":"Expanding the Memory Cache to many machines","depth":3}]},{"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<h3 id=\"sql-scaling-patterns\">SQL scaling patterns</h3>\n<ul>\n<li><a href=\"/learn/database#master-slave-replication\">Read replicas</a></li>\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<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":"sql-scaling-patterns","title":"SQL scaling patterns","depth":3},{"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":"query_cache_snippets.py","url":"https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/query_cache/query_cache_snippets.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>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> QueryApi</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, memory_cache, reverse_index_cluster):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.memory_cache </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> memory_cache</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reverse_index_cluster </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> reverse_index_cluster</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\"> parse_query</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Remove markup, break text into terms, deal with typos,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        normalize capitalization, convert to use boolean operations.</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"</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\"> process_query</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query):</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        query </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\">.parse_query(query)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        results </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\">.memory_cache.get(query)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            results </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\">.reverse_index_cluster.process_search(query)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.memory_cache.set(query, results)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</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\"> Node</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query, results):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.query </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> query</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</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\"> LinkedList</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.head </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.tail </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</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\"> move_to_front</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, node):</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\"> append_to_front</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, node):</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\"> remove_from_tail</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</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>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Cache</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</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:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, MAX_SIZE):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">MAX_SIZE</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> MAX_SIZE</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.size </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</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\">.linked_list </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> LinkedList()</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\"> get</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, query):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Get the stored query result from the cache.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        Accessing a node updates its position to the front of the LRU list.</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\">        node </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\">.lookup[query]</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            return</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.move_to_front(node)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node.results</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> set</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, results, query):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Set the result for the given query key in the cache.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        When updating an entry, updates its position to the front of the LRU list.</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        If the entry is new and the cache is at capacity, removes the oldest entry</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        before the new entry is added.</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\">        node </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\">.map[query]</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> not</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Key exists in cache, update the value</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            node.results </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> results</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.move_to_front(node)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Key does not exist in cache</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\">.size </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\">.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">MAX_SIZE</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">                # Remove the oldest entry from the linked list and lookup</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup.pop(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.tail.query, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">None</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\">.linked_list.remove_from_tail()</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            else</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\">.size </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">+=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">            # Add the new key and value</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            new_node </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Node(query, results)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.linked_list.append_to_front(new_node)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.lookup[query] </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> new_node</span></span></code></pre>"}]}